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 @@ -556,6 +556,58 @@ Ok(item.is_some()) } + pub async fn get_device_data( + &self, + user_id: impl Into, + device_id: impl Into, + ) -> Result, Error> { + let GetItemOutput { item, .. } = self + .client + .get_item() + .table_name(devices_table::NAME) + .key(ATTR_USER_ID, AttributeValue::S(user_id.into())) + .key(ATTR_ITEM_ID, DeviceIDAttribute(device_id.into()).into()) + .send() + .await + .map_err(|e| { + error!("Failed to fetch device data: {:?}", e); + Error::AwsSdk(e.into()) + })?; + + let Some(attrs) = item else { + return Ok(None); + }; + + let device_data = DeviceRow::try_from(attrs)?; + Ok(Some(device_data)) + } + + /// Fails if the device list is empty + pub async fn get_primary_device_data( + &self, + user_id: &str, + ) -> Result { + let device_list = self.get_current_device_list(user_id).await?; + let Some(primary_device_id) = device_list + .as_ref() + .and_then(|list| list.device_ids.first()) + else { + error!(user_id, "Device list is empty. Cannot fetch primary device"); + return Err(Error::DeviceList(DeviceListError::DeviceNotFound)); + }; + + self + .get_device_data(user_id, primary_device_id) + .await? + .ok_or_else(|| { + error!( + "Corrupt database. Missing primary device data for user {}", + user_id + ); + Error::MissingItem + }) + } + /// Required only for migration purposes (determining primary device) pub async fn update_device_login_time( &self, 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 @@ -201,14 +201,21 @@ .ok_or_else(|| tonic::Status::not_found("user not found"))?; let identity_info = IdentityInfo::try_from(identifier)?; - let identity = Some(Identity { identity_info: Some(identity_info), }); + let primary_device_data = self + .db_client + .get_primary_device_data(&message.user_id) + .await + .map_err(handle_db_error)?; + let primary_device_keys = primary_device_data.device_key_info; + let response = Response::new(KeyserverKeysResponse { keyserver_info, identity, + primary_device_identity_info: Some(primary_device_keys.into()), }); return Ok(response); diff --git a/shared/protos/identity_auth.proto b/shared/protos/identity_auth.proto --- a/shared/protos/identity_auth.proto +++ b/shared/protos/identity_auth.proto @@ -109,6 +109,7 @@ message KeyserverKeysResponse { optional OutboundKeyInfo keyserver_info = 1; Identity identity = 2; + identity.unauth.IdentityKeyInfo primary_device_identity_info = 3; } // GetOutboundKeysForUser diff --git a/web/protobufs/identity-auth-structs.cjs b/web/protobufs/identity-auth-structs.cjs --- a/web/protobufs/identity-auth-structs.cjs +++ b/web/protobufs/identity-auth-structs.cjs @@ -1617,7 +1617,8 @@ proto.identity.auth.KeyserverKeysResponse.toObject = function(includeInstance, msg) { var f, obj = { keyserverInfo: (f = msg.getKeyserverInfo()) && proto.identity.auth.OutboundKeyInfo.toObject(includeInstance, f), - identity: (f = msg.getIdentity()) && proto.identity.auth.Identity.toObject(includeInstance, f) + identity: (f = msg.getIdentity()) && proto.identity.auth.Identity.toObject(includeInstance, f), + primaryDeviceIdentityInfo: (f = msg.getPrimaryDeviceIdentityInfo()) && identity_unauth_pb.IdentityKeyInfo.toObject(includeInstance, f) }; if (includeInstance) { @@ -1664,6 +1665,11 @@ reader.readMessage(value,proto.identity.auth.Identity.deserializeBinaryFromReader); msg.setIdentity(value); break; + case 3: + var value = new identity_unauth_pb.IdentityKeyInfo; + reader.readMessage(value,identity_unauth_pb.IdentityKeyInfo.deserializeBinaryFromReader); + msg.setPrimaryDeviceIdentityInfo(value); + break; default: reader.skipField(); break; @@ -1709,6 +1715,14 @@ proto.identity.auth.Identity.serializeBinaryToWriter ); } + f = message.getPrimaryDeviceIdentityInfo(); + if (f != null) { + writer.writeMessage( + 3, + f, + identity_unauth_pb.IdentityKeyInfo.serializeBinaryToWriter + ); + } }; @@ -1786,6 +1800,43 @@ }; +/** + * optional identity.unauth.IdentityKeyInfo primary_device_identity_info = 3; + * @return {?proto.identity.unauth.IdentityKeyInfo} + */ +proto.identity.auth.KeyserverKeysResponse.prototype.getPrimaryDeviceIdentityInfo = function() { + return /** @type{?proto.identity.unauth.IdentityKeyInfo} */ ( + jspb.Message.getWrapperField(this, identity_unauth_pb.IdentityKeyInfo, 3)); +}; + + +/** + * @param {?proto.identity.unauth.IdentityKeyInfo|undefined} value + * @return {!proto.identity.auth.KeyserverKeysResponse} returns this +*/ +proto.identity.auth.KeyserverKeysResponse.prototype.setPrimaryDeviceIdentityInfo = function(value) { + return jspb.Message.setWrapperField(this, 3, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.identity.auth.KeyserverKeysResponse} returns this + */ +proto.identity.auth.KeyserverKeysResponse.prototype.clearPrimaryDeviceIdentityInfo = function() { + return this.setPrimaryDeviceIdentityInfo(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.identity.auth.KeyserverKeysResponse.prototype.hasPrimaryDeviceIdentityInfo = function() { + return jspb.Message.getField(this, 3) != null; +}; + + diff --git a/web/protobufs/identity-auth-structs.cjs.flow b/web/protobufs/identity-auth-structs.cjs.flow --- a/web/protobufs/identity-auth-structs.cjs.flow +++ b/web/protobufs/identity-auth-structs.cjs.flow @@ -156,6 +156,11 @@ hasIdentity(): boolean; clearIdentity(): KeyserverKeysResponse; + getPrimaryDeviceIdentityInfo(): identityStructs.IdentityKeyInfo | void; + setPrimaryDeviceIdentityInfo(value?: identityStructs.IdentityKeyInfo): KeyserverKeysResponse; + hasPrimaryDeviceIdentityInfo(): boolean; + clearPrimaryDeviceIdentityInfo(): KeyserverKeysResponse; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): KeyserverKeysResponseObject; static toObject(includeInstance: boolean, msg: KeyserverKeysResponse): KeyserverKeysResponseObject; @@ -167,6 +172,7 @@ export type KeyserverKeysResponseObject = { keyserverInfo?: OutboundKeyInfoObject, identity: ?IdentityObject, + primaryDeviceIdentityInfo: ?identityStructs.IdentityKeyInfoObject, }; declare export class OutboundKeysForUserResponse extends Message {