servatrice/webclient/src/websocket/commands/SessionCommands.ts
Jeremy Letto 2a54e9d7d1
Webatrice: fix saved password (#4563)
* fix saved label, and fix using hashedPassword when Save is unchecked

* update host only after successful login

* cleanup

* fix ability to deselect saved password on successful login

* cleanup

* clear options after connection

* fix registration saved username

* cleanup

* change label

* fix tests

Co-authored-by: Jeremy Letto <jeremy.letto@datasite.com>
2022-02-27 10:12:38 -06:00

527 lines
18 KiB
TypeScript

import { HostDTO } from 'services';
import { StatusEnum, WebSocketConnectReason, WebSocketConnectOptions } from 'types';
import { RoomPersistence, SessionPersistence } from '../persistence';
import webClient from '../WebClient';
import { guid, hashPassword } from '../utils';
import {
AccountActivationParams,
ForgotPasswordChallengeParams,
ForgotPasswordParams,
ForgotPasswordResetParams,
RequestPasswordSaltParams,
ServerRegisterParams
} from '../../store';
import NormalizeService from '../utils/NormalizeService';
export class SessionCommands {
static connect(options: WebSocketConnectOptions, reason: WebSocketConnectReason): void {
switch (reason) {
case WebSocketConnectReason.LOGIN:
case WebSocketConnectReason.REGISTER:
case WebSocketConnectReason.ACTIVATE_ACCOUNT:
case WebSocketConnectReason.PASSWORD_RESET_REQUEST:
case WebSocketConnectReason.PASSWORD_RESET_CHALLENGE:
case WebSocketConnectReason.PASSWORD_RESET:
SessionCommands.updateStatus(StatusEnum.CONNECTING, 'Connecting...');
break;
default:
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Unknown Connection Attempt: ' + reason);
return;
}
webClient.connect({ ...options, reason });
}
static disconnect(): void {
webClient.disconnect();
}
static login(options: WebSocketConnectOptions, passwordSalt?: string): void {
const { userName, password, hashedPassword } = options;
const loginConfig: any = {
...webClient.clientConfig,
clientid: 'webatrice',
userName,
};
if (passwordSalt) {
loginConfig.hashedPassword = hashedPassword || hashPassword(passwordSalt, password);
} else {
loginConfig.password = password;
}
const CmdLogin = webClient.protobuf.controller.Command_Login.create(loginConfig);
const command = webClient.protobuf.controller.SessionCommand.create({
'.Command_Login.ext': CmdLogin
});
webClient.protobuf.sendSessionCommand(command, raw => {
const resp = raw['.Response_Login.ext'];
if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespOk) {
const { buddyList, ignoreList, userInfo } = resp;
SessionPersistence.updateBuddyList(buddyList);
SessionPersistence.updateIgnoreList(ignoreList);
SessionPersistence.updateUser(userInfo);
SessionPersistence.loginSuccessful(loginConfig);
SessionCommands.listUsers();
SessionCommands.listRooms();
SessionCommands.updateStatus(StatusEnum.LOGGED_IN, 'Logged in.');
return;
}
switch (raw.responseCode) {
case webClient.protobuf.controller.Response.ResponseCode.RespClientUpdateRequired:
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: missing features');
break;
case webClient.protobuf.controller.Response.ResponseCode.RespWrongPassword:
case webClient.protobuf.controller.Response.ResponseCode.RespUsernameInvalid:
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: incorrect username or password');
break;
case webClient.protobuf.controller.Response.ResponseCode.RespWouldOverwriteOldSession:
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: duplicated user session');
break;
case webClient.protobuf.controller.Response.ResponseCode.RespUserIsBanned:
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: banned user');
break;
case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationRequired:
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: registration required');
break;
case webClient.protobuf.controller.Response.ResponseCode.RespClientIdRequired:
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: missing client ID');
break;
case webClient.protobuf.controller.Response.ResponseCode.RespContextError:
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: server error');
break;
case webClient.protobuf.controller.Response.ResponseCode.RespAccountNotActivated:
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: account not activated');
SessionPersistence.accountAwaitingActivation(options);
break;
default:
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, `Login failed: unknown error: ${raw.responseCode}`);
}
SessionPersistence.loginFailed();
SessionCommands.disconnect();
});
}
static requestPasswordSalt(options: WebSocketConnectOptions): void {
const { userName } = options as RequestPasswordSaltParams;
const registerConfig = {
...webClient.clientConfig,
userName,
};
const CmdRequestPasswordSalt = webClient.protobuf.controller.Command_RequestPasswordSalt.create(registerConfig);
const sc = webClient.protobuf.controller.SessionCommand.create({
'.Command_RequestPasswordSalt.ext': CmdRequestPasswordSalt
});
webClient.protobuf.sendSessionCommand(sc, raw => {
switch (raw.responseCode) {
case webClient.protobuf.controller.Response.ResponseCode.RespOk: {
const passwordSalt = raw['.Response_PasswordSalt.ext']?.passwordSalt;
switch (options.reason) {
case WebSocketConnectReason.ACTIVATE_ACCOUNT: {
SessionCommands.activateAccount(options, passwordSalt);
break;
}
case WebSocketConnectReason.PASSWORD_RESET: {
SessionCommands.resetPassword(options, passwordSalt);
break;
}
case WebSocketConnectReason.LOGIN:
default: {
SessionCommands.login(options, passwordSalt);
}
}
return;
}
case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationRequired: {
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: registration required');
break;
}
default: {
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: Unknown Reason');
}
}
switch (options.reason) {
case WebSocketConnectReason.ACTIVATE_ACCOUNT: {
SessionPersistence.accountActivationFailed();
break;
}
case WebSocketConnectReason.PASSWORD_RESET: {
SessionPersistence.resetPasswordFailed();
break;
}
case WebSocketConnectReason.LOGIN:
default: {
SessionPersistence.loginFailed();
}
}
SessionCommands.disconnect();
});
}
static register(options: WebSocketConnectOptions, passwordSalt?: string): void {
const { userName, password, email, country, realName } = options as ServerRegisterParams;
const registerConfig: any = {
...webClient.clientConfig,
userName,
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({
'.Command_Register.ext': CmdRegister
});
webClient.protobuf.sendSessionCommand(sc, raw => {
if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespRegistrationAccepted) {
SessionCommands.login(options, passwordSalt);
SessionPersistence.registrationSuccess()
return;
}
switch (raw.responseCode) {
case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationAcceptedNeedsActivation:
SessionPersistence.accountAwaitingActivation(options);
break;
case webClient.protobuf.controller.Response.ResponseCode.RespUserAlreadyExists:
SessionPersistence.registrationUserNameError('Username is taken');
break;
case webClient.protobuf.controller.Response.ResponseCode.RespUsernameInvalid:
console.error('ResponseCode.RespUsernameInvalid', raw.reasonStr);
SessionPersistence.registrationUserNameError('Invalid username');
break;
case webClient.protobuf.controller.Response.ResponseCode.RespPasswordTooShort:
SessionPersistence.registrationPasswordError('Your password was too short');
break;
case webClient.protobuf.controller.Response.ResponseCode.RespEmailRequiredToRegister:
SessionPersistence.registrationRequiresEmail();
break;
case webClient.protobuf.controller.Response.ResponseCode.RespEmailBlackListed:
SessionPersistence.registrationEmailError('This email provider has been blocked');
break;
case webClient.protobuf.controller.Response.ResponseCode.RespTooManyRequests:
SessionPersistence.registrationEmailError('Max accounts reached for this email');
break;
case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationDisabled:
SessionPersistence.registrationFailed('Registration is currently disabled');
break;
case webClient.protobuf.controller.Response.ResponseCode.RespUserIsBanned:
SessionPersistence.registrationFailed(NormalizeService.normalizeBannedUserError(raw.reasonStr, raw.endTime));
break;
case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationFailed:
default:
SessionPersistence.registrationFailed('Registration failed due to a server issue');
break;
}
SessionCommands.disconnect();
});
};
static activateAccount(options: WebSocketConnectOptions, passwordSalt?: string): void {
const { userName, token } = options as unknown as AccountActivationParams;
const accountActivationConfig = {
...webClient.clientConfig,
userName,
token,
};
const CmdActivate = webClient.protobuf.controller.Command_Activate.create(accountActivationConfig);
const sc = webClient.protobuf.controller.SessionCommand.create({
'.Command_Activate.ext': CmdActivate
});
webClient.protobuf.sendSessionCommand(sc, raw => {
if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespActivationAccepted) {
SessionPersistence.accountActivationSuccess();
SessionCommands.login(options, passwordSalt);
} else {
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Account Activation Failed');
SessionCommands.disconnect();
SessionPersistence.accountActivationFailed();
}
});
}
static resetPasswordRequest(options: WebSocketConnectOptions): void {
const { userName } = options as unknown as ForgotPasswordParams;
const forgotPasswordConfig = {
...webClient.clientConfig,
userName,
};
const CmdForgotPasswordRequest = webClient.protobuf.controller.Command_ForgotPasswordRequest.create(forgotPasswordConfig);
const sc = webClient.protobuf.controller.SessionCommand.create({
'.Command_ForgotPasswordRequest.ext': CmdForgotPasswordRequest
});
webClient.protobuf.sendSessionCommand(sc, raw => {
if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespOk) {
const resp = raw['.Response_ForgotPasswordRequest.ext'];
if (resp.challengeEmail) {
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null);
SessionPersistence.resetPasswordChallenge();
} else {
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null);
SessionPersistence.resetPassword();
}
} else {
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null);
SessionPersistence.resetPasswordFailed();
}
SessionCommands.disconnect();
});
}
static resetPasswordChallenge(options: WebSocketConnectOptions): void {
const { userName, email } = options as unknown as ForgotPasswordChallengeParams;
const forgotPasswordChallengeConfig = {
...webClient.clientConfig,
userName,
email,
};
const CmdForgotPasswordChallenge = webClient.protobuf.controller.Command_ForgotPasswordChallenge.create(forgotPasswordChallengeConfig);
const sc = webClient.protobuf.controller.SessionCommand.create({
'.Command_ForgotPasswordChallenge.ext': CmdForgotPasswordChallenge
});
webClient.protobuf.sendSessionCommand(sc, raw => {
if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespOk) {
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null);
SessionPersistence.resetPassword();
} else {
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null);
SessionPersistence.resetPasswordFailed();
}
SessionCommands.disconnect();
});
}
static resetPassword(options: WebSocketConnectOptions, passwordSalt?: string): void {
const { userName, token, newPassword } = options as unknown as ForgotPasswordResetParams;
const forgotPasswordResetConfig: any = {
...webClient.clientConfig,
userName,
token,
};
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({
'.Command_ForgotPasswordReset.ext': CmdForgotPasswordReset
});
webClient.protobuf.sendSessionCommand(sc, raw => {
if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespOk) {
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null);
SessionPersistence.resetPasswordSuccess();
} else {
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, null);
SessionPersistence.resetPasswordFailed();
}
SessionCommands.disconnect();
});
}
static listUsers(): void {
const CmdListUsers = webClient.protobuf.controller.Command_ListUsers.create();
const sc = webClient.protobuf.controller.SessionCommand.create({
'.Command_ListUsers.ext': CmdListUsers
});
webClient.protobuf.sendSessionCommand(sc, raw => {
const { responseCode } = raw;
const response = raw['.Response_ListUsers.ext'];
if (response) {
switch (responseCode) {
case webClient.protobuf.controller.Response.ResponseCode.RespOk:
SessionPersistence.updateUsers(response.userList);
break;
default:
console.log(`Failed to fetch Server Rooms [${responseCode}] : `, raw);
}
}
});
}
static listRooms(): void {
const CmdListRooms = webClient.protobuf.controller.Command_ListRooms.create();
const sc = webClient.protobuf.controller.SessionCommand.create({
'.Command_ListRooms.ext': CmdListRooms
});
webClient.protobuf.sendSessionCommand(sc);
}
static joinRoom(roomId: number): void {
const CmdJoinRoom = webClient.protobuf.controller.Command_JoinRoom.create({ roomId });
const sc = webClient.protobuf.controller.SessionCommand.create({
'.Command_JoinRoom.ext': CmdJoinRoom
});
webClient.protobuf.sendSessionCommand(sc, (raw) => {
const { responseCode } = raw;
let error;
switch (responseCode) {
case webClient.protobuf.controller.Response.ResponseCode.RespOk:
const { roomInfo } = raw['.Response_JoinRoom.ext'];
RoomPersistence.joinRoom(roomInfo);
return;
case webClient.protobuf.controller.Response.ResponseCode.RespNameNotFound:
error = 'Failed to join the room: it doesn\'t exist on the server.';
break;
case webClient.protobuf.controller.Response.ResponseCode.RespContextError:
error = 'The server thinks you are in the room but Cockatrice is unable to display it. Try restarting Cockatrice.';
break;
case webClient.protobuf.controller.Response.ResponseCode.RespUserLevelTooLow:
error = 'You do not have the required permission to join this room.';
break;
default:
error = 'Failed to join the room due to an unknown error.';
break;
}
if (error) {
console.error(responseCode, error);
}
});
}
static addToBuddyList(userName: string): void {
this.addToList('buddy', userName);
}
static removeFromBuddyList(userName: string): void {
this.removeFromList('buddy', userName);
}
static addToIgnoreList(userName: string): void {
this.addToList('ignore', userName);
}
static removeFromIgnoreList(userName: string): void {
this.removeFromList('ignore', userName);
}
static addToList(list: string, userName: string): void {
const CmdAddToList = webClient.protobuf.controller.Command_AddToList.create({ list, userName });
const sc = webClient.protobuf.controller.SessionCommand.create({
'.Command_AddToList.ext': CmdAddToList
});
webClient.protobuf.sendSessionCommand(sc, ({ responseCode }) => {
// @TODO: filter responseCode, pop snackbar for error
});
}
static removeFromList(list: string, userName: string): void {
const CmdRemoveFromList = webClient.protobuf.controller.Command_RemoveFromList.create({ list, userName });
const sc = webClient.protobuf.controller.SessionCommand.create({
'.Command_RemoveFromList.ext': CmdRemoveFromList
});
webClient.protobuf.sendSessionCommand(sc, ({ responseCode }) => {
// @TODO: filter responseCode, pop snackbar for error
});
}
static viewLogHistory(filters): void {
const CmdViewLogHistory = webClient.protobuf.controller.Command_ViewLogHistory.create(filters);
const sc = webClient.protobuf.controller.ModeratorCommand.create({
'.Command_ViewLogHistory.ext': CmdViewLogHistory
});
webClient.protobuf.sendModeratorCommand(sc, (raw) => {
const { responseCode } = raw;
let error;
switch (responseCode) {
case webClient.protobuf.controller.Response.ResponseCode.RespOk:
const { logMessage } = raw['.Response_ViewLogHistory.ext'];
SessionPersistence.viewLogs(logMessage)
return;
default:
error = 'Failed to retrieve log history.';
break;
}
if (error) {
console.error(responseCode, error);
}
});
}
static updateStatus(status: StatusEnum, description: string): void {
webClient.updateStatus(status, description);
}
}