diff --git a/keyserver/src/endpoints.js b/keyserver/src/endpoints.js --- a/keyserver/src/endpoints.js +++ b/keyserver/src/endpoints.js @@ -146,6 +146,7 @@ updatePasswordRequestInputValidator, updateUserSettingsInputValidator, claimUsernameResponder, + claimUsernameRequestInputValidator, } from './responders/user-responders.js'; import { codeVerificationResponder, @@ -459,7 +460,7 @@ }, claim_username: { responder: claimUsernameResponder, - inputValidator: ignoredArgumentValidator, + inputValidator: claimUsernameRequestInputValidator, policies: [], }, update_user_avatar: { 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 @@ -25,6 +25,7 @@ UpdatePasswordRequest, UpdateUserSettingsRequest, PolicyAcknowledgmentRequest, + ClaimUsernameRequest, ClaimUsernameResponse, } from 'lib/types/account-types.js'; import { @@ -947,24 +948,50 @@ return await updateUserAvatar(viewer, request); } +export const claimUsernameRequestInputValidator: TInterface = + tShape({ + username: t.String, + password: tPassword, + }); + async function claimUsernameResponder( viewer: Viewer, + request: ClaimUsernameRequest, ): Promise { - const [username, accountInfo] = await Promise.all([ - fetchUsername(viewer.userID), + const username = request.username; + + const userQuery = SQL` + SELECT id, hash, username + FROM users + WHERE LCASE(username) = LCASE(${request.username}) + `; + const [[userResult], accountInfo] = await Promise.all([ + dbQuery(userQuery), fetchOlmAccount('content'), ]); - if (!username) { + if (userResult.length === 0) { throw new ServerError('invalid_credentials'); } + const userRow = userResult[0]; + + if (!userRow.hash) { + throw new ServerError('invalid_parameters'); + } + + if (!bcrypt.compareSync(request.password, userRow.hash)) { + throw new ServerError('invalid_credentials'); + } + + const userID = userRow.id; + const issuedAt = new Date().toISOString(); const reservedUsernameMessage: ReservedUsernameMessage = { statement: 'This user is the owner of the following username and user ID', payload: { username, - userID: viewer.userID, + userID, }, issuedAt, }; diff --git a/lib/actions/user-actions.js b/lib/actions/user-actions.js --- a/lib/actions/user-actions.js +++ b/lib/actions/user-actions.js @@ -29,6 +29,7 @@ LegacyRegisterInfo, UpdateUserSettingsRequest, PolicyAcknowledgmentRequest, + ClaimUsernameRequest, ClaimUsernameResponse, LogInRequest, KeyserverAuthResult, @@ -211,16 +212,15 @@ success: 'CLAIM_USERNAME_SUCCESS', failed: 'CLAIM_USERNAME_FAILED', }); -const claimUsernameCallSingleKeyserverEndpointOptions = { timeout: 500 }; const claimUsername = ( callKeyserverEndpoint: CallKeyserverEndpoint, - ): (() => Promise) => - async () => { - const requests = { [authoritativeKeyserverID()]: {} }; - const responses = await callKeyserverEndpoint('claim_username', requests, { - ...claimUsernameCallSingleKeyserverEndpointOptions, - }); + ): (( + claimUsernameRequest: ClaimUsernameRequest, + ) => Promise) => + async (claimUsernameRequest: ClaimUsernameRequest) => { + const requests = { [authoritativeKeyserverID()]: claimUsernameRequest }; + const responses = await callKeyserverEndpoint('claim_username', requests); const response = responses[authoritativeKeyserverID()]; return { message: response.message, @@ -228,7 +228,9 @@ }; }; -function useClaimUsername(): () => Promise { +function useClaimUsername(): ( + claimUsernameRequest: ClaimUsernameRequest, +) => Promise { return useKeyserverCall(claimUsername); } diff --git a/lib/types/account-types.js b/lib/types/account-types.js --- a/lib/types/account-types.js +++ b/lib/types/account-types.js @@ -295,6 +295,11 @@ default_user_notifications: t.maybe(t.enums.of(notificationTypeValues)), }); +export type ClaimUsernameRequest = { + +username: string, + +password: string, +}; + export type ClaimUsernameResponse = { +message: string, +signature: string,