diff --git a/lib/actions/user-actions.js b/lib/actions/user-actions.js --- a/lib/actions/user-actions.js +++ b/lib/actions/user-actions.js @@ -478,6 +478,27 @@ [identityClient], ); } +function useIdentityWalletLogIn(): ( + walletAddress: string, + siweMessage: string, + siweSignature: string, +) => Promise { + const client = React.useContext(IdentityClientContext); + const identityClient = client?.identityClient; + return React.useCallback( + (walletAddress, siweMessage, siweSignature) => { + if (!identityClient) { + throw new Error('Identity service client is not initialized'); + } + return identityClient.logInWalletUser( + walletAddress, + siweMessage, + siweSignature, + ); + }, + [identityClient], + ); +} const logInActionTypes = Object.freeze({ started: 'LOG_IN_STARTED', @@ -772,6 +793,7 @@ logIn as logInRawAction, identityLogInActionTypes, useIdentityPasswordLogIn, + useIdentityWalletLogIn, useLogIn, logInActionTypes, useLogOut, 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 @@ -67,6 +67,11 @@ userID: string, ) => Promise; +generateNonce: () => Promise; + +logInWalletUser: ( + walletAddress: string, + siweMessage: string, + siweSignature: string, + ) => Promise; } export type IdentityServiceAuthLayer = { diff --git a/native/cpp/CommonCpp/CryptoTools/CryptoModule.cpp b/native/cpp/CommonCpp/CryptoTools/CryptoModule.cpp --- a/native/cpp/CommonCpp/CryptoTools/CryptoModule.cpp +++ b/native/cpp/CommonCpp/CryptoTools/CryptoModule.cpp @@ -133,7 +133,7 @@ this->keys.identityKeys.begin(), this->keys.identityKeys.end()}; } -std::string CryptoModule::getOneTimeKeys(size_t oneTimeKeysAmount) { +std::string CryptoModule::generateAndGetOneTimeKeys(size_t oneTimeKeysAmount) { this->generateOneTimeKeys(oneTimeKeysAmount); size_t publishedOneTimeKeys = this->publishOneTimeKeys(); if (publishedOneTimeKeys != oneTimeKeysAmount) { @@ -148,6 +148,14 @@ this->keys.oneTimeKeys.begin(), this->keys.oneTimeKeys.end()}; } +std::string CryptoModule::getOneTimeKeys(size_t oneTimeKeysAmount) { + // check how many unpublished one time keys we have + // generate enough to meet the threshold + // call publish method (this method also handles writing the otks to a buffer) + return std::string{ + this->keys.oneTimeKeys.begin(), this->keys.oneTimeKeys.end()}; +} + std::uint8_t CryptoModule::getNumPrekeys() { return reinterpret_cast(this->getOlmAccount())->num_prekeys; } 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 @@ -264,6 +264,47 @@ identityAuthResultValidator, ); }, + logInWalletUser: async ( + walletAddress: string, + siweMessage: string, + siweSignature: string, + ) => { + await commCoreModule.initializeCryptoAccount(); + const [ + { blobPayload, signature }, + notificationsOneTimeKeys, + primaryOneTimeKeys, + prekeys, + ] = await Promise.all([ + commCoreModule.getUserPublicKey(), + commCoreModule.getNotificationsOneTimeKeys(ONE_TIME_KEYS_NUMBER), // TODO: replace this with new method that only generates keys if needed + commCoreModule.getPrimaryOneTimeKeys(ONE_TIME_KEYS_NUMBER), // TODO: replace this with new method that only generates keys if needed + commCoreModule.validateAndGetPrekeys(), + ]); + const loginResult = await commRustModule.logInWalletUser( + siweMessage, + siweSignature, + blobPayload, + signature, + prekeys.contentPrekey, + prekeys.contentPrekeySignature, + prekeys.notifPrekey, + prekeys.notifPrekeySignature, + getOneTimeKeyValues(primaryOneTimeKeys), + getOneTimeKeyValues(notificationsOneTimeKeys), + ); + const { userID, accessToken: token } = JSON.parse(loginResult); + const identityAuthResult = { + accessToken: token, + userID, + username: walletAddress, + }; + + return assertWithValidator( + identityAuthResult, + identityAuthResultValidator, + ); + }, generateNonce: commRustModule.generateNonce, }), [getAuthMetadata], 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 @@ -28,6 +28,7 @@ OpaqueLoginFinishRequest, OpaqueLoginStartRequest, Prekey, + WalletLoginRequest, } from '../protobufs/identity-unauth-structs.cjs'; import * as IdentityUnauthClient from '../protobufs/identity-unauth.cjs'; @@ -282,6 +283,44 @@ return assertWithValidator(identityAuthResult, identityAuthResultValidator); }; + logInWalletUser: ( + walletAddress: string, + siweMessage: string, + siweSignature: string, + ) => Promise = async ( + walletAddress: string, + siweMessage: string, + siweSignature: string, + ) => { + const client = this.unauthClient; + if (!client) { + throw new Error('Identity service client is not initialized'); + } + + const identityDeviceKeyUpload = await this.getDeviceKeyUpload(); + + const deviceKeyUpload = grpcDeviceKeyUpload(identityDeviceKeyUpload); + + const loginRequest = new WalletLoginRequest(); + loginRequest.setSiweMessage(siweMessage); + loginRequest.setSiweSignature(siweSignature); + loginRequest.setDeviceKeyUpload(deviceKeyUpload); + + let loginResponse; + try { + loginResponse = await client.logInWalletUser(loginRequest); + } catch (e) { + console.log('Error calling logInWalletUser:', e); + throw new Error(getMessageForException(e) ?? 'unknown'); + } + + const userID = loginResponse.getUserId(); + const accessToken = loginResponse.getAccessToken(); + const identityAuthResult = { accessToken, userID, username: walletAddress }; + + return assertWithValidator(identityAuthResult, identityAuthResultValidator); + }; + generateNonce: () => Promise = async () => { const result = await this.unauthClient.generateNonce(new Empty()); return result.getNonce();