import * as msg from "../../utils/messages.js"; import * as security from "../../utils/security.js"; import { CreatePlayerState } from "./createPlayer.js"; import { JustLoggedInState } from "./justLoggedIn.js"; import { Session } from "../session.js"; const STATE_EXPECT_USERNAME = "promptUsername"; const STATE_EXPECT_PASSWORD = "promptPassword"; const USERNAME_PROMPT = [ "Please enter your username", "((type *:help* for help))", "((type *:create* if you want to create a new user))", ]; const PASSWORD_PROMPT = "Please enter your password"; const ERROR_INSANE_PASSWORD = "Invalid password."; const ERROR_INSANE_USERNAME = "Username invalid, must be at 4-20 characters, and may only contain [a-z], [A-Z], [0-9] and underscore" const ERROR_INCORRECT_PASSWOD = "Incorrect password."; /** @property {Session} session */ export class AuthState { subState = STATE_EXPECT_USERNAME; /** * @param {Session} session */ constructor(session) { /** @type {Session} */ this.session = session; } onAttach() { this.session.sendFigletMessage("M U U H D"); this.session.sendPrompt("username", USERNAME_PROMPT); } /** @param {msg.ClientMessage} message */ onMessage(message) { if (this.subState === STATE_EXPECT_USERNAME) { this.receiveUsername(message); return; } if (this.subState === STATE_EXPECT_PASSWORD) { this.receivePassword(message); return; } console.error("Logic error, we received a message after we should have been logged in"); this.session.sendError("I received a message didn't know what to do with!"); } /** @param {msg.ClientMessage} message */ receiveUsername(message) { // // handle invalid message types if (!message.isUsernameResponse()) { this.session.sendError("Incorrect message type!"); this.session.sendPrompt("username", USERNAME_PROMPT); return; } // // Handle the creation of new players if (message.username === ":create") { // TODO: // Set gamestate = CreateNewPlayer // // Also check if player creation is allowed in cfg/env this.session.setState(new CreatePlayerState(this.session)); return; } // // do basic syntax checks on usernames if (!security.isUsernameSane(message.username)) { this.session.sendError(ERROR_INSANE_USERNAME); this.session.sendPrompt("username", USERNAME_PROMPT); return; } const player = this.session.game.getPlayer(message.username); // // handle invalid username if (!player) { // // This is a security risk. In the perfect world we would allow the player to enter both // username and password before kicking them out, but since the player's username is not // an email address, and we discourage from using “important” usernames, then we tell the // player that they entered an invalid username right away. // // NOTE FOR ACCOUNT CREATION // Do adult-word checks, so we dont have Fucky_McFuckFace // https://www.npmjs.com/package/glin-profanity this.session.sendError("Incorrect username, try again"); this.session.sendPrompt("username", USERNAME_PROMPT); return; } // // username was correct, proceed to next step this.session.player = player; this.subState = STATE_EXPECT_PASSWORD; this.session.sendPrompt("password", PASSWORD_PROMPT); } /** @param {msg.ClientMessage} message */ receivePassword(message) { // // handle invalid message types if (!message.isPasswordResponse()) { this.session.sendError("Incorrect message type!"); this.session.sendPrompt("password", PASSWORD_PROMPT); return; } // // 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(message.password)) { this.session.sendError(ERROR_INSANE_PASSWORD); this.session.sendPrompt("password", PASSWORD_PROMPT); return; } // // Verify the password against the hash we've stored. if (!security.verifyPassword(message.password, this.session.player.passwordHash)) { this.session.sendError("Incorrect password!"); this.session.sendPrompt("password", PASSWORD_PROMPT); this.session.player.failedPasswordsSinceLastLogin++; return; } this.session.player.lastSucessfulLoginAt = new Date(); this.session.player.failedPasswordsSinceLastLogin = 0; // // Password correct, check if player is an admin if (this.session.player.isAdmin) { // set state AdminJustLoggedIn } // // Password was correct, go to main game this.session.setState(new JustLoggedInState(this.session)); } }