From 992e28797f5293ad920d168a0b9ed3d583925eb9 Mon Sep 17 00:00:00 2001 From: Jeremy Letto Date: Sun, 30 Jan 2022 22:09:16 -0600 Subject: [PATCH] Webatrice: support hashed passwords in register and resetPassword (#4549) * support hashed passwords in register and resetPassword * lint * support hashedPasswords for accountActivation * use salt in post-register login step Co-authored-by: Jeremy Letto --- .../src/websocket/commands/SessionCommands.ts | 52 +++++++++++++++---- .../src/websocket/events/SessionEvents.ts | 22 ++++++-- .../src/websocket/utils/passwordHasher.ts | 5 ++ 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/webclient/src/websocket/commands/SessionCommands.ts b/webclient/src/websocket/commands/SessionCommands.ts index 281c6ebf..9ad77f61 100644 --- a/webclient/src/websocket/commands/SessionCommands.ts +++ b/webclient/src/websocket/commands/SessionCommands.ts @@ -139,7 +139,29 @@ export class SessionCommands { switch (raw.responseCode) { case webClient.protobuf.controller.Response.ResponseCode.RespOk: { const passwordSalt = raw['.Response_PasswordSalt.ext']?.passwordSalt; - SessionCommands.login(passwordSalt); + + switch (webClient.options.reason) { + case WebSocketConnectReason.REGISTER: { + SessionCommands.register(passwordSalt); + break; + } + + case WebSocketConnectReason.ACTIVATE_ACCOUNT: { + SessionCommands.activateAccount(passwordSalt); + break; + } + + case WebSocketConnectReason.PASSWORD_RESET: { + SessionCommands.resetPassword(passwordSalt); + break; + } + + case WebSocketConnectReason.LOGIN: + default: { + SessionCommands.login(passwordSalt); + } + } + break; } case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationRequired: { @@ -155,19 +177,24 @@ export class SessionCommands { }); } - static register(): void { + static register(passwordSalt?: string): void { const { userName, password, email, country, realName } = webClient.options as unknown as ServerRegisterParams; - const registerConfig = { + const registerConfig: any = { ...webClient.clientConfig, clientid: 'webatrice', userName, - password, email, country, realName, }; + if (passwordSalt) { + registerConfig.hashedPassword = hashPassword(passwordSalt, password); + } else { + registerConfig.password = password; + } + const CmdRegister = webClient.protobuf.controller.Command_Register.create(registerConfig); const sc = webClient.protobuf.controller.SessionCommand.create({ @@ -176,7 +203,7 @@ export class SessionCommands { webClient.protobuf.sendSessionCommand(sc, raw => { if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespRegistrationAccepted) { - SessionCommands.login(); + SessionCommands.login(passwordSalt); return; } @@ -219,7 +246,7 @@ export class SessionCommands { }); }; - static activateAccount(): void { + static activateAccount(passwordSalt?: string): void { const { userName, token } = webClient.options as unknown as AccountActivationParams; const accountActivationConfig = { @@ -238,7 +265,7 @@ export class SessionCommands { webClient.protobuf.sendSessionCommand(sc, raw => { if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespActivationAccepted) { SessionPersistence.accountActivationSuccess(); - SessionCommands.login(); + SessionCommands.login(passwordSalt); } else { SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Account Activation Failed'); SessionCommands.disconnect(); @@ -311,17 +338,22 @@ export class SessionCommands { }); } - static resetPassword(): void { + static resetPassword(passwordSalt?: string): void { const { userName, token, newPassword } = webClient.options as unknown as ForgotPasswordResetParams; - const forgotPasswordResetConfig = { + const forgotPasswordResetConfig: any = { ...webClient.clientConfig, clientid: 'webatrice', userName, token, - newPassword, }; + if (passwordSalt) { + forgotPasswordResetConfig.hashedNewPassword = hashPassword(passwordSalt, newPassword); + } else { + forgotPasswordResetConfig.newPassword = newPassword; + } + const CmdForgotPasswordReset = webClient.protobuf.controller.Command_ForgotPasswordReset.create(forgotPasswordResetConfig); const sc = webClient.protobuf.controller.SessionCommand.create({ diff --git a/webclient/src/websocket/events/SessionEvents.ts b/webclient/src/websocket/events/SessionEvents.ts index c79dd513..723745ad 100644 --- a/webclient/src/websocket/events/SessionEvents.ts +++ b/webclient/src/websocket/events/SessionEvents.ts @@ -3,6 +3,7 @@ import { Room, StatusEnum, User, WebSocketConnectReason } from 'types'; import { SessionCommands } from '../commands'; import { RoomPersistence, SessionPersistence } from '../persistence'; import { ProtobufEvents } from '../services/ProtobufService'; +import { passwordSaltSupported } from '../utils'; import webClient from '../WebClient'; export const SessionEvents: ProtobufEvents = { @@ -122,18 +123,25 @@ function serverIdentification(info: ServerIdentificationData) { switch (webClient.options.reason) { case WebSocketConnectReason.LOGIN: SessionCommands.updateStatus(StatusEnum.LOGGING_IN, 'Logging In...'); - // Intentional use of Bitwise operator b/c of how Servatrice Enums work - if (serverOptions & webClient.protobuf.controller.Event_ServerIdentification.ServerOptions.SupportsPasswordHash) { + if (passwordSaltSupported(serverOptions, webClient)) { SessionCommands.requestPasswordSalt(); } else { SessionCommands.login(); } break; case WebSocketConnectReason.REGISTER: - SessionCommands.register(); + if (passwordSaltSupported(serverOptions, webClient)) { + SessionCommands.requestPasswordSalt(); + } else { + SessionCommands.register(); + } break; case WebSocketConnectReason.ACTIVATE_ACCOUNT: - SessionCommands.activateAccount(); + if (passwordSaltSupported(serverOptions, webClient)) { + SessionCommands.requestPasswordSalt(); + } else { + SessionCommands.activateAccount(); + } break; case WebSocketConnectReason.PASSWORD_RESET_REQUEST: SessionCommands.resetPasswordRequest(); @@ -142,7 +150,11 @@ function serverIdentification(info: ServerIdentificationData) { SessionCommands.resetPasswordChallenge(); break; case WebSocketConnectReason.PASSWORD_RESET: - SessionCommands.resetPassword(); + if (passwordSaltSupported(serverOptions, webClient)) { + SessionCommands.requestPasswordSalt(); + } else { + SessionCommands.resetPassword(); + } break; default: SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Unknown Connection Reason: ' + webClient.options.reason); diff --git a/webclient/src/websocket/utils/passwordHasher.ts b/webclient/src/websocket/utils/passwordHasher.ts index b274d403..77645694 100644 --- a/webclient/src/websocket/utils/passwordHasher.ts +++ b/webclient/src/websocket/utils/passwordHasher.ts @@ -24,3 +24,8 @@ export const generateSalt = (): string => { return salt; } + +export const passwordSaltSupported = (serverOptions, webClient): number => { + // Intentional use of Bitwise operator b/c of how Servatrice Enums work + return serverOptions & webClient.protobuf.controller.Event_ServerIdentification.ServerOptions.SupportsPasswordHash; +}