WFDASH-WEBAPP/src/lib/dev-identity.ts
SJCoetz 05abe499b0 feat(sprint): add new sprint pricing workflow and supporting code
Add full sprint pricing workflow and supporting infrastructure:
- New sidebar navigation entry for the sprint dashboard
- PocketBase category collection schema and migration script
- Server API routes for sprint item claims, attachments, counts, and file proxying
- Dev identity utilities and UI switcher for multi-user testing
- Reusable FilterTabs component replacing ItemStatusTabs
- Updated TypeScript types for sprint-related entities

Also include:
- Fix PocketBase file URL method casing (getUrl → getURL) across codebase
- Add development utility, smoke test, and helper scripts
- Update package.json with new sprint migration command
2026-07-03 07:29:43 +02:00

85 lines
2.9 KiB
TypeScript

/**
* Dev identity — temporary stand-in for real PB auth.
*
* The project is currently in "no auth" mode: PB's `users` collection
* is empty by design, so `assertBrowserSession` (used elsewhere in the
* API) always fails. Features that need caller identity for
* attribution (claim `claimed_by`, future audit trails, "claimed by
* me" detection) read identity from a cookie called
* `wfdash_dev_email` instead. Default is a placeholder email that
* matches the display name we expect in the UI badges.
*
* Identity resolution (server-side, in order):
* 1. `X-User-Email` request header (explicit per-request override)
* 2. `wfdash_dev_email` cookie (the dev user switcher in the UI)
* 3. `DEV_USER_EMAIL` env var (CI / scripted requests)
* 4. `DEFAULT_DEV_EMAIL` constant (fallback)
*
* Migration path: when real PB auth lands, replace `getServerIdentity`
* with `assertBrowserSession` and have the login flow write the real
* email to this cookie. The rest of the call sites stay the same.
*/
import type { NextRequest } from "next/server";
export const DEV_IDENTITY_COOKIE = "wfdash_dev_email";
export const DEV_IDENTITY_HEADER = "x-user-email";
export const DEFAULT_DEV_EMAIL = "david.els1@outlook.com";
function decode(value: string | undefined | null): string {
if (!value) return "";
try {
return decodeURIComponent(value);
} catch {
return value;
}
}
function normalize(raw: string | undefined | null): string {
const decoded = decode(raw).trim();
return decoded;
}
/**
* Server-side: read the caller's dev identity from the request.
* Always returns a non-empty string.
*/
export function getServerIdentity(req: NextRequest): string {
const header = req.headers.get(DEV_IDENTITY_HEADER);
const headerVal = normalize(header);
if (headerVal) return headerVal;
const cookieVal = normalize(req.cookies.get(DEV_IDENTITY_COOKIE)?.value);
if (cookieVal) return cookieVal;
return normalize(process.env.DEV_USER_EMAIL) || DEFAULT_DEV_EMAIL;
}
/**
* Client-side: read the current dev identity from `document.cookie`.
* Falls back to the default on SSR / when the cookie isn't set.
*/
export function getClientIdentity(): string {
if (typeof document === "undefined") return DEFAULT_DEV_EMAIL;
const escaped = DEV_IDENTITY_COOKIE.replace(
/[.*+?^${}()|[\]\\]/g,
"\\$&",
);
const m = document.cookie.match(
new RegExp(`(?:^|;\\s*)${escaped}=([^;]+)`),
);
if (!m) return DEFAULT_DEV_EMAIL;
const decoded = decode(m[1]).trim();
return decoded || DEFAULT_DEV_EMAIL;
}
/**
* Client-side: persist a new dev identity to the cookie.
* Path is `/` so the API routes can read it, max-age is 1 year.
*/
export function setClientIdentity(email: string): void {
if (typeof document === "undefined") return;
const value = encodeURIComponent(email.trim() || DEFAULT_DEV_EMAIL);
const oneYear = 60 * 60 * 24 * 365;
document.cookie = `${DEV_IDENTITY_COOKIE}=${value}; path=/; max-age=${oneYear}; SameSite=Lax`;
}