159 lines
5.3 KiB
JavaScript
Executable File
159 lines
5.3 KiB
JavaScript
Executable File
import { Session } from "../session.js";
|
|
import * as msg from "../../utils/messages.js";
|
|
import * as security from "../../utils/security.js";
|
|
import { JustLoggedInState } from "./justLoggedIn.js";
|
|
import { CreatePlayerState } from "./createPlayer.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;
|
|
}
|
|
|
|
if (this.session.game.players.size === 0) {
|
|
console.error("there are no players registered");
|
|
}
|
|
|
|
const player = this.session.game.players.get(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);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// 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));
|
|
}
|
|
}
|