Mobile API requests authenticated via OAuth2 bearer tokens bypassed the
Terms gate that the root loader applies to web cookie sessions. Extend
requireApiUser to compare the user's termsVersion with TERMS_VERSION
and return a structured 403 { code: "TERMS_OUTDATED", currentTermsVersion }
on mismatch so mobile clients can surface their own re-acceptance UI.
Spec delta on journal-auth captures the new requirement.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
38 lines
1.3 KiB
TypeScript
38 lines
1.3 KiB
TypeScript
import { getAuthenticatedUser } from "./oauth.server.ts";
|
|
import { TERMS_VERSION } from "./legal.ts";
|
|
import { ERROR_CODES } from "@trails-cool/api";
|
|
|
|
/**
|
|
* Require authentication for an API route. Returns the user or throws a
|
|
* Response: 401 if unauthenticated, 403 with `TERMS_OUTDATED` if the user's
|
|
* stored `terms_version` is missing or stale relative to the current
|
|
* `TERMS_VERSION`. Mirrors the cookie-session terms gate enforced by the
|
|
* root loader, so bearer-token API traffic can't bypass it.
|
|
*/
|
|
export async function requireApiUser(request: Request) {
|
|
const user = await getAuthenticatedUser(request);
|
|
if (!user) {
|
|
throw Response.json(
|
|
{ error: "Unauthorized", code: ERROR_CODES.UNAUTHORIZED },
|
|
{ status: 401 },
|
|
);
|
|
}
|
|
if (user.termsVersion !== TERMS_VERSION) {
|
|
throw Response.json(
|
|
{
|
|
error: "Terms of Service have been updated and must be re-accepted",
|
|
code: ERROR_CODES.TERMS_OUTDATED,
|
|
currentTermsVersion: TERMS_VERSION,
|
|
},
|
|
{ status: 403 },
|
|
);
|
|
}
|
|
return user;
|
|
}
|
|
|
|
/**
|
|
* Return a structured API error response.
|
|
*/
|
|
export function apiError(status: number, code: string, message: string, fields?: Array<{ field: string; message: string }>) {
|
|
return Response.json({ error: message, code, fields }, { status });
|
|
}
|