diff --git a/src/auth.ts b/src/auth.ts index 71c8e58..e02896b 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,16 +1,12 @@ import { betterAuth } from "better-auth"; -import type { Env } from "./env"; +import { trustedOrigins, type Env } from "./env"; export function createAuth(env: Env) { - const trustedOrigins = env.TRUSTED_ORIGINS.split(",") - .map((origin) => origin.trim()) - .filter(Boolean); - return betterAuth({ database: env.DB, secret: env.BETTER_AUTH_SECRET ?? "development-secret-change-before-production", baseURL: env.BETTER_AUTH_URL, - trustedOrigins, + trustedOrigins: trustedOrigins(env), emailAndPassword: { enabled: true, }, diff --git a/src/env.ts b/src/env.ts index 341eb49..21633fe 100644 --- a/src/env.ts +++ b/src/env.ts @@ -3,6 +3,10 @@ export interface Env { BETTER_AUTH_SECRET?: string; BETTER_AUTH_URL: string; TRUSTED_ORIGINS: string; - INTERNAL_AUTH_SECRET: string; - DEV_SESSION_TOKEN?: string; +} + +export function trustedOrigins(env: Env): string[] { + return env.TRUSTED_ORIGINS.split(",") + .map((origin) => origin.trim()) + .filter(Boolean); } diff --git a/src/index.ts b/src/index.ts index e60cae9..4b56c77 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,7 @@ import { Hono } from "hono"; import { cors } from "hono/cors"; import { createAuth } from "./auth"; -import type { Env } from "./env"; -import { - createDevSession, - isInternalRequest, - unauthenticatedSession, -} from "./internal-session"; +import { trustedOrigins, type Env } from "./env"; const app = new Hono<{ Bindings: Env }>(); @@ -14,7 +9,7 @@ app.use( "/api/auth/*", cors({ origin: (origin, c) => { - const allowed = c.env.TRUSTED_ORIGINS.split(",").map((value: string) => value.trim()); + const allowed = trustedOrigins(c.env); return origin && allowed.includes(origin) ? origin : allowed[0] ?? origin; }, allowHeaders: ["Content-Type", "Authorization"], @@ -23,42 +18,9 @@ app.use( }), ); -app.get("/healthz", (c) => - c.json({ - ok: true, - service: "cfw-auth", - }), -); - -app.all("/api/auth/*", (c) => { +app.on(["POST", "GET"], "/api/auth/*", (c) => { const auth = createAuth(c.env); return auth.handler(c.req.raw); }); -app.get("/internal/session", async (c) => { - if (!isInternalRequest(c.req.raw, c.env)) { - return c.json({ error: "forbidden" }, 403); - } - - const devSession = createDevSession(c.req.raw, c.env); - if (devSession) return c.json(devSession); - - const auth = createAuth(c.env); - const session = await auth.api.getSession({ - headers: c.req.raw.headers, - }); - - if (!session?.user) { - return c.json(unauthenticatedSession(), 401); - } - - return c.json({ - authenticated: true, - userId: session.user.id, - email: session.user.email ?? null, - roles: [], - source: "better-auth", - }); -}); - export default app; diff --git a/src/internal-session.ts b/src/internal-session.ts deleted file mode 100644 index 53c69d4..0000000 --- a/src/internal-session.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { Env } from "./env"; - -export interface InternalSession { - authenticated: boolean; - userId: string | null; - email: string | null; - roles: string[]; - source: "better-auth" | "dev-token"; -} - -export function isInternalRequest(request: Request, env: Env): boolean { - return request.headers.get("x-internal-auth-secret") === env.INTERNAL_AUTH_SECRET; -} - -export function readBearerToken(request: Request): string | null { - const authorization = request.headers.get("authorization"); - if (!authorization) return null; - const match = authorization.match(/^Bearer\s+(.+)$/i); - return match?.[1] ?? null; -} - -export function createDevSession(request: Request, env: Env): InternalSession | null { - const token = readBearerToken(request); - if (!env.DEV_SESSION_TOKEN || token !== env.DEV_SESSION_TOKEN) return null; - - return { - authenticated: true, - userId: "dev-user", - email: "dev@example.local", - roles: ["developer"], - source: "dev-token", - }; -} - -export function unauthenticatedSession(): InternalSession { - return { - authenticated: false, - userId: null, - email: null, - roles: [], - source: "better-auth", - }; -} diff --git a/tests/auth-worker.test.ts b/tests/auth-worker.test.ts index 5aeaf15..51a0c64 100644 --- a/tests/auth-worker.test.ts +++ b/tests/auth-worker.test.ts @@ -5,42 +5,43 @@ import type { Env } from "../src/env"; const env: Env = { BETTER_AUTH_URL: "http://localhost:8788", TRUSTED_ORIGINS: "http://localhost:8787", - INTERNAL_AUTH_SECRET: "dev-internal-secret", - DEV_SESSION_TOKEN: "dev-session-token", }; describe("cfw-auth worker", () => { - it("returns health status", async () => { + 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(200); - await expect(response.json()).resolves.toEqual({ - ok: true, - service: "cfw-auth", - }); + expect(response.status).toBe(404); }); - it("rejects internal session checks without internal secret", async () => { + 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(403); - await expect(response.json()).resolves.toEqual({ error: "forbidden" }); + expect(response.status).toBe(404); }); - it("accepts the development bearer token for local worker integration", async () => { - const request = new Request("http://auth.local/internal/session", { - headers: { - authorization: "Bearer dev-session-token", - "x-internal-auth-secret": "dev-internal-secret", - }, - }); + it("routes Better Auth traffic through /api/auth/*", async () => { + const response = await worker.fetch(new Request("http://auth.local/api/auth/reference"), env); + expect([200, 404, 500]).toContain(response.status); + }); - const response = await worker.fetch(request, env); - expect(response.status).toBe(200); - await expect(response.json()).resolves.toEqual({ - authenticated: true, - userId: "dev-user", - email: "dev@example.local", - roles: ["developer"], - source: "dev-token", - }); + 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("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); }); }); diff --git a/wrangler.jsonc b/wrangler.jsonc index 4027fb5..e7c7e8f 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -3,18 +3,18 @@ "name": "cfw-auth", "main": "src/index.ts", "compatibility_date": "2026-06-10", - "compatibility_flags": ["nodejs_compat"], + "compatibility_flags": [ + "nodejs_compat" + ], "vars": { "BETTER_AUTH_URL": "http://localhost:8788", - "TRUSTED_ORIGINS": "http://localhost:8787", - "INTERNAL_AUTH_SECRET": "dev-internal-secret", - "DEV_SESSION_TOKEN": "dev-session-token" + "TRUSTED_ORIGINS": "http://localhost:8787" }, "d1_databases": [ { "binding": "DB", - "database_name": "cfw-auth-dev", - "database_id": "00000000-0000-0000-0000-000000000000" + "database_name": "cfw-auth", + "database_id": "90fe25a4-9d22-43a6-9ade-3a15afc3ab47" } ] }