feat: resolve attachment user through cfw-auth binding
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
37
src/auth-session.ts
Normal file
37
src/auth-session.ts
Normal 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
33
src/problem.ts
Normal 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",
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
27
tests/auth-session.test.ts
Normal file
27
tests/auth-session.test.ts
Normal 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" });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user