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 @@ -8,10 +8,11 @@ use rand::rngs::OsRng; use siwe::eip55; use tonic::Response; -use tracing::{debug, error}; +use tracing::{debug, error, warn}; // Workspace crate imports use crate::config::CONFIG; +use crate::constants::request_metadata; use crate::database::{ DBDeviceTypeInt, DatabaseClient, DeviceType, KeyPayload, }; @@ -26,6 +27,7 @@ VerifyUserAccessTokenRequest, VerifyUserAccessTokenResponse, WalletLoginRequest, WalletLoginResponse, }; +use crate::grpc_services::shared::get_value; use crate::grpc_utils::DeviceKeyUploadActions; use crate::id::generate_uuid; use crate::nonce::generate_nonce_data; @@ -198,6 +200,7 @@ &self, request: tonic::Request, ) -> Result, tonic::Status> { + let code_version = get_code_version(&request); let message = request.into_inner(); if let Some(WorkflowInProgress::Registration(state)) = @@ -210,17 +213,24 @@ .finish(&message.opaque_registration_upload) .map_err(protocol_error_to_grpc_status)?; + let login_time = chrono::Utc::now(); let device_id = state.flattened_device_key_upload.device_id_key.clone(); let user_id = self .client - .add_password_user_to_users_table(*state, password_file) + .add_password_user_to_users_table( + *state, + password_file, + code_version, + login_time, + ) .await .map_err(handle_db_error)?; // Create access token - let token = AccessTokenData::new( + let token = AccessTokenData::with_created_time( user_id.clone(), device_id, + login_time, crate::token::AuthType::Password, &mut OsRng, ); @@ -309,6 +319,7 @@ &self, request: tonic::Request, ) -> Result, tonic::Status> { + let code_version = get_code_version(&request); let message = request.into_inner(); if let Some(WorkflowInProgress::Login(state)) = @@ -321,19 +332,23 @@ .finish(&message.opaque_login_upload) .map_err(protocol_error_to_grpc_status)?; + let login_time = chrono::Utc::now(); self .client .add_password_user_device_to_users_table( state.user_id.clone(), state.flattened_device_key_upload.clone(), + code_version, + login_time, ) .await .map_err(handle_db_error)?; // Create access token - let token = AccessTokenData::new( + let token = AccessTokenData::with_created_time( state.user_id.clone(), state.flattened_device_key_upload.device_id_key, + login_time, crate::token::AuthType::Password, &mut OsRng, ); @@ -360,6 +375,7 @@ &self, request: tonic::Request, ) -> Result, tonic::Status> { + let code_version = get_code_version(&request); let message = request.into_inner(); let parsed_message = parse_and_verify_siwe_message( @@ -390,6 +406,7 @@ .social_proof()? .ok_or_else(|| tonic::Status::invalid_argument("malformed payload"))?; + let login_time = chrono::Utc::now(); let user_id = match self .client .get_user_id_from_user_info(wallet_address.clone(), &AuthType::Wallet) @@ -404,6 +421,8 @@ id.clone(), flattened_device_key_upload.clone(), social_proof, + code_version, + chrono::Utc::now(), ) .await .map_err(handle_db_error)?; @@ -436,6 +455,8 @@ wallet_address, social_proof, None, + code_version, + login_time, ) .await .map_err(handle_db_error)? @@ -443,9 +464,10 @@ }; // Create access token - let token = AccessTokenData::new( + let token = AccessTokenData::with_created_time( user_id.clone(), flattened_device_key_upload.device_id_key, + login_time, crate::token::AuthType::Password, &mut OsRng, ); @@ -469,6 +491,7 @@ &self, request: tonic::Request, ) -> Result, tonic::Status> { + let code_version = get_code_version(&request); let message = request.into_inner(); let parsed_message = parse_and_verify_siwe_message( @@ -518,6 +541,7 @@ .social_proof()? .ok_or_else(|| tonic::Status::invalid_argument("malformed payload"))?; + let login_time = chrono::Utc::now(); self .client .add_wallet_user_to_users_table( @@ -525,13 +549,16 @@ wallet_address, social_proof, Some(user_id.clone()), + code_version, + login_time, ) .await .map_err(handle_db_error)?; - let token = AccessTokenData::new( + let token = AccessTokenData::with_created_time( user_id.clone(), flattened_device_key_upload.device_id_key, + login_time, crate::token::AuthType::Password, &mut OsRng, ); @@ -768,3 +795,15 @@ Ok(flattened_device_key_upload) } + +fn get_code_version(req: &tonic::Request) -> u64 { + get_value(req, request_metadata::CODE_VERSION) + .and_then(|version| version.parse().ok()) + .unwrap_or_else(|| { + warn!( + "Could not retrieve code version from request: {:?}. Defaulting to 0", + req + ); + Default::default() + }) +} 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 @@ -122,6 +122,10 @@ // device-list-specific attrs pub const ATTR_TIMESTAMP: &str = "timestamp"; pub const ATTR_DEVICE_IDS: &str = "deviceIDs"; + + // migration-specific attrs + pub const ATTR_CODE_VERSION: &str = "codeVersion"; + pub const ATTR_LOGIN_TIME: &str = "loginTime"; } // One time keys table, which need to exist in their own table to ensure diff --git a/services/identity/src/database.rs b/services/identity/src/database.rs --- a/services/identity/src/database.rs +++ b/services/identity/src/database.rs @@ -148,6 +148,8 @@ &self, registration_state: UserRegistrationInfo, password_file: Vec, + code_version: u64, + access_token_creation_time: DateTime, ) -> Result { let device_key_upload = registration_state.flattened_device_key_upload; let user_id = self @@ -160,7 +162,15 @@ ) .await?; - self.add_device(&user_id, device_key_upload, None).await?; + self + .add_device( + &user_id, + device_key_upload, + None, + code_version, + access_token_creation_time, + ) + .await?; Ok(user_id) } @@ -171,6 +181,8 @@ wallet_address: String, social_proof: String, user_id: Option, + code_version: u64, + access_token_creation_time: DateTime, ) -> Result { let social_proof = Some(social_proof); let user_id = self @@ -184,7 +196,13 @@ .await?; self - .add_device(&user_id, flattened_device_key_upload, social_proof) + .add_device( + &user_id, + flattened_device_key_upload, + social_proof, + code_version, + access_token_creation_time, + ) .await?; Ok(user_id) @@ -261,6 +279,8 @@ &self, user_id: String, flattened_device_key_upload: FlattenedDeviceKeyUpload, + code_version: u64, + access_token_creation_time: DateTime, ) -> Result<(), Error> { // add device to the legacy device list self @@ -284,7 +304,13 @@ } self - .add_device(user_id, flattened_device_key_upload, None) + .add_device( + user_id, + flattened_device_key_upload, + None, + code_version, + access_token_creation_time, + ) .await } @@ -293,6 +319,8 @@ user_id: String, flattened_device_key_upload: FlattenedDeviceKeyUpload, social_proof: String, + code_version: u64, + access_token_creation_time: DateTime, ) -> Result<(), Error> { // add device to the legacy device list self @@ -317,7 +345,13 @@ // add device to the new device list self - .add_device(user_id, flattened_device_key_upload, Some(social_proof)) + .add_device( + user_id, + flattened_device_key_upload, + Some(social_proof), + code_version, + access_token_creation_time, + ) .await } 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 @@ -45,6 +45,11 @@ pub device_key_info: IdentityKeyInfo, pub content_prekey: PreKey, pub notif_prekey: PreKey, + + // migration-related data + pub code_version: u64, + /// Timestamp of last login (access token generation) + pub login_time: DateTime, } #[derive(Clone, Debug)] @@ -72,6 +77,8 @@ user_id: impl Into, upload: FlattenedDeviceKeyUpload, social_proof: Option, + code_version: u64, + login_time: DateTime, ) -> Self { Self { user_id: user_id.into(), @@ -90,7 +97,9 @@ notif_prekey: PreKey { pre_key: upload.notif_prekey, pre_key_signature: upload.notif_prekey_signature, - } + }, + code_version, + login_time, } } } @@ -209,6 +218,14 @@ .cloned() .and_then(PreKey::try_from)?; + let code_version = attrs + .remove(ATTR_CODE_VERSION) + .and_then(|attr| attr.as_n().ok().cloned()) + .and_then(|val| val.parse::().ok()) + .unwrap_or_default(); + + let login_time: DateTime = attrs.take_attr(ATTR_LOGIN_TIME)?; + Ok(Self { user_id, device_id, @@ -216,6 +233,8 @@ device_key_info, content_prekey, notif_prekey, + code_version, + login_time, }) } } @@ -238,6 +257,15 @@ ), (ATTR_CONTENT_PREKEY.to_string(), value.content_prekey.into()), (ATTR_NOTIF_PREKEY.to_string(), value.notif_prekey.into()), + // migration attributes + ( + ATTR_CODE_VERSION.to_string(), + AttributeValue::N(value.code_version.to_string()), + ), + ( + ATTR_LOGIN_TIME.to_string(), + AttributeValue::S(value.login_time.to_rfc3339()), + ), ]) } } @@ -613,6 +641,8 @@ user_id: impl Into, device_key_upload: FlattenedDeviceKeyUpload, social_proof: Option, + code_version: u64, + login_time: DateTime, ) -> Result<(), Error> { let user_id: String = user_id.into(); self @@ -621,6 +651,8 @@ &user_id, device_key_upload, social_proof, + code_version, + login_time, ); if device_ids.iter().any(|id| &new_device.device_id == id) { diff --git a/services/identity/src/token.rs b/services/identity/src/token.rs --- a/services/identity/src/token.rs +++ b/services/identity/src/token.rs @@ -39,6 +39,23 @@ } } + pub fn with_created_time( + user_id: String, + signing_public_key: String, + creation_time: DateTime, + auth_type: AuthType, + rng: &mut (impl Rng + CryptoRng), + ) -> Self { + AccessTokenData { + user_id, + signing_public_key, + access_token: Alphanumeric.sample_string(rng, ACCESS_TOKEN_LENGTH), + created: creation_time, + auth_type, + valid: true, + } + } + pub fn is_valid(&self) -> bool { self.valid }