diff --git a/keyserver/addons/rust-node-addon/src/identity_client/login.rs b/keyserver/addons/rust-node-addon/src/identity_client/login.rs index 0a7158b30..ce1451efe 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/login.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/login.rs @@ -1,80 +1,84 @@ use super::*; use comm_opaque2::client::Login; use identity_client::{OpaqueLoginFinishRequest, OpaqueLoginStartRequest}; use tracing::debug; #[napi] #[instrument(skip_all)] pub async fn login_user( username: String, password: String, signed_identity_keys_blob: SignedIdentityKeysBlob, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, ) -> Result { debug!("Attempting to login user: {}", username); // Set up the gRPC client that will be used to talk to the Identity service let channel = get_identity_service_channel().await?; let mut identity_client = IdentityClientServiceClient::new(channel); // Start OPAQUE registration and send initial registration request let mut client_login = Login::new(); let opaque_login_request = client_login .start(&password) - .map_err(|_| Error::from_status(Status::GenericFailure))?; + .map_err(|_| Error::from_reason("Failed to create opaque login request"))?; let login_start_request = OpaqueLoginStartRequest { opaque_login_request, username: username, device_key_upload: Some(DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: signed_identity_keys_blob.payload, payload_signature: signed_identity_keys_blob.signature, social_proof: None, }), content_upload: Some(PreKey { pre_key: content_prekey, pre_key_signature: content_prekey_signature, }), notif_upload: Some(PreKey { pre_key: notif_prekey, pre_key_signature: notif_prekey_signature, }), onetime_content_prekeys: content_one_time_keys, onetime_notif_prekeys: notif_one_time_keys, }), }; + debug!("Starting login to identity service"); let login_start_response = identity_client .login_password_user_start(login_start_request) .await .map_err(handle_grpc_error)? .into_inner(); + debug!("Received login response from identity service"); let opaque_login_upload = client_login .finish(&login_start_response.opaque_login_response) - .map_err(|_| Error::from_status(Status::GenericFailure))?; + .map_err(|_| Error::from_reason("Failed to finish opaque login request"))?; let login_finish_request = OpaqueLoginFinishRequest { session_id: login_start_response.session_id, opaque_login_upload, }; + debug!("Attempting to finalize opaque login exchange with identity service"); let login_finish_response = identity_client .login_password_user_finish(login_finish_request) .await .map_err(handle_grpc_error)? .into_inner(); + debug!("Finished login with identity service"); let user_info = UserLoginInfo { user_id: login_finish_response.user_id, access_token: login_finish_response.access_token, }; Ok(user_info) } diff --git a/keyserver/src/user/login.js b/keyserver/src/user/login.js index 74640efb7..2c451913d 100644 --- a/keyserver/src/user/login.js +++ b/keyserver/src/user/login.js @@ -1,199 +1,200 @@ // @flow import type { Account as OlmAccount } from '@commapp/olm'; import type { QueryResults } from 'mysql'; import { getRustAPI } from 'rust-node-addon'; import type { OLMOneTimeKeys } from 'lib/types/crypto-types'; import { getCommConfig } from 'lib/utils/comm-config.js'; import { ServerError } from 'lib/utils/errors.js'; import { values } from 'lib/utils/objects.js'; import { SQL, dbQuery } from '../database/database.js'; import { getMessageForException } from '../responders/utils.js'; import { fetchCallUpdateOlmAccount } from '../updaters/olm-account-updater.js'; import { validateAccountPrekey } from '../utils/olm-utils.js'; type UserCredentials = { +username: string, +password: string }; type IdentityInfo = { +userId: string, +accessToken: string }; const userIDMetadataKey = 'user_id'; const accessTokenMetadataKey = 'access_token'; export type AccountKeysSet = { +identityKeys: string, +prekey: string, +prekeySignature: string, +oneTimeKey: $ReadOnlyArray, }; function getOneTimeKeyValues(keyBlob: string): $ReadOnlyArray { const content: OLMOneTimeKeys = JSON.parse(keyBlob); const keys: $ReadOnlyArray = values(content.curve25519); return keys; } function retrieveAccountKeysSet(account: OlmAccount): AccountKeysSet { const identityKeys = account.identity_keys(); validateAccountPrekey(account); const prekeyMap = JSON.parse(account.prekey()).curve25519; const [prekey] = values(prekeyMap); const prekeySignature = account.prekey_signature(); if (!prekeySignature || !prekey) { throw new ServerError('invalid_prekey'); } if (getOneTimeKeyValues(account.one_time_keys()).length < 10) { account.generate_one_time_keys(10); } const oneTimeKey = getOneTimeKeyValues(account.one_time_keys()); return { identityKeys, oneTimeKey, prekey, prekeySignature }; } // After register or login is successful function markKeysAsPublished(account: OlmAccount) { account.mark_prekey_as_published(); account.mark_keys_as_published(); } async function fetchIdentityInfo(): Promise { const versionQuery = SQL` SELECT data FROM metadata WHERE name IN (${userIDMetadataKey}, ${accessTokenMetadataKey}) `; const [[userId, accessToken]] = await dbQuery(versionQuery); if (!userId || !accessToken) { return null; } return { userId, accessToken }; } function saveIdentityInfo(userInfo: IdentityInfo): Promise { const updateQuery = SQL` REPLACE INTO metadata (name, data) VALUES (${userIDMetadataKey}, ${userInfo.userId}), (${accessTokenMetadataKey}, ${userInfo.accessToken}) `; return dbQuery(updateQuery); } async function verifyUserLoggedIn(): Promise { const result = await fetchIdentityInfo(); if (result) { return result; } const identityInfo = await registerOrLogin(); await saveIdentityInfo(identityInfo); return identityInfo; } async function registerOrLogin(): Promise { const rustAPIPromise = getRustAPI(); const userInfo = await getCommConfig({ folder: 'secrets', name: 'user_credentials', }); if (!userInfo) { throw new ServerError('missing_user_credentials'); } const { identityKeys: notificationsIdentityKeys, prekey: notificationsPrekey, prekeySignature: notificationsPrekeySignature, oneTimeKey: notificationsOneTimeKey, } = await fetchCallUpdateOlmAccount('notifications', retrieveAccountKeysSet); const contentAccountCallback = async (account: OlmAccount) => { const { identityKeys: contentIdentityKeys, oneTimeKey, prekey, prekeySignature, } = await retrieveAccountKeysSet(account); const identityKeysBlob = { primaryIdentityPublicKeys: JSON.parse(contentIdentityKeys), notificationIdentityPublicKeys: JSON.parse(notificationsIdentityKeys), }; const identityKeysBlobPayload = JSON.stringify(identityKeysBlob); const signedIdentityKeysBlob = { payload: identityKeysBlobPayload, signature: account.sign(identityKeysBlobPayload), }; return { signedIdentityKeysBlob, oneTimeKey, prekey, prekeySignature, }; }; const [ rustAPI, { signedIdentityKeysBlob, prekey: contentPrekey, prekeySignature: contentPrekeySignature, oneTimeKey: contentOneTimeKey, }, ] = await Promise.all([ rustAPIPromise, fetchCallUpdateOlmAccount('content', contentAccountCallback), ]); try { const identity_info = await rustAPI.loginUser( userInfo.username, userInfo.password, signedIdentityKeysBlob, contentPrekey, contentPrekeySignature, notificationsPrekey, notificationsPrekeySignature, contentOneTimeKey, notificationsOneTimeKey, ); await Promise.all([ fetchCallUpdateOlmAccount('content', markKeysAsPublished), fetchCallUpdateOlmAccount('notifications', markKeysAsPublished), ]); return identity_info; } catch (e) { + console.warn('Failed to login user: ' + getMessageForException(e)); try { const identity_info = await rustAPI.registerUser( userInfo.username, userInfo.password, signedIdentityKeysBlob, contentPrekey, contentPrekeySignature, notificationsPrekey, notificationsPrekeySignature, contentOneTimeKey, notificationsOneTimeKey, ); await Promise.all([ fetchCallUpdateOlmAccount('content', markKeysAsPublished), fetchCallUpdateOlmAccount('notifications', markKeysAsPublished), ]); return identity_info; } catch (err) { console.warn('Failed to register user: ' + getMessageForException(err)); throw new ServerError('identity_auth_failed'); } } } export { verifyUserLoggedIn };