diff --git a/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs b/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs new file mode 100644 index 000000000..4c597d21d --- /dev/null +++ b/keyserver/addons/rust-node-addon/src/identity_client/login_user.rs @@ -0,0 +1,137 @@ +use super::*; + +#[napi] +#[instrument(skip_all)] +async fn login_user_pake( + user_id: String, + signing_public_key: String, + password: String, + session_initialization_info: HashMap, +) -> Result { + let channel = Channel::from_static(&IDENTITY_SERVICE_SOCKET_ADDR) + .connect() + .await + .map_err(|_| { + Error::new( + Status::GenericFailure, + "Unable to connect to identity service".to_string(), + ) + })?; + let token: MetadataValue<_> = AUTH_TOKEN + .parse() + .map_err(|_| Error::from_status(Status::GenericFailure))?; + let mut identity_client = + IdentityServiceClient::with_interceptor(channel, |mut req: Request<()>| { + req.metadata_mut().insert("authorization", token.clone()); + Ok(req) + }); + + // Create a LoginRequest channel and use ReceiverStream to turn the + // MPSC receiver into a Stream for outbound messages + let (tx, rx) = mpsc::channel(1); + let stream = ReceiverStream::new(rx); + let request = Request::new(stream); + + // `response` is the Stream for inbound messages + let mut response = identity_client + .login_user(request) + .await + .map_err(|_| Error::from_status(Status::GenericFailure))? + .into_inner(); + + // Start PAKE login on client and send initial login request to Identity + // service + let mut client_rng = OsRng; + let client_login_start_result = pake_login_start(&mut client_rng, &password)?; + let pake_credential_request = + client_login_start_result.message.serialize().map_err(|e| { + error!("Could not serialize credential request: {}", e); + Error::new(Status::GenericFailure, e.to_string()) + })?; + let login_request = LoginRequest { + data: Some(PakeLoginRequest(PakeLoginRequestStruct { + data: Some(PakeCredentialRequestAndUserId( + PakeCredentialRequestAndUserIdStruct { + user_id, + signing_public_key, + pake_credential_request, + session_initialization_info: Some(SessionInitializationInfo { + info: session_initialization_info, + }), + }, + )), + })), + }; + + send_to_mpsc(tx.clone(), login_request).await?; + + // Handle responses from Identity service sequentially, making sure we get + // messages in the correct order + + // Finish PAKE login; send final login request to Identity service + let message = response.message().await.map_err(|e| { + error!("Received an error from inbound message stream: {}", e); + Error::from_status(Status::GenericFailure) + })?; + handle_login_credential_response( + message, + client_login_start_result.state, + tx, + ) + .await?; + + // Return access token + let message = response.message().await.map_err(|e| { + error!("Received an error from inbound message stream: {}", e); + Error::from_status(Status::GenericFailure) + })?; + handle_login_token_response(message) +} + +async fn handle_login_credential_response( + message: Option, + client_login: ClientLogin, + tx: mpsc::Sender, +) -> Result<(), Status> { + if let Some(LoginResponse { + data: + Some(LoginPakeLoginResponse(PakeLoginResponseStruct { + data: Some(PakeCredentialResponse(credential_response_bytes)), + })), + }) = message + { + let credential_finalization_bytes = + pake_login_finish(&credential_response_bytes, client_login)? + .serialize() + .map_err(|e| { + error!("Could not serialize credential request: {}", e); + Error::from_status(Status::GenericFailure) + })?; + let login_request = LoginRequest { + data: Some(PakeLoginRequest(PakeLoginRequestStruct { + data: Some(LoginPakeCredentialFinalization( + credential_finalization_bytes, + )), + })), + }; + send_to_mpsc(tx, login_request).await + } else { + Err(handle_unexpected_response(message)) + } +} + +fn handle_login_token_response( + message: Option, +) -> Result { + if let Some(LoginResponse { + data: + Some(LoginPakeLoginResponse(PakeLoginResponseStruct { + data: Some(AccessToken(access_token)), + })), + }) = message + { + Ok(access_token) + } else { + Err(handle_unexpected_response(message)) + } +} diff --git a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs index 19e245362..d7a761d39 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs @@ -1,94 +1,102 @@ pub mod delete_user; +pub mod login_user; pub mod register_user; pub mod identity { tonic::include_proto!("identity"); } use comm_opaque::Cipher; use identity::identity_service_client::IdentityServiceClient; use identity::{ + login_request::Data::PakeLoginRequest, + login_response::Data::PakeLoginResponse as LoginPakeLoginResponse, + pake_login_request::Data::PakeCredentialFinalization as LoginPakeCredentialFinalization, + pake_login_request::Data::PakeCredentialRequestAndUserId, pake_login_response::Data::AccessToken, pake_login_response::Data::PakeCredentialResponse, registration_request::Data::PakeCredentialFinalization as RegistrationPakeCredentialFinalization, registration_request::Data::PakeRegistrationRequestAndUserId, registration_request::Data::PakeRegistrationUploadAndCredentialRequest, registration_response::Data::PakeLoginResponse as RegistrationPakeLoginResponse, registration_response::Data::PakeRegistrationResponse, DeleteUserRequest, + LoginRequest, LoginResponse, + PakeCredentialRequestAndUserId as PakeCredentialRequestAndUserIdStruct, + PakeLoginRequest as PakeLoginRequestStruct, PakeLoginResponse as PakeLoginResponseStruct, PakeRegistrationRequestAndUserId as PakeRegistrationRequestAndUserIdStruct, PakeRegistrationUploadAndCredentialRequest as PakeRegistrationUploadAndCredentialRequestStruct, RegistrationRequest, RegistrationResponse as RegistrationResponseMessage, SessionInitializationInfo, }; use lazy_static::lazy_static; use napi::bindgen_prelude::*; use opaque_ke::{ ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters, ClientLoginStartResult, ClientRegistration, ClientRegistrationFinishParameters, CredentialFinalization, CredentialResponse, RegistrationResponse, RegistrationUpload, }; use rand::{rngs::OsRng, CryptoRng, Rng}; use std::collections::HashMap; use std::env::var; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; use tonic::{metadata::MetadataValue, transport::Channel, Request}; use tracing::{error, instrument}; lazy_static! { pub static ref IDENTITY_SERVICE_SOCKET_ADDR: String = var("COMM_IDENTITY_SERVICE_SOCKET_ADDR") .unwrap_or_else(|_| "https://[::1]:50051".to_string()); pub static ref AUTH_TOKEN: String = var("COMM_IDENTITY_SERVICE_AUTH_TOKEN") .unwrap_or_else(|_| "test".to_string()); } fn handle_unexpected_response(message: Option) -> Error { error!("Received an unexpected message: {:?}", message); Error::from_status(Status::GenericFailure) } async fn send_to_mpsc(tx: mpsc::Sender, request: T) -> Result<()> { if let Err(e) = tx.send(request).await { error!("Response was dropped: {}", e); return Err(Error::from_status(Status::GenericFailure)); } Ok(()) } fn pake_login_start( rng: &mut (impl Rng + CryptoRng), password: &str, ) -> Result> { ClientLogin::::start( rng, password.as_bytes(), ClientLoginStartParameters::default(), ) .map_err(|e| { error!("Failed to start PAKE login: {}", e); Error::from_status(Status::GenericFailure) }) } fn pake_login_finish( credential_response_bytes: &[u8], client_login: ClientLogin, ) -> Result> { client_login .finish( CredentialResponse::deserialize(credential_response_bytes).map_err( |e| { error!("Could not deserialize credential response bytes: {}", e); Error::from_status(Status::GenericFailure) }, )?, ClientLoginFinishParameters::default(), ) .map_err(|e| { error!("Failed to finish PAKE login: {}", e); Error::from_status(Status::GenericFailure) }) .map(|res| res.message) } diff --git a/keyserver/src/responders/user-responders.js b/keyserver/src/responders/user-responders.js index ef22e9e0b..8cc1e7dbe 100644 --- a/keyserver/src/responders/user-responders.js +++ b/keyserver/src/responders/user-responders.js @@ -1,632 +1,647 @@ // @flow import invariant from 'invariant'; +import { getRustAPI } from 'rust-node-addon'; import { ErrorTypes, SiweMessage } from 'siwe'; import t from 'tcomb'; import bcrypt from 'twin-bcrypt'; import { baseLegalPolicies, policies } from 'lib/facts/policies.js'; import { hasMinCodeVersion } from 'lib/shared/version-utils.js'; import type { ResetPasswordRequest, LogOutResponse, DeleteAccountRequest, RegisterResponse, RegisterRequest, LogInResponse, LogInRequest, UpdatePasswordRequest, UpdateUserSettingsRequest, PolicyAcknowledgmentRequest, } from 'lib/types/account-types.js'; import { userSettingsTypes, notificationTypeValues, logInActionSources, } from 'lib/types/account-types.js'; import type { IdentityKeysBlob, SignedIdentityKeysBlob, } from 'lib/types/crypto-types.js'; import type { CalendarQuery } from 'lib/types/entry-types.js'; import { defaultNumberPerThread } from 'lib/types/message-types.js'; import type { SIWEAuthRequest, SIWEMessage, SIWESocialProof, } from 'lib/types/siwe-types.js'; import type { SubscriptionUpdateRequest, SubscriptionUpdateResponse, } from 'lib/types/subscription-types.js'; import type { PasswordUpdate } from 'lib/types/user-types.js'; import { ServerError } from 'lib/utils/errors.js'; import { values } from 'lib/utils/objects.js'; import { promiseAll } from 'lib/utils/promises.js'; import { getPublicKeyFromSIWEStatement, isValidSIWEMessage, isValidSIWEStatementWithPublicKey, primaryIdentityPublicKeyRegex, } from 'lib/utils/siwe-utils.js'; import { tShape, tPlatformDetails, tPassword, tEmail, tOldValidUsername, tRegex, } from 'lib/utils/validation-utils.js'; import { entryQueryInputValidator, newEntryQueryInputValidator, normalizeCalendarQuery, verifyCalendarQueryThreadIDs, } from './entry-responders.js'; +import { handleAsyncPromise } from './handlers.js'; import { createAccount, processSIWEAccountCreation, } from '../creators/account-creator.js'; import { dbQuery, SQL } from '../database/database.js'; import { deleteAccount } from '../deleters/account-deleters.js'; import { deleteCookie } from '../deleters/cookie-deleters.js'; import { checkAndInvalidateSIWENonceEntry } from '../deleters/siwe-nonce-deleters.js'; import { fetchEntryInfos } from '../fetchers/entry-fetchers.js'; import { fetchMessageInfos } from '../fetchers/message-fetchers.js'; import { fetchNotAcknowledgedPolicies } from '../fetchers/policy-acknowledgment-fetchers.js'; import { fetchThreadInfos } from '../fetchers/thread-fetchers.js'; import { fetchKnownUserInfos, fetchLoggedInUserInfo, fetchUserIDForEthereumAddress, } from '../fetchers/user-fetchers.js'; import { createNewAnonymousCookie, createNewUserCookie, setNewSession, } from '../session/cookies.js'; import type { Viewer } from '../session/viewer.js'; import { accountUpdater, checkAndSendVerificationEmail, checkAndSendPasswordResetEmail, updatePassword, updateUserSettings, } from '../updaters/account-updaters.js'; import { userSubscriptionUpdater } from '../updaters/user-subscription-updaters.js'; import { viewerAcknowledgmentUpdater } from '../updaters/viewer-acknowledgment-updater.js'; import { getOLMUtility } from '../utils/olm-utils.js'; import type { OLMUtility } from '../utils/olm-utils.js'; import { validateInput } from '../utils/validation-utils.js'; const subscriptionUpdateRequestInputValidator = tShape({ threadID: t.String, updatedFields: tShape({ pushNotifs: t.maybe(t.Boolean), home: t.maybe(t.Boolean), }), }); async function userSubscriptionUpdateResponder( viewer: Viewer, input: any, ): Promise { const request: SubscriptionUpdateRequest = input; await validateInput(viewer, subscriptionUpdateRequestInputValidator, request); const threadSubscription = await userSubscriptionUpdater(viewer, request); return { threadSubscription }; } const accountUpdateInputValidator = tShape({ updatedFields: tShape({ email: t.maybe(tEmail), password: t.maybe(tPassword), }), currentPassword: tPassword, }); async function passwordUpdateResponder( viewer: Viewer, input: any, ): Promise { const request: PasswordUpdate = input; await validateInput(viewer, accountUpdateInputValidator, request); await accountUpdater(viewer, request); } async function sendVerificationEmailResponder(viewer: Viewer): Promise { await validateInput(viewer, null, null); await checkAndSendVerificationEmail(viewer); } const resetPasswordRequestInputValidator = tShape({ usernameOrEmail: t.union([tEmail, tOldValidUsername]), }); async function sendPasswordResetEmailResponder( viewer: Viewer, input: any, ): Promise { const request: ResetPasswordRequest = input; await validateInput(viewer, resetPasswordRequestInputValidator, request); await checkAndSendPasswordResetEmail(request); } async function logOutResponder(viewer: Viewer): Promise { await validateInput(viewer, null, null); if (viewer.loggedIn) { const [anonymousViewerData] = await Promise.all([ createNewAnonymousCookie({ platformDetails: viewer.platformDetails, deviceToken: viewer.deviceToken, }), deleteCookie(viewer.cookieID), ]); viewer.setNewCookie(anonymousViewerData); } return { currentUserInfo: { id: viewer.id, anonymous: true, }, }; } const deleteAccountRequestInputValidator = tShape({ password: t.maybe(tPassword), }); async function accountDeletionResponder( viewer: Viewer, input: any, ): Promise { const request: DeleteAccountRequest = input; await validateInput(viewer, deleteAccountRequestInputValidator, request); const result = await deleteAccount(viewer, request); invariant(result, 'deleteAccount should return result if handed request'); return result; } const deviceTokenUpdateRequestInputValidator = tShape({ deviceType: t.maybe(t.enums.of(['ios', 'android'])), deviceToken: t.String, }); const signedIdentityKeysBlobValidator = tShape({ payload: t.String, signature: t.String, }); const registerRequestInputValidator = tShape({ username: t.String, email: t.maybe(tEmail), password: tPassword, calendarQuery: t.maybe(newEntryQueryInputValidator), deviceTokenUpdateRequest: t.maybe(deviceTokenUpdateRequestInputValidator), platformDetails: tPlatformDetails, // We include `primaryIdentityPublicKey` to avoid breaking // old clients, but we no longer do anything with it. primaryIdentityPublicKey: t.maybe(tRegex(primaryIdentityPublicKeyRegex)), signedIdentityKeysBlob: t.maybe(signedIdentityKeysBlobValidator), }); async function accountCreationResponder( viewer: Viewer, input: any, ): Promise { const request: RegisterRequest = input; await validateInput(viewer, registerRequestInputValidator, request); const { signedIdentityKeysBlob } = request; if (signedIdentityKeysBlob) { const identityKeys: IdentityKeysBlob = JSON.parse( signedIdentityKeysBlob.payload, ); const olmUtil: OLMUtility = getOLMUtility(); try { olmUtil.ed25519_verify( identityKeys.primaryIdentityPublicKeys.ed25519, signedIdentityKeysBlob.payload, signedIdentityKeysBlob.signature, ); } catch (e) { throw new ServerError('invalid_signature'); } } return await createAccount(viewer, request); } type ProcessSuccessfulLoginParams = { +viewer: Viewer, +input: any, +userID: string, +calendarQuery: ?CalendarQuery, +socialProof?: ?SIWESocialProof, +signedIdentityKeysBlob?: ?SignedIdentityKeysBlob, }; async function processSuccessfulLogin( params: ProcessSuccessfulLoginParams, ): Promise { const { viewer, input, userID, calendarQuery, socialProof, signedIdentityKeysBlob, } = params; const request: LogInRequest = input; const newServerTime = Date.now(); const deviceToken = request.deviceTokenUpdateRequest ? request.deviceTokenUpdateRequest.deviceToken : viewer.deviceToken; const [userViewerData, notAcknowledgedPolicies] = await Promise.all([ createNewUserCookie(userID, { platformDetails: request.platformDetails, deviceToken, socialProof, signedIdentityKeysBlob, }), fetchNotAcknowledgedPolicies(userID, baseLegalPolicies), deleteCookie(viewer.cookieID), ]); viewer.setNewCookie(userViewerData); if ( notAcknowledgedPolicies.length && hasMinCodeVersion(viewer.platformDetails, 181) ) { const currentUserInfo = await fetchLoggedInUserInfo(viewer); return { notAcknowledgedPolicies, currentUserInfo: currentUserInfo, rawMessageInfos: [], truncationStatuses: {}, userInfos: [], rawEntryInfos: [], serverTime: 0, cookieChange: { threadInfos: {}, userInfos: [], }, }; } if (calendarQuery) { await setNewSession(viewer, calendarQuery, newServerTime); } const threadCursors = {}; for (const watchedThreadID of request.watchedIDs) { threadCursors[watchedThreadID] = null; } const messageSelectionCriteria = { threadCursors, joinedThreads: true }; const [ threadsResult, messagesResult, entriesResult, userInfos, currentUserInfo, ] = await Promise.all([ fetchThreadInfos(viewer), fetchMessageInfos(viewer, messageSelectionCriteria, defaultNumberPerThread), calendarQuery ? fetchEntryInfos(viewer, [calendarQuery]) : undefined, fetchKnownUserInfos(viewer), fetchLoggedInUserInfo(viewer), ]); const rawEntryInfos = entriesResult ? entriesResult.rawEntryInfos : null; const response: LogInResponse = { currentUserInfo, rawMessageInfos: messagesResult.rawMessageInfos, truncationStatuses: messagesResult.truncationStatuses, serverTime: newServerTime, userInfos: values(userInfos), cookieChange: { threadInfos: threadsResult.threadInfos, userInfos: [], }, }; if (rawEntryInfos) { return { ...response, rawEntryInfos, }; } return response; } const logInRequestInputValidator = tShape({ username: t.maybe(t.String), usernameOrEmail: t.maybe(t.union([tEmail, tOldValidUsername])), password: tPassword, watchedIDs: t.list(t.String), calendarQuery: t.maybe(entryQueryInputValidator), deviceTokenUpdateRequest: t.maybe(deviceTokenUpdateRequestInputValidator), platformDetails: tPlatformDetails, source: t.maybe(t.enums.of(values(logInActionSources))), // We include `primaryIdentityPublicKey` to avoid breaking // old clients, but we no longer do anything with it. primaryIdentityPublicKey: t.maybe(tRegex(primaryIdentityPublicKeyRegex)), signedIdentityKeysBlob: t.maybe(signedIdentityKeysBlobValidator), }); async function logInResponder( viewer: Viewer, input: any, ): Promise { await validateInput(viewer, logInRequestInputValidator, input); const request: LogInRequest = input; + let identityKeys: ?IdentityKeysBlob; const { signedIdentityKeysBlob } = request; if (signedIdentityKeysBlob) { - const identityKeys: IdentityKeysBlob = JSON.parse( - signedIdentityKeysBlob.payload, - ); + identityKeys = JSON.parse(signedIdentityKeysBlob.payload); const olmUtil: OLMUtility = getOLMUtility(); try { olmUtil.ed25519_verify( identityKeys.primaryIdentityPublicKeys.ed25519, signedIdentityKeysBlob.payload, signedIdentityKeysBlob.signature, ); } catch (e) { throw new ServerError('invalid_signature'); } } const calendarQuery = request.calendarQuery ? normalizeCalendarQuery(request.calendarQuery) : null; const promises = {}; if (calendarQuery) { promises.verifyCalendarQueryThreadIDs = verifyCalendarQueryThreadIDs(calendarQuery); } const username = request.username ?? request.usernameOrEmail; if (!username) { if (hasMinCodeVersion(viewer.platformDetails, 150)) { throw new ServerError('invalid_credentials'); } else { throw new ServerError('invalid_parameters'); } } const userQuery = SQL` SELECT id, hash, username FROM users WHERE LCASE(username) = LCASE(${username}) `; promises.userQuery = dbQuery(userQuery); + promises.rustAPI = getRustAPI(); const { userQuery: [userResult], + rustAPI, } = await promiseAll(promises); if (userResult.length === 0) { if (hasMinCodeVersion(viewer.platformDetails, 150)) { throw new ServerError('invalid_credentials'); } else { throw new ServerError('invalid_parameters'); } } const userRow = userResult[0]; if (!userRow.hash || !bcrypt.compareSync(request.password, userRow.hash)) { throw new ServerError('invalid_credentials'); } const id = userRow.id.toString(); + + if (identityKeys && signedIdentityKeysBlob) { + handleAsyncPromise( + rustAPI.loginUserPake( + id, + identityKeys.primaryIdentityPublicKeys.ed25519, + request.password, + signedIdentityKeysBlob, + ), + ); + } + return await processSuccessfulLogin({ viewer, input, userID: id, calendarQuery, signedIdentityKeysBlob, }); } const siweAuthRequestInputValidator = tShape({ signature: t.String, message: t.String, calendarQuery: entryQueryInputValidator, deviceTokenUpdateRequest: t.maybe(deviceTokenUpdateRequestInputValidator), platformDetails: tPlatformDetails, watchedIDs: t.list(t.String), signedIdentityKeysBlob: t.maybe(signedIdentityKeysBlobValidator), }); async function siweAuthResponder( viewer: Viewer, input: any, ): Promise { await validateInput(viewer, siweAuthRequestInputValidator, input); const request: SIWEAuthRequest = input; const { message, signature, deviceTokenUpdateRequest, platformDetails, signedIdentityKeysBlob, } = request; const calendarQuery = normalizeCalendarQuery(request.calendarQuery); // 1. Ensure that `message` is a well formed Comm SIWE Auth message. const siweMessage: SIWEMessage = new SiweMessage(message); if (!isValidSIWEMessage(siweMessage)) { throw new ServerError('invalid_parameters'); } // 2. Ensure that the `nonce` exists in the `siwe_nonces` table // AND hasn't expired. If those conditions are met, delete the entry to // ensure that the same `nonce` can't be re-used in a future request. const wasNonceCheckedAndInvalidated = await checkAndInvalidateSIWENonceEntry( siweMessage.nonce, ); if (!wasNonceCheckedAndInvalidated) { throw new ServerError('invalid_parameters'); } // 3. Validate SIWEMessage signature and handle possible errors. try { await siweMessage.validate(signature); } catch (error) { if (error === ErrorTypes.EXPIRED_MESSAGE) { // Thrown when the `expirationTime` is present and in the past. throw new ServerError('expired_message'); } else if (error === ErrorTypes.INVALID_SIGNATURE) { // Thrown when the `validate()` function can't verify the message. throw new ServerError('invalid_signature'); } else if (error === ErrorTypes.MALFORMED_SESSION) { // Thrown when some required field is missing. throw new ServerError('malformed_session'); } else { throw new ServerError('unknown_error'); } } // 4. Pull `primaryIdentityPublicKey` out from SIWEMessage `statement`. // We expect it to be included for BOTH native and web clients. const { statement } = siweMessage; const primaryIdentityPublicKey = statement && isValidSIWEStatementWithPublicKey(statement) ? getPublicKeyFromSIWEStatement(statement) : null; if (!primaryIdentityPublicKey) { throw new ServerError('invalid_siwe_statement_public_key'); } // 5. Verify `signedIdentityKeysBlob.payload` with included `signature` // if `signedIdentityKeysBlob` was included in the `SIWEAuthRequest`. let identityKeys: ?IdentityKeysBlob; if (signedIdentityKeysBlob) { identityKeys = JSON.parse(signedIdentityKeysBlob.payload); const olmUtil: OLMUtility = getOLMUtility(); try { olmUtil.ed25519_verify( identityKeys.primaryIdentityPublicKeys.ed25519, signedIdentityKeysBlob.payload, signedIdentityKeysBlob.signature, ); } catch (e) { throw new ServerError('invalid_signature'); } } // 6. Ensure that `primaryIdentityPublicKeys.ed25519` matches SIWE // statement `primaryIdentityPublicKey` if `identityKeys` exists. if ( identityKeys && identityKeys.primaryIdentityPublicKeys.ed25519 !== primaryIdentityPublicKey ) { throw new ServerError('primary_public_key_mismatch'); } // 7. Construct `SIWESocialProof` object with the stringified // SIWEMessage and the corresponding signature. const socialProof: SIWESocialProof = { siweMessage: siweMessage.toMessage(), siweMessageSignature: signature, }; // 8. Create account with call to `processSIWEAccountCreation(...)` // if address does not correspond to an existing user. let userID = await fetchUserIDForEthereumAddress(siweMessage.address); if (!userID) { const siweAccountCreationRequest = { address: siweMessage.address, calendarQuery, deviceTokenUpdateRequest, platformDetails, socialProof, }; userID = await processSIWEAccountCreation( viewer, siweAccountCreationRequest, ); } // 9. Complete login with call to `processSuccessfulLogin(...)`. return await processSuccessfulLogin({ viewer, input, userID, calendarQuery, socialProof, signedIdentityKeysBlob, }); } const updatePasswordRequestInputValidator = tShape({ code: t.String, password: tPassword, watchedIDs: t.list(t.String), calendarQuery: t.maybe(entryQueryInputValidator), deviceTokenUpdateRequest: t.maybe(deviceTokenUpdateRequestInputValidator), platformDetails: tPlatformDetails, }); async function oldPasswordUpdateResponder( viewer: Viewer, input: any, ): Promise { await validateInput(viewer, updatePasswordRequestInputValidator, input); const request: UpdatePasswordRequest = input; if (request.calendarQuery) { request.calendarQuery = normalizeCalendarQuery(request.calendarQuery); } return await updatePassword(viewer, request); } const updateUserSettingsInputValidator = tShape({ name: t.irreducible( userSettingsTypes.DEFAULT_NOTIFICATIONS, x => x === userSettingsTypes.DEFAULT_NOTIFICATIONS, ), data: t.enums.of(notificationTypeValues), }); async function updateUserSettingsResponder( viewer: Viewer, input: any, ): Promise { const request: UpdateUserSettingsRequest = input; await validateInput(viewer, updateUserSettingsInputValidator, request); return await updateUserSettings(viewer, request); } const policyAcknowledgmentRequestInputValidator = tShape({ policy: t.maybe(t.enums.of(policies)), }); async function policyAcknowledgmentResponder( viewer: Viewer, input: any, ): Promise { const request: PolicyAcknowledgmentRequest = input; await validateInput( viewer, policyAcknowledgmentRequestInputValidator, request, ); await viewerAcknowledgmentUpdater(viewer, request.policy); } export { userSubscriptionUpdateResponder, passwordUpdateResponder, sendVerificationEmailResponder, sendPasswordResetEmailResponder, logOutResponder, accountDeletionResponder, accountCreationResponder, logInResponder, siweAuthResponder, oldPasswordUpdateResponder, updateUserSettingsResponder, policyAcknowledgmentResponder, }; diff --git a/lib/types/rust-binding-types.js b/lib/types/rust-binding-types.js index debb832ee..ca2f262ff 100644 --- a/lib/types/rust-binding-types.js +++ b/lib/types/rust-binding-types.js @@ -1,28 +1,36 @@ // @flow +import type { SignedIdentityKeysBlob } from './crypto-types.js'; + type tunnelbrokerOnReceiveCallback = ( err: Error | null, payload: string, ) => mixed; declare class TunnelbrokerClientClass { constructor( deviceId: string, onReceiveCallback: tunnelbrokerOnReceiveCallback, ): TunnelbrokerClientClass; publish(toDeviceId: string, payload: string): Promise; } type RustNativeBindingAPI = { +registerUser: ( userId: string, deviceId: string, username: string, password: string, userPublicKey: string, ) => Promise, + +loginUserPake: ( + userId: string, + signingPublicKey: string, + password: string, + sessionInitializationInfo: SignedIdentityKeysBlob, + ) => Promise, +deleteUser: (userId: string) => Promise, +TunnelbrokerClient: Class, }; export type { RustNativeBindingAPI };