Skip to content
10 changes: 8 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@ const baseMetadata: Metadata = {
},
};

/**
* Generates application metadata with a request-aware base URL.
*
* @returns The application metadata, including its resolved base URL.
*/
export async function generateMetadata(): Promise<Metadata> {
const requestHeaders = await headers();
const allowRequestOrigin = process.env.NODE_ENV !== "production";
const requestHeaders = allowRequestOrigin ? await headers() : new Headers();
return {
...baseMetadata,
metadataBase: resolveMetadataBase(requestHeaders, {
configuredSiteUrl: process.env.NEXT_PUBLIC_SITE_URL,
trustedDeploymentDomain: process.env.RAILWAY_PUBLIC_DOMAIN,
allowRequestOrigin: process.env.NODE_ENV !== "production",
allowRequestOrigin,
}),
};
}
Expand Down
8 changes: 8 additions & 0 deletions src/components/DocumentViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1995,6 +1995,14 @@ function DocumentOverviewLanding({
);
}

/**
* Renders the clinical document viewer with source previews, extracted content, summaries, and document tools.
*
* @param documentId - The identifier of the document to load.
* @param initialPage - The page to display initially in the source preview.
* @param chunkId - An optional indexed passage to pin and scroll into view.
* @returns The document viewer interface.
*/
export function DocumentViewer({
documentId,
initialPage,
Expand Down
15 changes: 15 additions & 0 deletions src/lib/api-rate-limit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,21 @@ export async function consumeApiRateLimit(args: {
};
}

/**
* Applies an API rate limit to an owner or anonymous subject.
*
* Anonymous requests to answer and document upload buckets are constrained by
* both subject-specific and aggregate bucket limits. Limiter unavailability may
* use an in-memory fallback when permitted.
*
* @param args - Rate-limiting configuration and request subject.
* @param args.subject - The authenticated owner or anonymous subject to limit.
* @param args.bucket - The API resource bucket being limited.
* @param args.limit - Optional subject-specific request limit.
* @param args.windowSeconds - Optional subject-specific rate-limit window.
* @param args.allowInMemoryFallbackOnUnavailable - Whether local fallback may be used when the durable limiter is unavailable.
* @returns The computed rate-limit outcome.
*/
export async function consumeSubjectApiRateLimit(args: {
supabase: SupabaseAdmin;
subject: RateLimitSubject;
Expand Down
8 changes: 7 additions & 1 deletion src/lib/document-viewer-navigation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
/** Build a useful-page link without retaining evidence pinned to a different page. */
/**
* Builds a URL for a specific document page in the PDF preview section.
*
* @param documentId - The document identifier to include in the URL
* @param page - The requested page number, normalized to an integer of at least 1
* @returns A document page URL with the encoded document identifier and normalized page number
*/
export function documentPageHref(documentId: string, page: number) {
const normalizedPage = Number.isFinite(page) ? Math.max(1, Math.trunc(page)) : 1;
const params = new URLSearchParams({ page: String(normalizedPage) });
Expand Down
18 changes: 16 additions & 2 deletions src/lib/metadata-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ export type MetadataBaseOptions = {
allowRequestOrigin?: boolean;
};

/** Parse an absolute HTTP(S) URL without allowing invalid configuration to escape. */
/**
* Parses a value as an HTTP or HTTPS URL.
*
* @param value - The URL value to parse.
* @returns The parsed URL when it uses HTTP or HTTPS; `undefined` otherwise.
*/
function httpUrl(value: string | undefined) {
const candidate = value?.trim();
if (!candidate) return undefined;
Expand All @@ -16,7 +21,16 @@ function httpUrl(value: string | undefined) {
}
}

/** Resolve metadata from validated configuration, trusted deployment state, or an explicit dev fallback. */
/**
* Resolves the base URL used for metadata generation.
*
* Sources are considered in order: configured site URL, trusted deployment domain,
* and the request origin when explicitly enabled.
*
* @param requestHeaders - Headers used to derive the request origin.
* @param options - Configuration controlling the fallback sources.
* @returns A valid HTTP or HTTPS metadata base URL, or `undefined` when none can be resolved.
*/
export function resolveMetadataBase(requestHeaders: Headers, options: MetadataBaseOptions = {}) {
const configuredUrl = httpUrl(options.configuredSiteUrl);
if (configuredUrl) return configuredUrl;
Expand Down
21 changes: 18 additions & 3 deletions src/lib/public-api-access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ type AdminClient = ReturnType<typeof createAdminClient>;

export type RateLimitSubject = { kind: "owner"; ownerId: string } | { kind: "anonymous"; subjectKey: string };

/** Read the deployment proxy's appended address from a forwarded IP chain. */
/**
* Extracts the last non-empty IP address from a proxy forwarding header value.
*
* @param value - A comma-separated proxy forwarding header value
* @returns The last trimmed IP address, or an empty string when no address is present
*/
function trustedProxyIp(value: string | null) {
const forwarded = value
?.split(",")
Expand All @@ -20,7 +25,12 @@ function trustedProxyIp(value: string | null) {
return forwarded?.at(-1) ?? "";
}

/** Select the strongest deployment-owned request identity signal available. */
/**
* Determines the request's source IP from trusted proxy forwarding headers.
*
* @param request - The request containing the proxy forwarding headers
* @returns The last non-empty address from `x-forwarded-for`, the address from `x-real-ip`, or `"unknown-ip"` when neither header provides an address
*/
function requestIpSignal(request: Request) {
return (
trustedProxyIp(request.headers.get("x-forwarded-for")) ||
Expand All @@ -29,7 +39,12 @@ function requestIpSignal(request: Request) {
);
}

/** Derive a stable, non-reversible quota subject for an anonymous caller. */
/**
* Creates a stable anonymous rate-limit subject key from the request's trusted proxy IP signal.
*
* @param request - The request from which to derive the IP signal
* @returns A prefixed, truncated SHA-256 hash of the source IP signal
*/
export function anonymousApiSubjectKey(request: Request) {
// Trust only the deployment proxy's appended forwarding entry. Ignore the
// caller-controlled Cloudflare/User-Agent values and any leading XFF entries:
Expand Down
Loading