diff --git a/Cargo.lock b/Cargo.lock index 3cdea451..f48dc70b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6958,6 +6958,7 @@ dependencies = [ "flate2", "futures", "gtin-validate", + "http", "iban_validate", "jiff", "jiff-sqlx", @@ -6996,6 +6997,7 @@ dependencies = [ "tokio-test", "tonic", "tonic-reflection", + "tower", "tracing", "tracing-subscriber", "tui-canvas-validation-core", diff --git a/client b/client index 7319ea41..20316569 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit 7319ea4178e68e0f7cdaf52ff1433e55bae91ed4 +Subproject commit 20316569327165c51760005e063513ba414458b3 diff --git a/common/proto/auth.proto b/common/proto/auth.proto index 9ee65430..f8c057cb 100644 --- a/common/proto/auth.proto +++ b/common/proto/auth.proto @@ -12,6 +12,16 @@ service AuthService { rpc GetAuthorization(GetAuthorizationRequest) returns (AuthorizationSnapshot); rpc SetTimezone(SetTimezoneRequest) returns (UserPreferences); + // Ends the caller's own sessions. Every token issued to them before this + // call, on every device, stops being accepted -- including the one used to + // make the call, so the caller must log in again afterwards. Discarding a + // token client-side is not a logout; this is. + rpc Logout(LogoutRequest) returns (LogoutResponse); + // Ends every session of another user, for a leaked token or a departing + // account. Requires the struct:user area, and the target must rank strictly + // below the caller. + rpc RevokeUserSessions(RevokeUserSessionsRequest) returns (RevokeUserSessionsResponse); + // 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); @@ -80,6 +90,16 @@ message UserPreferences { string timezone = 1; } +message LogoutRequest {} + +message LogoutResponse {} + +message RevokeUserSessionsRequest { + string username = 1; +} + +message RevokeUserSessionsResponse {} + message GetAuthorizationRequest {} message Permission { diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 7d9b5083..96660fe2 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 bdc6dd63..b943e170 100644 --- a/common/src/proto/komp_ac.auth.rs +++ b/common/src/proto/komp_ac.auth.rs @@ -88,6 +88,17 @@ pub struct UserPreferences { pub timezone: ::prost::alloc::string::String, } #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct LogoutRequest {} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct LogoutResponse {} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RevokeUserSessionsRequest { + #[prost(string, tag = "1")] + pub username: ::prost::alloc::string::String, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct RevokeUserSessionsResponse {} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct GetAuthorizationRequest {} #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct Permission { @@ -450,6 +461,60 @@ pub mod auth_service_client { .insert(GrpcMethod::new("komp_ac.auth.AuthService", "SetTimezone")); self.inner.unary(req, path, codec).await } + /// Ends the caller's own sessions. Every token issued to them before this + /// call, on every device, stops being accepted -- including the one used to + /// make the call, so the caller must log in again afterwards. Discarding a + /// token client-side is not a logout; this is. + pub async fn logout( + &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/Logout", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("komp_ac.auth.AuthService", "Logout")); + self.inner.unary(req, path, codec).await + } + /// Ends every session of another user, for a leaked token or a departing + /// account. Requires the struct:user area, and the target must rank strictly + /// below the caller. + pub async fn revoke_user_sessions( + &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/RevokeUserSessions", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("komp_ac.auth.AuthService", "RevokeUserSessions"), + ); + 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( @@ -734,6 +799,24 @@ pub mod auth_service_server { &self, request: tonic::Request, ) -> std::result::Result, tonic::Status>; + /// Ends the caller's own sessions. Every token issued to them before this + /// call, on every device, stops being accepted -- including the one used to + /// make the call, so the caller must log in again afterwards. Discarding a + /// token client-side is not a logout; this is. + async fn logout( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + /// Ends every session of another user, for a leaked token or a departing + /// account. Requires the struct:user area, and the target must rank strictly + /// below the caller. + async fn revoke_user_sessions( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + 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( @@ -1091,6 +1174,97 @@ pub mod auth_service_server { }; Box::pin(fut) } + "/komp_ac.auth.AuthService/Logout" => { + #[allow(non_camel_case_types)] + struct LogoutSvc(pub Arc); + impl< + T: AuthService, + > tonic::server::UnaryService + for LogoutSvc { + type Response = super::LogoutResponse; + 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 { + ::logout(&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 = LogoutSvc(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/RevokeUserSessions" => { + #[allow(non_camel_case_types)] + struct RevokeUserSessionsSvc(pub Arc); + impl< + T: AuthService, + > tonic::server::UnaryService + for RevokeUserSessionsSvc { + type Response = super::RevokeUserSessionsResponse; + 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_user_sessions(&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 = RevokeUserSessionsSvc(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/ListRoles" => { #[allow(non_camel_case_types)] struct ListRolesSvc(pub Arc); diff --git a/server b/server index f3efeae0..891aa003 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit f3efeae04b782b5bef8cf5a4d9827933ac58190d +Subproject commit 891aa0038bb4c5083a36cc02470f088776078f99