diff --git a/keyserver/src/user/identity.js b/keyserver/src/user/identity.js new file mode 100644 index 000000000..bcfb6d59a --- /dev/null +++ b/keyserver/src/user/identity.js @@ -0,0 +1,38 @@ +// @flow + +import type { QueryResults } from 'mysql'; + +import { SQL, dbQuery } from '../database/database.js'; + +const userIDMetadataKey = 'user_id'; +const accessTokenMetadataKey = 'access_token'; + +// This information is minted when registering with identity service +// Naming should reflect the rust-bindings: userId -> user_id +export type IdentityInfo = { +userId: string, +accessToken: string }; + +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: userID.data, accessToken: accessToken.data }; +} + +function saveIdentityInfo(userInfo: IdentityInfo): Promise { + const updateQuery = SQL` + REPLACE INTO metadata (name, data) + VALUES (${userIDMetadataKey}, ${userInfo.userId}), + (${accessTokenMetadataKey}, ${userInfo.accessToken}) + `; + + return dbQuery(updateQuery); +} + +export { fetchIdentityInfo, saveIdentityInfo }; diff --git a/keyserver/src/user/login.js b/keyserver/src/user/login.js index 2c451913d..8ef4393fd 100644 --- a/keyserver/src/user/login.js +++ b/keyserver/src/user/login.js @@ -1,200 +1,175 @@ // @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 { + saveIdentityInfo, + fetchIdentityInfo, + type IdentityInfo, +} from './identity.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 };