Support Registration on Webatrice with a baseline of handling. (#4436)

* Support Registration on Webatrice with a baseline of handling. Still needs to support activation tokens & unit testing.

* Add support for account activation with token

* Activate Account refactor

* Fix typo

* Add Unit Testing for Commands/Events

* Changes based on review feedback
This commit is contained in:
Zach H 2021-10-20 22:07:35 -04:00 committed by GitHub
parent ebebb9c4bb
commit b1ef8220ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 483 additions and 68 deletions

View file

@ -1,9 +1,11 @@
import { StatusEnum } from 'types';
import {StatusEnum} from 'types';
import { SessionCommands } from './SessionCommands';
import {SessionCommands} from './SessionCommands';
import { RoomPersistence, SessionPersistence } from '../persistence';
import {RoomPersistence, SessionPersistence} from '../persistence';
import webClient from '../WebClient';
import {WebSocketConnectReason} from "../services/WebSocketService";
import {AccountActivationParams, ServerRegisterParams} from "../../store";
describe('SessionCommands', () => {
const roomId = 1;
@ -21,23 +23,49 @@ describe('SessionCommands', () => {
webClient.protobuf.controller.SessionCommand = { create: args => args };
});
describe('connect', () => {
it('should call SessionCommands.updateStatus and webClient.connect', () => {
let options;
beforeEach(() => {
spyOn(webClient, 'connect');
const options = {
options = {
host: 'host',
port: 'port',
user: 'user',
pass: 'pass',
};
});
SessionCommands.connect(options);
it('should call SessionCommands.updateStatus and webClient.connect when logging in', () => {
SessionCommands.connect(options, WebSocketConnectReason.LOGIN);
expect(SessionCommands.updateStatus).toHaveBeenCalled();
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, 'Connecting...');
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.CONNECTING, expect.any(String));
expect(webClient.connect).toHaveBeenCalled();
expect(webClient.connect).toHaveBeenCalledWith(options);
expect(webClient.connect).toHaveBeenCalledWith({ ...options, reason: WebSocketConnectReason.LOGIN });
});
it('should call SessionCommands.updateStatus and webClient.connect when registering', () => {
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 });
});
it('should call SessionCommands.updateStatus and webClient.connect when activating account', () => {
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 });
});
});
@ -215,6 +243,160 @@ describe('SessionCommands', () => {
});
});
describe('register', () => {
beforeEach(() => {
webClient.protobuf.controller.Command_Register = { create: args => args };
webClient.options = {
...webClient.options,
user: 'user',
pass: 'pass',
email: 'email@example.com',
country: 'us',
realName: 'realName',
clientid: 'abcdefg'
} as any;
});
it('should call protobuf controller methods and sendCommand', () => {
SessionCommands.register();
const options = webClient.options as unknown as ServerRegisterParams;
expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalled();
expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith({
'.Command_Register.ext': {
...webClient.clientConfig,
userName: options.user,
password: options.pass,
email: options.email,
country: options.country,
realName: options.realName,
clientid: jasmine.any(String)
}
}, jasmine.any(Function));
});
describe('response', () => {
const RespRegistrationAccepted = 'RespRegistrationAccepted';
const respKey = '.Response_Register.ext';
let response;
beforeEach(() => {
response = {
responseCode: RespRegistrationAccepted,
[respKey]: {
reasonStr: "",
endTime: 10000000
}
};
webClient.protobuf.controller.Response = { ResponseCode: { RespRegistrationAccepted }};
sendSessionCommandSpy.and.callFake((_, callback) => callback(response));
})
it("should login user if registration accepted without email verification", () => {
spyOn(SessionCommands, 'login');
spyOn(SessionPersistence, 'accountAwaitingActivation');
SessionCommands.register();
expect(SessionCommands.login).toHaveBeenCalled();
expect(SessionPersistence.accountAwaitingActivation).not.toHaveBeenCalled();
});
it("should prompt user if registration accepted with email verification", () => {
const RespRegistrationAcceptedNeedsActivation = 'RespRegistrationAcceptedNeedsActivation';
response.responseCode = RespRegistrationAcceptedNeedsActivation;
webClient.protobuf.controller.Response.ResponseCode.RespRegistrationAcceptedNeedsActivation = RespRegistrationAcceptedNeedsActivation;
spyOn(SessionCommands, 'login');
spyOn(SessionPersistence, 'accountAwaitingActivation');
SessionCommands.register();
expect(SessionCommands.login).not.toHaveBeenCalled();
expect(SessionPersistence.accountAwaitingActivation).toHaveBeenCalled();
});
it("should disconnect user if registration fails due to registration being disabled", () => {
const RespRegistrationDisabled = 'RespRegistrationDisabled';
response.responseCode = RespRegistrationDisabled;
webClient.protobuf.controller.Response.ResponseCode.RespRegistrationDisabled = RespRegistrationDisabled;
SessionCommands.register();
expect(SessionCommands.updateStatus).toHaveBeenCalledWith(StatusEnum.DISCONNECTED, expect.any(String));
});
});
});
describe('activateAccount', () => {
beforeEach(() => {
webClient.protobuf.controller.Command_Activate = { create: args => args };
webClient.options = {
...webClient.options,
user: 'user',
activationCode: 'token',
clientid: 'abcdefg'
} as any;
});
it('should call protobuf controller methods and sendCommand', () => {
SessionCommands.activateAccount();
const options = webClient.options as unknown as AccountActivationParams;
expect(webClient.protobuf.sendSessionCommand).toHaveBeenCalledWith({
'.Command_Activate.ext': {
...webClient.clientConfig,
userName: options.user,
token: options.activationCode,
clientid: jasmine.any(String)
}
}, jasmine.any(Function));
});
describe('response', () => {
const RespActivationAccepted = 'RespActivationAccepted';
const respKey = '.Response_Activate.ext';
let response;
beforeEach(() => {
response = {
responseCode: RespActivationAccepted,
[respKey]: {
}
};
webClient.protobuf.controller.Response = { ResponseCode: { RespActivationAccepted }};
sendSessionCommandSpy.and.callFake((_, callback) => callback(response));
spyOn(SessionCommands, 'login');
spyOn(SessionPersistence, 'accountActivationFailed');
});
it('should activate user and login if correct activation token used', () => {
SessionCommands.activateAccount();
expect(SessionCommands.login).toHaveBeenCalled();
expect(SessionPersistence.accountActivationFailed).not.toHaveBeenCalled();
});
it('should disconnect user if activation failed for any reason', () => {
const RespActivationFailed = 'RespActivationFailed';
response.responseCode = RespActivationFailed;
webClient.protobuf.controller.Response.ResponseCode.RespActivationFailed = RespActivationFailed;
SessionCommands.activateAccount();
expect(SessionCommands.login).not.toHaveBeenCalled();
expect(SessionPersistence.accountActivationFailed).toHaveBeenCalled();
});
});
});
describe('listUsers', () => {
beforeEach(() => {
webClient.protobuf.controller.Command_ListUsers = { create: () => ({}) };

View file

@ -1,14 +1,33 @@
import { ServerConnectParams } from 'store';
import { StatusEnum } from 'types';
import {StatusEnum} from 'types';
import { RoomPersistence, SessionPersistence } from '../persistence';
import {RoomPersistence, SessionPersistence} from '../persistence';
import webClient from '../WebClient';
import { guid } from '../utils';
import {guid} from '../utils';
import {WebSocketConnectReason, WebSocketOptions} from "../services/WebSocketService";
import {ServerRegisterParams, AccountActivationParams} from "../../store";
import NormalizeService from "../utils/NormalizeService";
export class SessionCommands {
static connect(options: ServerConnectParams): void {
SessionCommands.updateStatus(StatusEnum.CONNECTING, 'Connecting...');
webClient.connect(options);
static connect(options: WebSocketOptions, reason: WebSocketConnectReason): void {
switch (reason) {
case WebSocketConnectReason.LOGIN:
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;
}
webClient.connect({ ...options, reason });
}
static disconnect(): void {
@ -78,6 +97,7 @@ export class SessionCommands {
case webClient.protobuf.controller.Response.ResponseCode.RespAccountNotActivated:
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Login failed: account not activated');
SessionPersistence.accountAwaitingActivation();
break;
default:
@ -86,6 +106,102 @@ export class SessionCommands {
});
}
static register(): void {
const options = webClient.options as unknown as ServerRegisterParams;
const registerConfig = {
...webClient.clientConfig,
userName: options.user,
password: options.pass,
email: options.email,
country: options.country,
realName: options.realName,
clientid: 'webatrice'
};
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 => {
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:
error = 'Registration is currently disabled';
break;
case webClient.protobuf.controller.Response.ResponseCode.RespUserAlreadyExists:
error = 'There is already an existing user with this username';
break;
case webClient.protobuf.controller.Response.ResponseCode.RespEmailRequiredToRegister:
error = 'A valid email address is required to register';
break;
case webClient.protobuf.controller.Response.ResponseCode.RespEmailBlackListed:
error = 'The email address provider used has been blocked from use';
break;
case webClient.protobuf.controller.Response.ResponseCode.RespTooManyRequests:
error = 'This email address already has the maximum number of accounts you can register';
break;
case webClient.protobuf.controller.Response.ResponseCode.RespPasswordTooShort:
error = 'Your password was too short';
break;
case webClient.protobuf.controller.Response.ResponseCode.RespUserIsBanned:
error = NormalizeService.normalizeBannedUserError(raw.reasonStr, raw.endTime);
break;
case webClient.protobuf.controller.Response.ResponseCode.RespUsernameInvalid:
console.error("ResponseCode.RespUsernameInvalid", raw.reasonStr);
error = 'Invalid username';
break;
case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationFailed:
default:
console.error("ResponseCode Type", raw.responseCode);
error = 'Registration failed due to a server issue';
break;
}
if (error) {
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, `Registration Failed: ${error}`);
}
});
};
static activateAccount(): void {
const options = webClient.options as unknown as AccountActivationParams;
const accountActivationConfig = {
...webClient.clientConfig,
userName: options.user,
clientid: options.clientid,
token: options.activationCode
};
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) {
SessionCommands.updateStatus(StatusEnum.ACCOUNT_ACTIVATED, 'Account Activation Successful');
SessionCommands.login();
} else {
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Account Activation Failed');
SessionPersistence.accountActivationFailed();
}
});
}
static listUsers(): void {
const CmdListUsers = webClient.protobuf.controller.Command_ListUsers.create();