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 @@ -489,6 +489,28 @@ await callServerEndpoint('update_account', passwordUpdate); }; +const changeIdentityUserPasswordActionTypes = Object.freeze({ + started: 'CHANGE_IDENTITY_USER_PASSWORD_STARTED', + success: 'CHANGE_IDENTITY_USER_PASSWORD_SUCCESS', + failed: 'CHANGE_IDENTITY_USER_PASSWORD_FAILED', +}); + +function useChangeIdentityUserPassword(): ( + newPassword: string, +) => Promise { + const client = React.useContext(IdentityClientContext); + const identityClient = client?.identityClient; + return React.useCallback( + newPassword => { + if (!identityClient) { + throw new Error('Identity service client is not initialized'); + } + return identityClient.changePassword(newPassword); + }, + [identityClient], + ); +} + const searchUsersActionTypes = Object.freeze({ started: 'SEARCH_USERS_STARTED', success: 'SEARCH_USERS_SUCCESS', @@ -649,6 +671,8 @@ export { changeKeyserverUserPasswordActionTypes, changeKeyserverUserPassword, + changeIdentityUserPasswordActionTypes, + useChangeIdentityUserPassword, claimUsernameActionTypes, useClaimUsername, useDeleteKeyserverAccount, 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 @@ -22,6 +22,7 @@ export interface IdentityServiceClient { +deleteUser: () => Promise; +getKeyserverKeys: string => Promise; + +changePassword: string => Promise; } export type IdentityServiceAuthLayer = { 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 @@ -64,7 +64,7 @@ return { deleteUser: async () => { const { deviceID, userID, accessToken } = await getAuthMetadata(); - return commRustModule.deleteUser(userID, deviceID, accessToken); + return await commRustModule.deleteUser(userID, deviceID, accessToken); }, getKeyserverKeys: async (keyserverID: string) => { const { deviceID, userID, accessToken } = await getAuthMetadata(); @@ -87,6 +87,15 @@ } return resultObject; }, + changePassword: async (newPassword: string) => { + const { deviceID, userID, accessToken } = await getAuthMetadata(); + return await commRustModule.updatePassword( + userID, + deviceID, + accessToken, + newPassword, + ); + }, }; }, [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 @@ -1,13 +1,17 @@ // @flow +import { Registration } from '@commapp/opaque-ke-wasm'; + import identityServiceConfig from 'lib/facts/identity-service.js'; import type { IdentityServiceAuthLayer, IdentityServiceClient, OutboundKeyInfoResponse, } from 'lib/types/identity-service-types.js'; +import { getMessageForException } from 'lib/utils/errors.js'; import { VersionInterceptor, AuthInterceptor } from './interceptor.js'; +import { initOpaque } from '../crypto/opaque-utils.js'; import * as IdentityAuthClient from '../protobufs/identity-auth-client.cjs'; import * as IdentityAuthStructs from '../protobufs/identity-auth-structs.cjs'; import { Empty } from '../protobufs/identity-unauth-structs.cjs'; @@ -79,6 +83,62 @@ await this.authClient.deleteUser(new Empty()); }; + changePassword: (newPassword: string) => Promise = + async newPassword => { + const client = this.authClient; + if (!client) { + throw new Error('Identity service client is not initialized'); + } + await initOpaque(); + const opaqueRegistration = new Registration(); + const startRequestBytes = opaqueRegistration.start(newPassword); + + const updatePasswordStartRequest = + new IdentityAuthStructs.UpdateUserPasswordStartRequest(); + updatePasswordStartRequest.setOpaqueRegistrationRequest( + startRequestBytes, + ); + + let updatePasswordStartResponse; + try { + updatePasswordStartResponse = await client.updateUserPasswordStart( + updatePasswordStartRequest, + ); + } catch (e) { + console.log('Error calling updateUserPasswordStart:', e); + throw new Error( + `updateUserPasswordStart RPC failed: ${ + getMessageForException(e) ?? 'unknown' + }`, + ); + } + + const finishRequestBytes = opaqueRegistration.finish( + newPassword, + updatePasswordStartResponse.getOpaqueRegistrationResponse_asU8(), + ); + + const updatePasswordFinishRequest = + new IdentityAuthStructs.UpdateUserPasswordFinishRequest(); + updatePasswordFinishRequest.setSessionId( + updatePasswordStartResponse.getSessionId(), + ); + updatePasswordFinishRequest.setOpaqueRegistrationUpload( + finishRequestBytes, + ); + + try { + await client.updateUserPasswordFinish(updatePasswordFinishRequest); + } catch (e) { + console.log('Error calling updateUserPasswordFinish:', e); + throw new Error( + `updateUserPasswordFinish RPC failed: ${ + getMessageForException(e) ?? 'unknown' + }`, + ); + } + }; + getKeyserverKeys: (keyserverID: string) => Promise = async (keyserverID: string) => { const client = this.authClient;