diff --git a/services/identity/src/client_service.rs b/services/identity/src/client_service.rs --- a/services/identity/src/client_service.rs +++ b/services/identity/src/client_service.rs @@ -21,7 +21,7 @@ use crate::ddb_utils::{Identifier, is_transaction_conflict}; use crate::device_list::SignedDeviceList; use crate::error::{DeviceListError, Error as DBError, consume_error}; -use crate::grpc_services::authenticated::{DeletePasswordUserInfo, UpdatePasswordInfo}; +use crate::grpc_services::authenticated::{DeletePasswordUserInfo, UpdatePasswordInfo, PrivilegedPasswordResetInfo}; use crate::grpc_services::protos::unauth::{ find_user_id_request, AddReservedUsernamesRequest, AuthResponse, Empty, ExistingDeviceLoginRequest, FindUserIdRequest, FindUserIdResponse, @@ -59,6 +59,7 @@ Login(Box), Update(Box), PasswordUserDeletion(Box), + PrivilegedPasswordReset(Box), } #[derive(Clone, Serialize, Deserialize)] diff --git a/services/identity/src/constants.rs b/services/identity/src/constants.rs --- a/services/identity/src/constants.rs +++ b/services/identity/src/constants.rs @@ -363,3 +363,8 @@ pub const ONE_TIME_KEY_UPLOAD_LIMIT_PER_ACCOUNT: usize = 24; pub const ONE_TIME_KEY_SIZE: usize = 43; // as defined in olm pub const MAX_ONE_TIME_KEYS: usize = 100; // as defined in olm + +// Comm staff +pub mod staff { + pub const STAFF_USER_IDS: [&str; 1] = ["256"]; +} diff --git a/services/identity/src/grpc_services/authenticated.rs b/services/identity/src/grpc_services/authenticated.rs --- a/services/identity/src/grpc_services/authenticated.rs +++ b/services/identity/src/grpc_services/authenticated.rs @@ -9,7 +9,7 @@ use crate::log::redact_sensitive_data; use crate::{ client_service::{handle_db_error, WorkflowInProgress}, - constants::{error_types, request_metadata, tonic_status_messages}, + constants::{error_types, request_metadata, staff, tonic_status_messages}, database::DatabaseClient, grpc_services::shared::{get_platform_metadata, get_value}, }; @@ -643,10 +643,8 @@ &self, request: tonic::Request, ) -> Result, tonic::Status> { - const STAFF_USER_IDS: [&str; 1] = ["256"]; - let (user_id, _) = get_user_and_device_id(&request)?; - if !STAFF_USER_IDS.contains(&user_id.as_str()) { + if !staff::STAFF_USER_IDS.contains(&user_id.as_str()) { return Err(Status::permission_denied( tonic_status_messages::USER_IS_NOT_STAFF, )); @@ -666,20 +664,91 @@ #[tracing::instrument(skip_all)] async fn privileged_reset_user_password_start( &self, - _request: tonic::Request, + request: tonic::Request, ) -> Result< tonic::Response, tonic::Status, > { - unimplemented!() + let (staff_user_id, _) = get_user_and_device_id(&request)?; + if !staff::STAFF_USER_IDS.contains(&staff_user_id.as_str()) { + return Err(Status::permission_denied( + tonic_status_messages::USER_IS_NOT_STAFF, + )); + } + + let message = request.into_inner(); + debug!( + "Attempting to start resetting password for user: {:?}", + &message.username + ); + + let user_id_and_password_file = self + .db_client + .get_user_info_and_password_file_from_username(&message.username) + .await? + .ok_or(tonic::Status::not_found( + tonic_status_messages::USER_NOT_FOUND, + ))?; + + let server_registration = comm_opaque2::server::Registration::new(); + let registration_response = server_registration + .start( + &CONFIG.server_setup, + &message.opaque_registration_request, + &message.username.to_lowercase().as_bytes(), + ) + .map_err(protocol_error_to_grpc_status)?; + + let reset_state = + PrivilegedPasswordResetInfo::new(user_id_and_password_file.user_id); + let session_id = self + .db_client + .insert_workflow(WorkflowInProgress::PrivilegedPasswordReset(Box::new( + reset_state, + ))) + .await?; + + let response = PrivilegedResetUserPasswordStartResponse { + session_id, + opaque_registration_response: registration_response, + }; + Ok(Response::new(response)) } #[tracing::instrument(skip_all)] async fn privileged_reset_user_password_finish( &self, - _request: tonic::Request, + request: tonic::Request, ) -> Result, tonic::Status> { - unimplemented!() + let (staff_user_id, _) = get_user_and_device_id(&request)?; + if !staff::STAFF_USER_IDS.contains(&staff_user_id.as_str()) { + return Err(Status::permission_denied( + tonic_status_messages::USER_IS_NOT_STAFF, + )); + } + + let message = request.into_inner(); + + let Some(WorkflowInProgress::PrivilegedPasswordReset(state)) = + self.db_client.get_workflow(message.session_id).await? + else { + return Err(tonic::Status::not_found( + tonic_status_messages::SESSION_NOT_FOUND, + )); + }; + + let server_registration = comm_opaque2::server::Registration::new(); + let password_file = server_registration + .finish(&message.opaque_registration_upload) + .map_err(protocol_error_to_grpc_status)?; + + self + .db_client + .update_user_password(state.user_id, password_file) + .await?; + + let response = Empty {}; + Ok(Response::new(response)) } #[tracing::instrument(skip_all)] @@ -1051,3 +1120,10 @@ pub struct UpdatePasswordInfo { pub opaque_server_login: comm_opaque2::server::Login, } + +#[derive( + Clone, serde::Serialize, serde::Deserialize, derive_more::Constructor, +)] +pub struct PrivilegedPasswordResetInfo { + pub user_id: String, +}