Support password reset workflow on Webatrice (#4445)
* Support password reset workflow. Also fix issue where a user would be disconnected "randomly" if they had a failed login, then successful one. Refactored a bit on Status Labels since they weren't really necessary and added complexity. * Disconnect in default cases where we don't know what to do, but shouldn't stay connected to the server
This commit is contained in:
parent
013bb8269f
commit
ac300b0b6d
10 changed files with 189 additions and 77 deletions
|
@ -15,12 +15,24 @@ export default class AuthenticationService {
|
|||
SessionCommands.connect(options, WebSocketConnectReason.ACTIVATE_ACCOUNT);
|
||||
}
|
||||
|
||||
static resetPasswordRequest(options: WebSocketOptions): void {
|
||||
SessionCommands.connect(options, WebSocketConnectReason.PASSWORD_RESET_REQUEST);
|
||||
}
|
||||
|
||||
static resetPasswordChallenge(options: WebSocketOptions): void {
|
||||
SessionCommands.connect(options, WebSocketConnectReason.PASSWORD_RESET_CHALLENGE);
|
||||
}
|
||||
|
||||
static resetPassword(options: WebSocketOptions): void {
|
||||
SessionCommands.connect(options, WebSocketConnectReason.PASSWORD_RESET);
|
||||
}
|
||||
|
||||
static disconnect(): void {
|
||||
SessionCommands.disconnect();
|
||||
}
|
||||
|
||||
static isConnected(state: number): boolean {
|
||||
return state === StatusEnum.LOGGEDIN;
|
||||
return state === StatusEnum.LOGGED_IN;
|
||||
}
|
||||
|
||||
static isModerator(user: User): boolean {
|
||||
|
|
|
@ -15,7 +15,7 @@ const LoginForm = (props) => {
|
|||
const { dispatch, handleSubmit } = props;
|
||||
|
||||
const forgotPassword = () => {
|
||||
console.log('LoginForm.forgotPassword->openForgotPasswordDialog');
|
||||
console.log("Show recover password dialog, then AuthService.forgotPasswordRequest");
|
||||
};
|
||||
|
||||
const onHostChange = ({ host, port }) => {
|
||||
|
|
|
@ -18,6 +18,20 @@ export interface ServerRegisterParams {
|
|||
realName: string;
|
||||
}
|
||||
|
||||
export interface ForgotPasswordParams {
|
||||
user: string;
|
||||
clientid: string;
|
||||
}
|
||||
|
||||
export interface ForgotPasswordChallengeParams extends ForgotPasswordParams {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface ForgotPasswordResetParams extends ForgotPasswordParams {
|
||||
token: string;
|
||||
newPassword: string;
|
||||
}
|
||||
|
||||
export interface AccountActivationParams extends ServerRegisterParams {
|
||||
activationCode: string;
|
||||
clientid: string;
|
||||
|
|
|
@ -7,30 +7,11 @@ export enum StatusEnum {
|
|||
DISCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
LOGGINGIN,
|
||||
LOGGEDIN,
|
||||
REGISTERING,
|
||||
REGISTERED,
|
||||
ACTIVATING_ACCOUNT,
|
||||
ACCOUNT_ACTIVATED,
|
||||
RECOVERING_PASSWORD,
|
||||
LOGGING_IN,
|
||||
LOGGED_IN,
|
||||
DISCONNECTING = 99
|
||||
}
|
||||
|
||||
export enum StatusEnumLabel {
|
||||
"Disconnected",
|
||||
"Connecting" ,
|
||||
"Connected" ,
|
||||
"Loggingin",
|
||||
"Loggedin",
|
||||
"Registering",
|
||||
"Registered",
|
||||
"ActivatingAccount",
|
||||
"AccountActivated",
|
||||
"RecoveringPassword",
|
||||
"Disconnecting" = 99
|
||||
}
|
||||
|
||||
export class Host {
|
||||
id?: number;
|
||||
name: string;
|
||||
|
@ -83,14 +64,6 @@ export const KnownHosts = {
|
|||
[KnownHost.TETRARCH]: { port: 443, host: 'mtg.tetrarch.co/servatrice'},
|
||||
}
|
||||
|
||||
export const getStatusEnumLabel = (statusEnum: number) => {
|
||||
if (StatusEnumLabel[statusEnum] !== undefined) {
|
||||
return StatusEnumLabel[statusEnum];
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
};
|
||||
|
||||
export interface Log {
|
||||
message: string;
|
||||
senderId: string;
|
||||
|
|
|
@ -51,7 +51,6 @@ describe('SessionCommands', () => {
|
|||
SessionCommands.connect(options, WebSocketConnectReason.REGISTER);
|
||||
|
||||
expect(SessionCommands.updateStatus).toHaveBeenCalled();
|
||||
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.REGISTERING, expect.any(String));
|
||||
|
||||
expect(webClient.connect).toHaveBeenCalled();
|
||||
expect(webClient.connect).toHaveBeenCalledWith({ ...options, reason: WebSocketConnectReason.REGISTER });
|
||||
|
@ -62,7 +61,6 @@ describe('SessionCommands', () => {
|
|||
SessionCommands.connect(options, WebSocketConnectReason.ACTIVATE_ACCOUNT);
|
||||
|
||||
expect(SessionCommands.updateStatus).toHaveBeenCalled();
|
||||
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.ACTIVATING_ACCOUNT, expect.any(String));
|
||||
|
||||
expect(webClient.connect).toHaveBeenCalled();
|
||||
expect(webClient.connect).toHaveBeenCalledWith({ ...options, reason: WebSocketConnectReason.ACTIVATE_ACCOUNT });
|
||||
|
@ -138,7 +136,7 @@ describe('SessionCommands', () => {
|
|||
|
||||
expect(SessionCommands.listUsers).toHaveBeenCalled();
|
||||
expect(SessionCommands.listRooms).toHaveBeenCalled();
|
||||
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.LOGGEDIN, 'Logged in.');
|
||||
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.LOGGED_IN, 'Logged in.');
|
||||
});
|
||||
|
||||
it('RespClientUpdateRequired should update status', () => {
|
||||
|
|
|
@ -4,34 +4,35 @@ import {RoomPersistence, SessionPersistence} from '../persistence';
|
|||
import webClient from '../WebClient';
|
||||
import {guid} from '../utils';
|
||||
import {WebSocketConnectReason, WebSocketOptions} from "../services/WebSocketService";
|
||||
import {ServerRegisterParams, AccountActivationParams} from "../../store";
|
||||
import {
|
||||
AccountActivationParams,
|
||||
ForgotPasswordChallengeParams,
|
||||
ForgotPasswordParams,
|
||||
ForgotPasswordResetParams,
|
||||
ServerRegisterParams
|
||||
} from "../../store";
|
||||
import NormalizeService from "../utils/NormalizeService";
|
||||
|
||||
export class SessionCommands {
|
||||
static connect(options: WebSocketOptions, 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;
|
||||
case WebSocketConnectReason.REGISTER:
|
||||
SessionCommands.updateStatus(StatusEnum.REGISTERING, 'Registering...');
|
||||
break;
|
||||
case WebSocketConnectReason.ACTIVATE_ACCOUNT:
|
||||
SessionCommands.updateStatus(StatusEnum.ACTIVATING_ACCOUNT, 'Activating Account...');
|
||||
break;
|
||||
case WebSocketConnectReason.RECOVER_PASSWORD:
|
||||
SessionCommands.updateStatus(StatusEnum.RECOVERING_PASSWORD, 'Recovering Password...');
|
||||
break;
|
||||
default:
|
||||
console.error('Connection Failed', reason);
|
||||
break;
|
||||
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Unknown Connection Attempt: ' + reason);
|
||||
return;
|
||||
}
|
||||
|
||||
webClient.connect({ ...options, reason });
|
||||
}
|
||||
|
||||
static disconnect(): void {
|
||||
SessionCommands.updateStatus(StatusEnum.DISCONNECTING, 'Disconnecting...');
|
||||
webClient.disconnect();
|
||||
}
|
||||
|
||||
|
@ -52,20 +53,21 @@ export class SessionCommands {
|
|||
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);
|
||||
|
||||
SessionCommands.listUsers();
|
||||
SessionCommands.listRooms();
|
||||
|
||||
SessionCommands.updateStatus(StatusEnum.LOGGED_IN, 'Logged in.');
|
||||
return;
|
||||
}
|
||||
|
||||
switch(raw.responseCode) {
|
||||
case webClient.protobuf.controller.Response.ResponseCode.RespOk:
|
||||
const { buddyList, ignoreList, userInfo } = resp;
|
||||
|
||||
SessionPersistence.updateBuddyList(buddyList);
|
||||
SessionPersistence.updateIgnoreList(ignoreList);
|
||||
SessionPersistence.updateUser(userInfo);
|
||||
|
||||
SessionCommands.listUsers();
|
||||
SessionCommands.listRooms();
|
||||
|
||||
SessionCommands.updateStatus(StatusEnum.LOGGEDIN, 'Logged in.');
|
||||
break;
|
||||
|
||||
case webClient.protobuf.controller.Response.ResponseCode.RespClientUpdateRequired:
|
||||
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: missing features');
|
||||
break;
|
||||
|
@ -103,6 +105,8 @@ export class SessionCommands {
|
|||
default:
|
||||
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, `Login failed: unknown error: ${raw.responseCode}`);
|
||||
}
|
||||
|
||||
SessionCommands.disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -126,14 +130,15 @@ export class SessionCommands {
|
|||
});
|
||||
|
||||
webClient.protobuf.sendSessionCommand(sc, raw => {
|
||||
if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespRegistrationAccepted) {
|
||||
SessionCommands.login();
|
||||
return;
|
||||
}
|
||||
|
||||
let error;
|
||||
|
||||
switch (raw.responseCode) {
|
||||
case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationAccepted:
|
||||
SessionCommands.login();
|
||||
break;
|
||||
case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationAcceptedNeedsActivation:
|
||||
SessionCommands.updateStatus(StatusEnum.REGISTERED, "Registration Successful");
|
||||
SessionPersistence.accountAwaitingActivation();
|
||||
break;
|
||||
case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationDisabled:
|
||||
|
@ -171,6 +176,8 @@ export class SessionCommands {
|
|||
if (error) {
|
||||
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, `Registration Failed: ${error}`);
|
||||
}
|
||||
|
||||
SessionCommands.disconnect();
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -192,14 +199,100 @@ export class SessionCommands {
|
|||
|
||||
webClient.protobuf.sendSessionCommand(sc, raw => {
|
||||
if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespActivationAccepted) {
|
||||
SessionCommands.updateStatus(StatusEnum.ACCOUNT_ACTIVATED, 'Account Activation Successful');
|
||||
SessionCommands.login();
|
||||
} else {
|
||||
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Account Activation Failed');
|
||||
SessionCommands.disconnect();
|
||||
SessionPersistence.accountActivationFailed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static resetPasswordRequest(): void {
|
||||
const options = webClient.options as unknown as ForgotPasswordParams;
|
||||
|
||||
const forgotPasswordConfig = {
|
||||
...webClient.clientConfig,
|
||||
userName: options.user,
|
||||
clientid: options.clientid
|
||||
};
|
||||
|
||||
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) {
|
||||
SessionPersistence.resetPasswordChallenge();
|
||||
} else {
|
||||
SessionPersistence.resetPassword();
|
||||
}
|
||||
} else {
|
||||
SessionPersistence.resetPasswordFailed();
|
||||
}
|
||||
|
||||
SessionCommands.disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
static resetPasswordChallenge(): void {
|
||||
const options = webClient.options as unknown as ForgotPasswordChallengeParams;
|
||||
|
||||
const forgotPasswordChallengeConfig = {
|
||||
...webClient.clientConfig,
|
||||
userName: options.user,
|
||||
clientid: options.clientid,
|
||||
email: options.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) {
|
||||
SessionPersistence.resetPassword();
|
||||
} else {
|
||||
SessionPersistence.resetPasswordFailed();
|
||||
}
|
||||
|
||||
SessionCommands.disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
static resetPassword(): void {
|
||||
const options = webClient.options as unknown as ForgotPasswordResetParams;
|
||||
|
||||
const forgotPasswordResetConfig = {
|
||||
...webClient.clientConfig,
|
||||
userName: options.user,
|
||||
clientid: options.clientid,
|
||||
token: options.token,
|
||||
newPassword: options.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) {
|
||||
SessionPersistence.resetPasswordSuccess();
|
||||
} else {
|
||||
SessionPersistence.resetPasswordFailed();
|
||||
}
|
||||
|
||||
SessionCommands.disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
static listUsers(): void {
|
||||
|
|
|
@ -300,7 +300,7 @@ describe('SessionEvents', () => {
|
|||
event(data);
|
||||
|
||||
expect(SessionPersistence.updateInfo).toHaveBeenCalledWith(data.serverName, data.serverVersion);
|
||||
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.LOGGINGIN, expect.any(String));
|
||||
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.LOGGING_IN, expect.any(String));
|
||||
expect(SessionCommands.login).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -312,7 +312,6 @@ describe('SessionEvents', () => {
|
|||
event(data);
|
||||
|
||||
expect(SessionPersistence.updateInfo).toHaveBeenCalledWith(data.serverName, data.serverVersion);
|
||||
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.REGISTERING, expect.any(String));
|
||||
expect(SessionCommands.register).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -324,7 +323,6 @@ describe('SessionEvents', () => {
|
|||
event(data);
|
||||
|
||||
expect(SessionPersistence.updateInfo).toHaveBeenCalledWith(data.serverName, data.serverVersion);
|
||||
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.ACTIVATING_ACCOUNT, expect.any(String));
|
||||
expect(SessionCommands.activateAccount).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ function addToList({ listName, userInfo}: AddToListData) {
|
|||
}
|
||||
|
||||
function connectionClosed({ reason, reasonStr }: ConnectionClosedData) {
|
||||
let message = '';
|
||||
let message;
|
||||
|
||||
// @TODO (5)
|
||||
if (reasonStr) {
|
||||
|
@ -116,29 +116,34 @@ function serverIdentification(info: ServerIdentificationData) {
|
|||
const { serverName, serverVersion, protocolVersion } = info;
|
||||
|
||||
if (protocolVersion !== webClient.protocolVersion) {
|
||||
SessionCommands.disconnect();
|
||||
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, `Protocol version mismatch: ${protocolVersion}`);
|
||||
SessionCommands.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (webClient.options.reason) {
|
||||
case WebSocketConnectReason.LOGIN:
|
||||
SessionCommands.updateStatus(StatusEnum.LOGGINGIN, 'Logging in...');
|
||||
SessionCommands.updateStatus(StatusEnum.LOGGING_IN, 'Logging In...');
|
||||
SessionCommands.login();
|
||||
break;
|
||||
case WebSocketConnectReason.REGISTER:
|
||||
SessionCommands.updateStatus(StatusEnum.REGISTERING, 'Registering...');
|
||||
SessionCommands.register();
|
||||
break;
|
||||
case WebSocketConnectReason.ACTIVATE_ACCOUNT:
|
||||
SessionCommands.updateStatus(StatusEnum.ACTIVATING_ACCOUNT, 'Activating account...');
|
||||
SessionCommands.activateAccount();
|
||||
break;
|
||||
case WebSocketConnectReason.RECOVER_PASSWORD:
|
||||
console.log('ServerIdentificationData.recoverPassword');
|
||||
case WebSocketConnectReason.PASSWORD_RESET_REQUEST:
|
||||
SessionCommands.resetPasswordRequest();
|
||||
break;
|
||||
case WebSocketConnectReason.PASSWORD_RESET_CHALLENGE:
|
||||
SessionCommands.resetPasswordChallenge();
|
||||
break;
|
||||
case WebSocketConnectReason.PASSWORD_RESET:
|
||||
SessionCommands.resetPassword();
|
||||
break;
|
||||
default:
|
||||
console.error("Undefined type", webClient.options.reason);
|
||||
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, "Unknown Connection Reason: " + webClient.options.reason);
|
||||
SessionCommands.disconnect();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -80,4 +80,21 @@ export class SessionPersistence {
|
|||
static accountActivationFailed() {
|
||||
console.log("Account activation failed, show an action here");
|
||||
}
|
||||
|
||||
static resetPasswordChallenge() {
|
||||
console.log("Open Modal asking for Email address associated with account");
|
||||
}
|
||||
|
||||
static resetPassword() {
|
||||
console.log("Open Modal asking for reset token & new password");
|
||||
|
||||
}
|
||||
|
||||
static resetPasswordSuccess() {
|
||||
console.log("User password successfully changed Alert!");
|
||||
}
|
||||
|
||||
static resetPasswordFailed() {
|
||||
console.log("Open Alert telling user their password request failed for some reason");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,9 @@ export enum WebSocketConnectReason {
|
|||
LOGIN,
|
||||
REGISTER,
|
||||
ACTIVATE_ACCOUNT,
|
||||
RECOVER_PASSWORD,
|
||||
PASSWORD_RESET_REQUEST,
|
||||
PASSWORD_RESET_CHALLENGE,
|
||||
PASSWORD_RESET
|
||||
}
|
||||
|
||||
export class WebSocketService {
|
||||
|
|
Loading…
Reference in a new issue