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 @@ -26,6 +26,18 @@ username: string, password: string, ) => Promise; + +logInPasswordUser: ( + username: string, + password: string, + keyPayload: string, + keyPayloadSignature: string, + contentPrekey: string, + contentPrekeySignature: string, + notifPrekey: string, + notifPrekeySignature: string, + contentOneTimeKeys: Array, + notifOneTimeKeys: Array, + ) => Promise; } export type IdentityServiceAuthLayer = { @@ -55,3 +67,12 @@ }; export const ONE_TIME_KEYS_NUMBER = 10; + +export const DeviceType = Object.freeze({ + KEYSERVER: 0, + WEB: 1, + IOS: 2, + ANDROID: 3, + WINDOWS: 4, + MAC_OS: 5, +}); 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 @@ -117,6 +117,33 @@ const { userID, accessToken } = JSON.parse(registrationResult); return { accessToken, userID, username }; }, + logInPasswordUser: async ( + username: string, + password: string, + keyPayload: string, + keyPayloadSignature: string, + contentPrekey: string, + contentPrekeySignature: string, + notifPrekey: string, + notifPrekeySignature: string, + contentOneTimeKeys: Array, + notifOneTimeKeys: Array, + ) => { + const loginResult = await commRustModule.logInPasswordUser( + username, + password, + keyPayload, + keyPayloadSignature, + contentPrekey, + contentPrekeySignature, + notifPrekey, + notifPrekeySignature, + contentOneTimeKeys, + notifOneTimeKeys, + ); + const { userID, accessToken } = JSON.parse(loginResult); + return { accessToken, userID, username }; + }, }; }, [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,16 +1,29 @@ // @flow +import { Login } from '@commapp/opaque-ke-wasm'; + import identityServiceConfig from 'lib/facts/identity-service.js'; import type { IdentityServiceAuthLayer, IdentityServiceClient, OutboundKeyInfoResponse, + IdentityAuthResult, } from 'lib/types/identity-service-types.js'; +import { DeviceType } 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'; +import { + DeviceKeyUpload, + Empty, + IdentityKeyInfo, + OpaqueLoginFinishRequest, + OpaqueLoginStartRequest, + Prekey, +} from '../protobufs/identity-unauth-structs.cjs'; import * as IdentityUnauthClient from '../protobufs/identity-unauth.cjs'; class IdentityServiceClientWrapper implements IdentityServiceClient { @@ -79,6 +92,101 @@ await this.authClient.deleteUser(new Empty()); }; + logInPasswordUser: ( + username: string, + password: string, + keyPayload: string, + keyPayloadSignature: string, + contentPrekey: string, + contentPrekeySignature: string, + notifPrekey: string, + notifPrekeySignature: string, + contentOneTimeKeys: Array, + notifOneTimeKeys: Array, + ) => Promise = async ( + username: string, + password: string, + keyPayload: string, + keyPayloadSignature: string, + contentPrekey: string, + contentPrekeySignature: string, + notifPrekey: string, + notifPrekeySignature: string, + contentOneTimeKeys: Array, + notifOneTimeKeys: Array, + ) => { + const client = this.unauthClient; + if (!client) { + throw new Error('Identity service client is not initialized'); + } + await initOpaque(); + const opaqueLogin = new Login(); + const startRequestBytes = opaqueLogin.start(password); + + const identityKeyInfo = new IdentityKeyInfo(); + identityKeyInfo.setPayload(keyPayload); + identityKeyInfo.setPayloadSignature(keyPayloadSignature); + + const contentPrekeyUpload = new Prekey(); + contentPrekeyUpload.setPrekey(contentPrekey); + contentPrekeyUpload.setPrekeySignature(contentPrekeySignature); + + const notifPrekeyUpload = new Prekey(); + notifPrekeyUpload.setPrekey(notifPrekey); + notifPrekeyUpload.setPrekeySignature(notifPrekeySignature); + + const deviceKeyUpload = new DeviceKeyUpload(); + deviceKeyUpload.setDeviceKeyInfo(identityKeyInfo); + deviceKeyUpload.setContentUpload(contentPrekeyUpload); + deviceKeyUpload.setNotifUpload(notifPrekeyUpload); + deviceKeyUpload.setOneTimeContentPrekeysList(contentOneTimeKeys); + deviceKeyUpload.setOneTimeNotifPrekeysList(notifOneTimeKeys); + deviceKeyUpload.setDeviceType(DeviceType.WEB); + + const loginStartRequest = new OpaqueLoginStartRequest(); + loginStartRequest.setUsername(username); + loginStartRequest.setOpaqueLoginRequest(startRequestBytes); + loginStartRequest.setDeviceKeyUpload(deviceKeyUpload); + + let loginStartResponse; + try { + loginStartResponse = + await client.logInPasswordUserStart(loginStartRequest); + } catch (e) { + console.log('Error calling logInPasswordUserStart:', e); + throw new Error( + `logInPasswordUserStart RPC failed: ${ + getMessageForException(e) ?? 'unknown' + }`, + ); + } + const finishRequestBytes = opaqueLogin.finish( + loginStartResponse.getOpaqueLoginResponse_asU8(), + ); + + const loginFinishRequest = new OpaqueLoginFinishRequest(); + loginFinishRequest.setSessionId(loginStartResponse.getSessionId()); + loginFinishRequest.setOpaqueLoginUpload(finishRequestBytes); + + let loginFinishResponse; + try { + loginFinishResponse = + await client.logInPasswordUserFinish(loginFinishRequest); + } catch (e) { + console.log('Error calling logInPasswordUserFinish:', e); + throw new Error( + `logInPasswordUserFinish RPC failed: ${ + getMessageForException(e) ?? 'unknown' + }`, + ); + } + + const userID = loginFinishResponse.getUserId(); + const accessToken = loginFinishResponse.getAccessToken(); + + return { userID, accessToken, username }; + }; + getKeyserverKeys: (keyserverID: string) => Promise = async (keyserverID: string) => { const client = this.authClient;