104 lines
3.1 KiB
TypeScript
104 lines
3.1 KiB
TypeScript
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<string, number>();
|
|
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<Uint8Array<ArrayBuffer>>(
|
|
{
|
|
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,
|
|
);
|
|
});
|