@palbase/web 4.0.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +20 -3
  2. package/dist/{analytics-facade-DwOtlXPP.d.cts → analytics-facade-CaluA4bW.d.cts} +26 -1
  3. package/dist/{analytics-facade-Nk6J9Ncm.d.ts → analytics-facade-Cp3d4QI-.d.ts} +26 -1
  4. package/dist/chunk-BNAGRUIQ.js +381 -0
  5. package/dist/chunk-BNAGRUIQ.js.map +1 -0
  6. package/dist/{chunk-NXEPAEF5.js → chunk-CFDU23TB.js} +3 -3
  7. package/dist/chunk-CFDU23TB.js.map +1 -0
  8. package/dist/chunk-OWLTZG2V.js +24 -0
  9. package/dist/chunk-OWLTZG2V.js.map +1 -0
  10. package/dist/{chunk-TGDS6VMT.js → chunk-XTQXFZUP.js} +68 -376
  11. package/dist/chunk-XTQXFZUP.js.map +1 -0
  12. package/dist/gen/cli.cjs +5 -5
  13. package/dist/gen/cli.cjs.map +1 -1
  14. package/dist/gen/cli.js +5 -5
  15. package/dist/gen/cli.js.map +1 -1
  16. package/dist/index.cjs +55 -3
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +3 -3
  19. package/dist/index.d.ts +3 -3
  20. package/dist/index.js +6 -4
  21. package/dist/internal.cjs +57 -5
  22. package/dist/internal.cjs.map +1 -1
  23. package/dist/internal.d.cts +3 -3
  24. package/dist/internal.d.ts +3 -3
  25. package/dist/internal.js +3 -2
  26. package/dist/next/client.cjs +57 -5
  27. package/dist/next/client.cjs.map +1 -1
  28. package/dist/next/client.js +3 -2
  29. package/dist/next/client.js.map +1 -1
  30. package/dist/next/index.cjs +61 -93
  31. package/dist/next/index.cjs.map +1 -1
  32. package/dist/next/index.d.cts +4 -64
  33. package/dist/next/index.d.ts +4 -64
  34. package/dist/next/index.js +9 -103
  35. package/dist/next/index.js.map +1 -1
  36. package/dist/next/middleware.cjs +263 -0
  37. package/dist/next/middleware.cjs.map +1 -0
  38. package/dist/next/middleware.d.cts +101 -0
  39. package/dist/next/middleware.d.ts +101 -0
  40. package/dist/next/middleware.js +107 -0
  41. package/dist/next/middleware.js.map +1 -0
  42. package/dist/{pb-FG_nV7NR.d.ts → pb-DUK5qy81.d.ts} +1 -1
  43. package/dist/{pb-C9v5dEO6.d.cts → pb-DvV6ftmf.d.cts} +1 -1
  44. package/dist/react/index.cjs +10 -2
  45. package/dist/react/index.cjs.map +1 -1
  46. package/dist/react/index.d.cts +1 -1
  47. package/dist/react/index.d.ts +1 -1
  48. package/dist/react/index.js +3 -2
  49. package/dist/react/index.js.map +1 -1
  50. package/package.json +11 -1
  51. package/dist/chunk-NXEPAEF5.js.map +0 -1
  52. package/dist/chunk-TGDS6VMT.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { P as PB } from '../pb-C9v5dEO6.cjs';
3
- import '../analytics-facade-DwOtlXPP.cjs';
1
+ import { NextRequest } from 'next/server';
2
+ import { P as PB } from '../pb-DvV6ftmf.cjs';
3
+ import '../analytics-facade-CaluA4bW.cjs';
4
4
  import '../storage-BPaeSG8K.cjs';
5
5
  import './pooled-flags-4GtDtsiu.js';
6
6
 
