Refactor
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import { PasswordPrompt } from "./passwordPrompt.js";
|
import { Security } from "../../utils/security.js";
|
||||||
import { Scene } from "../scene.js";
|
import { Config } from "../../config.js";
|
||||||
import { UsernamePrompt } from "./usernamePrompt.js";
|
|
||||||
import { PlayerCreationScene } from "../playerCreation/playerCreationSene.js";
|
|
||||||
import { GameScene } from "../gameLoop/gameScene.js";
|
import { GameScene } from "../gameLoop/gameScene.js";
|
||||||
|
import { PlayerCreationScene } from "../playerCreation/playerCreationSene.js";
|
||||||
|
import { Prompt } from "../prompt.js";
|
||||||
|
import { Scene } from "../scene.js";
|
||||||
|
import { gGame } from "../../models/globals.js";
|
||||||
|
|
||||||
/** @typedef {import("../../models/player.js").Player} Player */
|
/** @typedef {import("../../models/player.js").Player} Player */
|
||||||
|
|
||||||
@@ -16,7 +18,6 @@ export class AuthenticationScene extends Scene {
|
|||||||
player;
|
player;
|
||||||
|
|
||||||
onReady() {
|
onReady() {
|
||||||
// current prompt
|
|
||||||
this.show(UsernamePrompt);
|
this.show(UsernamePrompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,3 +45,136 @@ export class AuthenticationScene extends Scene {
|
|||||||
this.session.setScene(new PlayerCreationScene(this.session));
|
this.session.setScene(new PlayerCreationScene(this.session));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UsernamePrompt extends Prompt {
|
||||||
|
//
|
||||||
|
promptText = [
|
||||||
|
"Please enter your username:", //
|
||||||
|
"(((type *:create* if you want to create a new user)))", //
|
||||||
|
];
|
||||||
|
|
||||||
|
//
|
||||||
|
// When player types :help
|
||||||
|
helpText = [
|
||||||
|
"This is where you log in.",
|
||||||
|
"If you don't already have a player profile on this server, you can type *:create* to create one",
|
||||||
|
];
|
||||||
|
|
||||||
|
//
|
||||||
|
// Let the client know that we're asking for a username
|
||||||
|
promptOptions = { username: true };
|
||||||
|
|
||||||
|
/** @returns {AuthenticationScene} */
|
||||||
|
get scene() {
|
||||||
|
return this._scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// User replied to our prompt
|
||||||
|
onReply(username) {
|
||||||
|
//
|
||||||
|
// do basic syntax checks on usernames
|
||||||
|
if (!Security.isUsernameSane(username)) {
|
||||||
|
console.info("Someone entered insane username: '%s'", username);
|
||||||
|
this.sendError("Incorrect username, try again");
|
||||||
|
this.execute();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// try and fetch the player object from the game
|
||||||
|
const player = gGame.getPlayerByUsername(username);
|
||||||
|
|
||||||
|
//
|
||||||
|
// handle invalid username
|
||||||
|
if (!player) {
|
||||||
|
console.info("Someone entered incorrect username: '%s'", username);
|
||||||
|
this.sendError("Incorrect username, try again");
|
||||||
|
this.execute();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Tell daddy that we're done
|
||||||
|
this.scene.usernameAccepted(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PasswordPrompt extends Prompt {
|
||||||
|
//
|
||||||
|
promptText = "Please enter your password";
|
||||||
|
|
||||||
|
//
|
||||||
|
// Let the client know that we're asking for a password
|
||||||
|
// so it can set <input type="password">
|
||||||
|
promptOptions = { password: true };
|
||||||
|
|
||||||
|
get player() {
|
||||||
|
return this.scene.player;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {AuthenticationScene} */
|
||||||
|
get scene() {
|
||||||
|
return this._scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
onReply(text) {
|
||||||
|
//
|
||||||
|
// Check of the password is sane. This is both bad from a security point
|
||||||
|
// of view, and technically not necessary as insane passwords couldn't
|
||||||
|
// reside in the player lists. However, let's save some CPU cycles on
|
||||||
|
// not hashing an insane password 1000+ times.
|
||||||
|
// This is technically bad practice, but since this is just a game,
|
||||||
|
// do it anyway.
|
||||||
|
if (!Security.isPasswordSane(text)) {
|
||||||
|
this.sendError("Insane password");
|
||||||
|
this.execute();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Block users who enter bad passwords too many times.
|
||||||
|
if (this.player.failedPasswordsSinceLastLogin > Config.maxFailedLogins) {
|
||||||
|
this.blockedUntil = Date.now() + Config.accountLockoutSeconds * 1000;
|
||||||
|
this.calamity("You have been locked out for too many failed password attempts, come back later");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Handle blocked users.
|
||||||
|
// They don't even get to have their password verified.
|
||||||
|
if (this.player.blockedUntil > Date.now()) {
|
||||||
|
//
|
||||||
|
// Try to re-login too soon, and your lockout lasts longer.
|
||||||
|
this.blockedUntil += Config.accountLockoutSeconds * 1000;
|
||||||
|
this.calamity("You have been locked out for too many failed password attempts, come back later");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Verify the password against the hash we've stored.
|
||||||
|
if (!Security.verifyPassword(text, this.player.passwordHash)) {
|
||||||
|
this.sendError("Incorrect password!");
|
||||||
|
this.player.failedPasswordsSinceLastLogin++;
|
||||||
|
|
||||||
|
this.session.sendDebug(`Failed login attempt #${this.player.failedPasswordsSinceLastLogin}`);
|
||||||
|
this.execute();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player.lastSucessfulLoginAt = new Date();
|
||||||
|
this.player.failedPasswordsSinceLastLogin = 0;
|
||||||
|
|
||||||
|
//
|
||||||
|
// We do not allow a player to be logged in more than once!
|
||||||
|
if (this.player.loggedIn) {
|
||||||
|
this.calamity("This player is already logged in");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Password was correct, go to main game
|
||||||
|
// this.scene.passwordAccepted();
|
||||||
|
this.scene.passwordAccepted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
import { Prompt } from "../prompt.js";
|
|
||||||
import * as security from "../../utils/security.js";
|
|
||||||
import { Config } from "../../config.js";
|
|
||||||
|
|
||||||
/** @typedef {import("./authentication.js").AuthenticationScene} AuthenticationScene */
|
|
||||||
|
|
||||||
export class PasswordPrompt extends Prompt {
|
|
||||||
//
|
|
||||||
promptText = "Please enter your password";
|
|
||||||
|
|
||||||
//
|
|
||||||
// Let the client know that we're asking for a password
|
|
||||||
// so it can set <input type="password">
|
|
||||||
promptOptions = { password: true };
|
|
||||||
|
|
||||||
get player() {
|
|
||||||
return this.scene.player;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {AuthenticationScene} */
|
|
||||||
get scene() {
|
|
||||||
return this._scene;
|
|
||||||
}
|
|
||||||
|
|
||||||
onReply(text) {
|
|
||||||
//
|
|
||||||
// Check of the password is sane. This is both bad from a security point
|
|
||||||
// of view, and technically not necessary as insane passwords couldn't
|
|
||||||
// reside in the player lists. However, let's save some CPU cycles on
|
|
||||||
// not hashing an insane password 1000+ times.
|
|
||||||
// This is technically bad practice, but since this is just a game,
|
|
||||||
// do it anyway.
|
|
||||||
if (!security.isPasswordSane(text)) {
|
|
||||||
this.sendError("Insane password");
|
|
||||||
this.execute();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Block users who enter bad passwords too many times.
|
|
||||||
if (this.player.failedPasswordsSinceLastLogin > Config.maxFailedLogins) {
|
|
||||||
this.blockedUntil = Date.now() + Config.accountLockoutSeconds * 1000;
|
|
||||||
this.calamity("You have been locked out for too many failed password attempts, come back later");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Handle blocked users.
|
|
||||||
// They don't even get to have their password verified.
|
|
||||||
if (this.player.blockedUntil > Date.now()) {
|
|
||||||
//
|
|
||||||
// Try to re-login too soon, and your lockout lasts longer.
|
|
||||||
this.blockedUntil += Config.accountLockoutSeconds * 1000;
|
|
||||||
this.calamity("You have been locked out for too many failed password attempts, come back later");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Verify the password against the hash we've stored.
|
|
||||||
if (!security.verifyPassword(text, this.player.passwordHash)) {
|
|
||||||
this.sendError("Incorrect password!");
|
|
||||||
this.player.failedPasswordsSinceLastLogin++;
|
|
||||||
|
|
||||||
this.session.sendDebug(`Failed login attempt #${this.player.failedPasswordsSinceLastLogin}`);
|
|
||||||
this.execute();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.player.lastSucessfulLoginAt = new Date();
|
|
||||||
this.player.failedPasswordsSinceLastLogin = 0;
|
|
||||||
|
|
||||||
//
|
|
||||||
// We do not allow a player to be logged in more than once!
|
|
||||||
if (this.player.loggedIn) {
|
|
||||||
this.calamity("This player is already logged in");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Password was correct, go to main game
|
|
||||||
// this.scene.passwordAccepted();
|
|
||||||
this.scene.passwordAccepted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import { Prompt } from "../prompt.js";
|
|
||||||
import { gGame } from "../../models/globals.js";
|
|
||||||
import * as security from "../../utils/security.js";
|
|
||||||
|
|
||||||
/** @typedef {import("./authenticationScene.js").AuthenticationScene} AuthenticationScene */
|
|
||||||
/** @typedef {import("../../models/player.js").Player} Player */
|
|
||||||
|
|
||||||
export class UsernamePrompt extends Prompt {
|
|
||||||
//
|
|
||||||
promptText = [
|
|
||||||
"Please enter your username:", //
|
|
||||||
"(((type *:create* if you want to create a new user)))", //
|
|
||||||
];
|
|
||||||
|
|
||||||
//
|
|
||||||
// When player types :help
|
|
||||||
helpText = [
|
|
||||||
"This is where you log in.",
|
|
||||||
"If you don't already have a player profile on this server, you can type *:create* to create one",
|
|
||||||
];
|
|
||||||
|
|
||||||
//
|
|
||||||
// Let the client know that we're asking for a username
|
|
||||||
promptOptions = { username: true };
|
|
||||||
|
|
||||||
/** @returns {AuthenticationScene} */
|
|
||||||
get scene() {
|
|
||||||
return this._scene;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// User replied to our prompt
|
|
||||||
onReply(username) {
|
|
||||||
//
|
|
||||||
// do basic syntax checks on usernames
|
|
||||||
if (!security.isUsernameSane(username)) {
|
|
||||||
console.info("Someone entered insane username: '%s'", username);
|
|
||||||
this.sendError("Incorrect username, try again");
|
|
||||||
this.execute();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// try and fetch the player object from the game
|
|
||||||
const player = gGame.getPlayerByUsername(username);
|
|
||||||
|
|
||||||
//
|
|
||||||
// handle invalid username
|
|
||||||
if (!player) {
|
|
||||||
console.info("Someone entered incorrect username: '%s'", username);
|
|
||||||
this.sendError("Incorrect username, try again");
|
|
||||||
this.execute();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Tell daddy that we're done
|
|
||||||
this.scene.usernameAccepted(player);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,7 @@ export class GameScene extends Scene {
|
|||||||
// If player does not have a previous session
|
// If player does not have a previous session
|
||||||
// then we start in the Adventurers Guild in the Hovedstad
|
// then we start in the Adventurers Guild in the Hovedstad
|
||||||
//
|
//
|
||||||
this.doPrompt("new command prompt or whatever");
|
this.showBasicPrompt(this.castle);
|
||||||
}
|
}
|
||||||
|
|
||||||
get castle() {
|
get castle() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Prompt } from "../prompt.js";
|
import { Prompt } from "../prompt.js";
|
||||||
import * as security from "../../utils/security.js";
|
import { Security } from "../../utils/security.js";
|
||||||
import { Config } from "../../config.js";
|
import { Config } from "../../config.js";
|
||||||
|
|
||||||
export class CreatePasswordPrompt extends Prompt {
|
export class CreatePasswordPrompt extends Prompt {
|
||||||
@@ -23,7 +23,7 @@ export class CreatePasswordPrompt extends Prompt {
|
|||||||
// not hashing an insane password 1000+ times.
|
// not hashing an insane password 1000+ times.
|
||||||
// This is technically bad practice, but since this is just a game,
|
// This is technically bad practice, but since this is just a game,
|
||||||
// do it anyway.
|
// do it anyway.
|
||||||
if (!security.isPasswordSane(text)) {
|
if (!Security.isPasswordSane(text)) {
|
||||||
this.sendError("Insane password");
|
this.sendError("Insane password");
|
||||||
this.execute();
|
this.execute();
|
||||||
return;
|
return;
|
||||||
@@ -50,7 +50,7 @@ export class CreatePasswordPrompt extends Prompt {
|
|||||||
|
|
||||||
//
|
//
|
||||||
// Verify the password against the hash we've stored.
|
// Verify the password against the hash we've stored.
|
||||||
if (!security.verifyPassword(text, this.player.passwordHash)) {
|
if (!Security.verifyPassword(text, this.player.passwordHash)) {
|
||||||
this.sendError("Incorrect password!");
|
this.sendError("Incorrect password!");
|
||||||
this.player.failedPasswordsSinceLastLogin++;
|
this.player.failedPasswordsSinceLastLogin++;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Prompt } from "../prompt.js";
|
import { Prompt } from "../prompt.js";
|
||||||
import * as security from "../../utils/security.js";
|
import { Security } from "../../utils/security.js";
|
||||||
import { gGame } from "../../models/globals.js";
|
import { gGame } from "../../models/globals.js";
|
||||||
|
|
||||||
/** @typedef {import("./playerCreationScene.js").PlayerCreationScene} PlayerCreationScene */
|
/** @typedef {import("./playerCreationScene.js").PlayerCreationScene} PlayerCreationScene */
|
||||||
@@ -35,7 +35,7 @@ export class CreateUsernamePrompt extends Prompt {
|
|||||||
onReply(username) {
|
onReply(username) {
|
||||||
//
|
//
|
||||||
// do basic syntax checks on usernames
|
// do basic syntax checks on usernames
|
||||||
if (!security.isUsernameSane(username)) {
|
if (!Security.isUsernameSane(username)) {
|
||||||
console.info("Someone entered insane username: '%s'", username);
|
console.info("Someone entered insane username: '%s'", username);
|
||||||
this.sendError("Incorrect username, try again.");
|
this.sendError("Incorrect username, try again.");
|
||||||
this.execute();
|
this.execute();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Config } from "../../config.js";
|
import { Config } from "../../config.js";
|
||||||
import { gGame } from "../../models/globals.js";
|
import { gGame } from "../../models/globals.js";
|
||||||
import { generateHash } from "../../utils/security.js";
|
import { Security } from "../../utils/security.js";
|
||||||
import { Scene } from "../scene.js";
|
import { Scene } from "../scene.js";
|
||||||
import { CreateUsernamePrompt } from "./createUsernamePrompt.js";
|
import { CreateUsernamePrompt } from "./createUsernamePrompt.js";
|
||||||
|
|
||||||
@@ -48,6 +48,6 @@ export class PlayerCreationScene extends Scene {
|
|||||||
passwordAccepted(password) {
|
passwordAccepted(password) {
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.session.sendText("*_Success_* ✅ You will now be asked to log in again, sorry for that ;)");
|
this.session.sendText("*_Success_* ✅ You will now be asked to log in again, sorry for that ;)");
|
||||||
this.player.setPasswordHash(generateHash(this.password));
|
this.player.setPasswordHash(Security.generateHash(this.password));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,13 @@ export class Scene {
|
|||||||
*/
|
*/
|
||||||
introText = "";
|
introText = "";
|
||||||
|
|
||||||
|
/** @constant @readonly @type {Prompt?} */
|
||||||
|
introPrompt;
|
||||||
|
|
||||||
/** @readonly @constant @protected @type {Session} */
|
/** @readonly @constant @protected @type {Session} */
|
||||||
_session;
|
#session;
|
||||||
get session() {
|
get session() {
|
||||||
return this._session;
|
return this.#session;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,21 +37,27 @@ export class Scene {
|
|||||||
* @readonly
|
* @readonly
|
||||||
* @type {Prompt}
|
* @type {Prompt}
|
||||||
*/
|
*/
|
||||||
_currentPrompt;
|
#currentPrompt;
|
||||||
get currentPrompt() {
|
get currentPrompt() {
|
||||||
return this._currentPrompt;
|
return this.#currentPrompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
/** @param {Session} session */
|
/** @param {Session} session */
|
||||||
execute(session) {
|
execute(session) {
|
||||||
this._session = session;
|
this.#session = session;
|
||||||
|
|
||||||
if (this.introText) {
|
if (this.introText) {
|
||||||
this.session.sendText(this.introText);
|
this.session.sendText(this.introText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.introPrompt) {
|
||||||
|
this.showPrompt(this.introPrompt);
|
||||||
|
} else {
|
||||||
this.onReady();
|
this.onReady();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @abstract */
|
/** @abstract */
|
||||||
onReady() {
|
onReady() {
|
||||||
@@ -59,7 +68,7 @@ export class Scene {
|
|||||||
* @param {Prompt} prompt
|
* @param {Prompt} prompt
|
||||||
*/
|
*/
|
||||||
showPrompt(prompt) {
|
showPrompt(prompt) {
|
||||||
this._currentPrompt = prompt;
|
this.#currentPrompt = prompt;
|
||||||
prompt.execute();
|
prompt.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,24 +6,25 @@ const ITERATIONS = 1000; // MAGIC CONSTANT - move to Config
|
|||||||
const DIGEST = "sha256"; // MAGIC CONSTANT - move to Config
|
const DIGEST = "sha256"; // MAGIC CONSTANT - move to Config
|
||||||
const KEYLEN = 32; // MAGIC CONSTANT - move to Config
|
const KEYLEN = 32; // MAGIC CONSTANT - move to Config
|
||||||
|
|
||||||
/**
|
export class Security {
|
||||||
|
/**
|
||||||
* Generate a hash from a string
|
* Generate a hash from a string
|
||||||
* @param {string} source @returns {string}
|
* @param {string} source @returns {string}
|
||||||
*/
|
*/
|
||||||
export function generateHash(source) {
|
static generateHash(source) {
|
||||||
const salt = randomBytes(16).toString("hex"); // 128-bit salt
|
const salt = randomBytes(16).toString("hex"); // 128-bit salt
|
||||||
const hash = pbkdf2Sync(source, salt, ITERATIONS, KEYLEN, DIGEST).toString("hex");
|
const hash = pbkdf2Sync(source, salt, ITERATIONS, KEYLEN, DIGEST).toString("hex");
|
||||||
return `${ITERATIONS}:${salt}:${hash}`;
|
return `${ITERATIONS}:${salt}:${hash}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify that a password is correct against a given hash.
|
* Verify that a password is correct against a given hash.
|
||||||
*
|
*
|
||||||
* @param {string} password_candidate
|
* @param {string} password_candidate
|
||||||
* @param {string} stored_password_hash
|
* @param {string} stored_password_hash
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function verifyPassword(password_candidate, stored_password_hash) {
|
static verifyPassword(password_candidate, stored_password_hash) {
|
||||||
const [iterations, salt, hash] = stored_password_hash.split(":");
|
const [iterations, salt, hash] = stored_password_hash.split(":");
|
||||||
const derived = pbkdf2Sync(password_candidate, salt, Number(iterations), KEYLEN, DIGEST).toString("hex");
|
const derived = pbkdf2Sync(password_candidate, salt, Number(iterations), KEYLEN, DIGEST).toString("hex");
|
||||||
const success = hash === derived;
|
const success = hash === derived;
|
||||||
@@ -36,23 +37,24 @@ export function verifyPassword(password_candidate, stored_password_hash) {
|
|||||||
" Derived : %s (the hashed version of the input password)\n" +
|
" Derived : %s (the hashed version of the input password)\n" +
|
||||||
" Verified : %s (was the password valid)",
|
" Verified : %s (was the password valid)",
|
||||||
password_candidate,
|
password_candidate,
|
||||||
generateHash(password_candidate),
|
this.generateHash(password_candidate),
|
||||||
stored_password_hash,
|
stored_password_hash,
|
||||||
derived,
|
derived,
|
||||||
success,
|
success,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {string} candidate */
|
/** @param {string} candidate */
|
||||||
export function isUsernameSane(candidate) {
|
static isUsernameSane(candidate) {
|
||||||
return Config.usernameSanityRegex.test(candidate);
|
return Config.usernameSanityRegex.test(candidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {string} candidate */
|
/** @param {string} candidate */
|
||||||
export function isPasswordSane(candidate) {
|
static isPasswordSane(candidate) {
|
||||||
// We know the password must adhere to one of our client-side-hashed crypto schemes,
|
// We know the password must adhere to one of our client-side-hashed crypto schemes,
|
||||||
// so we can be fairly strict with the allowed passwords
|
// so we can be fairly strict with the allowed passwords
|
||||||
return Config.passwordHashSanityRegex.test(candidate);
|
return Config.passwordHashSanityRegex.test(candidate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user