This commit is contained in:
Kim Ravn Hansen
2025-09-14 13:04:48 +02:00
parent 8c196bb6a1
commit ed91a7f2f7
43 changed files with 2279 additions and 1727 deletions

View File

@@ -0,0 +1,80 @@
import { Prompt } from "../prompt.js";
import * as security from "../../utils/security.js";
import { Config } from "../../config.js";
import { context } from "../../utils/messages.js";
export class CreatePasswordPrompt extends Prompt {
//
promptText = ["Enter a 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;
}
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;
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;
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;
}
this.scene.passwordAccepted();
//
// Password was correct, go to main game
this.session.setState(new JustLoggedInState(this.session));
}
}

View File

@@ -0,0 +1,58 @@
import { Prompt } from "../prompt.js";
import * as security from "../../utils/security.js";
import { gGame } from "../../models/globals.js";
import { PlayerCreationScene } from "./playerCreationSene.js";
import { Config } from "../../config.js";
export class CreateUsernamePrompt extends Prompt {
//
promptText = [
"Enter your username", //
"((type *:help* for more info))" //
];
//
// When player types :help
helpText = [
"Your username.",
"It's used, along with your password, when you log in.",
"Other players can see it.",
"Other players can use it to chat or trade with you",
"It may only consist of the letters _a-z_, _A-Z_, _0-9_, and _underscore_",
];
//
// Let the client know that we're asking for a username
promptOptions = { username: true };
onReply(text) {
//
// do basic syntax checks on usernames
if (!security.isUsernameSane(text)) {
console.info("Someone entered insane username: '%s'", text);
this.sendError(
"Incorrect username, try again.",
);
this.execute();
return;
}
//
// try and fetch the player object from the game
const player = gGame.getPlayer(text);
//
// handle invalid username
if (player) {
console.info("Someone tried to create a user with an occupied username: '%s'", text);
this.sendError("Occupied, try something else");
this.execute();
return;
}
//
// Tell daddy that we're done
this.scene.onUsernameAccepted(player);
}
}

View File

@@ -0,0 +1,50 @@
import { Config } from "../../config.js";
import { gGame } from "../../models/globals.js";
import { Scene } from "../scene.js";
import { CreateUsernamePrompt } from "./createUsernamePrompt.js";
export class PlayerCreationScene extends Scene {
introText = "= Create Player";
/** @protected @type {Player} */
player;
/** @protected @type {string} */
password;
onReady() {
//
// If there are too many players, stop allowing new players in.
if (gGame._players.size >= Config.maxPlayers) {
this.session.calamity("Server is full, no more players can be created");
}
this.doPrompt(new CreateUsernamePrompt(this));
}
/**
* Called when the player has entered a valid and available username.
*
* @param {string} username
*/
onUsernameAccepted(username) {
const player = gGame.createPlayer(username);
this.player = player;
this.session.sendSystemMessage("salt", player.salt);
this.session.sendText(`Username _*${username}*_ is available, and I've reserved it for you :)`);
this.doPrompt("new passwordprompt");
}
/**
*
* Called when the player has entered a password and confirmed it.
*
* @param {string} password
*/
onPasswordAccepted(password) {
this.password = password;
this.session.sendText("*_Success_* ✅ You will now be asked to log in again, sorry for that ;)");
this.player.setPasswordHash(security.generateHash(this.password));
}
}