2026-08-04 06:50:54 +00:00
|
|
|
import type { NextRequest } from "next/server";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validates a browser-forwarded PocketBase auth token against PocketBase.
|
|
|
|
|
*
|
|
|
|
|
* Dashboard auth lives in localStorage, so Route Handlers cannot read it from
|
|
|
|
|
* cookies. Browser callers send the current PB token in `Authorization`; this
|
|
|
|
|
* helper asks PB to refresh/validate it instead of trusting or decoding the
|
|
|
|
|
* token locally.
|
|
|
|
|
*/
|
|
|
|
|
export async function assertBrowserSession(req: NextRequest): Promise<void> {
|
|
|
|
|
const authorization = req.headers.get("authorization") || "";
|
|
|
|
|
const token = authorization.startsWith("Bearer ")
|
|
|
|
|
? authorization.slice(7)
|
|
|
|
|
: authorization;
|
|
|
|
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
throw Object.assign(new Error("Not authenticated."), { status: 401 });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pbUrl =
|
|
|
|
|
process.env.NEXT_PUBLIC_POCKETBASE_URL || "http://127.0.0.1:8090";
|
|
|
|
|
const response = await fetch(
|
|
|
|
|
`${pbUrl.replace(/\/$/, "")}/api/collections/users/auth-refresh`,
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { Authorization: token },
|
|
|
|
|
signal: AbortSignal.timeout(5_000),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw Object.assign(new Error("Invalid or expired session."), {
|
|
|
|
|
status: 401,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-31 23:33:10 +00:00
|
|
|
|
|
|
|
|
export interface BrowserSessionUser {
|
|
|
|
|
id: string;
|
|
|
|
|
user_type: string[] | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Same validation as `assertBrowserSession`, but returns the resolved
|
|
|
|
|
* PB user record instead of discarding it — for routes that need to
|
|
|
|
|
* scope queries to "whoever is logged in" (e.g. the rep dashboard).
|
|
|
|
|
*/
|
|
|
|
|
export async function getBrowserSessionUser(
|
|
|
|
|
req: NextRequest,
|
|
|
|
|
): Promise<BrowserSessionUser> {
|
|
|
|
|
const authorization = req.headers.get("authorization") || "";
|
|
|
|
|
const token = authorization.startsWith("Bearer ")
|
|
|
|
|
? authorization.slice(7)
|
|
|
|
|
: authorization;
|
|
|
|
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
throw Object.assign(new Error("Not authenticated."), { status: 401 });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pbUrl =
|
|
|
|
|
process.env.NEXT_PUBLIC_POCKETBASE_URL || "http://127.0.0.1:8090";
|
|
|
|
|
const response = await fetch(
|
|
|
|
|
`${pbUrl.replace(/\/$/, "")}/api/collections/users/auth-refresh`,
|
|
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { Authorization: token },
|
|
|
|
|
signal: AbortSignal.timeout(5_000),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw Object.assign(new Error("Invalid or expired session."), {
|
|
|
|
|
status: 401,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const body = (await response.json()) as {
|
|
|
|
|
record?: { id?: string; user_type?: string[] | null };
|
|
|
|
|
};
|
|
|
|
|
const id = body.record?.id;
|
|
|
|
|
if (!id) {
|
|
|
|
|
throw Object.assign(new Error("Invalid or expired session."), {
|
|
|
|
|
status: 401,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { id, user_type: body.record?.user_type ?? null };
|
|
|
|
|
}
|