import { test } from "node:test"; import assert from "node:assert/strict"; import { authActionSchema, authDestination, displayNameSchema, newPasswordSchema, profileUpdateSchema, } from "../shared/account"; 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"); 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, ); });