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 @@ -35,6 +35,8 @@ type UpdateUserAvatarResponse, } from 'lib/types/avatar-types.js'; import type { + ReservedUsernameMessage, + AccountOwnershipResponse, IdentityKeysBlob, SignedIdentityKeysBlob, } from 'lib/types/crypto-types.js'; @@ -113,6 +115,7 @@ fetchKnownUserInfos, fetchLoggedInUserInfo, fetchUserIDForEthereumAddress, + fetchUsername, } from '../fetchers/user-fetchers.js'; import { createNewAnonymousCookie, @@ -121,6 +124,7 @@ } from '../session/cookies.js'; import { verifyClientSupported } from '../session/version.js'; import type { Viewer } from '../session/viewer.js'; +import { accountOwnershipStatement } from '../shared/message-statements.js'; import { accountUpdater, checkAndSendVerificationEmail, @@ -129,6 +133,7 @@ updateUserSettings, updateUserAvatar, } from '../updaters/account-updaters.js'; +import { fetchOlmAccount } from '../updaters/olm-account-updater.js'; import { userSubscriptionUpdater } from '../updaters/user-subscription-updaters.js'; import { viewerAcknowledgmentUpdater } from '../updaters/viewer-acknowledgment-updater.js'; import { getOlmUtility } from '../utils/olm-utils.js'; @@ -813,6 +818,41 @@ ); } +export const accountOwnershipResponseValidator: TInterface = + tShape({ + message: t.String, + signature: t.String, + }); + +async function accountOwnershipResponder( + viewer: Viewer, +): Promise { + const promises = {}; + promises.username = fetchUsername(viewer.userID); + promises.accountInfo = fetchOlmAccount('content'); + const { username, accountInfo } = await promiseAll(promises); + + if (!username) { + throw new ServerError('invalid_credentials'); + } + + const issuedAt = new Date().toISOString(); + const reservedUsernameMessage: ReservedUsernameMessage = { + statement: accountOwnershipStatement, + payload: username, + issuedAt, + }; + const message = JSON.stringify(reservedUsernameMessage); + const signature = accountInfo.account.sign(message); + const response = { message, signature }; + + return validateOutput( + viewer.platformDetails, + accountOwnershipResponseValidator, + response, + ); +} + export { userSubscriptionUpdateResponder, passwordUpdateResponder, @@ -827,4 +867,5 @@ updateUserSettingsResponder, policyAcknowledgmentResponder, updateUserAvatarResponder, + accountOwnershipResponder, }; diff --git a/keyserver/src/shared/message-statements.js b/keyserver/src/shared/message-statements.js --- a/keyserver/src/shared/message-statements.js +++ b/keyserver/src/shared/message-statements.js @@ -6,4 +6,11 @@ const removeReservedUsernameStatement = 'Remove the following username from reserved list'; -export { addReservedUsernamesStatement, removeReservedUsernameStatement }; +const accountOwnershipStatement = + 'This user is the owner of the following username'; + +export { + addReservedUsernamesStatement, + removeReservedUsernameStatement, + accountOwnershipStatement, +}; diff --git a/lib/types/crypto-types.js b/lib/types/crypto-types.js --- a/lib/types/crypto-types.js +++ b/lib/types/crypto-types.js @@ -44,6 +44,11 @@ +issuedAt: string, }; +export type AccountOwnershipResponse = { + +message: string, + +signature: string, +}; + export const olmEncryptedMessageTypes = Object.freeze({ PREKEY: 0, TEXT: 1,