@@ -130,66 +130,6 @@ declare function decodeSessionCookies(get: (name: string) => string | undefined,
130
130
  */
131
131
  declare function clearedSessionCookieNames(environmentRef: string, present: (name: string) => boolean): string[];
132
132
 
133
- /**
134
- * palbeMiddleware — proactive cookie-session refresh in Next.js middleware.
135
- *
136
- * REQUIRED — not optional — for session-bearing RSC apps. Server Components
137
- * cannot write cookies, so pbServer's reactive refresh cannot PERSIST a
138
- * rotated session there: every RSC render would re-refresh from the SAME
139
- * cookie refresh token, and once two of those land more than 30s apart
140
- * (outside palauth's rotation grace) the reuse detector REVOKES THE WHOLE
141
- * TOKEN FAMILY — the user is force-logged-out everywhere. This middleware
142
- * refreshes the cookie BEFORE the RSC tree renders, so pbServer hydrates a
143
- * still-valid session and never burns the token itself.
144
- *
145
- * MODULE-GRAPH RULES (same as ./index.ts): 'next/server' is imported only
146
- * lazily (the type-only NextRequest/NextResponse imports erase at build
147
- * time), and middleware.ts must import the generated palbe.gen.ts itself —
148
- * Next bundles middleware as its OWN module graph; the layout.tsx import
149
- * does not reach it.
150
- *
151
- * ```ts
152
- * // middleware.ts
153
- * import './palbe.gen';
154
- * import { palbeMiddleware } from '@palbase/web/next';
155
- * import type { NextRequest } from 'next/server';
156
- *
157
- * export function middleware(request: NextRequest) {
158
- * return palbeMiddleware(request);
159
- * }
160
- * export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] };
161
- * ```
162
- */
163
-
164
- interface PalbeMiddlewareOptions {
165
- /** Refresh when the access token expires within this window. Default 60s. */
166
- refreshMarginMs?: number;
167
- /**
168
- * Pre-built response to decorate (e.g. from chained middleware) instead of
169
- * building `NextResponse.next({ request })`. Pass-through cases return it
170
- * UNMODIFIED. Caveat: a pre-built response snapshotted the request headers
171
- * at ITS creation, so a rotated cookie reaches the browser (Set-Cookie)
172
- * but not the downstream RSC render of THIS request — prefer omitting this
173
- * and building your response after palbeMiddleware when possible.
174
- */
175
- response?: NextResponse;
176
- }
177
- /**
178
- * Keep the session cookie fresh for the request. No session / still-fresh
179
- * session → untouched pass-through (no cache-control changes). Stale or
180
- * expired → single-flight refresh, then the rotated (or cleared, on a dead
181
- * token) cookies are written onto BOTH the request (for the downstream RSC
182
- * render via `NextResponse.next({ request })`) and the response (for the
183
- * browser), with `Cache-Control: private, no-store` on touched responses.
184
- *
185
- * Cookie writes go through Next's cookie APIs with the DECODED codec values:
186
- * Next percent-encodes on serialization, so the wire bytes come out exactly
187
- * as the shared codec's (see encodeSessionCookiesDecoded). NextRequest's
188
- * cookie store is NOT a CookieStoreLike (its set is the 2-arg/RequestCookie
189
- * form), hence the direct codec calls here instead of the pbServer adapter.
190
- */
191
- declare function palbeMiddleware(request: NextRequest, opts?: PalbeMiddlewareOptions): Promise<NextResponse>;
192
-
193
133
  /**
194
134
  * Structural slice of Next's request cookie store (`await cookies()`); any
195
135
  * object honoring it works — the seam for tests and non-Next servers.
@@ -227,4 +167,4 @@ declare function pbServer(opts?: {
227
167
  cookies?: CookieStoreLike;
228
168
  }): Promise<PB>;
229
169
 
230
- export { type CookieStoreLike, type HandleAuthCallbackOptions, type PalbeMiddlewareOptions, SESSION_COOKIE_ATTRS, type SessionCookieWrite, type StoredSession, clearedSessionCookieNames, decodeSessionCookies, encodeSessionCookies, encodeSessionCookiesDecoded, environmentRefFromApiKey, handleAuthCallback, palbeMiddleware, pbServer, sessionCookieName };
170
+ export { type CookieStoreLike, type HandleAuthCallbackOptions, SESSION_COOKIE_ATTRS, type SessionCookieWrite, type StoredSession, clearedSessionCookieNames, decodeSessionCookies, encodeSessionCookies, encodeSessionCookiesDecoded, environmentRefFromApiKey, handleAuthCallback, pbServer, sessionCookieName };
@@ -1,6 +1,6 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { P as PB } from '../pb-FG_nV7NR.js';
3
- import '../analytics-facade-Nk6J9Ncm.js';
1
+ import { NextRequest } from 'next/server';
2
+ import { P as PB } from '../pb-DUK5qy81.js';
3
+ import '../analytics-facade-Cp3d4QI-.js';
4
4
  import '../storage-BPaeSG8K.js';
5
5
  import './pooled-flags-4GtDtsiu.js';
6
6
 
@@ -130,66 +130,6 @@ declare function decodeSessionCookies(get: (name: string) => string | undefined,
130
130
  */
131
131
  declare function clearedSessionCookieNames(environmentRef: string, present: (name: string) => boolean): string[];
132
132
 
133
- /**
134
- * palbeMiddleware — proactive cookie-session refresh in Next.js middleware.
135
- *
136
- * REQUIRED — not optional — for session-bearing RSC apps. Server Components
137
- * cannot write cookies, so pbServer's reactive refresh cannot PERSIST a
138
- * rotated session there: every RSC render would re-refresh from the SAME
139
- * cookie refresh token, and once two of those land more than 30s apart
140
- * (outside palauth's rotation grace) the reuse detector REVOKES THE WHOLE
141
- * TOKEN FAMILY — the user is force-logged-out everywhere. This middleware
142
- * refreshes the cookie BEFORE the RSC tree renders, so pbServer hydrates a
143
- * still-valid session and never burns the token itself.
144
- *
145
- * MODULE-GRAPH RULES (same as ./index.ts): 'next/server' is imported only
146
- * lazily (the type-only NextRequest/NextResponse imports erase at build
147
- * time), and middleware.ts must import the generated palbe.gen.ts itself —
148
- * Next bundles middleware as its OWN module graph; the layout.tsx import
149
- * does not reach it.
150
- *
151
- * ```ts
152
- * // middleware.ts
153
- * import './palbe.gen';
154
- * import { palbeMiddleware } from '@palbase/web/next';
155
- * import type { NextRequest } from 'next/server';
156
- *
157
- * export function middleware(request: NextRequest) {
158
- * return palbeMiddleware(request);
159
- * }
160
- * export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] };
161
- * ```
162
- */
163
-
164
- interface PalbeMiddlewareOptions {
165
- /** Refresh when the access token expires within this window. Default 60s. */
166
- refreshMarginMs?: number;
167
- /**
168
- * Pre-built response to decorate (e.g. from chained middleware) instead of
169
- * building `NextResponse.next({ request })`. Pass-through cases return it
170
- * UNMODIFIED. Caveat: a pre-built response snapshotted the request headers
171
- * at ITS creation, so a rotated cookie reaches the browser (Set-Cookie)
172
- * but not the downstream RSC render of THIS request — prefer omitting this
173
- * and building your response after palbeMiddleware when possible.
174
- */
175
- response?: NextResponse;
176
- }
177
- /**
178
- * Keep the session cookie fresh for the request. No session / still-fresh
179
- * session → untouched pass-through (no cache-control changes). Stale or
180
- * expired → single-flight refresh, then the rotated (or cleared, on a dead
181
- * token) cookies are written onto BOTH the request (for the downstream RSC
182
- * render via `NextResponse.next({ request })`) and the response (for the
183
- * browser), with `Cache-Control: private, no-store` on touched responses.
184
- *
185
- * Cookie writes go through Next's cookie APIs with the DECODED codec values:
186
- * Next percent-encodes on serialization, so the wire bytes come out exactly
187
- * as the shared codec's (see encodeSessionCookiesDecoded). NextRequest's
188
- * cookie store is NOT a CookieStoreLike (its set is the 2-arg/RequestCookie
189
- * form), hence the direct codec calls here instead of the pbServer adapter.
190
- */
191
- declare function palbeMiddleware(request: NextRequest, opts?: PalbeMiddlewareOptions): Promise<NextResponse>;
192
-
193
133
  /**
194
134
  * Structural slice of Next's request cookie store (`await cookies()`); any
195
135
  * object honoring it works — the seam for tests and non-Next servers.
@@ -227,4 +167,4 @@ declare function pbServer(opts?: {
227
167
  cookies?: CookieStoreLike;
228
168
  }): Promise<PB>;
229
169
 
230
- export { type CookieStoreLike, type HandleAuthCallbackOptions, type PalbeMiddlewareOptions, SESSION_COOKIE_ATTRS, type SessionCookieWrite, type StoredSession, clearedSessionCookieNames, decodeSessionCookies, encodeSessionCookies, encodeSessionCookiesDecoded, environmentRefFromApiKey, handleAuthCallback, palbeMiddleware, pbServer, sessionCookieName };
170
+ export { type CookieStoreLike, type HandleAuthCallbackOptions, SESSION_COOKIE_ATTRS, type SessionCookieWrite, type StoredSession, clearedSessionCookieNames, decodeSessionCookies, encodeSessionCookies, encodeSessionCookiesDecoded, environmentRefFromApiKey, handleAuthCallback, pbServer, sessionCookieName };
@@ -1,3 +1,6 @@
1
+ import {
2
+ importNextServer
3
+ } from "../chunk-OWLTZG2V.js";
1
4
  import {
2
5
  SESSION_COOKIE_ATTRS,
3
6
  clearedSessionCookieNames,
@@ -7,18 +10,20 @@ import {
7
10
  sessionCookieName
8
11
  } from "../chunk-6VGKMNXO.js";
9
12
  import {
10
- BackendError,
11
13
  asWireAuthResult,
12
14
  buildRuntime,
13
15
  createBoundClient,
14
16
  getRuntime
15
- } from "../chunk-TGDS6VMT.js";
17
+ } from "../chunk-XTQXFZUP.js";
18
+ import {
19
+ BackendError
20
+ } from "../chunk-BNAGRUIQ.js";
16
21
  import {
17
22
  environmentRefFromApiKey
18
- } from "../chunk-NXEPAEF5.js";
23
+ } from "../chunk-CFDU23TB.js";
19
24
  import "../chunk-PZ5AY32C.js";
20
25
 
21
- // src/next/shared.ts
26
+ // src/next/global-config.ts
22
27
  function requireGlobalConfig(hint) {
23
28
  try {
24
29
  return getRuntime().config;
@@ -29,20 +34,6 @@ function requireGlobalConfig(hint) {
29
34
  });
30
35
  }
31
36
  }
32
- var nextServerModule;
33
- async function importNextServer(caller) {
34
- nextServerModule ??= import("next/server");
35
- try {
36
- return await nextServerModule;
37
- } catch (cause) {
38
- nextServerModule = void 0;
39
- const detail = cause instanceof Error && cause.message ? ` (${cause.message})` : "";
40
- throw new BackendError("validation", {
41
- code: "next_required",
42
- message: `${caller}() requires Next.js \u2014 'next/server' could not be resolved${detail}.`
43
- });
44
- }
45
- }
46
37
 
47
38
  // src/next/callback.ts
48
39
  function safeNextPath(next, origin) {
@@ -134,90 +125,6 @@ function handleAuthCallback(opts) {
134
125
  };
135
126
  }
136
127
 
137
- // src/next/middleware.ts
138
- var DEFAULT_REFRESH_MARGIN_MS = 6e4;
139
- var inflightRefreshes = /* @__PURE__ */ new Map();
140
- function refreshSingleFlight(config, refreshToken) {
141
- const existing = inflightRefreshes.get(refreshToken);
142
- if (existing) return existing;
143
- const pending = refresh(config, refreshToken).finally(() => {
144
- inflightRefreshes.delete(refreshToken);
145
- });
146
- inflightRefreshes.set(refreshToken, pending);
147
- return pending;
148
- }
149
- async function refresh(config, refreshToken) {
150
- try {
151
- const res = await fetch(`${config.url}/auth/token/refresh`, {
152
- method: "POST",
153
- headers: { apikey: config.apiKey, "content-type": "application/json" },
154
- body: JSON.stringify({ refresh_token: refreshToken })
155
- });
156
- if (res.status === 400 || res.status === 401 || res.status === 403) {
157
- return { kind: "terminal" };
158
- }
159
- if (!res.ok) return { kind: "transient" };
160
- const raw = await res.json();
161
- if (typeof raw === "object" && raw !== null) {
162
- const obj = raw;
163
- if (typeof obj.access_token === "string" && typeof obj.refresh_token === "string" && typeof obj.expires_in === "number") {
164
- return {
165
- kind: "rotated",
166
- session: {
167
- accessToken: obj.access_token,
168
- refreshToken: obj.refresh_token,
169
- expiresAt: Date.now() + obj.expires_in * 1e3
170
- }
171
- };
172
- }
173
- }
174
- return { kind: "transient" };
175
- } catch {
176
- return { kind: "transient" };
177
- }
178
- }
179
- async function palbeMiddleware(request, opts) {
180
- const config = requireGlobalConfig(
181
- "import the generated palbe.gen.ts at the top of middleware.ts \u2014 Next bundles middleware as its own module graph, so the layout.tsx import does not reach it."
182
- );
183
- const { NextResponse } = await importNextServer("palbeMiddleware");
184
- const passThrough = () => opts?.response ?? NextResponse.next({ request });
185
- const ref = config.environmentRef;
186
- const session = decodeSessionCookies((name) => request.cookies.get(name)?.value, ref);
187
- if (!session) return passThrough();
188
- if (session.expiresAt - Date.now() > (opts?.refreshMarginMs ?? DEFAULT_REFRESH_MARGIN_MS)) {
189
- return passThrough();
190
- }
191
- const outcome = await refreshSingleFlight(config, session.refreshToken);
192
- if (outcome.kind === "transient") return passThrough();
193
- const staleNames = clearedSessionCookieNames(ref, (name) => request.cookies.has(name));
194
- if (outcome.kind === "terminal") {
195
- for (const name of staleNames) request.cookies.delete(name);
196
- const response2 = opts?.response ?? NextResponse.next({ request });
197
- for (const name of staleNames) {
198
- response2.cookies.set(name, "", { ...SESSION_COOKIE_ATTRS, maxAge: 0 });
199
- }
200
- response2.headers.set("cache-control", "private, no-store");
201
- return response2;
202
- }
203
- const write = encodeSessionCookiesDecoded(ref, outcome.session);
204
- for (const name of staleNames) request.cookies.delete(name);
205
- for (const { name, value } of write.set) request.cookies.set(name, value);
206
- for (const name of write.clear) request.cookies.delete(name);
207
- const response = opts?.response ?? NextResponse.next({ request });
208
- const written = new Set(write.set.map((c) => c.name));
209
- for (const { name, value } of write.set) {
210
- response.cookies.set(name, value, SESSION_COOKIE_ATTRS);
211
- }
212
- for (const name of /* @__PURE__ */ new Set([...write.clear, ...staleNames])) {
213
- if (!written.has(name)) {
214
- response.cookies.set(name, "", { ...SESSION_COOKIE_ATTRS, maxAge: 0 });
215
- }
216
- }
217
- response.headers.set("cache-control", "private, no-store");
218
- return response;
219
- }
220
-
221
128
  // src/next/server.ts
222
129
  function nextRequired(cause) {
223
130
  const detail = cause instanceof Error && cause.message ? ` (${cause.message})` : "";
@@ -306,7 +213,6 @@ export {
306
213
  encodeSessionCookiesDecoded,
307
214
  environmentRefFromApiKey,
308
215
  handleAuthCallback,
309
- palbeMiddleware,
310
216
  pbServer,
311
217
  sessionCookieName
312
218
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/next/shared.ts","../../src/next/callback.ts","../../src/next/middleware.ts","../../src/next/server.ts"],"sourcesContent":["/**\n * Cross-surface helpers for the palbe/next server entries (pbServer,\n * palbeMiddleware, handleAuthCallback): global-config lookup with a\n * surface-specific guided error, and the lazy 'next/server' import.\n */\nimport type { PalbeConfig } from '../config.js';\nimport { BackendError } from '../errors.js';\nimport { getRuntime } from '../internal.js';\n\n/**\n * Global config with a surface-specific guided error when absent. Each\n * surface names the import location that configures ITS module graph —\n * Next bundles middleware separately from the server graph, so the hint\n * matters (layout.tsx for Server Components / Route Handlers, middleware.ts\n * for the middleware).\n */\nexport function requireGlobalConfig(hint: string): PalbeConfig {\n try {\n return getRuntime().config;\n } catch {\n throw new BackendError('notConfigured', {\n code: 'not_configured',\n message: `Palbe is not configured in this module graph. Run 'palbase web link' and ${hint}`,\n });\n }\n}\n\nlet nextServerModule: Promise<typeof import('next/server')> | undefined;\n\n/**\n * Lazy 'next/server' import (MODULE-GRAPH RULE: never top-level — importing\n * palbe/next must work without next installed) with a guided failure.\n * The module promise is memoized: bundler dynamic-import machinery can take\n * macrotask-scale hops PER CALL, so without the cache two near-simultaneous\n * middleware invocations could straddle a refresh — every call after the\n * first now resolves in pure microtasks.\n */\nexport async function importNextServer(caller: string): Promise<typeof import('next/server')> {\n nextServerModule ??= import('next/server');\n try {\n return await nextServerModule;\n } catch (cause) {\n nextServerModule = undefined; // never poison later calls with a cached rejection\n const detail = cause instanceof Error && cause.message ? ` (${cause.message})` : '';\n throw new BackendError('validation', {\n code: 'next_required',\n message: `${caller}() requires Next.js — 'next/server' could not be resolved${detail}.`,\n });\n }\n}\n","/**\n * handleAuthCallback — the OAuth completion route handler factory.\n *\n * The app's redirect_uri (registered when calling signInWithOAuth with\n * `redirectTo: '<app>/auth/callback?provider=google&next=/dash'`) lands\n * here with `?code&state` from the provider. The handler exchanges them\n * SERVER-SIDE against palauth (PKCE verifier + state live server-side; the\n * browser never holds them), writes the session cookies onto a redirect\n * response, and sends the user on.\n *\n * ```ts\n * // app/auth/callback/route.ts\n * import { handleAuthCallback } from '@palbase/web/next';\n * export const GET = handleAuthCallback({ defaultNext: '/' });\n * ```\n *\n * MODULE-GRAPH RULE (same as ./index.ts): 'next/server' only lazily; the\n * type-only NextRequest import erases at build time.\n */\nimport type { NextRequest } from 'next/server';\nimport { asWireAuthResult } from '../auth-wire.js';\nimport { encodeSessionCookiesDecoded, SESSION_COOKIE_ATTRS } from './cookie-codec.js';\nimport { importNextServer, requireGlobalConfig } from './shared.js';\n\nexport interface HandleAuthCallbackOptions {\n /** Where to land when the callback carries no (valid) `next` param — and on every error/MFA redirect. Default '/'. */\n defaultNext?: string;\n}\n\n/**\n * Open-redirect guard: `next` rides the OAuth redirect querystring, so it is\n * attacker-influencable — only same-origin, path-absolute relative paths are\n * honored, and the post-auth victim is never sent off-origin.\n *\n * A leading-'/' heuristic is NOT enough. The value is later resolved with\n * `new URL(next, base)`, and the WHATWG URL parser STRIPS ASCII tab (0x09),\n * LF (0x0A) and CR (0x0D) before parsing — so `/<TAB>/evil.com` slips past a\n * \"next[1] is not '/'\" check yet parses to `//evil.com` → `https://evil.com`\n * (CWE-601 open redirect). We defend with TWO independent gates that must\n * BOTH pass, matching OWASP unvalidated-redirect guidance:\n *\n * 1. RAW-STRING gate (reject before normalization can hide the payload):\n * - reject any C0 control char (\\x00-\\x1F, incl. \\t \\n \\r) or other\n * whitespace — these are exactly what URL parsing strips/normalizes\n * away;\n * - require a path-absolute prefix: a single '/' NOT followed by '/' or\n * '\\\\' (blocks '//host' protocol-relative and '/\\\\host' backslash\n * tricks);\n * - reject a scheme: any ':' appearing before the first '/' ('javascript:',\n * 'https:'). Path-absolute inputs start with '/', so this also rejects\n * bare 'foo:bar'.\n * 2. CANONICAL gate: resolve `new URL(next, origin)` and require the result\n * to land on the SAME origin. This is the authoritative check — it sees\n * exactly what the browser will, after normalization.\n *\n * Anything ambiguous returns null so the caller falls back to a safe default.\n */\nfunction safeNextPath(next: string | null, origin: string): string | null {\n if (!next) return null;\n // 1a. No control chars / whitespace — URL parsing would strip these and\n // change what the path means (the \\t \\n \\r open-redirect class).\n // biome-ignore lint/suspicious/noControlCharactersInRegex: rejecting C0 is the point of the guard.\n if (/[\\u0000-\\u0020\\u007f\\s]/.test(next)) return null;\n // 1b. Must be path-absolute: a single leading '/', not '//' or '/\\\\'.\n if (next[0] !== '/' || next[1] === '/' || next[1] === '\\\\') return null;\n // 1c. No scheme before the first path separator (defense in depth; a\n // path-absolute string can't carry one, but reject explicitly).\n const firstSlash = next.indexOf('/');\n const firstColon = next.indexOf(':');\n if (firstColon !== -1 && firstColon < firstSlash) return null;\n // 2. Canonical same-origin check — resolve as the browser/redirect helper\n // will and confirm we never leave this origin.\n try {\n if (new URL(next, origin).origin !== origin) return null;\n } catch {\n return null;\n }\n return next;\n}\n\nfunction wireErrorCode(raw: unknown): string {\n if (typeof raw === 'object' && raw !== null) {\n const code = (raw as Record<string, unknown>).error;\n if (typeof code === 'string' && code !== '') return code;\n }\n return 'oauth_exchange_failed';\n}\n\n/**\n * Build the callback route handler. Outcomes (all 307 redirects, all with\n * `Cache-Control: private, no-store` — auth outcomes must never cache):\n * - provider `error` param → `?auth_error=<error>` (+ `auth_error_description`)\n * - missing code/state/provider → `?auth_error=invalid_callback`\n * - exchanged AuthResult → session cookies on the redirect to `next ?? defaultNext ?? '/'`\n * - MFA union → `?mfa_token=…&mfa_factors=a,b` on defaultNext, NO cookies\n * - exchange failure → `?auth_error=<wire code | oauth_exchange_failed>`\n */\nexport function handleAuthCallback(\n opts?: HandleAuthCallbackOptions,\n): (request: NextRequest) => Promise<Response> {\n return async (request) => {\n const config = requireGlobalConfig(\n 'import the generated palbe.gen.ts once in app/layout.tsx — route handlers share the server module graph with the root layout.',\n );\n const { NextResponse } = await importNextServer('handleAuthCallback');\n const fallback = opts?.defaultNext ?? '/';\n\n const redirect = (path: string, query?: Record<string, string>) => {\n const url = new URL(path, request.nextUrl);\n for (const [key, value] of Object.entries(query ?? {})) url.searchParams.set(key, value);\n const response = NextResponse.redirect(url); // 307 — the callback GET stays a GET\n response.headers.set('cache-control', 'private, no-store');\n return response;\n };\n\n const params = request.nextUrl.searchParams;\n const providerError = params.get('error');\n if (providerError) {\n const query: Record<string, string> = { auth_error: providerError };\n const description = params.get('error_description');\n if (description) query.auth_error_description = description;\n return redirect(fallback, query);\n }\n\n const code = params.get('code');\n const state = params.get('state');\n const provider = params.get('provider');\n if (!code || !state || !provider) {\n return redirect(fallback, { auth_error: 'invalid_callback' });\n }\n\n let exchange: Response;\n try {\n exchange = await fetch(\n `${config.url}/auth/oauth/${encodeURIComponent(provider)}/callback` +\n `?code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`,\n { headers: { apikey: config.apiKey } },\n );\n } catch {\n return redirect(fallback, { auth_error: 'oauth_exchange_failed' });\n }\n let raw: unknown = null;\n try {\n raw = await exchange.json();\n } catch {\n // malformed body — the shape checks below turn this into auth_error\n }\n if (!exchange.ok) return redirect(fallback, { auth_error: wireErrorCode(raw) });\n\n if (typeof raw === 'object' && raw !== null) {\n const obj = raw as Record<string, unknown>;\n // MFA union: relay the challenge to the app — NO cookies until it completes.\n if (obj.mfa_required === true && typeof obj.mfa_token === 'string') {\n const factors = Array.isArray(obj.mfa_factors)\n ? obj.mfa_factors.filter((f): f is string => typeof f === 'string')\n : [];\n return redirect(fallback, { mfa_token: obj.mfa_token, mfa_factors: factors.join(',') });\n }\n }\n\n // Zombie-session guard (shared with the auth facade): cookies are only\n // ever written for a FULLY-proven AuthResult.\n const wire = asWireAuthResult(raw);\n if (!wire) return redirect(fallback, { auth_error: 'oauth_exchange_failed' });\n\n const response = redirect(safeNextPath(params.get('next'), request.nextUrl.origin) ?? fallback);\n // DECODED codec values: NextResponse.cookies percent-encodes on write,\n // producing exactly the shared-codec wire bytes (encodeSessionCookiesDecoded).\n const write = encodeSessionCookiesDecoded(config.environmentRef, {\n accessToken: wire.access_token,\n refreshToken: wire.refresh_token,\n expiresAt: Date.now() + wire.expires_in * 1000,\n });\n for (const { name, value } of write.set) {\n response.cookies.set(name, value, SESSION_COOKIE_ATTRS);\n }\n // Codec overflow guard: expire one-past-the-end of the written run.\n for (const name of write.clear) {\n response.cookies.set(name, '', { ...SESSION_COOKIE_ATTRS, maxAge: 0 });\n }\n return response;\n };\n}\n","/**\n * palbeMiddleware — proactive cookie-session refresh in Next.js middleware.\n *\n * REQUIRED — not optional — for session-bearing RSC apps. Server Components\n * cannot write cookies, so pbServer's reactive refresh cannot PERSIST a\n * rotated session there: every RSC render would re-refresh from the SAME\n * cookie refresh token, and once two of those land more than 30s apart\n * (outside palauth's rotation grace) the reuse detector REVOKES THE WHOLE\n * TOKEN FAMILY — the user is force-logged-out everywhere. This middleware\n * refreshes the cookie BEFORE the RSC tree renders, so pbServer hydrates a\n * still-valid session and never burns the token itself.\n *\n * MODULE-GRAPH RULES (same as ./index.ts): 'next/server' is imported only\n * lazily (the type-only NextRequest/NextResponse imports erase at build\n * time), and middleware.ts must import the generated palbe.gen.ts itself —\n * Next bundles middleware as its OWN module graph; the layout.tsx import\n * does not reach it.\n *\n * ```ts\n * // middleware.ts\n * import './palbe.gen';\n * import { palbeMiddleware } from '@palbase/web/next';\n * import type { NextRequest } from 'next/server';\n *\n * export function middleware(request: NextRequest) {\n * return palbeMiddleware(request);\n * }\n * export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] };\n * ```\n */\nimport type { NextRequest, NextResponse } from 'next/server';\nimport type { PalbeConfig } from '../config.js';\nimport {\n clearedSessionCookieNames,\n decodeSessionCookies,\n encodeSessionCookiesDecoded,\n SESSION_COOKIE_ATTRS,\n type StoredSession,\n} from './cookie-codec.js';\nimport { importNextServer, requireGlobalConfig } from './shared.js';\n\nconst DEFAULT_REFRESH_MARGIN_MS = 60_000;\n\nexport interface PalbeMiddlewareOptions {\n /** Refresh when the access token expires within this window. Default 60s. */\n refreshMarginMs?: number;\n /**\n * Pre-built response to decorate (e.g. from chained middleware) instead of\n * building `NextResponse.next({ request })`. Pass-through cases return it\n * UNMODIFIED. Caveat: a pre-built response snapshotted the request headers\n * at ITS creation, so a rotated cookie reaches the browser (Set-Cookie)\n * but not the downstream RSC render of THIS request — prefer omitting this\n * and building your response after palbeMiddleware when possible.\n */\n response?: NextResponse;\n}\n\ntype RefreshOutcome =\n | { kind: 'rotated'; session: StoredSession }\n | { kind: 'terminal' } // 400/401/403 — refresh token dead, sign out\n | { kind: 'transient' }; // network / 5xx / malformed — keep the session\n\n/**\n * Per-process single-flight: concurrent middleware invocations holding the\n * SAME refresh token (parallel tabs / prefetches) share one refresh call —\n * a per-token thundering herd would race palauth rotation. Multi-pod\n * concurrency is covered by palauth's 30s rotation grace (concurrent\n * refreshes of one token all succeed); per-process dedup is all that's\n * needed here (Studio precedent — don't over-engineer).\n */\nconst inflightRefreshes = new Map<string, Promise<RefreshOutcome>>();\n\nfunction refreshSingleFlight(config: PalbeConfig, refreshToken: string): Promise<RefreshOutcome> {\n const existing = inflightRefreshes.get(refreshToken);\n if (existing) return existing;\n const pending = refresh(config, refreshToken).finally(() => {\n inflightRefreshes.delete(refreshToken);\n });\n inflightRefreshes.set(refreshToken, pending);\n return pending;\n}\n\n/** Direct palauth refresh call (no runtime: middleware needs only this one wire). */\nasync function refresh(config: PalbeConfig, refreshToken: string): Promise<RefreshOutcome> {\n try {\n const res = await fetch(`${config.url}/auth/token/refresh`, {\n method: 'POST',\n headers: { apikey: config.apiKey, 'content-type': 'application/json' },\n body: JSON.stringify({ refresh_token: refreshToken }),\n });\n if (res.status === 400 || res.status === 401 || res.status === 403) {\n return { kind: 'terminal' };\n }\n if (!res.ok) return { kind: 'transient' };\n const raw: unknown = await res.json();\n if (typeof raw === 'object' && raw !== null) {\n const obj = raw as Record<string, unknown>;\n if (\n typeof obj.access_token === 'string' &&\n typeof obj.refresh_token === 'string' &&\n typeof obj.expires_in === 'number'\n ) {\n return {\n kind: 'rotated',\n session: {\n accessToken: obj.access_token,\n refreshToken: obj.refresh_token,\n expiresAt: Date.now() + obj.expires_in * 1000,\n },\n };\n }\n }\n return { kind: 'transient' }; // malformed 200 — never destroy a session over it\n } catch {\n return { kind: 'transient' }; // network failure\n }\n}\n\n/**\n * Keep the session cookie fresh for the request. No session / still-fresh\n * session → untouched pass-through (no cache-control changes). Stale or\n * expired → single-flight refresh, then the rotated (or cleared, on a dead\n * token) cookies are written onto BOTH the request (for the downstream RSC\n * render via `NextResponse.next({ request })`) and the response (for the\n * browser), with `Cache-Control: private, no-store` on touched responses.\n *\n * Cookie writes go through Next's cookie APIs with the DECODED codec values:\n * Next percent-encodes on serialization, so the wire bytes come out exactly\n * as the shared codec's (see encodeSessionCookiesDecoded). NextRequest's\n * cookie store is NOT a CookieStoreLike (its set is the 2-arg/RequestCookie\n * form), hence the direct codec calls here instead of the pbServer adapter.\n */\nexport async function palbeMiddleware(\n request: NextRequest,\n opts?: PalbeMiddlewareOptions,\n): Promise<NextResponse> {\n const config = requireGlobalConfig(\n 'import the generated palbe.gen.ts at the top of middleware.ts — Next bundles middleware as its own module graph, so the layout.tsx import does not reach it.',\n );\n const { NextResponse } = await importNextServer('palbeMiddleware');\n const passThrough = (): NextResponse => opts?.response ?? NextResponse.next({ request });\n\n const ref = config.environmentRef;\n const session = decodeSessionCookies((name) => request.cookies.get(name)?.value, ref);\n if (!session) return passThrough();\n if (session.expiresAt - Date.now() > (opts?.refreshMarginMs ?? DEFAULT_REFRESH_MARGIN_MS)) {\n return passThrough();\n }\n\n const outcome = await refreshSingleFlight(config, session.refreshToken);\n if (outcome.kind === 'transient') return passThrough(); // keep the session over blips\n\n // Session-cookie names in the incoming jar — the browser's state mirror.\n const staleNames = clearedSessionCookieNames(ref, (name) => request.cookies.has(name));\n\n if (outcome.kind === 'terminal') {\n for (const name of staleNames) request.cookies.delete(name);\n const response = opts?.response ?? NextResponse.next({ request });\n for (const name of staleNames) {\n response.cookies.set(name, '', { ...SESSION_COOKIE_ATTRS, maxAge: 0 });\n }\n response.headers.set('cache-control', 'private, no-store');\n return response;\n }\n\n const write = encodeSessionCookiesDecoded(ref, outcome.session);\n for (const name of staleNames) request.cookies.delete(name);\n for (const { name, value } of write.set) request.cookies.set(name, value);\n for (const name of write.clear) request.cookies.delete(name);\n // Build AFTER the request-cookie mutation: NextResponse.next({ request })\n // snapshots the request headers for the downstream RSC render.\n const response = opts?.response ?? NextResponse.next({ request });\n const written = new Set(write.set.map((c) => c.name));\n for (const { name, value } of write.set) {\n response.cookies.set(name, value, SESSION_COOKIE_ATTRS);\n }\n // Browser-side cleanup: stale names not part of the new run (a shrunken /\n // grown chunk layout) + the codec's one-past-the-end overflow guard.\n for (const name of new Set([...write.clear, ...staleNames])) {\n if (!written.has(name)) {\n response.cookies.set(name, '', { ...SESSION_COOKIE_ATTRS, maxAge: 0 });\n }\n }\n response.headers.set('cache-control', 'private, no-store');\n return response;\n}\n","/**\n * pbServer — the per-request server client of the Next.js adapter.\n *\n * Every call builds a FRESH runtime over the request's cookie store and\n * returns a runtime-BOUND `PB` client: typed namespaces, `call`, `upload`\n * and `auth` all resolve that runtime, never the process-global slot — two\n * bound clients serving concurrent requests cannot leak each other's\n * sessions (see createBoundClient). The store itself is read lazily per\n * adapter operation, so nothing is snapshotted beyond the call.\n *\n * MODULE-GRAPH RULE (same as ./index.ts): 'next/headers' is imported ONLY\n * lazily inside the default-store path — importing this module, and using\n * pbServer with an injected store, must work without next installed.\n */\nimport { BackendError } from '../errors.js';\nimport { buildRuntime, createBoundClient } from '../internal.js';\nimport type { PB } from '../pb.js';\nimport type { PersistedSession, SessionStorageAdapter } from '../storage.js';\nimport {\n clearedSessionCookieNames,\n decodeSessionCookies,\n encodeSessionCookiesDecoded,\n SESSION_COOKIE_ATTRS,\n} from './cookie-codec.js';\nimport { requireGlobalConfig } from './shared.js';\n\n/**\n * Structural slice of Next's request cookie store (`await cookies()`); any\n * object honoring it works — the seam for tests and non-Next servers.\n * `set`/`delete` are optional because RSC-scope stores are read-only.\n */\nexport interface CookieStoreLike {\n get(name: string): { name: string; value: string } | undefined;\n getAll?(): Array<{ name: string; value: string }>;\n set?(name: string, value: string, options?: Record<string, unknown>): void;\n delete?(name: string): void;\n}\n\nfunction nextRequired(cause: unknown): BackendError {\n const detail = cause instanceof Error && cause.message ? ` (${cause.message})` : '';\n return new BackendError('validation', {\n code: 'next_required',\n message:\n 'pbServer() could not resolve a Next.js request cookie store: call it inside a Server Component, Server Action or Route Handler — or pass a store explicitly via pbServer({ cookies })' +\n `${detail}.`,\n });\n}\n\n/**\n * Next CONTROL-FLOW errors must pass through UNWRAPPED. During static\n * prerender (`next build`) `cookies()` throws DynamicServerError (digest\n * 'DYNAMIC_SERVER_USAGE'), which Next catches upstream to mark the route\n * dynamic — wrapping it into a BackendError would turn that bailout signal\n * into a real prerender error and FAIL THE BUILD of every page calling\n * pbServer (caught live in the P3 Next smoke). 'NEXT_'-prefixed digests are\n * the same sentinel family (redirect / not-found / static-gen bailout).\n * Genuine outside-request-scope errors carry no digest and still map to\n * next_required.\n */\nexport function isNextControlFlowError(err: unknown): boolean {\n if (typeof err !== 'object' || err === null) return false;\n const digest = (err as { digest?: unknown }).digest;\n return (\n typeof digest === 'string' && (digest === 'DYNAMIC_SERVER_USAGE' || digest.startsWith('NEXT_'))\n );\n}\n\n/** Lazy default store: `await (await import('next/headers')).cookies()`. */\nasync function nextCookieStore(): Promise<CookieStoreLike> {\n let cookies: () => Promise<CookieStoreLike>;\n try {\n ({ cookies } = await import('next/headers'));\n } catch (cause) {\n throw nextRequired(cause); // next not installed / not resolvable\n }\n try {\n return await cookies();\n } catch (cause) {\n if (isNextControlFlowError(cause)) throw cause; // static-prerender bailout etc.\n throw nextRequired(cause); // installed, but outside a request scope\n }\n}\n\n/** Delete one cookie: set-with-maxAge-0 when the store can set (parity with the browser side), else delete, else swallow (read-only RSC store). */\nfunction removeCookie(store: CookieStoreLike, name: string): void {\n try {\n if (store.set) store.set(name, '', { ...SESSION_COOKIE_ATTRS, maxAge: 0 });\n else store.delete?.(name);\n } catch {\n // RSC read-only store — cookie mutation is the middleware's job there\n }\n}\n\n/**\n * SessionStorageAdapter over the request cookie store.\n *\n * SAVE SEMANTICS (decision, documented): only FULL sessions (access token +\n * expiry present) are written; a refresh-only save is SKIPPED with no throw\n * rather than written as {a:'',e:0}. The cookie is SHARED with the browser —\n * degrading it would drop an access token the browser may still be using and\n * force a refresh per page-load. The refresh-only branch is a defensive\n * legacy path anyway: every successful refresh saves a full session.\n *\n * All writes swallow per-cookie failures: in RSC scope Next's store throws\n * on mutation; the request still proceeds with the in-memory session but the\n * rotated cookie is NOT persisted. palbeMiddleware is therefore REQUIRED —\n * not optional — for session-bearing RSC apps: without it every RSC render\n * re-refreshes from the SAME cookie refresh token, and once two such\n * refreshes land more than 30s apart (outside palauth's rotation grace) the\n * reuse detector REVOKES THE WHOLE TOKEN FAMILY — the user is\n * force-logged-out everywhere.\n *\n * Values are written DECODED (encodeSessionCookiesDecoded): Next's real\n * cookies() store percent-encodes on serialization, yielding exactly the\n * shared-codec wire bytes; a raw store (tests, custom servers) persists the\n * bare JSON, which the load path's decode tolerates identically (tokens are\n * %-free base64url, so decodeURIComponent is the identity on them).\n */\nfunction serverCookieAdapter(\n store: CookieStoreLike,\n environmentRef: string,\n): SessionStorageAdapter {\n const present = (name: string): boolean => store.get(name) !== undefined;\n return {\n load(): PersistedSession | null {\n const stored = decodeSessionCookies((name) => store.get(name)?.value, environmentRef);\n if (!stored) return null;\n // A browser-side refresh-only write round-trips as a:'' / e:0 —\n // normalize to the legacy shape so hydration takes the expired-trick.\n return stored.accessToken && stored.expiresAt > 0\n ? {\n refreshToken: stored.refreshToken,\n accessToken: stored.accessToken,\n expiresAt: stored.expiresAt,\n }\n : { refreshToken: stored.refreshToken };\n },\n save(session: PersistedSession): void {\n if (!session.accessToken || !session.expiresAt) return; // see SAVE SEMANTICS above\n for (const name of clearedSessionCookieNames(environmentRef, present)) {\n removeCookie(store, name);\n }\n const { set, clear } = encodeSessionCookiesDecoded(environmentRef, {\n accessToken: session.accessToken,\n refreshToken: session.refreshToken,\n expiresAt: session.expiresAt,\n });\n for (const { name, value } of set) {\n try {\n store.set?.(name, value, { ...SESSION_COOKIE_ATTRS });\n } catch {\n // RSC read-only store\n }\n }\n // Overflow guard (codec contract): delete one-past-the-end so a stale\n // orphan chunk behind a gap can never join a future chunk run.\n for (const name of clear) removeCookie(store, name);\n },\n clear(): void {\n for (const name of clearedSessionCookieNames(environmentRef, present)) {\n removeCookie(store, name);\n }\n },\n };\n}\n\n/**\n * Build a per-request `PB` client over the request's cookies. Reads the\n * session the browser SDK wrote (shared cookie codec), refreshes pre-flight\n * when expired, and — where the store is writable (Server Actions, Route\n * Handlers) — persists the rotated session back.\n *\n * Each call builds a fresh runtime (a handful of small objects, no I/O) —\n * cheap enough to call per data access; code paths invoking it many times\n * within one request MAY memoize per-request (e.g. React's `cache()`), but\n * don't have to.\n *\n * ```ts\n * const pb = await pbServer();\n * const todos = await pb.todos.list();\n * ```\n */\nexport async function pbServer(opts?: { cookies?: CookieStoreLike }): Promise<PB> {\n const config = requireGlobalConfig(\n 'import the generated palbe.gen.ts once in app/layout.tsx — the root-layout import configures Server Components and Route Handlers too.',\n );\n const store = opts?.cookies ?? (await nextCookieStore());\n const ref = config.environmentRef;\n const rt = buildRuntime({ ...config, storage: serverCookieAdapter(store, ref) });\n return createBoundClient(rt);\n}\n"],"mappings":"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","names":["response","response"]}
1
+ {"version":3,"sources":["../../src/next/global-config.ts","../../src/next/callback.ts","../../src/next/server.ts"],"sourcesContent":["/**\n * Global-config lookup for the palbe/next surfaces that run inside the\n * SERVER module graph (pbServer, handleAuthCallback) — the graph the root\n * layout's `import './palbe.gen'` configures.\n *\n * This module reaches `../internal.js` (and therefore the whole runtime\n * graph: livekit-client + the MLS WASM loader) on purpose — those surfaces\n * build a real runtime anyway. It is kept OUT of ./shared.ts so the\n * middleware entry, which must stay Edge-lean, never pulls it in.\n */\nimport type { PalbeConfig } from '../config.js';\nimport { BackendError } from '../errors.js';\nimport { getRuntime } from '../internal.js';\n\n/**\n * Global config with a surface-specific guided error when absent. Each\n * surface names the import location that configures ITS module graph.\n */\nexport function requireGlobalConfig(hint: string): PalbeConfig {\n try {\n return getRuntime().config;\n } catch {\n throw new BackendError('notConfigured', {\n code: 'not_configured',\n message: `Palbe is not configured in this module graph. Run 'palbase web link' and ${hint}`,\n });\n }\n}\n","/**\n * handleAuthCallback — the OAuth completion route handler factory.\n *\n * The app's redirect_uri (registered when calling signInWithOAuth with\n * `redirectTo: '<app>/auth/callback?provider=google&next=/dash'`) lands\n * here with `?code&state` from the provider. The handler exchanges them\n * SERVER-SIDE against palauth (PKCE verifier + state live server-side; the\n * browser never holds them), writes the session cookies onto a redirect\n * response, and sends the user on.\n *\n * ```ts\n * // app/auth/callback/route.ts\n * import { handleAuthCallback } from '@palbase/web/next';\n * export const GET = handleAuthCallback({ defaultNext: '/' });\n * ```\n *\n * MODULE-GRAPH RULE (same as ./index.ts): 'next/server' only lazily; the\n * type-only NextRequest import erases at build time.\n */\nimport type { NextRequest } from 'next/server';\nimport { asWireAuthResult } from '../auth-wire.js';\nimport { encodeSessionCookiesDecoded, SESSION_COOKIE_ATTRS } from './cookie-codec.js';\nimport { requireGlobalConfig } from './global-config.js';\nimport { importNextServer } from './shared.js';\n\nexport interface HandleAuthCallbackOptions {\n /** Where to land when the callback carries no (valid) `next` param — and on every error/MFA redirect. Default '/'. */\n defaultNext?: string;\n}\n\n/**\n * Open-redirect guard: `next` rides the OAuth redirect querystring, so it is\n * attacker-influencable — only same-origin, path-absolute relative paths are\n * honored, and the post-auth victim is never sent off-origin.\n *\n * A leading-'/' heuristic is NOT enough. The value is later resolved with\n * `new URL(next, base)`, and the WHATWG URL parser STRIPS ASCII tab (0x09),\n * LF (0x0A) and CR (0x0D) before parsing — so `/<TAB>/evil.com` slips past a\n * \"next[1] is not '/'\" check yet parses to `//evil.com` → `https://evil.com`\n * (CWE-601 open redirect). We defend with TWO independent gates that must\n * BOTH pass, matching OWASP unvalidated-redirect guidance:\n *\n * 1. RAW-STRING gate (reject before normalization can hide the payload):\n * - reject any C0 control char (\\x00-\\x1F, incl. \\t \\n \\r) or other\n * whitespace — these are exactly what URL parsing strips/normalizes\n * away;\n * - require a path-absolute prefix: a single '/' NOT followed by '/' or\n * '\\\\' (blocks '//host' protocol-relative and '/\\\\host' backslash\n * tricks);\n * - reject a scheme: any ':' appearing before the first '/' ('javascript:',\n * 'https:'). Path-absolute inputs start with '/', so this also rejects\n * bare 'foo:bar'.\n * 2. CANONICAL gate: resolve `new URL(next, origin)` and require the result\n * to land on the SAME origin. This is the authoritative check — it sees\n * exactly what the browser will, after normalization.\n *\n * Anything ambiguous returns null so the caller falls back to a safe default.\n */\nfunction safeNextPath(next: string | null, origin: string): string | null {\n if (!next) return null;\n // 1a. No control chars / whitespace — URL parsing would strip these and\n // change what the path means (the \\t \\n \\r open-redirect class).\n // biome-ignore lint/suspicious/noControlCharactersInRegex: rejecting C0 is the point of the guard.\n if (/[\\u0000-\\u0020\\u007f\\s]/.test(next)) return null;\n // 1b. Must be path-absolute: a single leading '/', not '//' or '/\\\\'.\n if (next[0] !== '/' || next[1] === '/' || next[1] === '\\\\') return null;\n // 1c. No scheme before the first path separator (defense in depth; a\n // path-absolute string can't carry one, but reject explicitly).\n const firstSlash = next.indexOf('/');\n const firstColon = next.indexOf(':');\n if (firstColon !== -1 && firstColon < firstSlash) return null;\n // 2. Canonical same-origin check — resolve as the browser/redirect helper\n // will and confirm we never leave this origin.\n try {\n if (new URL(next, origin).origin !== origin) return null;\n } catch {\n return null;\n }\n return next;\n}\n\nfunction wireErrorCode(raw: unknown): string {\n if (typeof raw === 'object' && raw !== null) {\n const code = (raw as Record<string, unknown>).error;\n if (typeof code === 'string' && code !== '') return code;\n }\n return 'oauth_exchange_failed';\n}\n\n/**\n * Build the callback route handler. Outcomes (all 307 redirects, all with\n * `Cache-Control: private, no-store` — auth outcomes must never cache):\n * - provider `error` param → `?auth_error=<error>` (+ `auth_error_description`)\n * - missing code/state/provider → `?auth_error=invalid_callback`\n * - exchanged AuthResult → session cookies on the redirect to `next ?? defaultNext ?? '/'`\n * - MFA union → `?mfa_token=…&mfa_factors=a,b` on defaultNext, NO cookies\n * - exchange failure → `?auth_error=<wire code | oauth_exchange_failed>`\n */\nexport function handleAuthCallback(\n opts?: HandleAuthCallbackOptions,\n): (request: NextRequest) => Promise<Response> {\n return async (request) => {\n const config = requireGlobalConfig(\n 'import the generated palbe.gen.ts once in app/layout.tsx — route handlers share the server module graph with the root layout.',\n );\n const { NextResponse } = await importNextServer('handleAuthCallback');\n const fallback = opts?.defaultNext ?? '/';\n\n const redirect = (path: string, query?: Record<string, string>) => {\n const url = new URL(path, request.nextUrl);\n for (const [key, value] of Object.entries(query ?? {})) url.searchParams.set(key, value);\n const response = NextResponse.redirect(url); // 307 — the callback GET stays a GET\n response.headers.set('cache-control', 'private, no-store');\n return response;\n };\n\n const params = request.nextUrl.searchParams;\n const providerError = params.get('error');\n if (providerError) {\n const query: Record<string, string> = { auth_error: providerError };\n const description = params.get('error_description');\n if (description) query.auth_error_description = description;\n return redirect(fallback, query);\n }\n\n const code = params.get('code');\n const state = params.get('state');\n const provider = params.get('provider');\n if (!code || !state || !provider) {\n return redirect(fallback, { auth_error: 'invalid_callback' });\n }\n\n let exchange: Response;\n try {\n exchange = await fetch(\n `${config.url}/auth/oauth/${encodeURIComponent(provider)}/callback` +\n `?code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`,\n { headers: { apikey: config.apiKey } },\n );\n } catch {\n return redirect(fallback, { auth_error: 'oauth_exchange_failed' });\n }\n let raw: unknown = null;\n try {\n raw = await exchange.json();\n } catch {\n // malformed body — the shape checks below turn this into auth_error\n }\n if (!exchange.ok) return redirect(fallback, { auth_error: wireErrorCode(raw) });\n\n if (typeof raw === 'object' && raw !== null) {\n const obj = raw as Record<string, unknown>;\n // MFA union: relay the challenge to the app — NO cookies until it completes.\n if (obj.mfa_required === true && typeof obj.mfa_token === 'string') {\n const factors = Array.isArray(obj.mfa_factors)\n ? obj.mfa_factors.filter((f): f is string => typeof f === 'string')\n : [];\n return redirect(fallback, { mfa_token: obj.mfa_token, mfa_factors: factors.join(',') });\n }\n }\n\n // Zombie-session guard (shared with the auth facade): cookies are only\n // ever written for a FULLY-proven AuthResult.\n const wire = asWireAuthResult(raw);\n if (!wire) return redirect(fallback, { auth_error: 'oauth_exchange_failed' });\n\n const response = redirect(safeNextPath(params.get('next'), request.nextUrl.origin) ?? fallback);\n // DECODED codec values: NextResponse.cookies percent-encodes on write,\n // producing exactly the shared-codec wire bytes (encodeSessionCookiesDecoded).\n const write = encodeSessionCookiesDecoded(config.environmentRef, {\n accessToken: wire.access_token,\n refreshToken: wire.refresh_token,\n expiresAt: Date.now() + wire.expires_in * 1000,\n });\n for (const { name, value } of write.set) {\n response.cookies.set(name, value, SESSION_COOKIE_ATTRS);\n }\n // Codec overflow guard: expire one-past-the-end of the written run.\n for (const name of write.clear) {\n response.cookies.set(name, '', { ...SESSION_COOKIE_ATTRS, maxAge: 0 });\n }\n return response;\n };\n}\n","/**\n * pbServer — the per-request server client of the Next.js adapter.\n *\n * Every call builds a FRESH runtime over the request's cookie store and\n * returns a runtime-BOUND `PB` client: typed namespaces, `call`, `upload`\n * and `auth` all resolve that runtime, never the process-global slot — two\n * bound clients serving concurrent requests cannot leak each other's\n * sessions (see createBoundClient). The store itself is read lazily per\n * adapter operation, so nothing is snapshotted beyond the call.\n *\n * MODULE-GRAPH RULE (same as ./index.ts): 'next/headers' is imported ONLY\n * lazily inside the default-store path — importing this module, and using\n * pbServer with an injected store, must work without next installed.\n */\nimport { BackendError } from '../errors.js';\nimport { buildRuntime, createBoundClient } from '../internal.js';\nimport type { PB } from '../pb.js';\nimport type { PersistedSession, SessionStorageAdapter } from '../storage.js';\nimport {\n clearedSessionCookieNames,\n decodeSessionCookies,\n encodeSessionCookiesDecoded,\n SESSION_COOKIE_ATTRS,\n} from './cookie-codec.js';\nimport { requireGlobalConfig } from './global-config.js';\n\n/**\n * Structural slice of Next's request cookie store (`await cookies()`); any\n * object honoring it works — the seam for tests and non-Next servers.\n * `set`/`delete` are optional because RSC-scope stores are read-only.\n */\nexport interface CookieStoreLike {\n get(name: string): { name: string; value: string } | undefined;\n getAll?(): Array<{ name: string; value: string }>;\n set?(name: string, value: string, options?: Record<string, unknown>): void;\n delete?(name: string): void;\n}\n\nfunction nextRequired(cause: unknown): BackendError {\n const detail = cause instanceof Error && cause.message ? ` (${cause.message})` : '';\n return new BackendError('validation', {\n code: 'next_required',\n message:\n 'pbServer() could not resolve a Next.js request cookie store: call it inside a Server Component, Server Action or Route Handler — or pass a store explicitly via pbServer({ cookies })' +\n `${detail}.`,\n });\n}\n\n/**\n * Next CONTROL-FLOW errors must pass through UNWRAPPED. During static\n * prerender (`next build`) `cookies()` throws DynamicServerError (digest\n * 'DYNAMIC_SERVER_USAGE'), which Next catches upstream to mark the route\n * dynamic — wrapping it into a BackendError would turn that bailout signal\n * into a real prerender error and FAIL THE BUILD of every page calling\n * pbServer (caught live in the P3 Next smoke). 'NEXT_'-prefixed digests are\n * the same sentinel family (redirect / not-found / static-gen bailout).\n * Genuine outside-request-scope errors carry no digest and still map to\n * next_required.\n */\nexport function isNextControlFlowError(err: unknown): boolean {\n if (typeof err !== 'object' || err === null) return false;\n const digest = (err as { digest?: unknown }).digest;\n return (\n typeof digest === 'string' && (digest === 'DYNAMIC_SERVER_USAGE' || digest.startsWith('NEXT_'))\n );\n}\n\n/** Lazy default store: `await (await import('next/headers')).cookies()`. */\nasync function nextCookieStore(): Promise<CookieStoreLike> {\n let cookies: () => Promise<CookieStoreLike>;\n try {\n ({ cookies } = await import('next/headers'));\n } catch (cause) {\n throw nextRequired(cause); // next not installed / not resolvable\n }\n try {\n return await cookies();\n } catch (cause) {\n if (isNextControlFlowError(cause)) throw cause; // static-prerender bailout etc.\n throw nextRequired(cause); // installed, but outside a request scope\n }\n}\n\n/** Delete one cookie: set-with-maxAge-0 when the store can set (parity with the browser side), else delete, else swallow (read-only RSC store). */\nfunction removeCookie(store: CookieStoreLike, name: string): void {\n try {\n if (store.set) store.set(name, '', { ...SESSION_COOKIE_ATTRS, maxAge: 0 });\n else store.delete?.(name);\n } catch {\n // RSC read-only store — cookie mutation is the middleware's job there\n }\n}\n\n/**\n * SessionStorageAdapter over the request cookie store.\n *\n * SAVE SEMANTICS (decision, documented): only FULL sessions (access token +\n * expiry present) are written; a refresh-only save is SKIPPED with no throw\n * rather than written as {a:'',e:0}. The cookie is SHARED with the browser —\n * degrading it would drop an access token the browser may still be using and\n * force a refresh per page-load. The refresh-only branch is a defensive\n * legacy path anyway: every successful refresh saves a full session.\n *\n * All writes swallow per-cookie failures: in RSC scope Next's store throws\n * on mutation; the request still proceeds with the in-memory session but the\n * rotated cookie is NOT persisted. palbeMiddleware is therefore REQUIRED —\n * not optional — for session-bearing RSC apps: without it every RSC render\n * re-refreshes from the SAME cookie refresh token, and once two such\n * refreshes land more than 30s apart (outside palauth's rotation grace) the\n * reuse detector REVOKES THE WHOLE TOKEN FAMILY — the user is\n * force-logged-out everywhere.\n *\n * Values are written DECODED (encodeSessionCookiesDecoded): Next's real\n * cookies() store percent-encodes on serialization, yielding exactly the\n * shared-codec wire bytes; a raw store (tests, custom servers) persists the\n * bare JSON, which the load path's decode tolerates identically (tokens are\n * %-free base64url, so decodeURIComponent is the identity on them).\n */\nfunction serverCookieAdapter(\n store: CookieStoreLike,\n environmentRef: string,\n): SessionStorageAdapter {\n const present = (name: string): boolean => store.get(name) !== undefined;\n return {\n load(): PersistedSession | null {\n const stored = decodeSessionCookies((name) => store.get(name)?.value, environmentRef);\n if (!stored) return null;\n // A browser-side refresh-only write round-trips as a:'' / e:0 —\n // normalize to the legacy shape so hydration takes the expired-trick.\n return stored.accessToken && stored.expiresAt > 0\n ? {\n refreshToken: stored.refreshToken,\n accessToken: stored.accessToken,\n expiresAt: stored.expiresAt,\n }\n : { refreshToken: stored.refreshToken };\n },\n save(session: PersistedSession): void {\n if (!session.accessToken || !session.expiresAt) return; // see SAVE SEMANTICS above\n for (const name of clearedSessionCookieNames(environmentRef, present)) {\n removeCookie(store, name);\n }\n const { set, clear } = encodeSessionCookiesDecoded(environmentRef, {\n accessToken: session.accessToken,\n refreshToken: session.refreshToken,\n expiresAt: session.expiresAt,\n });\n for (const { name, value } of set) {\n try {\n store.set?.(name, value, { ...SESSION_COOKIE_ATTRS });\n } catch {\n // RSC read-only store\n }\n }\n // Overflow guard (codec contract): delete one-past-the-end so a stale\n // orphan chunk behind a gap can never join a future chunk run.\n for (const name of clear) removeCookie(store, name);\n },\n clear(): void {\n for (const name of clearedSessionCookieNames(environmentRef, present)) {\n removeCookie(store, name);\n }\n },\n };\n}\n\n/**\n * Build a per-request `PB` client over the request's cookies. Reads the\n * session the browser SDK wrote (shared cookie codec), refreshes pre-flight\n * when expired, and — where the store is writable (Server Actions, Route\n * Handlers) — persists the rotated session back.\n *\n * Each call builds a fresh runtime (a handful of small objects, no I/O) —\n * cheap enough to call per data access; code paths invoking it many times\n * within one request MAY memoize per-request (e.g. React's `cache()`), but\n * don't have to.\n *\n * ```ts\n * const pb = await pbServer();\n * const todos = await pb.todos.list();\n * ```\n */\nexport async function pbServer(opts?: { cookies?: CookieStoreLike }): Promise<PB> {\n const config = requireGlobalConfig(\n 'import the generated palbe.gen.ts once in app/layout.tsx — the root-layout import configures Server Components and Route Handlers too.',\n );\n const store = opts?.cookies ?? (await nextCookieStore());\n const ref = config.environmentRef;\n const rt = buildRuntime({ ...config, storage: serverCookieAdapter(store, ref) });\n return createBoundClient(rt);\n}\n"],"mappings":"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","names":["response"]}