release: WRNexusJS 0.5.0
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import type { Context } from "@wrnexus/core";
|
||||
import { createAuthEngine } from "../src/engine.ts";
|
||||
import { setDefaultAuthEngine } from "../src/runtime.ts";
|
||||
import { MemoryAuthStore } from "../src/stores/memory.ts";
|
||||
import { POST as requestPasswordReset } from "../src/routes/api/password-request.ts";
|
||||
import { GET as listSessions } from "../src/routes/api/sessions.ts";
|
||||
import { POST as passkeyLoginOptions } from "../src/routes/api/passkeys-login-options.ts";
|
||||
|
||||
function context(request: Request): Context {
|
||||
const values = new Map<string, unknown>();
|
||||
return {
|
||||
req: request,
|
||||
// Deliberately use unrelated values. A route-specific package entry must
|
||||
// not infer its endpoint from ctx.url, ctx.req.url, params, or locals.
|
||||
url: new URL("https://example.test/__wrnexus/rewritten"),
|
||||
params: {},
|
||||
locals: {},
|
||||
lang: "en",
|
||||
t: (key: string) => key,
|
||||
ip: "127.0.0.1",
|
||||
user: null,
|
||||
cookies: {
|
||||
get: (name: string) => (name === "wire-csrf" ? "route-csrf-token" : undefined),
|
||||
} as Context["cookies"],
|
||||
localStorage: {} as Context["localStorage"],
|
||||
session: {
|
||||
id: () => "route-test-session",
|
||||
get: <T>(key: string) => values.get(key) as T | undefined,
|
||||
getAll: () => Object.fromEntries(values),
|
||||
set: (key: string, value: unknown) => {
|
||||
values.set(key, value);
|
||||
},
|
||||
delete: (key: string) => {
|
||||
values.delete(key);
|
||||
},
|
||||
regenerate: () => {},
|
||||
clear: () => {
|
||||
values.clear();
|
||||
},
|
||||
},
|
||||
} as Context;
|
||||
}
|
||||
|
||||
test("route-specific password recovery entry cannot fall through to Not Found", async () => {
|
||||
setDefaultAuthEngine(
|
||||
createAuthEngine({
|
||||
store: new MemoryAuthStore(),
|
||||
secret: "route-specific-entry-secret-longer-than-thirty-two-characters",
|
||||
}),
|
||||
);
|
||||
|
||||
const request = new Request("https://example.test/completely/unrelated", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-csrf-token": "route-csrf-token",
|
||||
},
|
||||
body: JSON.stringify({ identifier: "missing@example.test" }),
|
||||
});
|
||||
|
||||
const response = await requestPasswordReset(context(request));
|
||||
expect(response.status).toBe(200);
|
||||
expect(await response.json()).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
test("package auth routes reject unsafe requests without CSRF verification", async () => {
|
||||
const request = new Request("https://example.test/api/auth/password/request", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ identifier: "missing@example.test" }),
|
||||
});
|
||||
const response = await requestPasswordReset(context(request));
|
||||
expect(response.status).toBe(403);
|
||||
expect(await response.json()).toMatchObject({ ok: false, error: "Invalid CSRF token" });
|
||||
});
|
||||
|
||||
test("safe package auth routes do not require a CSRF token", async () => {
|
||||
const engine = createAuthEngine({
|
||||
store: new MemoryAuthStore(),
|
||||
secret: "safe-route-secret-that-is-longer-than-thirty-two-characters",
|
||||
});
|
||||
const registered = await engine.register({
|
||||
email: "sessions@example.test",
|
||||
password: "StrongPassword123",
|
||||
});
|
||||
setDefaultAuthEngine(engine);
|
||||
const ctx = context(new Request("https://example.test/api/auth/sessions", { method: "GET" }));
|
||||
ctx.user = registered.user!;
|
||||
ctx.locals.authUser = registered.user!;
|
||||
|
||||
const response = await listSessions(ctx);
|
||||
expect(response.status).toBe(200);
|
||||
expect(await response.json()).toMatchObject({ ok: true, sessions: [] });
|
||||
});
|
||||
|
||||
test("passkey routes return a controlled response when no provider is configured", async () => {
|
||||
setDefaultAuthEngine(
|
||||
createAuthEngine({
|
||||
store: new MemoryAuthStore(),
|
||||
secret: "missing-passkey-provider-secret-longer-than-thirty-two-characters",
|
||||
}),
|
||||
);
|
||||
const request = new Request("https://example.test/api/auth/passkeys/login/options", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-csrf-token": "route-csrf-token",
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
|
||||
const response = await passkeyLoginOptions(context(request));
|
||||
expect(response.status).toBe(503);
|
||||
expect(await response.json()).toMatchObject({
|
||||
ok: false,
|
||||
code: "passkey-provider-not-configured",
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user