diff --git a/client b/client index 443d7b9a..7c491c38 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit 443d7b9ab9113dacf1de73b457b53dd759e5b63d +Subproject commit 7c491c38cab7c363396efa6fcbab0d2c14b1d068 diff --git a/common/proto/auth.proto b/common/proto/auth.proto index 1d8bc78d..39aa6eb9 100644 --- a/common/proto/auth.proto +++ b/common/proto/auth.proto @@ -7,11 +7,25 @@ import "common.proto"; service AuthService { rpc Register(RegisterRequest) returns (AuthResponse); rpc Login(LoginRequest) returns (LoginResponse); + // Claims a bootstrap account that has never had a password set. + rpc SetInitialPassword(SetInitialPasswordRequest) returns (AuthResponse); rpc GetAuthorization(GetAuthorizationRequest) returns (AuthorizationSnapshot); rpc SetTimezone(SetTimezoneRequest) returns (UserPreferences); + + // Role administration. Every call requires the struct:role area, and every + // target role must rank strictly below the caller's own role. rpc ListRoles(ListRolesRequest) returns (ListRolesResponse); rpc AddRole(AddRoleRequest) returns (Role); rpc RemoveRole(RemoveRoleRequest) returns (Role); + + // Grant administration on the data plane. + rpc GrantPermission(GrantPermissionRequest) returns (RolePermissions); + rpc RevokePermission(RevokePermissionRequest) returns (RolePermissions); + rpc ListRolePermissions(ListRolePermissionsRequest) returns (RolePermissions); + + // User administration. + rpc AssignUserRole(AssignUserRoleRequest) returns (UserSummary); + rpc ListUsers(ListUsersRequest) returns (ListUsersResponse); } message RegisterRequest { @@ -19,16 +33,21 @@ message RegisterRequest { string email = 2; string password = 3; string password_confirmation = 4; - string role = 5; - string timezone = 6; // IANA timezone, for example Europe/Bratislava - string phone_country = 7; // ISO 3166-1 alpha-2 country used for national phone numbers, for example SK + string timezone = 5; // IANA timezone, for example Europe/Bratislava + string phone_country = 6; // ISO 3166-1 alpha-2 country used for national phone numbers, for example SK } message AuthResponse { string id = 1; // UUID in string format string username = 2; // Registered username string email = 3; // Registered email (if provided) - string role = 4; // Default role: 'accountant' + string role = 4; // Always 'guest' for a self-registration +} + +message SetInitialPasswordRequest { + string username = 1; + string password = 2; + string password_confirmation = 3; } message LoginRequest { @@ -59,18 +78,31 @@ message UserPreferences { message GetAuthorizationRequest {} message Permission { - string resource = 1; + // Canonical object string, one of: + // struct: structural area, never grantable at runtime + // data:/ one root table and its whole template family + // data:/* every table in a profile, present and future + // data:* every table everywhere + // journal: one profile's accounting journal + // journal:* every profile's journal + string object = 1; + // manage for structural areas; read/insert/update/delete on the data plane. string action = 2; } message AuthorizationSnapshot { string role = 1; + // Every permission the role holds, inherited ones included. repeated Permission permissions = 2; } message Role { string name = 1; - bool built_in = 2; + // 'structural' (designs the system, never writes data) or 'data'. + string kind = 2; + bool built_in = 3; + // Role this one inherits every grant from; empty when it has no parent. + string parent = 4; } message ListRolesRequest {} @@ -81,8 +113,52 @@ message ListRolesResponse { message AddRoleRequest { string name = 1; + // Optional data role to inherit from. Must rank below the caller. + string parent = 2; } message RemoveRoleRequest { string name = 1; } + +message GrantPermissionRequest { + string role = 1; + string object = 2; + string action = 3; +} + +message RevokePermissionRequest { + string role = 1; + string object = 2; + string action = 3; +} + +message ListRolePermissionsRequest { + string role = 1; +} + +message RolePermissions { + string role = 1; + // Grants stored against this role alone, without inherited ones. + repeated Permission permissions = 2; + // Everything the role can actually do, inheritance resolved. + repeated Permission effective_permissions = 3; +} + +message AssignUserRoleRequest { + string username = 1; + string role = 2; +} + +message UserSummary { + string id = 1; + string username = 2; + string email = 3; + string role = 4; +} + +message ListUsersRequest {} + +message ListUsersResponse { + repeated UserSummary users = 1; +} diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index ef79f907..ff0f25cb 100644 Binary files a/common/src/proto/descriptor.bin and b/common/src/proto/descriptor.bin differ diff --git a/common/src/proto/komp_ac.auth.rs b/common/src/proto/komp_ac.auth.rs index 2fa7cf86..5451ccaf 100644 --- a/common/src/proto/komp_ac.auth.rs +++ b/common/src/proto/komp_ac.auth.rs @@ -9,13 +9,11 @@ pub struct RegisterRequest { pub password: ::prost::alloc::string::String, #[prost(string, tag = "4")] pub password_confirmation: ::prost::alloc::string::String, - #[prost(string, tag = "5")] - pub role: ::prost::alloc::string::String, /// IANA timezone, for example Europe/Bratislava - #[prost(string, tag = "6")] + #[prost(string, tag = "5")] pub timezone: ::prost::alloc::string::String, /// ISO 3166-1 alpha-2 country used for national phone numbers, for example SK - #[prost(string, tag = "7")] + #[prost(string, tag = "6")] pub phone_country: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] @@ -29,11 +27,20 @@ pub struct AuthResponse { /// Registered email (if provided) #[prost(string, tag = "3")] pub email: ::prost::alloc::string::String, - /// Default role: 'accountant' + /// Always 'guest' for a self-registration #[prost(string, tag = "4")] pub role: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct SetInitialPasswordRequest { + #[prost(string, tag = "1")] + pub username: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub password: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub password_confirmation: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct LoginRequest { /// Can be username or email #[prost(string, tag = "1")] @@ -82,8 +89,16 @@ pub struct UserPreferences { pub struct GetAuthorizationRequest {} #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct Permission { + /// Canonical object string, one of: + /// struct: structural area, never grantable at runtime + /// data:/
one root table and its whole template family + /// data:/\* every table in a profile, present and future + /// data:\* every table everywhere + /// journal: one profile's accounting journal + /// journal:\* every profile's journal #[prost(string, tag = "1")] - pub resource: ::prost::alloc::string::String, + pub object: ::prost::alloc::string::String, + /// manage for structural areas; read/insert/update/delete on the data plane. #[prost(string, tag = "2")] pub action: ::prost::alloc::string::String, } @@ -91,6 +106,7 @@ pub struct Permission { pub struct AuthorizationSnapshot { #[prost(string, tag = "1")] pub role: ::prost::alloc::string::String, + /// Every permission the role holds, inherited ones included. #[prost(message, repeated, tag = "2")] pub permissions: ::prost::alloc::vec::Vec, } @@ -98,8 +114,14 @@ pub struct AuthorizationSnapshot { pub struct Role { #[prost(string, tag = "1")] pub name: ::prost::alloc::string::String, - #[prost(bool, tag = "2")] + /// 'structural' (designs the system, never writes data) or 'data'. + #[prost(string, tag = "2")] + pub kind: ::prost::alloc::string::String, + #[prost(bool, tag = "3")] pub built_in: bool, + /// Role this one inherits every grant from; empty when it has no parent. + #[prost(string, tag = "4")] + pub parent: ::prost::alloc::string::String, } #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct ListRolesRequest {} @@ -112,12 +134,74 @@ pub struct ListRolesResponse { pub struct AddRoleRequest { #[prost(string, tag = "1")] pub name: ::prost::alloc::string::String, + /// Optional data role to inherit from. Must rank below the caller. + #[prost(string, tag = "2")] + pub parent: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct RemoveRoleRequest { #[prost(string, tag = "1")] pub name: ::prost::alloc::string::String, } +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GrantPermissionRequest { + #[prost(string, tag = "1")] + pub role: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub object: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub action: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RevokePermissionRequest { + #[prost(string, tag = "1")] + pub role: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub object: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub action: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ListRolePermissionsRequest { + #[prost(string, tag = "1")] + pub role: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RolePermissions { + #[prost(string, tag = "1")] + pub role: ::prost::alloc::string::String, + /// Grants stored against this role alone, without inherited ones. + #[prost(message, repeated, tag = "2")] + pub permissions: ::prost::alloc::vec::Vec, + /// Everything the role can actually do, inheritance resolved. + #[prost(message, repeated, tag = "3")] + pub effective_permissions: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct AssignUserRoleRequest { + #[prost(string, tag = "1")] + pub username: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub role: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct UserSummary { + #[prost(string, tag = "1")] + pub id: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub username: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub email: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub role: ::prost::alloc::string::String, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ListUsersRequest {} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ListUsersResponse { + #[prost(message, repeated, tag = "1")] + pub users: ::prost::alloc::vec::Vec, +} /// Generated client implementations. pub mod auth_service_client { #![allow( @@ -251,6 +335,30 @@ pub mod auth_service_client { .insert(GrpcMethod::new("komp_ac.auth.AuthService", "Login")); self.inner.unary(req, path, codec).await } + /// Claims a bootstrap account that has never had a password set. + pub async fn set_initial_password( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.auth.AuthService/SetInitialPassword", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("komp_ac.auth.AuthService", "SetInitialPassword"), + ); + self.inner.unary(req, path, codec).await + } pub async fn get_authorization( &mut self, request: impl tonic::IntoRequest, @@ -299,6 +407,8 @@ pub mod auth_service_client { .insert(GrpcMethod::new("komp_ac.auth.AuthService", "SetTimezone")); self.inner.unary(req, path, codec).await } + /// Role administration. Every call requires the struct:role area, and every + /// target role must rank strictly below the caller's own role. pub async fn list_roles( &mut self, request: impl tonic::IntoRequest, @@ -365,6 +475,127 @@ pub mod auth_service_client { .insert(GrpcMethod::new("komp_ac.auth.AuthService", "RemoveRole")); self.inner.unary(req, path, codec).await } + /// Grant administration on the data plane. + pub async fn grant_permission( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.auth.AuthService/GrantPermission", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("komp_ac.auth.AuthService", "GrantPermission")); + self.inner.unary(req, path, codec).await + } + pub async fn revoke_permission( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.auth.AuthService/RevokePermission", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("komp_ac.auth.AuthService", "RevokePermission")); + self.inner.unary(req, path, codec).await + } + pub async fn list_role_permissions( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.auth.AuthService/ListRolePermissions", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("komp_ac.auth.AuthService", "ListRolePermissions"), + ); + self.inner.unary(req, path, codec).await + } + /// User administration. + pub async fn assign_user_role( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.auth.AuthService/AssignUserRole", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("komp_ac.auth.AuthService", "AssignUserRole")); + self.inner.unary(req, path, codec).await + } + pub async fn list_users( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.auth.AuthService/ListUsers", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("komp_ac.auth.AuthService", "ListUsers")); + self.inner.unary(req, path, codec).await + } } } /// Generated server implementations. @@ -388,6 +619,11 @@ pub mod auth_service_server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status>; + /// Claims a bootstrap account that has never had a password set. + async fn set_initial_password( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; async fn get_authorization( &self, request: tonic::Request, @@ -399,6 +635,8 @@ pub mod auth_service_server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status>; + /// Role administration. Every call requires the struct:role area, and every + /// target role must rank strictly below the caller's own role. async fn list_roles( &self, request: tonic::Request, @@ -414,6 +652,31 @@ pub mod auth_service_server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status>; + /// Grant administration on the data plane. + async fn grant_permission( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn revoke_permission( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn list_role_permissions( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// User administration. + async fn assign_user_role( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn list_users( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; } #[derive(Debug)] pub struct AuthServiceServer { @@ -579,6 +842,52 @@ pub mod auth_service_server { }; Box::pin(fut) } + "/komp_ac.auth.AuthService/SetInitialPassword" => { + #[allow(non_camel_case_types)] + struct SetInitialPasswordSvc(pub Arc); + impl< + T: AuthService, + > tonic::server::UnaryService + for SetInitialPasswordSvc { + type Response = super::AuthResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::set_initial_password(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = SetInitialPasswordSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/komp_ac.auth.AuthService/GetAuthorization" => { #[allow(non_camel_case_types)] struct GetAuthorizationSvc(pub Arc); @@ -804,6 +1113,232 @@ pub mod auth_service_server { }; Box::pin(fut) } + "/komp_ac.auth.AuthService/GrantPermission" => { + #[allow(non_camel_case_types)] + struct GrantPermissionSvc(pub Arc); + impl< + T: AuthService, + > tonic::server::UnaryService + for GrantPermissionSvc { + type Response = super::RolePermissions; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::grant_permission(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GrantPermissionSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/komp_ac.auth.AuthService/RevokePermission" => { + #[allow(non_camel_case_types)] + struct RevokePermissionSvc(pub Arc); + impl< + T: AuthService, + > tonic::server::UnaryService + for RevokePermissionSvc { + type Response = super::RolePermissions; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::revoke_permission(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = RevokePermissionSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/komp_ac.auth.AuthService/ListRolePermissions" => { + #[allow(non_camel_case_types)] + struct ListRolePermissionsSvc(pub Arc); + impl< + T: AuthService, + > tonic::server::UnaryService + for ListRolePermissionsSvc { + type Response = super::RolePermissions; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::list_role_permissions(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ListRolePermissionsSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/komp_ac.auth.AuthService/AssignUserRole" => { + #[allow(non_camel_case_types)] + struct AssignUserRoleSvc(pub Arc); + impl< + T: AuthService, + > tonic::server::UnaryService + for AssignUserRoleSvc { + type Response = super::UserSummary; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::assign_user_role(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = AssignUserRoleSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/komp_ac.auth.AuthService/ListUsers" => { + #[allow(non_camel_case_types)] + struct ListUsersSvc(pub Arc); + impl< + T: AuthService, + > tonic::server::UnaryService + for ListUsersSvc { + type Response = super::ListUsersResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::list_users(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ListUsersSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } _ => { Box::pin(async move { let mut response = http::Response::new( diff --git a/server b/server index f0945096..5dd123bf 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit f09450969170f8a098b38dff6c07d57ddf27b17e +Subproject commit 5dd123bf34e3254520e4d198494872601a34f8d0 diff --git a/web/src/pages/register/logic.rs b/web/src/pages/register/logic.rs index 89c01223..a1a8ab38 100644 --- a/web/src/pages/register/logic.rs +++ b/web/src/pages/register/logic.rs @@ -31,7 +31,6 @@ pub(crate) async fn register( email: input.email.trim().to_string(), password: input.password.trim().to_string(), password_confirmation: input.password_confirmation.trim().to_string(), - role: input.role.trim().to_string(), timezone: timezone.clone(), phone_country: phone_country.clone(), })) diff --git a/web/src/pages/register/state.rs b/web/src/pages/register/state.rs index d5359060..f5598be5 100644 --- a/web/src/pages/register/state.rs +++ b/web/src/pages/register/state.rs @@ -12,8 +12,6 @@ pub(crate) struct RegisterInput { #[serde(default)] pub password_confirmation: String, #[serde(default)] - pub role: String, - #[serde(default)] pub timezone: String, #[serde(default)] pub phone_country: String, diff --git a/web/src/pages/register/suggestions.rs b/web/src/pages/register/suggestions.rs index a77d5e75..82a26bc3 100644 --- a/web/src/pages/register/suggestions.rs +++ b/web/src/pages/register/suggestions.rs @@ -6,8 +6,6 @@ //! The client filters these itself as the field is typed into; here the lists //! ship whole in `` elements and the browser does the filtering. -pub(crate) const ROLES: &[&str] = &["admin", "moderator", "accountant", "viewer"]; - pub(crate) fn timezones() -> Vec { jiff::tz::db() .available() diff --git a/web/src/pages/register/ui.rs b/web/src/pages/register/ui.rs index 0d958c21..fe475ba7 100644 --- a/web/src/pages/register/ui.rs +++ b/web/src/pages/register/ui.rs @@ -12,7 +12,6 @@ use super::suggestions; #[template(path = "pages/register/register.html")] struct RegisterPage { nav: Nav, - roles: &'static [&'static str], timezones: Vec, phone_countries: Vec, } @@ -20,7 +19,6 @@ struct RegisterPage { pub(crate) fn render_page(nav: Nav) -> String { render(&RegisterPage { nav, - roles: suggestions::ROLES, timezones: suggestions::timezones(), phone_countries: suggestions::phone_countries(), }) diff --git a/web/templates/pages/register/register.html b/web/templates/pages/register/register.html index fdb30164..0a7c47c3 100644 --- a/web/templates/pages/register/register.html +++ b/web/templates/pages/register/register.html @@ -13,14 +13,13 @@ {# - The three fields the client offers suggestions for. A datalist keeps the + The two fields the client offers suggestions for. A datalist keeps the lists open — the backend, not this form, decides what is accepted — while still showing the same choices the client's suggestion popup does. + + There is no role field: everyone registers as `guest` and an admin + assigns a real role afterwards. #} - - - {% for role in roles %}{% endfor %} - {% for timezone in timezones %}{% endfor %}