All checks were successful
Deploy MineGeo to Dokploy / deploy (push) Successful in 6m29s
148 lines
4.8 KiB
TypeScript
148 lines
4.8 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
authActionSchema,
|
|
authDestination,
|
|
displayNameSchema,
|
|
newPasswordSchema,
|
|
profileUpdateSchema,
|
|
usernameSchema,
|
|
} from "../shared/account";
|
|
test("usernames are normalized, unique-key compatible and changeable through profile updates", () => {
|
|
assert.equal(usernameSchema.parse(" Perfect_Python500 "), "perfect_python500");
|
|
assert.deepEqual(profileUpdateSchema.parse({ username: "New_Name" }), { username: "new_name" });
|
|
for (const bad of ["ab", "a".repeat(33), "name space", "name@server", "юзер", "a\nb"]) assert.equal(usernameSchema.safeParse(bad).success, false);
|
|
const path = "/moderator-application/" + "a".repeat(64);
|
|
assert.equal(authDestination(path), path);
|
|
assert.equal(authDestination(path + "?next=https://evil.example"), "/dashboard");
|
|
});
|
|
import {
|
|
createRecoveryGrant,
|
|
verifyRecoveryGrant,
|
|
} from "../shared/password-recovery";
|
|
test("display names allow Unicode and spaces, normalize surrounding whitespace, reject hidden controls", () => {
|
|
assert.equal(displayNameSchema.parse(" Pavel (: "), "Pavel (:");
|
|
assert.equal(displayNameSchema.parse("Исследователь"), "Исследователь");
|
|
for (const bad of [
|
|
"A",
|
|
"a".repeat(33),
|
|
"Name\nAdmin",
|
|
"Name\u202eadmin",
|
|
"\u0000Explorer",
|
|
])
|
|
assert.equal(displayNameSchema.safeParse(bad).success, false);
|
|
assert.equal(
|
|
profileUpdateSchema.safeParse({
|
|
displayName: "Explorer",
|
|
role: "moderator",
|
|
}).success,
|
|
false,
|
|
);
|
|
assert.equal(
|
|
profileUpdateSchema.safeParse({ displayName: "Explorer", id: "other-user" })
|
|
.success,
|
|
false,
|
|
);
|
|
});
|
|
test("new passwords require 12 characters and fit bcrypt UTF-8 input without trimming", () => {
|
|
assert.equal(newPasswordSchema.safeParse("short").success, false);
|
|
assert.equal(
|
|
newPasswordSchema.parse(" a passphrase with spaces "),
|
|
" a passphrase with spaces ",
|
|
);
|
|
assert.equal(newPasswordSchema.safeParse("x".repeat(72)).success, true);
|
|
assert.equal(newPasswordSchema.safeParse("x".repeat(73)).success, false);
|
|
assert.equal(newPasswordSchema.safeParse("🌍".repeat(20)).success, false);
|
|
// Existing accounts can log in using their original password policy.
|
|
assert.equal(
|
|
authActionSchema.safeParse({
|
|
action: "login",
|
|
email: "user@example.com",
|
|
password: "oldpass",
|
|
}).success,
|
|
true,
|
|
);
|
|
assert.equal(
|
|
authActionSchema.safeParse({
|
|
action: "signup",
|
|
email: "user@example.com",
|
|
password: "oldpass",
|
|
displayName: "Explorer",
|
|
}).success,
|
|
false,
|
|
);
|
|
});
|
|
test("authentication redirects accept supported local routes and reject open redirects", () => {
|
|
for (const next of [
|
|
"https://evil.example",
|
|
"//evil.example",
|
|
"/\\evil.example",
|
|
"/%2f%2fevil.example",
|
|
"/dashboard?next=https://evil.example",
|
|
"javascript:alert(1)",
|
|
])
|
|
assert.equal(authDestination(next), "/dashboard");
|
|
assert.equal(authDestination("/settings"), "/settings");
|
|
assert.equal(authDestination("/servers"), "/servers");
|
|
const id = "123e4567-e89b-12d3-a456-426614174000";
|
|
for (const next of [
|
|
"/ban/appeal",
|
|
"/dev/api",
|
|
"/admin",
|
|
"/moderation",
|
|
"/tournaments",
|
|
`/servers/${id}`,
|
|
`/tournaments/${id}`,
|
|
])
|
|
assert.equal(authDestination(next), next);
|
|
assert.equal(
|
|
authDestination(`/servers/${id}?next=//evil.example`),
|
|
"/dashboard",
|
|
);
|
|
const tournamentMap = `/tournaments?map=${id}`;
|
|
assert.equal(authDestination(tournamentMap), tournamentMap);
|
|
assert.equal(
|
|
authDestination(`${tournamentMap}&next=//evil.example`),
|
|
"/dashboard",
|
|
);
|
|
assert.equal(authDestination("/pair?code=1234ABCD"), "/pair?code=1234ABCD");
|
|
assert.equal(authDestination("/auth/reset-password"), "/auth/reset-password");
|
|
});
|
|
test("recovery grants bind account, session, signature and a ten-minute expiry", () => {
|
|
const now = 1700000000000,
|
|
key = "test-only-secret",
|
|
token = createRecoveryGrant("account", "session", key, now);
|
|
assert.equal(
|
|
verifyRecoveryGrant(token, "account", "session", key, now + 1000),
|
|
true,
|
|
);
|
|
for (const [user, session, secret, time] of [
|
|
["other", "session", key, now],
|
|
["account", "other", key, now],
|
|
["account", "session", "wrong", now],
|
|
["account", "session", key, now + 600000],
|
|
] as const)
|
|
assert.equal(
|
|
verifyRecoveryGrant(token, user, session, secret, time),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
verifyRecoveryGrant(
|
|
token.replace("1700000600", "1700009999"),
|
|
"account",
|
|
"session",
|
|
key,
|
|
now,
|
|
),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
verifyRecoveryGrant(undefined, "account", "session", key, now),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
verifyRecoveryGrant("malformed", "account", "session", key, now),
|
|
false,
|
|
);
|
|
});
|