diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js --- a/lib/types/identity-service-types.js +++ b/lib/types/identity-service-types.js @@ -135,6 +135,10 @@ // updating device list is possible only on Native // web cannot be a primary device, so there's no need to expose it to JS +updateDeviceList?: (newDeviceList: SignedDeviceList) => Promise<void>; + +uploadKeysForRegisteredDeviceAndLogIn: ( + userID: string, + nonceChallengeResponse: SignedMessage, + ) => Promise<IdentityAuthResult>; } export type IdentityServiceAuthLayer = { @@ -184,6 +188,15 @@ export const signedDeviceListHistoryValidator: TList<Array<SignedDeviceList>> = t.list(signedDeviceListValidator); +export type NonceChallenge = { + +nonce: string, +}; + +export type SignedMessage = { + +message: string, + +signature: string, +}; + export const ONE_TIME_KEYS_NUMBER = 10; export const identityDeviceTypes = Object.freeze({ diff --git a/native/identity-service/identity-service-context-provider.react.js b/native/identity-service/identity-service-context-provider.react.js --- a/native/identity-service/identity-service-context-provider.react.js +++ b/native/identity-service/identity-service-context-provider.react.js @@ -12,6 +12,7 @@ import { type SignedDeviceList, signedDeviceListHistoryValidator, + type SignedMessage, type DeviceOlmOutboundKeys, deviceOlmOutboundKeysValidator, type IdentityServiceClient, @@ -450,6 +451,50 @@ return validatedResult; }, + uploadKeysForRegisteredDeviceAndLogIn: async ( + userID: string, + nonceChallengeResponse: SignedMessage, + ) => { + await commCoreModule.initializeCryptoAccount(); + const [ + { blobPayload, signature, primaryIdentityPublicKeys }, + { contentOneTimeKeys, notificationsOneTimeKeys }, + prekeys, + ] = await Promise.all([ + commCoreModule.getUserPublicKey(), + commCoreModule.getOneTimeKeys(ONE_TIME_KEYS_NUMBER), + commCoreModule.validateAndGetPrekeys(), + ]); + const challengeResponse = JSON.stringify(nonceChallengeResponse); + const registrationResult = + await commRustModule.uploadSecondaryDeviceKeysAndLogIn( + userID, + challengeResponse, + blobPayload, + signature, + prekeys.contentPrekey, + prekeys.contentPrekeySignature, + prekeys.notifPrekey, + prekeys.notifPrekeySignature, + getOneTimeKeyValues(contentOneTimeKeys), + getOneTimeKeyValues(notificationsOneTimeKeys), + ); + const { accessToken: token } = JSON.parse(registrationResult); + + const identityAuthResult = { accessToken: token, userID, username: '' }; + const validatedResult = assertWithValidator( + identityAuthResult, + identityAuthResultValidator, + ); + + await commCoreModule.setCommServicesAuthMetadata( + validatedResult.userID, + primaryIdentityPublicKeys.ed25519, + validatedResult.accessToken, + ); + + return validatedResult; + }, generateNonce: commRustModule.generateNonce, getDeviceListHistoryForUser: async ( userID: string, 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 @@ -647,8 +647,6 @@ .put_item() .table_name(devices_table::NAME) .set_item(Some(new_device.into())) - .expression_attribute_names("#user_id", ATTR_USER_ID) - .expression_attribute_names("#item_id", ATTR_ITEM_ID) .send() .await .map_err(|e| { diff --git a/web/grpc/identity-service-client-proxy.js b/web/grpc/identity-service-client-proxy.js --- a/web/grpc/identity-service-client-proxy.js +++ b/web/grpc/identity-service-client-proxy.js @@ -6,6 +6,7 @@ } from 'lib/types/crypto-types.js'; import type { SignedDeviceList, + SignedMessage, IdentityServiceClient, IdentityServiceAuthLayer, DeviceOlmOutboundKeys, @@ -101,6 +102,13 @@ siweSignature: string, ) => Promise<IdentityAuthResult> = this.proxyToWorker('logInWalletUser'); + uploadKeysForRegisteredDeviceAndLogIn: ( + userID: string, + nonceChallengeResponse: SignedMessage, + ) => Promise<IdentityAuthResult> = this.proxyToWorker( + 'uploadKeysForRegisteredDeviceAndLogIn', + ); + generateNonce: () => Promise<string> = this.proxyToWorker('generateNonce'); publishWebPrekeys: (prekeys: SignedPrekeys) => Promise<void> = diff --git a/web/grpc/identity-service-client-wrapper.js b/web/grpc/identity-service-client-wrapper.js --- a/web/grpc/identity-service-client-wrapper.js +++ b/web/grpc/identity-service-client-wrapper.js @@ -11,6 +11,7 @@ import { type SignedDeviceList, signedDeviceListHistoryValidator, + type SignedMessage, type IdentityServiceAuthLayer, type IdentityServiceClient, type DeviceOlmOutboundKeys, @@ -40,6 +41,7 @@ OpaqueLoginStartRequest, Prekey, WalletAuthRequest, + SecondaryDeviceKeysUploadRequest, } from '../protobufs/identity-unauth-structs.cjs'; import * as IdentityUnauthClient from '../protobufs/identity-unauth.cjs'; @@ -423,6 +425,38 @@ return assertWithValidator(identityAuthResult, identityAuthResultValidator); }; + uploadKeysForRegisteredDeviceAndLogIn: ( + ownerUserID: string, + nonceChallengeResponse: SignedMessage, + ) => Promise<IdentityAuthResult> = async ( + ownerUserID, + nonceChallengeResponse, + ) => { + const identityDeviceKeyUpload = await this.getDeviceKeyUpload(); + const deviceKeyUpload = authDeviceKeyUpload(identityDeviceKeyUpload); + const challengeResponse = JSON.stringify(nonceChallengeResponse); + + const request = new SecondaryDeviceKeysUploadRequest(); + request.setUserId(ownerUserID); + request.setChallengeResponse(challengeResponse); + request.setDeviceKeyUpload(deviceKeyUpload); + + let response; + try { + response = + await this.unauthClient.uploadKeysForRegisteredDeviceAndLogIn(request); + } catch (e) { + console.log('Error calling uploadKeysForRegisteredDeviceAndLogIn:', e); + throw new Error(getMessageForException(e) ?? 'unknown'); + } + + const userID = response.getUserId(); + const accessToken = response.getAccessToken(); + const identityAuthResult = { accessToken, userID, username: '' }; + + return assertWithValidator(identityAuthResult, identityAuthResultValidator); + }; + generateNonce: () => Promise<string> = async () => { const result = await this.unauthClient.generateNonce(new Empty()); return result.getNonce();