test: lock auth worker routing
This commit is contained in:
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
44
src/index.ts
44
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;
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user