diff --git a/keyserver/src/creators/account-creator.js b/keyserver/src/creators/account-creator.js --- a/keyserver/src/creators/account-creator.js +++ b/keyserver/src/creators/account-creator.js @@ -247,8 +247,10 @@ viewer.setNewCookie(userViewerData); await setNewSession(viewer, calendarQuery, 0); - - await processAccountCreationCommon(viewer); + await Promise.all([ + viewerAcknowledgmentUpdater(viewer, policyTypes.tosAndPrivacyPolicy), + processAccountCreationCommon(viewer), + ]); ignorePromiseRejections( createAndSendReservedUsernameMessage([ @@ -259,68 +261,17 @@ return id; } -export type ProcessOLMAccountCreationRequest = { - +userID: string, - +username: string, - +walletAddress?: ?string, - +calendarQuery: CalendarQuery, - +deviceTokenUpdateRequest?: ?DeviceTokenUpdateRequest, - +platformDetails: PlatformDetails, - +signedIdentityKeysBlob: SignedIdentityKeysBlob, -}; -// Note: `processOLMAccountCreation(...)` assumes that the validity of -// `ProcessOLMAccountCreationRequest` was checked at call site. -async function processOLMAccountCreation( - viewer: Viewer, - request: ProcessOLMAccountCreationRequest, -): Promise { - const { calendarQuery, signedIdentityKeysBlob } = request; - await verifyCalendarQueryThreadIDs(calendarQuery); - - const time = Date.now(); - const deviceToken = request.deviceTokenUpdateRequest - ? request.deviceTokenUpdateRequest.deviceToken - : viewer.deviceToken; - const newUserRow = [ - request.userID, - request.username, - request.walletAddress, - time, - ]; - const newUserQuery = SQL` - INSERT INTO users(id, username, ethereum_address, creation_time) - VALUES ${[newUserRow]} - `; - const [userViewerData] = await Promise.all([ - createNewUserCookie(request.userID, { - platformDetails: request.platformDetails, - deviceToken, - signedIdentityKeysBlob, - }), - deleteCookie(viewer.cookieID), - dbQuery(newUserQuery), - ]); - viewer.setNewCookie(userViewerData); - - await setNewSession(viewer, calendarQuery, 0); - - await processAccountCreationCommon(viewer); -} - async function processAccountCreationCommon(viewer: Viewer) { const admin = await thisKeyserverAdmin(); - await Promise.all([ - updateThread( - createScriptViewer(admin.id), - { - threadID: genesis().id, - changes: { newMemberIDs: [viewer.userID] }, - }, - { forceAddMembers: true, silenceMessages: true, ignorePermissions: true }, - ), - viewerAcknowledgmentUpdater(viewer, policyTypes.tosAndPrivacyPolicy), - ]); + await updateThread( + createScriptViewer(admin.id), + { + threadID: genesis().id, + changes: { newMemberIDs: [viewer.userID] }, + }, + { forceAddMembers: true, silenceMessages: true, ignorePermissions: true }, + ); const [privateThreadResult, ashoatThreadResult] = await Promise.all([ createPrivateThread(viewer), @@ -375,4 +326,8 @@ await rustAPI.addReservedUsernames(stringifiedMessage, signature); } -export { createAccount, processSIWEAccountCreation, processOLMAccountCreation }; +export { + createAccount, + processSIWEAccountCreation, + processAccountCreationCommon, +}; diff --git a/keyserver/src/responders/user-responders.js b/keyserver/src/responders/user-responders.js --- a/keyserver/src/responders/user-responders.js +++ b/keyserver/src/responders/user-responders.js @@ -11,6 +11,7 @@ baseLegalPolicies, policies, policyTypeValidator, + policyTypes, } from 'lib/facts/policies.js'; import { mixedRawThreadInfoValidator } from 'lib/permissions/minimally-encoded-raw-thread-info-validators.js'; import { hasMinCodeVersion } from 'lib/shared/version-utils.js'; @@ -104,14 +105,13 @@ verifyCalendarQueryThreadIDs, } from './entry-responders.js'; import { - processOLMAccountCreation, + processAccountCreationCommon, createAccount, processSIWEAccountCreation, } from '../creators/account-creator.js'; import { createOlmSession, persistFreshOlmSession, - createAndPersistOlmSession, } from '../creators/olm-session-creator.js'; import { dbQuery, SQL } from '../database/database.js'; import { deleteAccount } from '../deleters/account-deleters.js'; @@ -308,7 +308,7 @@ +signedIdentityKeysBlob?: ?SignedIdentityKeysBlob, +initialNotificationsEncryptedMessage?: string, +pickledContentOlmSession?: string, - +cookieHasBeenSet?: boolean, + +shouldMarkPoliciesAsAcceptedAfterCookieCreation?: boolean, }; async function processSuccessfulLogin( @@ -325,20 +325,30 @@ signedIdentityKeysBlob, initialNotificationsEncryptedMessage, pickledContentOlmSession, - cookieHasBeenSet, + shouldMarkPoliciesAsAcceptedAfterCookieCreation, } = params; + // Olm sessions have to be created before createNewUserCookie is called, + // to avoid propagating a user cookie in case session creation fails + const olmNotifSession = await (async () => { + if (initialNotificationsEncryptedMessage && signedIdentityKeysBlob) { + return await createOlmSession( + initialNotificationsEncryptedMessage, + 'notifications', + ); + } + return null; + })(); + const newServerTime = Date.now(); const deviceToken = deviceTokenUpdateRequest ? deviceTokenUpdateRequest.deviceToken : viewer.deviceToken; + const setNewCookiePromise = (async () => { - if (cookieHasBeenSet) { - return; - } const [userViewerData] = await Promise.all([ createNewUserCookie(userID, { - platformDetails: platformDetails, + platformDetails, deviceToken, socialProof, signedIdentityKeysBlob, @@ -347,8 +357,18 @@ ]); viewer.setNewCookie(userViewerData); })(); + const policiesCheckAndUpdate = (async () => { + if (shouldMarkPoliciesAsAcceptedAfterCookieCreation) { + await setNewCookiePromise; + await viewerAcknowledgmentUpdater( + viewer, + policyTypes.tosAndPrivacyPolicy, + ); + } + return await fetchNotAcknowledgedPolicies(userID, baseLegalPolicies); + })(); const [notAcknowledgedPolicies] = await Promise.all([ - fetchNotAcknowledgedPolicies(userID, baseLegalPolicies), + policiesCheckAndUpdate, setNewCookiePromise, ]); @@ -375,14 +395,10 @@ if (calendarQuery) { await setNewSession(viewer, calendarQuery, newServerTime); } - const olmNotifSessionPromise = (async () => { - if ( - viewer.cookieID && - initialNotificationsEncryptedMessage && - signedIdentityKeysBlob - ) { - await createAndPersistOlmSession( - initialNotificationsEncryptedMessage, + const persistOlmNotifSessionPromise = (async () => { + if (olmNotifSession && viewer.cookieID) { + await persistFreshOlmSession( + olmNotifSession, 'notifications', viewer.cookieID, ); @@ -391,7 +407,7 @@ // `pickledContentOlmSession` is created in `keyserverAuthResponder(...)` in // order to authenticate the user. Here, we simply persist the session if it // exists. - const olmContentSessionPromise = (async () => { + const persistOlmContentSessionPromise = (async () => { if (viewer.cookieID && pickledContentOlmSession) { await persistFreshOlmSession( pickledContentOlmSession, @@ -426,8 +442,8 @@ entriesPromise, fetchKnownUserInfos(viewer), fetchLoggedInUserInfo(viewer), - olmNotifSessionPromise, - olmContentSessionPromise, + persistOlmNotifSessionPromise, + persistOlmContentSessionPromise, ]); const rawEntryInfos = entriesResult ? entriesResult.rawEntryInfos : null; @@ -728,8 +744,6 @@ const { userID, deviceID, - deviceTokenUpdateRequest, - platformDetails, initialContentEncryptedMessage, initialNotificationsEncryptedMessage, doNotRegister, @@ -744,6 +758,7 @@ getContentSigningKey(), verifyUserLoggedIn(), getRustAPI(), + verifyCalendarQueryThreadIDs(calendarQuery), ]); if (!existingUsername && doNotRegister) { throw new ServerError('account_does_not_exist'); @@ -780,18 +795,16 @@ throw new ServerError('invalid_identity_keys_blob'); } - // 3. Create content olm session. (The notif session is not required for auth - // and will be created later in `processSuccessfulLogin(...)`.) + // 3. Create content olm session. (The notif session was introduced first and + // as such is created in legacy auth responders as well. It's factored out + // into in the shared utility `processSuccessfulLogin(...)`.) const pickledContentOlmSessionPromise = createOlmSession( initialContentEncryptedMessage, 'content', identityKeys.primaryIdentityPublicKeys.curve25519, ); - // 4. Create account with call to `processOLMAccountCreation(...)` - // if username does not correspond to an existing user. If we successfully - // create a new account, we set `cookieHasBeenSet` to true to avoid - // creating a new cookie again in `processSuccessfulLogin`. + // 4. Create account if username does not correspond to an existing user. const signedIdentityKeysBlob: SignedIdentityKeysBlob = { payload: inboundKeysForUser.payload, signature: inboundKeysForUser.payloadSignature, @@ -800,16 +813,19 @@ if (existingUsername) { return; } - const olmAccountCreationRequest = { + + const time = Date.now(); + const newUserRow = [ userID, - username: username, - walletAddress: inboundKeysForUser.walletAddress, - signedIdentityKeysBlob, - calendarQuery, - deviceTokenUpdateRequest, - platformDetails, - }; - await processOLMAccountCreation(viewer, olmAccountCreationRequest); + username, + inboundKeysForUser.walletAddress, + time, + ]; + const newUserQuery = SQL` + INSERT INTO users(id, username, ethereum_address, creation_time) + VALUES ${[newUserRow]} + `; + await dbQuery(newUserQuery); })(); const [pickledContentOlmSession] = await Promise.all([ @@ -818,7 +834,7 @@ ]); // 5. Complete login with call to `processSuccessfulLogin(...)`. - return await processSuccessfulLogin({ + const result = await processSuccessfulLogin({ viewer, platformDetails: request.platformDetails, deviceTokenUpdateRequest: request.deviceTokenUpdateRequest, @@ -828,8 +844,16 @@ signedIdentityKeysBlob, initialNotificationsEncryptedMessage, pickledContentOlmSession, - cookieHasBeenSet: !existingUsername, + shouldMarkPoliciesAsAcceptedAfterCookieCreation: !existingUsername, }); + + // 6. Create threads with call to `processAccountCreationCommon(...)`, + // if the account has just been registered. + if (!existingUsername) { + await processAccountCreationCommon(viewer); + } + + return result; } export const updatePasswordRequestInputValidator: TInterface =