Add basic token generation, primitive memory profiling

This commit is contained in:
Hri7566 2024-07-26 19:38:24 -04:00
parent 4d11fc1049
commit 7138e02570
9 changed files with 70 additions and 28 deletions

View File

@ -46,6 +46,7 @@ Brandon's server originally used MongoDB for storing user data, but there are to
## TODO ## TODO
- Implement both UUID-based and JWT-based token auth - Implement both UUID-based and JWT-based token auth
- Add `openssl genrsa -out mppkey 2048` to the instructions
- Redo all of the validations with Zod - Redo all of the validations with Zod
- This probably means making Zod schemas for every single message type - This probably means making Zod schemas for every single message type
- Also user and channel data - Also user and channel data

BIN
bun.lockb

Binary file not shown.

View File

@ -23,8 +23,15 @@ adminParticipant:
# Allows admins to evaluate code through the "eval" message. # Allows admins to evaluate code through the "eval" message.
# This is a security risk, so only enable this if you trust your admins. # This is a security risk, so only enable this if you trust your admins.
enableAdminEval: false enableAdminEval: true
# The token validation scheme. Valid values are "none", "jwt" and "uuid". # The token validation scheme. Valid values are "none", "jwt" and "uuid".
# This server will still validate existing tokens generated with other schemes if not set to "none". # This server will still validate existing tokens generated with other schemes if not set to "none".
tokenAuth: none tokenAuth: none
# The browser challenge scheme. Valid values are "none", "obf" and "basic".
# This is to change what is sent in the "b" message.
# "none" will disable the browser challenge,
# "obf" will sent an obfuscated function to the client,
# and "basic" will just send a simple function that expects a boolean.
browserChallenge: none

View File

@ -9,27 +9,27 @@
"dependencies": { "dependencies": {
"@prisma/client": "5.7.0", "@prisma/client": "5.7.0",
"@t3-oss/env-core": "^0.6.1", "@t3-oss/env-core": "^0.6.1",
"bun-types": "^1.0.1", "bun-types": "^1.1.20",
"commander": "^11.1.0", "commander": "^11.1.0",
"date-holidays": "^3.21.5", "date-holidays": "^3.23.12",
"events": "^3.3.0", "events": "^3.3.0",
"fancy-text-converter": "^1.0.9", "fancy-text-converter": "^1.0.9",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"keccak": "^2.1.0", "keccak": "^2.1.0",
"nunjucks": "^3.2.4", "nunjucks": "^3.2.4",
"unique-names-generator": "^4.7.1", "unique-names-generator": "^4.7.1",
"yaml": "^2.3.2", "yaml": "^2.4.5",
"zod": "^3.22.2" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "latest", "@types/bun": "latest",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.5.9", "@types/node": "^20.14.12",
"@types/nunjucks": "^3.2.6", "@types/nunjucks": "^3.2.6",
"@typescript-eslint/eslint-plugin": "^6.19.1", "@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.19.1", "@typescript-eslint/parser": "^6.21.0",
"eslint": "^8.56.0", "eslint": "^8.57.0",
"prisma": "5.7.0", "prisma": "5.7.0",
"typescript": "^5.2.2" "typescript": "^5.5.4"
} }
} }

View File

@ -20,6 +20,7 @@ import { config as usersConfig } from "../ws/usersConfig";
import { saveChatHistory, getChatHistory } from "../data/history"; import { saveChatHistory, getChatHistory } from "../data/history";
import { mixin, darken } from "../util/helpers"; import { mixin, darken } from "../util/helpers";
import { User } from "@prisma/client"; import { User } from "@prisma/client";
import { heapStats } from "bun:jsc";
interface CachedKickban { interface CachedKickban {
userId: string; userId: string;
@ -112,6 +113,12 @@ export class Channel extends EventEmitter {
this.settings.owner_id = owner_id; this.settings.owner_id = owner_id;
this.logger.info("Created"); this.logger.info("Created");
if (this.getID() == "test/mem") {
setInterval(() => {
this.printMemoryInChat();
}, 1000);
}
} }
private alreadyBound = false; private alreadyBound = false;
@ -205,8 +212,8 @@ export class Channel extends EventEmitter {
const ownsChannel = this.hasUser(socket.getUserID()); const ownsChannel = this.hasUser(socket.getUserID());
if (cmd == "help") { if (cmd == "help") {
} else if (cmd == "") { } else if (cmd == "mem") {
this.printMemoryInChat();
} }
}); });
@ -1139,6 +1146,11 @@ export class Channel extends EventEmitter {
} }
} }
} }
public printMemoryInChat() {
const mem = heapStats();
this.sendChatAdmin(`Size: ${(mem.heapSize / 1000 / 1000).toFixed(2)}M / Capacity: ${(mem.heapCapacity / 1000 / 1000).toFixed(2)}M`);
}
} }
export default Channel; export default Channel;

View File

@ -49,5 +49,17 @@ export function generateToken(id: string): Promise<string | undefined> | undefin
if (token) resolve(token); if (token) resolve(token);
}); });
} else return undefined; } else return new Promise(() => undefined);
}
export function verifyToken(token: string) {
return jsonwebtoken.verify(token, privkey, { algorithms: ["RS256"] });
}
export async function decryptJWT(token: string) {
if (config.tokenAuth != "jwt") return undefined;
if (!privkey) throw new Error("Cannot decrypt JWT without private key loaded");
return jsonwebtoken.decode(token);
} }

View File

@ -1,7 +1,10 @@
import { Logger } from "../../../../util/Logger";
import { generateToken } from "../../../../util/token"; import { generateToken } from "../../../../util/token";
import { ServerEventListener } from "../../../../util/types"; import { ServerEventListener } from "../../../../util/types";
import { config } from "../../../usersConfig"; import { config } from "../../../usersConfig";
const logger = new Logger("Hi handler");
export const hi: ServerEventListener<"hi"> = { export const hi: ServerEventListener<"hi"> = {
id: "hi", id: "hi",
callback: async (msg, socket) => { callback: async (msg, socket) => {
@ -9,21 +12,25 @@ export const hi: ServerEventListener<"hi"> = {
let generatedToken: string | undefined; let generatedToken: string | undefined;
// Is the browser challenge enabled and has the user completed it?
if (config.browserChallenge !== "none" && !socket.gateway.hasCompletedBrowserChallenge) return;
// Is token auth enabled?
if (config.tokenAuth !== "none") { if (config.tokenAuth !== "none") {
if (socket.gateway.hasCompletedBrowserChallenge) { logger.debug("token auth is enabled");
if (msg.token) {
// Check if they have passed the browser challenge // Is the browser challenge enabled and has the user completed it?
// Send the token to the authenticator if (msg.token) {
// TODO // Check if they have passed the browser challenge
} else { // Send the token to the authenticator
// Generate a token // TODO
generatedToken = await generateToken(socket.getUserID()) as string | undefined;
if (!generatedToken) return;
}
} else { } else {
// TODO Ban the user for logging in without the browser // Generate a token
// TODO config for this generatedToken = await generateToken(socket.getUserID());
if (!generatedToken) return;
} }
logger.debug("token:", generatedToken);
} }
if (socket.rateLimits) if (socket.rateLimits)
@ -51,7 +58,8 @@ export const hi: ServerEventListener<"hi"> = {
_id: part._id, _id: part._id,
color: part.color, color: part.color,
name: part.name name: part.name
} },
token: generatedToken
} }
]); ]);

View File

@ -91,7 +91,7 @@ export const app = Bun.serve<{ ip: string }>({
// logger.debug("Connection at " + socket.getIP()); // logger.debug("Connection at " + socket.getIP());
// Let's put it in the dinner bucket. // Let's put it in the dinner bucket.
socketsBySocketID.set(socket.socketID, socket); socketsBySocketID.set((socket.socketID as any), socket);
}, },
message: (ws, message) => { message: (ws, message) => {

View File

@ -9,6 +9,7 @@ export interface UsersConfig {
adminParticipant: Participant; adminParticipant: Participant;
enableAdminEval: boolean; enableAdminEval: boolean;
tokenAuth: "jwt" | "uuid" | "none"; tokenAuth: "jwt" | "uuid" | "none";
browserChallenge: "none" | "obf" | "basic";
} }
export const usersConfigPath = "config/users.yml"; export const usersConfigPath = "config/users.yml";
@ -27,7 +28,8 @@ export const defaultUsersConfig: UsersConfig = {
id: "0" id: "0"
}, },
enableAdminEval: false, enableAdminEval: false,
tokenAuth: "none" tokenAuth: "none",
browserChallenge: "none"
}; };
// Importing this elsewhere causes bun to segfault // Importing this elsewhere causes bun to segfault