Files
Reactive-Resume/packages/api/src/context.ts
T
Amruth Pillai 9a803305c8 fix: address verified v6 app findings
Fix authentication recovery, account imports, application tracking, resume
editing and exports, sharing, API contracts, provider selection, and private
local attachments. Preserve authored content during PDF pagination.

Update guides, generated OpenAPI output, and translation catalogs to match
verified behavior and documented constraints.

Validation: 1,019 tests passed; 12 database/OAuth integration tests skipped.
Ten affected package typechecks, production build, Biome, and package
boundaries passed.
2026-09-30 06:15:09 +02:00

101 lines
2.9 KiB
TypeScript

import type { Locale } from "@reactive-resume/utils/locale";
import type { User } from "better-auth";
import { ORPCError, os } from "@orpc/server";
import { eq } from "drizzle-orm";
import { auth, verifyOAuthToken } from "@reactive-resume/auth/config";
import { db } from "@reactive-resume/db/client";
import { user } from "@reactive-resume/db/schema";
interface ORPCContext {
locale: Locale;
reqHeaders: Headers;
resHeaders?: Headers;
trustedClient?: string;
}
async function getUserFromBearerToken(headers: Headers): Promise<User | null> {
try {
const authHeader = headers.get("authorization");
if (!authHeader?.startsWith("Bearer ")) return null;
const payload = await verifyOAuthToken(authHeader.slice(7));
if (!payload?.sub) return null;
const [userResult] = await db.select().from(user).where(eq(user.id, payload.sub)).limit(1);
return userResult ?? null;
} catch (error) {
console.warn("Bearer token verification failed:", error);
return null;
}
}
async function getUserFromHeaders(headers: Headers): Promise<User | null> {
try {
const result = await auth.api.getSession({ headers });
if (!result?.user) return null;
return result.user;
} catch (error) {
console.warn("Session verification failed:", error);
return null;
}
}
async function getUserFromApiKey(apiKey: string): Promise<User | null> {
try {
const result = await auth.api.verifyApiKey({ body: { key: apiKey } });
if (!result.key || !result.valid) return null;
const [userResult] = await db.select().from(user).where(eq(user.id, result.key.referenceId)).limit(1);
if (!userResult) return null;
return userResult;
} catch (error) {
console.warn("API key verification failed:", error);
return null;
}
}
/**
* Resolve the authenticated user from the same headers oRPC uses (`x-api-key`,
* `Authorization: Bearer`, or session cookies). Tries each auth method in
* priority order and returns the first valid identity. Used directly by
* oRPC's `publicProcedure` and by callers outside oRPC handlers (e.g. MCP
* tools) where `context.user` is not in scope.
*/
export async function resolveUserFromRequestHeaders(headers: Headers): Promise<User | null> {
const apiKey = headers.get("x-api-key");
if (apiKey) {
const apiKeyUser = await getUserFromApiKey(apiKey);
if (apiKeyUser) return apiKeyUser;
}
const bearerUser = await getUserFromBearerToken(headers);
if (bearerUser) return bearerUser;
return getUserFromHeaders(headers);
}
const base = os.$context<ORPCContext>();
export const publicProcedure = base.use(async ({ context, next }) => {
const user = await resolveUserFromRequestHeaders(context.reqHeaders);
return next({
context: {
...context,
user,
},
});
});
export const protectedProcedure = publicProcedure.use(({ context, next }) => {
if (!context.user) throw new ORPCError("UNAUTHORIZED");
return next({
context: {
...context,
user: context.user,
},
});
});