188 lines
5.7 KiB
TypeScript
188 lines
5.7 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import worker from "../src/index";
|
|
import type { Env } from "../src/env";
|
|
|
|
interface OpenAPISchemaResponse {
|
|
openapi: string;
|
|
info: {
|
|
title: string;
|
|
};
|
|
paths: Record<string, unknown>;
|
|
}
|
|
|
|
const env: Env = {
|
|
BETTER_AUTH_URL: "http://localhost:8788",
|
|
TRUSTED_ORIGINS: "http://localhost:8787",
|
|
};
|
|
|
|
let log: ReturnType<typeof vi.spyOn>;
|
|
|
|
beforeEach(() => {
|
|
log = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe("cfw-auth worker", () => {
|
|
it("does not expose a custom health endpoint", async () => {
|
|
const response = await worker.fetch(new Request("http://auth.local/healthz"), env);
|
|
expect(response.status).toBe(404);
|
|
});
|
|
|
|
it("does not expose a custom session wrapper", async () => {
|
|
const response = await worker.fetch(new Request("http://auth.local/internal/session"), env);
|
|
expect(response.status).toBe(404);
|
|
});
|
|
|
|
it("does not expose a custom Autumn sync endpoint", async () => {
|
|
const response = await worker.fetch(new Request("http://auth.local/internal/autumn/sync"), env);
|
|
expect(response.status).toBe(404);
|
|
});
|
|
|
|
it("exposes Better Auth OpenAPI reference", async () => {
|
|
const response = await worker.fetch(new Request("http://auth.local/api/auth/reference"), env);
|
|
expect(response.status).toBe(200);
|
|
expect(response.headers.get("content-type") ?? "").toContain("text/html");
|
|
});
|
|
|
|
it("exposes Better Auth OpenAPI schema", async () => {
|
|
const response = await worker.fetch(
|
|
new Request("http://auth.local/api/auth/open-api/generate-schema"),
|
|
env,
|
|
);
|
|
const schema = (await response.json()) as OpenAPISchemaResponse;
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.headers.get("content-type") ?? "").toContain("application/json");
|
|
expect(schema).toMatchObject({
|
|
openapi: expect.any(String),
|
|
info: {
|
|
title: expect.any(String),
|
|
},
|
|
});
|
|
expect(Object.keys(schema.paths)).toEqual(expect.arrayContaining(["/sign-in/email"]));
|
|
});
|
|
|
|
it("applies configured CORS origin for auth routes", async () => {
|
|
const response = await worker.fetch(
|
|
new Request("http://auth.local/api/auth/reference", {
|
|
headers: {
|
|
Origin: "http://localhost:8787",
|
|
},
|
|
}),
|
|
env,
|
|
);
|
|
expect(response.headers.get("access-control-allow-origin")).toBe("http://localhost:8787");
|
|
});
|
|
|
|
it("applies CORS for loopback origins without restricting the port", async () => {
|
|
const response = await worker.fetch(
|
|
new Request("http://auth.local/api/auth/reference", {
|
|
headers: {
|
|
Origin: "http://localhost:5174",
|
|
},
|
|
}),
|
|
{
|
|
...env,
|
|
TRUSTED_ORIGINS: "http://localhost",
|
|
},
|
|
);
|
|
|
|
expect(response.headers.get("access-control-allow-origin")).toBe("http://localhost:5174");
|
|
});
|
|
|
|
it("does not fall back to the first configured CORS origin for unknown origins", async () => {
|
|
const response = await worker.fetch(
|
|
new Request("http://auth.local/api/auth/reference", {
|
|
headers: {
|
|
Origin: "https://unknown.example.com",
|
|
},
|
|
}),
|
|
env,
|
|
);
|
|
|
|
expect(response.headers.get("access-control-allow-origin")).toBeNull();
|
|
});
|
|
|
|
it("reflects web.bowong.cc when configured as a trusted CORS origin", async () => {
|
|
const response = await worker.fetch(
|
|
new Request("http://auth.local/api/auth/reference", {
|
|
headers: {
|
|
Origin: "https://web.bowong.cc",
|
|
},
|
|
}),
|
|
{
|
|
...env,
|
|
TRUSTED_ORIGINS: `${env.TRUSTED_ORIGINS},https://web.bowong.cc`,
|
|
},
|
|
);
|
|
|
|
expect(response.headers.get("access-control-allow-origin")).toBe("https://web.bowong.cc");
|
|
});
|
|
|
|
it("handles password reset requests without exposing account existence", async () => {
|
|
const response = await worker.fetch(
|
|
new Request("http://auth.local/api/auth/request-password-reset", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Origin: "http://localhost:8787",
|
|
},
|
|
body: JSON.stringify({
|
|
email: "missing@example.com",
|
|
redirectTo: "http://localhost:8787/reset-password",
|
|
}),
|
|
}),
|
|
env,
|
|
);
|
|
expect([200, 400, 403]).toContain(response.status);
|
|
});
|
|
|
|
it("does not route non-GET/POST auth requests to Better Auth", async () => {
|
|
const response = await worker.fetch(
|
|
new Request("http://auth.local/api/auth/session", {
|
|
method: "PUT",
|
|
}),
|
|
env,
|
|
);
|
|
expect(response.status).toBe(404);
|
|
});
|
|
|
|
it("logs auth request timing without changing the response", async () => {
|
|
const response = await worker.fetch(
|
|
new Request("http://auth.local/api/auth/reference", {
|
|
headers: {
|
|
"cf-ray": "test-ray",
|
|
Authorization: "Bearer secret-token",
|
|
Cookie: "better-auth.session_token=secret-cookie",
|
|
},
|
|
}),
|
|
env,
|
|
);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.headers.get("content-type") ?? "").toContain("text/html");
|
|
expect(log).toHaveBeenCalledTimes(1);
|
|
|
|
const [rawMessage] = log.mock.calls[0] ?? [];
|
|
expect(typeof rawMessage).toBe("string");
|
|
const message = String(rawMessage);
|
|
const event = JSON.parse(message) as Record<string, unknown>;
|
|
|
|
expect(event).toMatchObject({
|
|
event: "auth_request",
|
|
method: "GET",
|
|
path: "/api/auth/reference",
|
|
status: 200,
|
|
cfRay: "test-ray",
|
|
});
|
|
expect(typeof event.durationMs).toBe("number");
|
|
expect(message).not.toContain("secret-token");
|
|
expect(message).not.toContain("secret-cookie");
|
|
expect(message.toLowerCase()).not.toContain("authorization");
|
|
expect(message.toLowerCase()).not.toContain("cookie");
|
|
});
|
|
});
|