Skip to content

Commit

Permalink
[Keyserver] Attempt login to identity service
Browse files Browse the repository at this point in the history
Summary:
Have keyserver attempt to login with identity service
at start up. Save userId and access token.

Part of https://linear.app/comm/issue/ENG-3978

Depends on D8404

Test Plan:
```
# start docker
# start localstack
comm-dev services start

# start identity service
(cd services/identity && RUST_LOG=debug cargo run -- server &)

# start keyserver
cd keyserver && yarn dev

# assert user was written to database
awslocal dynamodb scan --table-name identity-users

# assert that credentials were written to keyserver's metadata table
```

Reviewers: ashoat, varun

Reviewed By: ashoat

Subscribers: tomek

Differential Revision: https://phab.comm.dev/D8405
  • Loading branch information
jonringer-comm committed Jul 17, 2023
1 parent 0955edc commit 5092f6e
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 2 deletions.
9 changes: 9 additions & 0 deletions keyserver/src/keyserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
multimediaUploadResponder,
uploadDownloadResponder,
} from './uploads/uploads.js';
import { verifyUserLoggedIn } from './user/login.js';
import { initENSCache } from './utils/ens-cache.js';
import {
prefetchAllURLFacts,
Expand Down Expand Up @@ -58,6 +59,14 @@ import {
// in https://github.com/remy/nodemon/issues/751
process.exit(2);
}

// Allow login to be optional until staging environment is available
try {
await verifyUserLoggedIn();
} catch (e) {
console.warn('failed_identity_login');
}

const cpuCount = os.cpus().length;
for (let i = 0; i < cpuCount; i++) {
cluster.fork();
Expand Down
2 changes: 1 addition & 1 deletion keyserver/src/updaters/olm-account-updater.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const olmAccountUpdateRetryDelay = 200;

async function fetchCallUpdateOlmAccount<T>(
olmAccountType: 'content' | 'notifications',
callback: (account: OlmAccount, picklingKey: string) => Promise<T>,
callback: (account: OlmAccount, picklingKey: string) => Promise<T> | T,
): Promise<T> {
const isContent = olmAccountType === 'content';
let retriesLeft = maxOlmAccountUpdateRetriesCount;
Expand Down
199 changes: 199 additions & 0 deletions keyserver/src/user/login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// @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<string>,
};

function getOneTimeKeyValues(keyBlob: string): $ReadOnlyArray<string> {
const content: OLMOneTimeKeys = JSON.parse(keyBlob);
const keys: $ReadOnlyArray<string> = 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<?IdentityInfo> {
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<QueryResults> {
const updateQuery = SQL`
REPLACE INTO metadata (name, data)
VALUES (${userIDMetadataKey}, ${userInfo.userId}),
(${accessTokenMetadataKey}, ${userInfo.accessToken})
`;

return dbQuery(updateQuery);
}

async function verifyUserLoggedIn(): Promise<IdentityInfo> {
const result = await fetchIdentityInfo();

if (result) {
return result;
}

const identityInfo = await registerOrLogin();
await saveIdentityInfo(identityInfo);
return identityInfo;
}

async function registerOrLogin(): Promise<IdentityInfo> {
const rustAPIPromise = getRustAPI();

const userInfo = await getCommConfig<UserCredentials>({
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) {
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 };
2 changes: 1 addition & 1 deletion keyserver/src/utils/olm-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function getOlmUtility(): OlmUtility {
return cachedOLMUtility;
}

async function validateAccountPrekey(account: OlmAccount): Promise<void> {
function validateAccountPrekey(account: OlmAccount) {
const currentDate = new Date();
const lastPrekeyPublishDate = new Date(account.last_prekey_publish_time());

Expand Down

0 comments on commit 5092f6e

Please sign in to comment.