feat: add getBrowserSessionUser for routes that need the caller's identity
This commit is contained in:
parent
5c210546e9
commit
652b60706e
2 changed files with 114 additions and 0 deletions
62
src/lib/server/browser-session.test.ts
Normal file
62
src/lib/server/browser-session.test.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { NextRequest } from "next/server";
|
||||
import { getBrowserSessionUser } from "./browser-session";
|
||||
|
||||
const ORIGINAL_FETCH = global.fetch;
|
||||
|
||||
function makeRequest(authHeader: string | null): NextRequest {
|
||||
const headers = new Headers();
|
||||
if (authHeader !== null) headers.set("authorization", authHeader);
|
||||
return new NextRequest("http://localhost:4001/api/v1/rep-dashboard", {
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
describe("getBrowserSessionUser", () => {
|
||||
beforeEach(() => {
|
||||
global.fetch = ORIGINAL_FETCH;
|
||||
});
|
||||
afterEach(() => {
|
||||
global.fetch = ORIGINAL_FETCH;
|
||||
});
|
||||
|
||||
it("throws a 401-tagged error when no token is present", async () => {
|
||||
await expect(getBrowserSessionUser(makeRequest(null))).rejects.toMatchObject({
|
||||
status: 401,
|
||||
});
|
||||
});
|
||||
|
||||
it("throws a 401-tagged error when PB rejects the token", async () => {
|
||||
global.fetch = vi.fn(async () => new Response("", { status: 401 })) as unknown as typeof fetch;
|
||||
await expect(
|
||||
getBrowserSessionUser(makeRequest("Bearer bad-token")),
|
||||
).rejects.toMatchObject({ status: 401 });
|
||||
});
|
||||
|
||||
it("returns the resolved user's id and user_type on success", async () => {
|
||||
global.fetch = vi.fn(async () =>
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
token: "refreshed-token",
|
||||
record: { id: "user123", user_type: ["REP"], name: "Test Rep" },
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
),
|
||||
) as unknown as typeof fetch;
|
||||
|
||||
const result = await getBrowserSessionUser(makeRequest("Bearer good-token"));
|
||||
expect(result).toEqual({ id: "user123", user_type: ["REP"] });
|
||||
});
|
||||
|
||||
it("defaults user_type to null when the record omits it", async () => {
|
||||
global.fetch = vi.fn(async () =>
|
||||
new Response(
|
||||
JSON.stringify({ token: "t", record: { id: "user456" } }),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
),
|
||||
) as unknown as typeof fetch;
|
||||
|
||||
const result = await getBrowserSessionUser(makeRequest("Bearer good-token"));
|
||||
expect(result).toEqual({ id: "user456", user_type: null });
|
||||
});
|
||||
});
|
||||
|
|
@ -35,3 +35,55 @@ export async function assertBrowserSession(req: NextRequest): Promise<void> {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue