diff --git a/services/identity/src/database/device_list.rs b/services/identity/src/database/device_list.rs --- a/services/identity/src/database/device_list.rs +++ b/services/identity/src/database/device_list.rs @@ -27,6 +27,7 @@ USERS_TABLE_PARTITION_KEY, }, ddb_utils::is_transaction_conflict, + device_list::RawDeviceList, error::{DeviceListError, Error}, grpc_services::{ protos::{self, unauth::DeviceType}, @@ -120,6 +121,25 @@ pub raw_payload: String, } +impl DeviceListUpdate { + pub fn new_unsigned( + devices: Vec, + ) -> Result { + let timestamp = Utc::now(); + let raw_list = RawDeviceList { + devices: devices.clone(), + timestamp: timestamp.timestamp_millis(), + }; + Ok(Self { + devices, + timestamp, + current_primary_signature: None, + last_primary_signature: None, + raw_payload: serde_json::to_string(&raw_list)?, + }) + } +} + impl DeviceRow { #[tracing::instrument(skip_all)] pub fn from_device_key_upload( diff --git a/services/identity/src/device_list.rs b/services/identity/src/device_list.rs --- a/services/identity/src/device_list.rs +++ b/services/identity/src/device_list.rs @@ -14,9 +14,9 @@ // serde helper for serializing/deserializing // device list JSON payload #[derive(serde::Serialize, serde::Deserialize)] -struct RawDeviceList { - devices: Vec, - timestamp: i64, +pub struct RawDeviceList { + pub devices: Vec, + pub timestamp: i64, } /// Signed device list payload that is serializable to JSON. 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 @@ -2,6 +2,7 @@ use crate::comm_service::{backup, blob, tunnelbroker}; use crate::config::CONFIG; +use crate::constants::staff::AUTHORITATIVE_KEYSERVER_OWNER_USER_ID; use crate::database::{DeviceListUpdate, PlatformDetails}; use crate::device_list::validation::DeviceListValidator; use crate::device_list::SignedDeviceList; @@ -37,7 +38,7 @@ UploadOneTimeKeysRequest, UserDevicesPlatformDetails, UserIdentitiesRequest, UserIdentitiesResponse, }; -use super::protos::unauth::Empty; +use super::protos::unauth::{DeviceType, Empty}; #[derive(derive_more::Constructor)] pub struct AuthenticatedService { @@ -478,6 +479,11 @@ ) .await?; + if user_id == AUTHORITATIVE_KEYSERVER_OWNER_USER_ID { + self.log_out_authoritative_keyserver_owner().await?; + return Ok(Response::new(Empty {})); + } + // Get and verify singleton device list let parsed_device_list: SignedDeviceList = message.signed_device_list.parse()?; @@ -1198,6 +1204,38 @@ let blob_client = self.blob_client.with_authentication(s2s_token.into()); Ok(blob_client) } + + /// for authoritatative keyserver owner, instead of primary device logout, + /// we should remove all devices but keyserver, remove backup + /// and create an unsigned device list update, effectively downgrading + /// the user back to v1 flows + async fn log_out_authoritative_keyserver_owner( + &self, + ) -> Result<(), tonic::Status> { + let user_id = AUTHORITATIVE_KEYSERVER_OWNER_USER_ID; + let devices = self.db_client.get_current_devices(user_id).await?; + let keyserver_device_id = devices + .iter() + .find(|it| matches!(it.device_type(), DeviceType::Keyserver)) + .map(|keyserver| &keyserver.device_id); + + let new_device_list = if let Some(keyserver_id) = keyserver_device_id { + vec![keyserver_id.to_string()] + } else { + Vec::new() + }; + + let device_list_update = DeviceListUpdate::new_unsigned(new_device_list)?; + let validator = None::; + self + .db_client + .apply_devicelist_update(user_id, device_list_update, validator, true) + .await?; + + backup::delete_backup_user_data(user_id, &self.comm_auth_service).await?; + + Ok(()) + } } #[derive(