feat: resolve attachment user through cfw-auth binding

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-07-02 07:34:48 -07:00
parent fb7afb69f3
commit c4a1b167a1
3 changed files with 97 additions and 0 deletions

37
src/auth-session.ts Normal file
View File

@@ -0,0 +1,37 @@
import type { Env } from "./env";
import { HttpProblem } from "./problem";
export interface CurrentUser {
id: string;
email?: string;
}
interface BetterAuthSessionResponse {
user?: {
id?: unknown;
email?: unknown;
};
}
export async function resolveCurrentUser(request: Request, env: Env): Promise<CurrentUser> {
const sessionRequest = new Request("https://cfw-auth.internal/api/auth/get-session", {
method: "GET",
headers: request.headers,
});
const response = await env.AUTH.fetch(sessionRequest);
if (!response.ok) {
throw new HttpProblem(401, "authentication-required", "Authentication required");
}
const body = (await response.json()) as BetterAuthSessionResponse | null;
const id = body?.user?.id;
if (typeof id !== "string" || id.length === 0) {
throw new HttpProblem(401, "authentication-required", "Authentication required");
}
return {
id,
email: typeof body?.user?.email === "string" ? body.user.email : undefined,
};
}

33
src/problem.ts Normal file
View File

@@ -0,0 +1,33 @@
export class HttpProblem extends Error {
constructor(
readonly status: number,
readonly code: string,
message: string,
readonly detail: Record<string, unknown> = {},
) {
super(message);
}
}
export function problemResponse(error: unknown): Response {
const problem =
error instanceof HttpProblem
? error
: new HttpProblem(500, "internal-error", "Internal server error");
return Response.json(
{
type: `https://cfw-attachment.bowong.cc/problems/${problem.code}`,
title: problem.message,
status: problem.status,
detail: problem.detail,
code: problem.code,
},
{
status: problem.status,
headers: {
"content-type": "application/problem+json",
},
},
);
}

View File

@@ -0,0 +1,27 @@
import { describe, expect, it, vi } from "vitest";
import { resolveCurrentUser } from "../src/auth-session";
import type { Env } from "../src/env";
function envWithAuth(response: Response): Env {
return {
AUTH: { fetch: vi.fn().mockResolvedValue(response) },
DB: {} as D1Database,
ATTACHMENTS: {} as R2Bucket,
};
}
describe("resolveCurrentUser", () => {
it("returns the Better Auth user id from cfw-auth", async () => {
const env = envWithAuth(Response.json({ user: { id: "user_123", email: "a@example.com" } }));
await expect(resolveCurrentUser(new Request("https://attachment.local/api/attachments"), env))
.resolves.toEqual({ id: "user_123", email: "a@example.com" });
});
it("throws authentication-required when cfw-auth has no session", async () => {
const env = envWithAuth(Response.json(null));
await expect(resolveCurrentUser(new Request("https://attachment.local/api/attachments"), env))
.rejects.toMatchObject({ status: 401, code: "authentication-required" });
});
});