mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
Support HashedPassword workflow for logins (#4469)
* Support HashedPassword workflow for logins * Address comments in PR
This commit is contained in:
parent
45d86e7ab7
commit
43eee6b32e
8 changed files with 93 additions and 7 deletions
|
|
@ -6,6 +6,7 @@
|
|||
"@material-ui/core": "^4.11.4",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/styles": "^4.11.4",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dexie": "^3.0.3",
|
||||
"jquery": "^3.4.1",
|
||||
"lodash": "^4.17.15",
|
||||
|
|
|
|||
|
|
@ -67,4 +67,8 @@ export interface ServerStateLogs {
|
|||
|
||||
export interface ServerStateSortUsersBy extends SortBy {
|
||||
field: UserSortField
|
||||
}
|
||||
|
||||
export interface RequestPasswordSaltParams {
|
||||
user: string;
|
||||
}
|
||||
|
|
@ -30,6 +30,13 @@ export const DefaultHosts: Host[] = [
|
|||
localHost: 'server.cockatrice.us',
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: 'Rooster Beta',
|
||||
host: 'beta.cockatrice.us/servatrice',
|
||||
port: '4748',
|
||||
localHost: 'beta.cockatrice.us',
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
name: 'Tetrarch',
|
||||
host: 'mtg.tetrarch.co/servatrice',
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ const ProtoFiles = [
|
|||
"response_join_room.proto",
|
||||
"response_list_users.proto",
|
||||
"response_login.proto",
|
||||
"response_password_salt.proto",
|
||||
"response_register.proto",
|
||||
"response_replay_download.proto",
|
||||
"response_replay_list.proto",
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ import {StatusEnum} from 'types';
|
|||
|
||||
import {RoomPersistence, SessionPersistence} from '../persistence';
|
||||
import webClient from '../WebClient';
|
||||
import {guid} from '../utils';
|
||||
import {guid, hashPassword} from '../utils';
|
||||
import {WebSocketConnectReason, WebSocketOptions} from "../services/WebSocketService";
|
||||
import {
|
||||
AccountActivationParams,
|
||||
ForgotPasswordChallengeParams,
|
||||
ForgotPasswordParams,
|
||||
ForgotPasswordResetParams,
|
||||
RequestPasswordSaltParams,
|
||||
ServerRegisterParams
|
||||
} from "../../store";
|
||||
import NormalizeService from "../utils/NormalizeService";
|
||||
|
|
@ -36,14 +37,19 @@ export class SessionCommands {
|
|||
webClient.disconnect();
|
||||
}
|
||||
|
||||
static login(): void {
|
||||
const loginConfig = {
|
||||
static login(passwordSalt?: string): void {
|
||||
const loginConfig: any = {
|
||||
...webClient.clientConfig,
|
||||
userName: webClient.options.user,
|
||||
password: webClient.options.pass,
|
||||
clientid: guid()
|
||||
};
|
||||
|
||||
if (passwordSalt) {
|
||||
loginConfig.hashedPassword = hashPassword(passwordSalt, webClient.options.pass);
|
||||
} else {
|
||||
loginConfig.password = webClient.options.pass;
|
||||
}
|
||||
|
||||
const CmdLogin = webClient.protobuf.controller.Command_Login.create(loginConfig);
|
||||
|
||||
const command = webClient.protobuf.controller.SessionCommand.create({
|
||||
|
|
@ -110,6 +116,40 @@ export class SessionCommands {
|
|||
});
|
||||
}
|
||||
|
||||
static requestPasswordSalt(): void {
|
||||
const options = webClient.options as unknown as RequestPasswordSaltParams;
|
||||
|
||||
const registerConfig = {
|
||||
...webClient.clientConfig,
|
||||
userName: options.user,
|
||||
};
|
||||
|
||||
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;
|
||||
SessionCommands.login(passwordSalt);
|
||||
break;
|
||||
|
||||
case webClient.protobuf.controller.Response.ResponseCode.RespRegistrationRequired:
|
||||
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, "Login failed: incorrect username or password");
|
||||
SessionCommands.disconnect();
|
||||
break;
|
||||
|
||||
default:
|
||||
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, "Login failed: Unknown Reason");
|
||||
SessionCommands.disconnect();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static register(): void {
|
||||
const options = webClient.options as unknown as ServerRegisterParams;
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ function removeFromList({ listName, userName }: RemoveFromListData) {
|
|||
}
|
||||
|
||||
function serverIdentification(info: ServerIdentificationData) {
|
||||
const { serverName, serverVersion, protocolVersion } = info;
|
||||
const { serverName, serverVersion, protocolVersion, serverOptions } = info;
|
||||
|
||||
if (protocolVersion !== webClient.protocolVersion) {
|
||||
SessionCommands.updateStatus(StatusEnum.DISCONNECTED, `Protocol version mismatch: ${protocolVersion}`);
|
||||
|
|
@ -124,7 +124,12 @@ function serverIdentification(info: ServerIdentificationData) {
|
|||
switch (webClient.options.reason) {
|
||||
case WebSocketConnectReason.LOGIN:
|
||||
SessionCommands.updateStatus(StatusEnum.LOGGING_IN, 'Logging In...');
|
||||
SessionCommands.login();
|
||||
// Intentional use of Bitwise operator b/c of how Servatrice Enums work
|
||||
if (serverOptions & webClient.protobuf.controller.Event_ServerIdentification.ServerOptions.SupportsPasswordHash) {
|
||||
SessionCommands.requestPasswordSalt();
|
||||
} else {
|
||||
SessionCommands.login();
|
||||
}
|
||||
break;
|
||||
case WebSocketConnectReason.REGISTER:
|
||||
SessionCommands.register();
|
||||
|
|
@ -198,6 +203,7 @@ export interface ServerIdentificationData {
|
|||
protocolVersion: number;
|
||||
serverName: string;
|
||||
serverVersion: string;
|
||||
serverOptions: number;
|
||||
}
|
||||
|
||||
export interface ServerMessageData {
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export * from "./guid.util";
|
||||
export * from "./sanitizeHtml.util";
|
||||
export * from "./sanitizeHtml.util";
|
||||
export * from "./passwordHasher";
|
||||
26
webclient/src/websocket/utils/passwordHasher.ts
Normal file
26
webclient/src/websocket/utils/passwordHasher.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import sha512 from 'crypto-js/sha512';
|
||||
import Base64 from 'crypto-js/enc-base64';
|
||||
|
||||
const HASH_ROUNDS = 1_000;
|
||||
const SALT_LENGTH = 16;
|
||||
|
||||
export const hashPassword = (salt: string, password: string): string => {
|
||||
let hashedPassword = salt + password;
|
||||
for (let i = 0; i < HASH_ROUNDS; i++) {
|
||||
// WHY DO WE DO IT THIS WAY?
|
||||
hashedPassword = sha512(hashedPassword);
|
||||
}
|
||||
|
||||
return salt + Base64.stringify(hashedPassword);
|
||||
};
|
||||
|
||||
export const generateSalt = (): string => {
|
||||
const characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
let salt = "";
|
||||
for (let i = 0; i < SALT_LENGTH; i++) {
|
||||
salt += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
|
||||
return salt;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue