import assert from "node:assert/strict"; import { test } from "node:test"; import { browserAccount, limitRoomCreation } from "../lib/request-security"; import { jsonBody, jsonBodyLimit } from "../lib/json-body"; import { ServiceError } from "../lib/service-error"; test("device credentials cannot authorize staff access or create replacement credentials", async () => { let lookups = 0; for (const authorization of ["Bearer mg_device-token", "Basic ignored", ""]) { await assert.rejects( browserAccount({ headers: new Headers({ authorization }) }, async () => { lookups++; return { id: "administrator", role: "admin" }; }), (error) => error instanceof ServiceError && error.status === 403, ); } assert.equal(lookups, 0); await assert.rejects( browserAccount({ headers: new Headers() }, async () => null), (error) => error instanceof ServiceError && error.status === 401, ); assert.deepEqual( await browserAccount({ headers: new Headers() }, async () => ({ id: "owner", })), { id: "owner" }, ); }); test("clearing a multiplayer cookie cannot reset the account's room creation limit", async () => { const counts = new Map(); const limit = async (key: string, maximum: number, seconds: number) => { assert.equal(seconds, 3600); const count = (counts.get(key) ?? 0) + 1; counts.set(key, count); if (count > maximum) throw new Error("Too many requests"); }; for (let i = 0; i < 20; i++) await limitRoomCreation("owner", `cookie-${i}`, limit); await assert.rejects( limitRoomCreation("owner", "fresh-cookie", limit), /Too many/, ); assert.equal(counts.has("multiplayer:create:fresh-cookie"), false); await assert.doesNotReject( limitRoomCreation("another-owner", "other-cookie", limit), ); }); test("JSON bodies enforce the byte limit while streaming and report HTTP 413", async () => { let cancelled = false; let chunk = 0; const oversized = new ReadableStream>( { pull(controller) { chunk++; controller.enqueue(new Uint8Array(jsonBodyLimit / 2 + 1)); }, cancel() { cancelled = true; }, }, { highWaterMark: 0 }, ); await assert.rejects( jsonBody({ body: oversized }), (error) => error instanceof ServiceError && error.status === 413, ); assert.equal(cancelled, true); assert.equal(chunk, 2); const atLimit = '"' + "x".repeat(jsonBodyLimit - 2) + '"'; assert.equal( ( await jsonBody( new Request("https://minegeo.example", { method: "POST", body: atLimit, }), ) ).length, jsonBodyLimit - 2, ); assert.deepEqual( await jsonBody( new Request("https://minegeo.example", { method: "POST", body: JSON.stringify({ message: "Привет (:" }), }), ), { message: "Привет (:" }, ); await assert.rejects( jsonBody({ body: null }), (error) => error instanceof ServiceError && error.status === 400, ); await assert.rejects( jsonBody( new Request("https://minegeo.example", { method: "POST", body: "{" }), ), SyntaxError, ); });