Changeset View
Changeset View
Standalone View
Standalone View
keyserver/src/updaters/olm-session-updater.js
- This file was added.
// @flow | |||||
import type { EncryptResult } from '@commapp/olm'; | |||||
import { ServerError } from 'lib/utils/errors.js'; | |||||
import sleep from 'lib/utils/sleep.js'; | |||||
import { fetchOlmAccount } from './olm-account-updater.js'; | |||||
import { SQL, dbQuery } from '../database/database.js'; | |||||
import { unpickleOlmSession } from '../utils/olm-utils.js'; | |||||
const maxOlmSessionUpdateAttemptTime = 30000; | |||||
const olmSessionUpdateRetryDelay = 50; | |||||
async function encryptAndUpdateOlmSession( | |||||
cookieID: string, | |||||
olmSessionType: 'content' | 'notifications', | |||||
messagesToEncrypt: $ReadOnly<{ [string]: string }>, | |||||
ashoat: If we're going to do encryption of all fields in one pass, should we simplify this function so… | |||||
marcinAuthorUnsubmitted Done Inline ActionsMy opinion on this is that it would be better to have this function taking one message if it was designed to deal specifically with notifications. However signature of this function indicates that it is supposed to work with both types of sessions. I am not sure what kind of scenarios are going to utilise content session, but if they need possibility to encrypt a couple of messages at once if is beneficial to have such API. marcin: My opinion on this is that it would be better to have this function taking one message if it… | |||||
ashoatUnsubmitted Not Done Inline ActionsOkay, we can leave as-is ashoat: Okay, we can leave as-is | |||||
): Promise<{ [string]: EncryptResult }> { | |||||
const isContent = olmSessionType === 'content'; | |||||
const { picklingKey } = await fetchOlmAccount(olmSessionType); | |||||
const olmUpdateAttemptStartTime = Date.now(); | |||||
while ( | |||||
Date.now() - olmUpdateAttemptStartTime < | |||||
maxOlmSessionUpdateAttemptTime | |||||
) { | |||||
const [olmSessionResult] = await dbQuery( | |||||
SQL` | |||||
SELECT version, pickled_olm_session | |||||
FROM olm_sessions | |||||
WHERE cookie_id = ${cookieID} AND is_content = ${isContent} | |||||
`, | |||||
); | |||||
if (olmSessionResult.length === 0) { | |||||
throw new ServerError('missing_olm_session'); | |||||
} | |||||
const [{ version, pickled_olm_session: pickledSession }] = olmSessionResult; | |||||
const session = await unpickleOlmSession(pickledSession, picklingKey); | |||||
const encryptedMessages = {}; | |||||
for (const messageName in messagesToEncrypt) { | |||||
encryptedMessages[messageName] = session.encrypt( | |||||
messagesToEncrypt[messageName], | |||||
); | |||||
} | |||||
const updatedSession = session.pickle(picklingKey); | |||||
const [transactionResult] = await dbQuery( | |||||
SQL` | |||||
START TRANSACTION; | |||||
SELECT version INTO @currentVersion | |||||
FROM olm_sessions | |||||
WHERE cookie_id = ${cookieID} AND is_content = ${isContent} | |||||
FOR UPDATE; | |||||
UPDATE olm_sessions | |||||
SET | |||||
pickled_olm_session = ${updatedSession}, | |||||
version = ${version} + 1 | |||||
WHERE version = ${version} AND is_content = ${isContent} | |||||
AND cookie_id = ${cookieID}; | |||||
COMMIT; | |||||
SELECT @currentVersion AS versionOnUpdateAttempt; | |||||
`, | |||||
{ multipleStatements: true }, | |||||
); | |||||
const selectResult = transactionResult.pop(); | |||||
const [{ versionOnUpdateAttempt }] = selectResult; | |||||
if (version === versionOnUpdateAttempt) { | |||||
return encryptedMessages; | |||||
} | |||||
await sleep(olmSessionUpdateRetryDelay); | |||||
} | |||||
throw new ServerError('max_olm_account_update_retry_exceeded'); | |||||
} | |||||
export { encryptAndUpdateOlmSession }; |
If we're going to do encryption of all fields in one pass, should we simplify this function so that it only takes one message?