@iglo.tech/iglo.base 0.0.0-stage → 1.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 (69) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +18 -0
  3. package/README.md +778 -2
  4. package/dist/iglo-base.es.mjs +4 -0
  5. package/dist/iglo-base.es.mjs.map +34 -0
  6. package/dist/ssr-astro.es.mjs +4 -0
  7. package/dist/ssr-astro.es.mjs.map +12 -0
  8. package/dist/ssr-browser-client.es.mjs +4 -0
  9. package/dist/ssr-browser-client.es.mjs.map +13 -0
  10. package/dist/ssr-browser.es.mjs +4 -0
  11. package/dist/ssr-browser.es.mjs.map +10 -0
  12. package/dist/ssr-next.es.mjs +4 -0
  13. package/dist/ssr-next.es.mjs.map +12 -0
  14. package/dist/ssr-nuxt.es.mjs +4 -0
  15. package/dist/ssr-nuxt.es.mjs.map +12 -0
  16. package/dist/ssr-react-router.es.mjs +4 -0
  17. package/dist/ssr-react-router.es.mjs.map +12 -0
  18. package/dist/ssr-sveltekit.es.mjs +4 -0
  19. package/dist/ssr-sveltekit.es.mjs.map +12 -0
  20. package/dist/ssr.es.mjs +4 -0
  21. package/dist/ssr.es.mjs.map +22 -0
  22. package/dist/types/src/Client.d.ts +233 -0
  23. package/dist/types/src/ClientResponseError.d.ts +22 -0
  24. package/dist/types/src/index.d.ts +18 -0
  25. package/dist/types/src/services/BackupService.d.ts +59 -0
  26. package/dist/types/src/services/BaseService.d.ts +8 -0
  27. package/dist/types/src/services/BatchService.d.ts +56 -0
  28. package/dist/types/src/services/CollectionService.d.ts +47 -0
  29. package/dist/types/src/services/CronService.d.ts +20 -0
  30. package/dist/types/src/services/CrudService.d.ts +85 -0
  31. package/dist/types/src/services/FileService.d.ts +19 -0
  32. package/dist/types/src/services/HealthService.d.ts +16 -0
  33. package/dist/types/src/services/LogService.d.ts +35 -0
  34. package/dist/types/src/services/RealtimeService.d.ts +124 -0
  35. package/dist/types/src/services/RecordService.d.ts +374 -0
  36. package/dist/types/src/services/SQLService.d.ts +21 -0
  37. package/dist/types/src/services/SettingsService.d.ts +46 -0
  38. package/dist/types/src/ssr/adapter.d.ts +133 -0
  39. package/dist/types/src/ssr/astro.d.ts +27 -0
  40. package/dist/types/src/ssr/browser-client.d.ts +100 -0
  41. package/dist/types/src/ssr/browser-token.d.ts +29 -0
  42. package/dist/types/src/ssr/browser.d.ts +1 -0
  43. package/dist/types/src/ssr/cache.d.ts +9 -0
  44. package/dist/types/src/ssr/cookies.d.ts +68 -0
  45. package/dist/types/src/ssr/csrf.d.ts +38 -0
  46. package/dist/types/src/ssr/index.d.ts +12 -0
  47. package/dist/types/src/ssr/next.d.ts +53 -0
  48. package/dist/types/src/ssr/no-store-fetch.d.ts +7 -0
  49. package/dist/types/src/ssr/nuxt.d.ts +41 -0
  50. package/dist/types/src/ssr/oauth2.d.ts +56 -0
  51. package/dist/types/src/ssr/popup.d.ts +14 -0
  52. package/dist/types/src/ssr/react-router.d.ts +53 -0
  53. package/dist/types/src/ssr/renew.d.ts +25 -0
  54. package/dist/types/src/ssr/server-client.d.ts +83 -0
  55. package/dist/types/src/ssr/superuser-client.d.ts +24 -0
  56. package/dist/types/src/ssr/sveltekit.d.ts +44 -0
  57. package/dist/types/src/ssr/types.d.ts +118 -0
  58. package/dist/types/src/stores/AsyncAuthStore.d.ts +52 -0
  59. package/dist/types/src/stores/BaseAuthStore.d.ts +104 -0
  60. package/dist/types/src/stores/LocalAuthStore.d.ts +48 -0
  61. package/dist/types/src/tools/cookie.d.ts +31 -0
  62. package/dist/types/src/tools/dtos.d.ts +122 -0
  63. package/dist/types/src/tools/formdata.d.ts +30 -0
  64. package/dist/types/src/tools/jwt.d.ts +14 -0
  65. package/dist/types/src/tools/legacy.d.ts +2 -0
  66. package/dist/types/src/tools/loose.d.ts +5 -0
  67. package/dist/types/src/tools/options.d.ts +91 -0
  68. package/dist/types/src/tools/refresh.d.ts +3 -0
  69. package/package.json +73 -4
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [".js/src/ssr/adapter.js", ".js/src/ssr/cookies.js", ".js/src/ssr/react-router.js"],
4
+ "sourcesContent": [
5
+ "// docs/design/13-ssr.md (\"Framework adapters\", \"OAuth2\", \"CSRF\", \"Browser client and realtime\").\n//\n// The part every framework adapter shares: the route table, the per-request session, the CSRF\n// guard and the browser-token mint. Each adapter (next.ts, sveltekit.ts, nuxt.ts, astro.ts,\n// react-router.ts) maps its framework's request, cookies and locals onto these functions. Nothing\n// here keeps state between requests: the session cache and the superuser client belong to the\n// app, and the app passes them in the options.\nimport { ClientResponseError } from \"@sdk/ClientResponseError\";\nimport { DEFAULT_COOKIE_KEY, hasSessionCookieName, isHttpLocalUrl } from \"@sdk/ssr/cookies\";\nimport { checkCsrf, createRequestCookieAdapter, createServerClient, handleBrowserTokenRenew, handlePopupHandOff, mintBrowserToken, OAuth2Error, SSR_NO_CACHE_HEADERS, } from \"@sdk/ssr/index\";\nexport const DEFAULT_ROUTES = Object.freeze({\n renew: \"/api/pb/renew\",\n popup: \"/api/pb/popup\",\n oauth2Start: \"/api/pb/oauth2/start\",\n oauth2Callback: \"/api/pb/oauth2/callback\",\n});\n/**\n * Checks the configuration once, when the app creates its adapter. Without superuser\n * credentials the adapter can only serve pages in fallback mode, so it must say so here.\n *\n * @throws {TypeError} without a superuser client and without `readableCookie: true`\n */\nexport function resolveAdapterOptions(options) {\n if (!options.superuser && options.readableCookie !== true) {\n throw new TypeError(\"the adapter needs a superuser client, or readableCookie: true\");\n }\n const routes = {\n renew: options.routes?.renew ?? DEFAULT_ROUTES.renew,\n popup: options.routes?.popup ?? DEFAULT_ROUTES.popup,\n oauth2Start: options.routes?.oauth2Start ?? DEFAULT_ROUTES.oauth2Start,\n oauth2Callback: options.routes?.oauth2Callback ?? DEFAULT_ROUTES.oauth2Callback,\n };\n return { ...options, routes };\n}\n/** The cookie security this request's app uses: the option, else the app origin, else the request. */\nfunction secureFor(request, options) {\n if (options.secure !== undefined) {\n return options.secure;\n }\n if (options.origin !== undefined) {\n return !isHttpLocalUrl(options.origin);\n }\n return undefined;\n}\n/** The app's public origin: the option, or the origin of the request. */\nexport function appOrigin(request, options) {\n return options.origin ?? new URL(request.url).origin;\n}\n/**\n * The policy of one request. `onActive` runs once, when the policy turns on: at once when the\n * request carries a session cookie, otherwise at the first write or mint. For a framework that\n * sets headers before the handler's response is sent (Nitro) it sets them as they become needed.\n */\nexport function noStorePolicy(request, options, onActive) {\n const baseName = options.cookieName ?? DEFAULT_COOKIE_KEY;\n let active = hasSessionCookieName(createRequestCookieAdapter(request).getAll(), baseName);\n const policy = {\n get active() {\n return active;\n },\n markUserData() {\n if (!active) {\n active = true;\n onActive?.();\n }\n },\n apply(response) {\n // a network error (Response.error()) has no status to send and immutable headers: it goes out\n // as it is, as withMutableHeaders returns it\n if (!active || response.type === \"error\") {\n return response;\n }\n const mutable = withMutableHeaders(response);\n setNoStoreHeaders(mutable.headers);\n return mutable;\n },\n };\n if (active) {\n onActive?.();\n }\n return policy;\n}\n/**\n * Sets SSR_NO_CACHE_HEADERS, replacing any the app set: a user's response must not be stored with\n * a public `Cache-Control`. The headers must be mutable: see `withMutableHeaders`.\n */\nexport function setNoStoreHeaders(headers) {\n for (const [name, value] of Object.entries(SSR_NO_CACHE_HEADERS)) {\n headers.set(name, value);\n }\n}\n/** A name no response carries: deleting it changes nothing, and it throws on immutable headers. */\nconst MUTABILITY_PROBE = \"x-iglo-base-probe\";\n/**\n * The response with headers that can be changed. In Node, the runtime the SSR apps run on, the\n * headers of a `fetch()` response and of `Response.redirect()` are immutable, and so the no-cache\n * headers could not be set on them. Such a response (an endpoint that returns a fetched response\n * unchanged, say) is rebuilt with a copy of its headers, body and status. The copy does not keep\n * `url` or `redirected`, which are read-only. Callers send the response this returns.\n *\n * A network error (`Response.error()`, status 0) cannot be copied: `new Response` rejects status 0.\n * It is returned as it is, and so are its headers.\n */\nexport function withMutableHeaders(response) {\n if (response.type === \"error\") {\n return response;\n }\n try {\n response.headers.delete(MUTABILITY_PROBE);\n return response;\n }\n catch {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: new Headers(response.headers),\n });\n }\n}\n/**\n * Resumes the session of one request with `createServerClient`. `cookies` is the framework's\n * cookie access, mapped to `{ getAll, setAll, isSecure }`: writes made by the session check\n * (a refresh, a revoked session) go through its `setAll`. Each write and each minted browser token\n * turns `policy` on, so the adapter sets the no-cache headers on the response it returns.\n */\nexport async function resumeSession(cookies, request, options, policy = noStorePolicy(request, options)) {\n const tracked = {\n ...cookies,\n setAll: cookies.setAll\n ? (toSet, headers) => {\n if (toSet.length > 0) {\n policy.markUserData();\n }\n return cookies.setAll?.(toSet, headers);\n }\n : undefined,\n };\n const server = await createServerClient({\n url: options.url,\n cookies: tracked,\n origin: appOrigin(request, options),\n secure: secureFor(request, options),\n sessionCache: options.sessionCache,\n cookieName: options.cookieName,\n cookieOptions: options.cookieOptions,\n readableCookie: options.readableCookie,\n lang: options.lang,\n clock: options.clock,\n fetch: options.fetch,\n });\n return {\n server,\n browserToken: async () => {\n const token = await mintBrowserToken(server, options.superuser, {\n duration: options.browserTokenDuration,\n });\n if (token) {\n policy.markUserData();\n }\n return token;\n },\n };\n}\n/**\n * The origins the CSRF guard accepts besides the request's own: the app's public origin (when it\n * is configured, since behind a proxy the request's URL can name another origin) and the\n * `allowedOrigins` the app lists.\n */\nfunction allowedOriginsOf(options) {\n const listed = options.csrf === false ? [] : (options.csrf?.allowedOrigins ?? []);\n return options.origin ? [new URL(options.origin).origin, ...listed] : [...listed];\n}\n/**\n * The CSRF guard for a request, before the app's handler runs: `undefined` lets it through, and\n * a response is the ready 403. It only looks at requests that carry the session cookie, so\n * the guard never rejects a sign-in form or a public page (see `checkCsrf`).\n */\nexport function csrfRejection(request, options) {\n if (options.csrf === false) {\n return undefined;\n }\n const result = checkCsrf(request, {\n allowedOrigins: allowedOriginsOf(options),\n cookieName: options.cookieName,\n });\n return result.ok ? undefined : result.response;\n}\n/** A `302` to `location` that carries the cookies and headers queued by `cookies`. */\nexport function redirectWithCookies(location, cookies) {\n const response = new Response(null, {\n status: 302,\n headers: { Location: location, ...SSR_NO_CACHE_HEADERS },\n });\n return cookies.applyToResponse(response);\n}\nfunction jsonError(status, message) {\n return new Response(JSON.stringify({ status, message, data: {} }), {\n status,\n headers: { \"Content-Type\": \"application/json\", ...SSR_NO_CACHE_HEADERS },\n });\n}\n/** The provider name in `{oauth2Start}/{provider}`, or `null` when the path has none. */\nfunction providerOf(pathname, start) {\n const rest = pathname.slice(start.length + 1);\n if (rest === \"\" || rest.includes(\"/\")) {\n return null;\n }\n try {\n return decodeURIComponent(rest);\n }\n catch {\n return null;\n }\n}\n/**\n * The adapter's routes. Returns the route's response, or `undefined` when the request is not for\n * one of them (the app then handles it). The renew and popup routes run their own origin checks.\n */\nexport async function handleRoute(request, options) {\n const { pathname, searchParams } = new URL(request.url);\n const { routes } = options;\n const secure = secureFor(request, options);\n const common = {\n url: options.url,\n sessionCache: options.sessionCache,\n cookieName: options.cookieName,\n cookieOptions: options.cookieOptions,\n readableCookie: options.readableCookie,\n lang: options.lang,\n clock: options.clock,\n fetch: options.fetch,\n secure,\n };\n if (routes.renew !== false && pathname === routes.renew) {\n return handleBrowserTokenRenew(request, {\n ...common,\n superuser: options.superuser,\n duration: options.browserTokenDuration,\n allowedOrigins: allowedOriginsOf(options),\n });\n }\n if (routes.popup !== false && pathname === routes.popup) {\n return handlePopupHandOff(request, {\n ...common,\n allowedOrigins: allowedOriginsOf(options),\n });\n }\n if (routes.oauth2Start !== false && pathname.startsWith(`${routes.oauth2Start}/`)) {\n if (request.method !== \"GET\") {\n return methodNotAllowed(\"GET\");\n }\n const provider = providerOf(pathname, routes.oauth2Start);\n // without a callback route there is no redirect URI to send the provider to\n if (provider === null || routes.oauth2Callback === false) {\n return jsonError(404, \"Not found.\");\n }\n const cookies = createRequestCookieAdapter(request, { secure });\n const origin = appOrigin(request, options);\n const server = await createServerClient({ ...common, origin, cookies });\n try {\n const location = await server.startOAuth2(provider, {\n callbackUrl: new URL(routes.oauth2Callback, origin).href,\n redirectTo: searchParams.get(\"redirectTo\") ?? \"/\",\n });\n return redirectWithCookies(location, cookies);\n }\n catch (err) {\n if (err instanceof OAuth2Error) {\n const status = err.kind === \"unknown-provider\" ? 404 : err.kind === \"unsupported-provider\" ? 400 : 500;\n return jsonError(status, err.message);\n }\n if (err instanceof ClientResponseError) {\n return jsonError(502, \"iglo.base refused the OAuth2 start.\");\n }\n throw err;\n }\n }\n if (routes.oauth2Callback !== false && pathname === routes.oauth2Callback) {\n if (request.method !== \"GET\") {\n return methodNotAllowed(\"GET\");\n }\n const cookies = createRequestCookieAdapter(request, { secure });\n const server = await createServerClient({\n ...common,\n origin: appOrigin(request, options),\n cookies,\n });\n const result = await server.completeOAuth2(request.url);\n const target = new URL(result.redirectTo, appOrigin(request, options));\n if (!result.ok) {\n target.searchParams.set(\"oauth2_error\", result.error instanceof OAuth2Error ? result.error.kind : \"server\");\n }\n // absolute: Next.js parses a middleware's Location header without a base URL\n return redirectWithCookies(target.href, cookies);\n }\n return undefined;\n}\nfunction methodNotAllowed(allow) {\n const response = jsonError(405, \"Method not allowed.\");\n response.headers.set(\"Allow\", allow);\n return response;\n}\n",
6
+ "// docs/design/13-ssr.md (\"Session cookie\", \"Clients\")\nimport { BaseAuthStore } from \"@sdk/stores/BaseAuthStore\";\nimport { cookieParse, cookieSerialize } from \"@sdk/tools/cookie\";\nimport { getTokenPayload } from \"@sdk/tools/jwt\";\nexport const SSR_NO_CACHE_HEADERS = Object.freeze({\n \"Cache-Control\": \"private, no-cache, no-store, must-revalidate, max-age=0\",\n Expires: \"0\",\n Pragma: \"no-cache\",\n});\nexport const DEFAULT_COOKIE_KEY = \"pb_auth\";\n/** The temporary cookie of the OAuth2 redirect flow (docs/design/13-ssr.md, \"OAuth2\"). */\nexport const OAUTH2_COOKIE_KEY = \"pb_oauth2\";\nexport const OAUTH2_COOKIE_MAX_AGE_SEC = 600;\nexport function isLocalhost(hostname) {\n const clean = hostname.replace(/^\\[|\\]$/g, \"\").split(\":\")[0] ?? \"\";\n return clean === \"localhost\" || clean === \"127.0.0.1\" || clean === \"::1\";\n}\nexport function isHttpLocalUrl(urlStr) {\n try {\n const parsed = new URL(urlStr);\n return parsed.protocol === \"http:\" && isLocalhost(parsed.hostname);\n }\n catch {\n return false;\n }\n}\nexport function normalizeCookies(raw) {\n if (!raw) {\n return [];\n }\n if (Array.isArray(raw)) {\n return raw;\n }\n return Object.entries(raw).map(([name, value]) => ({ name, value }));\n}\n/** The name a cookie is written under: `__Host-` prefixed on secure origins, plain otherwise. */\nexport function cookieWireName(baseName, secure) {\n return secure ? `__Host-${baseName}` : baseName;\n}\n/**\n * The cookie the SDK reads, by the one name it is written under for `secure` (see\n * `cookieWireName`). On a secure origin the plain name is never read: a sibling subdomain can set\n * a cookie for the parent domain under any name, but only the host itself can set a `__Host-` one.\n */\nexport function findCookie(cookies, baseName, secure) {\n const name = cookieWireName(baseName, secure);\n return cookies.find((c) => c.name === name);\n}\n/**\n * Whether a session cookie is present under either name. The CSRF guard does not know how the\n * app named its cookie (`secure` may come from the app's settings), so both names count: the\n * guard checks more requests, never fewer. Reading a value is still `findCookie`'s job.\n */\nexport function hasSessionCookieName(cookies, baseName) {\n return cookies.some((c) => c.name === `__Host-${baseName}` || c.name === baseName);\n}\n/**\n * The record that belongs to a token, or null: a record for another user is dropped, since a\n * cookie can be edited by its user. Shared by the server and the browser client.\n */\nexport function recordForToken(token, record) {\n const payload = getTokenPayload(token);\n if (record && payload.id && record.id !== payload.id) {\n return null;\n }\n return record;\n}\nexport function safeDecode(value) {\n try {\n return decodeURIComponent(value);\n }\n catch {\n return value;\n }\n}\n/**\n * Reads the token and user record from a session cookie value. Adapters may hand over\n * the value decoded (the request adapter does) or as sent by the browser (Next.js does),\n * so the raw text is tried first and the percent-decoded text second, each decoded once.\n */\nexport function parseSessionValue(raw) {\n const candidates = raw === safeDecode(raw) ? [raw] : [raw, safeDecode(raw)];\n for (const candidate of candidates) {\n let parsed;\n try {\n parsed = JSON.parse(candidate);\n }\n catch {\n continue;\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return undefined;\n }\n const obj = parsed;\n const token = typeof obj[\"token\"] === \"string\" ? obj[\"token\"] : \"\";\n if (!token) {\n return undefined;\n }\n const record = (obj[\"record\"] ?? obj[\"model\"] ?? null);\n return { token, record };\n }\n return undefined;\n}\nexport function formatSessionCookieValue(wireName, token, record, options) {\n const tempStore = new BaseAuthStore();\n tempStore.save(token, record);\n const cookieStr = tempStore.exportToCookie(options, wireName);\n const parsed = cookieParse(cookieStr);\n return parsed[wireName] ?? \"\";\n}\n/** `cookieOptions.secure` overrides the flag; the default is secure (`__Host-`). */\nexport function isSecureSession(settings) {\n return settings.cookieOptions?.secure ?? settings.secure ?? true;\n}\n/**\n * Serialize options shared by writes and clears. The app's `cookieOptions` come first,\n * then the name rules: `__Host-` requires `Secure`, `Path=/` and no `Domain`.\n */\nfunction serializeBase(settings, secure) {\n const opts = {\n path: \"/\",\n sameSite: \"lax\",\n httpOnly: true,\n ...settings.cookieOptions,\n secure,\n };\n if (settings.readable) {\n // the browser client reads this cookie (no superuser credentials; see 13-ssr.md, \"Browser client\")\n opts.httpOnly = false;\n }\n if (secure) {\n delete opts.domain;\n opts.path = \"/\";\n }\n return opts;\n}\nexport function buildSessionCookieToSet(token, record, options) {\n const secure = isSecureSession(options);\n const wireName = secure\n ? `__Host-${options.cookieName ?? DEFAULT_COOKIE_KEY}`\n : (options.cookieName ?? DEFAULT_COOKIE_KEY);\n const nowSec = options.nowSeconds ?? Math.floor(Date.now() / 1000);\n const exp = options.exp;\n const serializeOpts = {\n ...serializeBase(options, secure),\n maxAge: exp !== undefined ? Math.max(0, exp - nowSec) : undefined,\n expires: exp !== undefined ? new Date(exp * 1000) : undefined,\n };\n return {\n name: wireName,\n value: formatSessionCookieValue(wireName, token, record, serializeOpts),\n options: serializeOpts,\n };\n}\n/**\n * Expiring cookies for a cookie's end, one per name the cookie may have been written under.\n * When `secure`, the plain name is cleared too. It is never read on a secure origin, so this\n * only removes what some other writer or an older build left under it. `optionsFor` gives the\n * serialize options of each name's write.\n */\nfunction buildClearedCookies(baseName, secure, optionsFor) {\n const clear = (name, cookieSecure) => ({\n name,\n value: \"\",\n options: { ...optionsFor(cookieSecure), maxAge: 0, expires: new Date(0) },\n });\n if (secure) {\n return [clear(`__Host-${baseName}`, true), clear(baseName, false)];\n }\n return [clear(baseName, false)];\n}\n/** Expiring cookies for a session end. */\nexport function buildClearedSessionCookies(settings) {\n return buildClearedCookies(settings.cookieName ?? DEFAULT_COOKIE_KEY, isSecureSession(settings), (cookieSecure) => serializeBase(settings, cookieSecure));\n}\n/**\n * The temporary OAuth2 cookie: HttpOnly, `SameSite=Lax` (the provider's redirect is a top-level\n * GET), `Path=/`, `Max-Age` 600 and the same `Secure`/`__Host-` rule as the session cookie.\n * The app's `cookieOptions` are not applied (apart from `secure`), so an app cannot make the\n * cookie unreachable from the provider's redirect.\n */\nexport function buildOAuth2CookieToSet(value, settings) {\n const secure = isSecureSession(settings);\n return {\n name: secure ? `__Host-${OAUTH2_COOKIE_KEY}` : OAUTH2_COOKIE_KEY,\n value,\n options: {\n path: \"/\",\n sameSite: \"lax\",\n httpOnly: true,\n secure,\n maxAge: OAUTH2_COOKIE_MAX_AGE_SEC,\n },\n };\n}\n/** Expiring cookies for the temporary OAuth2 cookie, written with the same name rule. */\nexport function buildClearedOAuth2Cookies(settings) {\n return buildClearedCookies(OAUTH2_COOKIE_KEY, isSecureSession(settings), (cookieSecure) => ({\n path: \"/\",\n sameSite: \"lax\",\n httpOnly: true,\n secure: cookieSecure,\n }));\n}\nexport function createRequestCookieAdapter(request, options) {\n const queuedCookies = [];\n const queuedHeaders = {};\n const reqUrl = new URL(request.url);\n const isLocal = reqUrl.protocol === \"http:\" && isLocalhost(reqUrl.hostname);\n const isSecure = options?.secure ?? !isLocal;\n return {\n isSecure,\n getAll() {\n const cookieHeader = request.headers.get(\"cookie\") || \"\";\n const parsed = cookieParse(cookieHeader);\n return Object.entries(parsed).map(([name, value]) => ({\n name,\n value: typeof value === \"string\" ? value : String(value),\n }));\n },\n setAll(cookiesToSet, headers) {\n for (const c of cookiesToSet) {\n queuedCookies.push(c);\n }\n if (headers) {\n for (const [k, v] of Object.entries(headers)) {\n queuedHeaders[k] = v;\n }\n }\n },\n applyToResponse(response) {\n for (const [k, v] of Object.entries(queuedHeaders)) {\n response.headers.set(k, v);\n }\n for (const c of queuedCookies) {\n const serialized = cookieSerialize(c.name, c.value, c.options);\n response.headers.append(\"Set-Cookie\", serialized);\n }\n return response;\n },\n get queuedCookies() {\n return queuedCookies;\n },\n get queuedHeaders() {\n return queuedHeaders;\n },\n };\n}\n",
7
+ "// docs/design/13-ssr.md (\"Framework adapters\": @iglo.tech/iglo.base/ssr/react-router; \"Session cookie\";\n// \"Session cache, check and refresh\"; \"CSRF\"; \"OAuth2\"; \"Response headers\"). The React Router 7 and\n// Remix adapter: a loader, action or resource route asks for the session of its Web Request, and\n// returns its data with the headers the session wrote. Each route that calls the session, and each\n// route with an ErrorBoundary (at least the root route), also re-exports `sessionHeaders` as its\n// `headers` function, because React Router sends only the Set-Cookie lines of a loader's headers\n// unless the route has that export.\nimport { csrfRejection, handleRoute, noStorePolicy, resolveAdapterOptions, resumeSession, setNoStoreHeaders, } from \"@sdk/ssr/adapter\";\nimport { createRequestCookieAdapter } from \"@sdk/ssr/index\";\n/**\n * The `headers` function of a route that uses the session, and of every route with an\n * `ErrorBoundary` (at least the root route). The route re-exports it:\n * `export { sessionHeaders as headers } from \"@iglo.tech/iglo.base/ssr/react-router\";`\n *\n * React Router builds the headers of a page and of a `.data` response from the route's `headers`\n * export. A route without one copies only the Set-Cookie lines of its loader's and action's\n * headers, so the no-cache headers that `headers()` returns would be lost. This function starts\n * from the parent headers, as React Router does by default, and adds the no-cache headers when the\n * loader, the action or the error response carries them. Nothing is kept between calls.\n *\n * The error headers are the thrown response's headers, passed to the boundary route only. When a\n * session route throws `data(…, { headers: headers() })`, the boundary route (the root, in the\n * default template) renders it and gets those headers as `errorHeaders`, not the session route.\n * A boundary route without this export drops them. On an anonymous request the error headers carry\n * no no-store, so the page stays cacheable.\n */\nexport function sessionHeaders({ loaderHeaders, actionHeaders, parentHeaders, errorHeaders, }) {\n const headers = new Headers(parentHeaders);\n const carriesUserData = [loaderHeaders, actionHeaders, errorHeaders].some((source) => source?.get(\"cache-control\")?.includes(\"no-store\") === true);\n if (carriesUserData) {\n setNoStoreHeaders(headers);\n }\n return headers;\n}\n/**\n * The React Router adapter. The options carry the app's session cache and superuser client, which\n * it creates once; nothing here keeps state between requests.\n *\n * @throws {TypeError} without a superuser client and without `readableCookie: true`\n */\nexport function createReactRouterAuth(options) {\n const resolved = resolveAdapterOptions(options);\n return {\n async session(request) {\n const policy = noStorePolicy(request, resolved);\n const rejected = csrfRejection(request, resolved);\n if (rejected) {\n return { response: policy.apply(rejected) };\n }\n const routed = await handleRoute(request, resolved);\n if (routed) {\n return { response: policy.apply(routed) };\n }\n const cookies = createRequestCookieAdapter(request, { secure: resolved.secure });\n const session = await resumeSession(cookies, request, resolved, policy);\n return {\n session,\n headers: () => {\n // the cookies queued so far, with the no-cache headers when the request carries user data\n const response = cookies.applyToResponse(new Response(null));\n return policy.apply(response).headers;\n },\n };\n },\n };\n}\n"
8
+ ],
9
+ "mappings": "AAOA,oDCNA,8CAGO,IAAM,qBAAuB,OAAO,OAAO,CAC9C,gBAAiB,0DACjB,QAAS,IACT,OAAQ,UACZ,CAAC,EACY,mBAAqB,UAI3B,SAAS,WAAW,CAAC,SAAU,CAClC,IAAM,MAAQ,SAAS,QAAQ,WAAY,EAAE,EAAE,MAAM,GAAG,EAAE,IAAM,GAChE,OAAO,QAAU,aAAe,QAAU,aAAe,QAAU,MAEhE,SAAS,cAAc,CAAC,OAAQ,CACnC,GAAI,CACA,IAAM,OAAS,IAAI,IAAI,MAAM,EAC7B,OAAO,OAAO,WAAa,SAAW,YAAY,OAAO,QAAQ,EAErE,KAAM,CACF,MAAO,IA8BR,SAAS,oBAAoB,CAAC,QAAS,SAAU,CACpD,OAAO,QAAQ,KAAK,CAAC,IAAM,EAAE,OAAS,UAAU,YAAc,EAAE,OAAS,QAAQ,ED7CrF,+JAAgJ,yCACzI,IAAM,eAAiB,OAAO,OAAO,CACxC,MAAO,gBACP,MAAO,gBACP,YAAa,uBACb,eAAgB,yBACpB,CAAC,EAOM,SAAS,qBAAqB,CAAC,QAAS,CAC3C,GAAI,CAAC,QAAQ,WAAa,QAAQ,iBAAmB,GACjD,MAAU,UAAU,+DAA+D,EAEvF,IAAM,OAAS,CACX,MAAO,QAAQ,QAAQ,OAAS,eAAe,MAC/C,MAAO,QAAQ,QAAQ,OAAS,eAAe,MAC/C,YAAa,QAAQ,QAAQ,aAAe,eAAe,YAC3D,eAAgB,QAAQ,QAAQ,gBAAkB,eAAe,cACrE,EACA,MAAO,IAAK,QAAS,MAAO,EAGhC,SAAS,SAAS,CAAC,QAAS,QAAS,CACjC,GAAI,QAAQ,SAAW,OACnB,OAAO,QAAQ,OAEnB,GAAI,QAAQ,SAAW,OACnB,MAAO,CAAC,eAAe,QAAQ,MAAM,EAEzC,OAGG,SAAS,SAAS,CAAC,QAAS,QAAS,CACxC,OAAO,QAAQ,QAAU,IAAI,IAAI,QAAQ,GAAG,EAAE,OAO3C,SAAS,aAAa,CAAC,QAAS,QAAS,SAAU,CACtD,IAAM,SAAW,QAAQ,YAAc,mBACnC,OAAS,qBAAqB,2BAA2B,OAAO,EAAE,OAAO,EAAG,QAAQ,EAClF,OAAS,KACP,MAAM,EAAG,CACT,OAAO,QAEX,YAAY,EAAG,CACX,GAAI,CAAC,OACD,OAAS,GACT,WAAW,GAGnB,KAAK,CAAC,SAAU,CAGZ,GAAI,CAAC,QAAU,SAAS,OAAS,QAC7B,OAAO,SAEX,IAAM,QAAU,mBAAmB,QAAQ,EAE3C,OADA,kBAAkB,QAAQ,OAAO,EAC1B,QAEf,EACA,GAAI,OACA,WAAW,EAEf,OAAO,OAMJ,SAAS,iBAAiB,CAAC,QAAS,CACvC,QAAY,KAAM,SAAU,OAAO,QAAQ,qBAAoB,EAC3D,QAAQ,IAAI,KAAM,KAAK,EAI/B,IAAM,iBAAmB,oBAWlB,SAAS,kBAAkB,CAAC,SAAU,CACzC,GAAI,SAAS,OAAS,QAClB,OAAO,SAEX,GAAI,CAEA,OADA,SAAS,QAAQ,OAAO,gBAAgB,EACjC,SAEX,KAAM,CACF,OAAO,IAAI,SAAS,SAAS,KAAM,CAC/B,OAAQ,SAAS,OACjB,WAAY,SAAS,WACrB,QAAS,IAAI,QAAQ,SAAS,OAAO,CACzC,CAAC,GASF,eAAe,aAAa,CAAC,QAAS,QAAS,QAAS,OAAS,cAAc,QAAS,OAAO,EAAG,CACrG,IAAM,QAAU,IACT,QACH,OAAQ,QAAQ,OACV,CAAC,MAAO,UAAY,CAClB,GAAI,MAAM,OAAS,EACf,OAAO,aAAa,EAExB,OAAO,QAAQ,SAAS,MAAO,OAAO,GAExC,MACV,EACM,OAAS,MAAM,mBAAmB,CACpC,IAAK,QAAQ,IACb,QAAS,QACT,OAAQ,UAAU,QAAS,OAAO,EAClC,OAAQ,UAAU,QAAS,OAAO,EAClC,aAAc,QAAQ,aACtB,WAAY,QAAQ,WACpB,cAAe,QAAQ,cACvB,eAAgB,QAAQ,eACxB,KAAM,QAAQ,KACd,MAAO,QAAQ,MACf,MAAO,QAAQ,KACnB,CAAC,EACD,MAAO,CACH,OACA,aAAc,SAAY,CACtB,IAAM,MAAQ,MAAM,iBAAiB,OAAQ,QAAQ,UAAW,CAC5D,SAAU,QAAQ,oBACtB,CAAC,EACD,GAAI,MACA,OAAO,aAAa,EAExB,OAAO,MAEf,EAOJ,SAAS,gBAAgB,CAAC,QAAS,CAC/B,IAAM,OAAS,QAAQ,OAAS,GAAQ,CAAC,EAAK,QAAQ,MAAM,gBAAkB,CAAC,EAC/E,OAAO,QAAQ,OAAS,CAAC,IAAI,IAAI,QAAQ,MAAM,EAAE,OAAQ,GAAG,MAAM,EAAI,CAAC,GAAG,MAAM,EAO7E,SAAS,aAAa,CAAC,QAAS,QAAS,CAC5C,GAAI,QAAQ,OAAS,GACjB,OAEJ,IAAM,OAAS,UAAU,QAAS,CAC9B,eAAgB,iBAAiB,OAAO,EACxC,WAAY,QAAQ,UACxB,CAAC,EACD,OAAO,OAAO,GAAK,OAAY,OAAO,SAGnC,SAAS,mBAAmB,CAAC,SAAU,QAAS,CACnD,IAAM,SAAW,IAAI,SAAS,KAAM,CAChC,OAAQ,IACR,QAAS,CAAE,SAAU,YAAa,qBAAqB,CAC3D,CAAC,EACD,OAAO,QAAQ,gBAAgB,QAAQ,EAE3C,SAAS,SAAS,CAAC,OAAQ,QAAS,CAChC,OAAO,IAAI,SAAS,KAAK,UAAU,CAAE,OAAQ,QAAS,KAAM,CAAC,CAAE,CAAC,EAAG,CAC/D,OACA,QAAS,CAAE,eAAgB,sBAAuB,qBAAqB,CAC3E,CAAC,EAGL,SAAS,UAAU,CAAC,SAAU,MAAO,CACjC,IAAM,KAAO,SAAS,MAAM,MAAM,OAAS,CAAC,EAC5C,GAAI,OAAS,IAAM,KAAK,SAAS,GAAG,EAChC,OAAO,KAEX,GAAI,CACA,OAAO,mBAAmB,IAAI,EAElC,KAAM,CACF,OAAO,MAOR,eAAe,WAAW,CAAC,QAAS,QAAS,CAChD,IAAQ,SAAU,cAAiB,IAAI,IAAI,QAAQ,GAAG,GAC9C,QAAW,QACb,OAAS,UAAU,QAAS,OAAO,EACnC,OAAS,CACX,IAAK,QAAQ,IACb,aAAc,QAAQ,aACtB,WAAY,QAAQ,WACpB,cAAe,QAAQ,cACvB,eAAgB,QAAQ,eACxB,KAAM,QAAQ,KACd,MAAO,QAAQ,MACf,MAAO,QAAQ,MACf,MACJ,EACA,GAAI,OAAO,QAAU,IAAS,WAAa,OAAO,MAC9C,OAAO,wBAAwB,QAAS,IACjC,OACH,UAAW,QAAQ,UACnB,SAAU,QAAQ,qBAClB,eAAgB,iBAAiB,OAAO,CAC5C,CAAC,EAEL,GAAI,OAAO,QAAU,IAAS,WAAa,OAAO,MAC9C,OAAO,mBAAmB,QAAS,IAC5B,OACH,eAAgB,iBAAiB,OAAO,CAC5C,CAAC,EAEL,GAAI,OAAO,cAAgB,IAAS,SAAS,WAAW,GAAG,OAAO,cAAc,EAAG,CAC/E,GAAI,QAAQ,SAAW,MACnB,OAAO,iBAAiB,KAAK,EAEjC,IAAM,SAAW,WAAW,SAAU,OAAO,WAAW,EAExD,GAAI,WAAa,MAAQ,OAAO,iBAAmB,GAC/C,OAAO,UAAU,IAAK,YAAY,EAEtC,IAAM,QAAU,2BAA2B,QAAS,CAAE,MAAO,CAAC,EACxD,OAAS,UAAU,QAAS,OAAO,EACnC,OAAS,MAAM,mBAAmB,IAAK,OAAQ,OAAQ,OAAQ,CAAC,EACtE,GAAI,CACA,IAAM,SAAW,MAAM,OAAO,YAAY,SAAU,CAChD,YAAa,IAAI,IAAI,OAAO,eAAgB,MAAM,EAAE,KACpD,WAAY,aAAa,IAAI,YAAY,GAAK,GAClD,CAAC,EACD,OAAO,oBAAoB,SAAU,OAAO,EAEhD,MAAO,IAAK,CACR,GAAI,eAAe,YAAa,CAC5B,IAAM,OAAS,IAAI,OAAS,mBAAqB,IAAM,IAAI,OAAS,uBAAyB,IAAM,IACnG,OAAO,UAAU,OAAQ,IAAI,OAAO,EAExC,GAAI,eAAe,oBACf,OAAO,UAAU,IAAK,qCAAqC,EAE/D,MAAM,KAGd,GAAI,OAAO,iBAAmB,IAAS,WAAa,OAAO,eAAgB,CACvE,GAAI,QAAQ,SAAW,MACnB,OAAO,iBAAiB,KAAK,EAEjC,IAAM,QAAU,2BAA2B,QAAS,CAAE,MAAO,CAAC,EAMxD,OAAS,MALA,MAAM,mBAAmB,IACjC,OACH,OAAQ,UAAU,QAAS,OAAO,EAClC,OACJ,CAAC,GAC2B,eAAe,QAAQ,GAAG,EAChD,OAAS,IAAI,IAAI,OAAO,WAAY,UAAU,QAAS,OAAO,CAAC,EACrE,GAAI,CAAC,OAAO,GACR,OAAO,aAAa,IAAI,eAAgB,OAAO,iBAAiB,YAAc,OAAO,MAAM,KAAO,QAAQ,EAG9G,OAAO,oBAAoB,OAAO,KAAM,OAAO,EAEnD,OAEJ,SAAS,gBAAgB,CAAC,MAAO,CAC7B,IAAM,SAAW,UAAU,IAAK,qBAAqB,EAErD,OADA,SAAS,QAAQ,IAAI,QAAS,KAAK,EAC5B,SEpSX,qCAAS,+CAkBF,SAAS,cAAc,EAAG,cAAe,cAAe,cAAe,cAAiB,CAC3F,IAAM,QAAU,IAAI,QAAQ,aAAa,EAEzC,GADwB,CAAC,cAAe,cAAe,YAAY,EAAE,KAAK,CAAC,SAAW,QAAQ,IAAI,eAAe,GAAG,SAAS,UAAU,IAAM,EAAI,EAE7I,kBAAkB,OAAO,EAE7B,OAAO,QAQJ,SAAS,qBAAqB,CAAC,QAAS,CAC3C,IAAM,SAAW,sBAAsB,OAAO,EAC9C,MAAO,OACG,OAAO,CAAC,QAAS,CACnB,IAAM,OAAS,cAAc,QAAS,QAAQ,EACxC,SAAW,cAAc,QAAS,QAAQ,EAChD,GAAI,SACA,MAAO,CAAE,SAAU,OAAO,MAAM,QAAQ,CAAE,EAE9C,IAAM,OAAS,MAAM,YAAY,QAAS,QAAQ,EAClD,GAAI,OACA,MAAO,CAAE,SAAU,OAAO,MAAM,MAAM,CAAE,EAE5C,IAAM,QAAU,4BAA2B,QAAS,CAAE,OAAQ,SAAS,MAAO,CAAC,EAE/E,MAAO,CACH,QAFY,MAAM,cAAc,QAAS,QAAS,SAAU,MAAM,EAGlE,QAAS,IAAM,CAEX,IAAM,SAAW,QAAQ,gBAAgB,IAAI,SAAS,IAAI,CAAC,EAC3D,OAAO,OAAO,MAAM,QAAQ,EAAE,QAEtC,EAER",
10
+ "debugId": "F7C64D06518C224864756E2164756E21",
11
+ "names": []
12
+ }
@@ -0,0 +1,4 @@
1
+ import{createRequestCookieAdapter as createRequestCookieAdapter2}from"./ssr.es.mjs";import{ClientResponseError}from"./iglo-base.es.mjs";import{BaseAuthStore}from"./iglo-base.es.mjs";var SSR_NO_CACHE_HEADERS=Object.freeze({"Cache-Control":"private, no-cache, no-store, must-revalidate, max-age=0",Expires:"0",Pragma:"no-cache"}),DEFAULT_COOKIE_KEY="pb_auth";function isLocalhost(hostname){let clean=hostname.replace(/^\[|\]$/g,"").split(":")[0]??"";return clean==="localhost"||clean==="127.0.0.1"||clean==="::1"}function isHttpLocalUrl(urlStr){try{let parsed=new URL(urlStr);return parsed.protocol==="http:"&&isLocalhost(parsed.hostname)}catch{return!1}}function hasSessionCookieName(cookies,baseName){return cookies.some((c)=>c.name===`__Host-${baseName}`||c.name===baseName)}import{checkCsrf,createRequestCookieAdapter,createServerClient,handleBrowserTokenRenew,handlePopupHandOff,mintBrowserToken,OAuth2Error,SSR_NO_CACHE_HEADERS as SSR_NO_CACHE_HEADERS2}from"./ssr.es.mjs";var DEFAULT_ROUTES=Object.freeze({renew:"/api/pb/renew",popup:"/api/pb/popup",oauth2Start:"/api/pb/oauth2/start",oauth2Callback:"/api/pb/oauth2/callback"});function resolveAdapterOptions(options){if(!options.superuser&&options.readableCookie!==!0)throw TypeError("the adapter needs a superuser client, or readableCookie: true");let routes={renew:options.routes?.renew??DEFAULT_ROUTES.renew,popup:options.routes?.popup??DEFAULT_ROUTES.popup,oauth2Start:options.routes?.oauth2Start??DEFAULT_ROUTES.oauth2Start,oauth2Callback:options.routes?.oauth2Callback??DEFAULT_ROUTES.oauth2Callback};return{...options,routes}}function secureFor(request,options){if(options.secure!==void 0)return options.secure;if(options.origin!==void 0)return!isHttpLocalUrl(options.origin);return}function appOrigin(request,options){return options.origin??new URL(request.url).origin}function noStorePolicy(request,options,onActive){let baseName=options.cookieName??DEFAULT_COOKIE_KEY,active=hasSessionCookieName(createRequestCookieAdapter(request).getAll(),baseName),policy={get active(){return active},markUserData(){if(!active)active=!0,onActive?.()},apply(response){if(!active||response.type==="error")return response;let mutable=withMutableHeaders(response);return setNoStoreHeaders(mutable.headers),mutable}};if(active)onActive?.();return policy}function setNoStoreHeaders(headers){for(let[name,value]of Object.entries(SSR_NO_CACHE_HEADERS2))headers.set(name,value)}var MUTABILITY_PROBE="x-iglo-base-probe";function withMutableHeaders(response){if(response.type==="error")return response;try{return response.headers.delete(MUTABILITY_PROBE),response}catch{return new Response(response.body,{status:response.status,statusText:response.statusText,headers:new Headers(response.headers)})}}async function resumeSession(cookies,request,options,policy=noStorePolicy(request,options)){let tracked={...cookies,setAll:cookies.setAll?(toSet,headers)=>{if(toSet.length>0)policy.markUserData();return cookies.setAll?.(toSet,headers)}:void 0},server=await createServerClient({url:options.url,cookies:tracked,origin:appOrigin(request,options),secure:secureFor(request,options),sessionCache:options.sessionCache,cookieName:options.cookieName,cookieOptions:options.cookieOptions,readableCookie:options.readableCookie,lang:options.lang,clock:options.clock,fetch:options.fetch});return{server,browserToken:async()=>{let token=await mintBrowserToken(server,options.superuser,{duration:options.browserTokenDuration});if(token)policy.markUserData();return token}}}function allowedOriginsOf(options){let listed=options.csrf===!1?[]:options.csrf?.allowedOrigins??[];return options.origin?[new URL(options.origin).origin,...listed]:[...listed]}function csrfRejection(request,options){if(options.csrf===!1)return;let result=checkCsrf(request,{allowedOrigins:allowedOriginsOf(options),cookieName:options.cookieName});return result.ok?void 0:result.response}function redirectWithCookies(location,cookies){let response=new Response(null,{status:302,headers:{Location:location,...SSR_NO_CACHE_HEADERS2}});return cookies.applyToResponse(response)}function jsonError(status,message){return new Response(JSON.stringify({status,message,data:{}}),{status,headers:{"Content-Type":"application/json",...SSR_NO_CACHE_HEADERS2}})}function providerOf(pathname,start){let rest=pathname.slice(start.length+1);if(rest===""||rest.includes("/"))return null;try{return decodeURIComponent(rest)}catch{return null}}async function handleRoute(request,options){let{pathname,searchParams}=new URL(request.url),{routes}=options,secure=secureFor(request,options),common={url:options.url,sessionCache:options.sessionCache,cookieName:options.cookieName,cookieOptions:options.cookieOptions,readableCookie:options.readableCookie,lang:options.lang,clock:options.clock,fetch:options.fetch,secure};if(routes.renew!==!1&&pathname===routes.renew)return handleBrowserTokenRenew(request,{...common,superuser:options.superuser,duration:options.browserTokenDuration,allowedOrigins:allowedOriginsOf(options)});if(routes.popup!==!1&&pathname===routes.popup)return handlePopupHandOff(request,{...common,allowedOrigins:allowedOriginsOf(options)});if(routes.oauth2Start!==!1&&pathname.startsWith(`${routes.oauth2Start}/`)){if(request.method!=="GET")return methodNotAllowed("GET");let provider=providerOf(pathname,routes.oauth2Start);if(provider===null||routes.oauth2Callback===!1)return jsonError(404,"Not found.");let cookies=createRequestCookieAdapter(request,{secure}),origin=appOrigin(request,options),server=await createServerClient({...common,origin,cookies});try{let location=await server.startOAuth2(provider,{callbackUrl:new URL(routes.oauth2Callback,origin).href,redirectTo:searchParams.get("redirectTo")??"/"});return redirectWithCookies(location,cookies)}catch(err){if(err instanceof OAuth2Error){let status=err.kind==="unknown-provider"?404:err.kind==="unsupported-provider"?400:500;return jsonError(status,err.message)}if(err instanceof ClientResponseError)return jsonError(502,"iglo.base refused the OAuth2 start.");throw err}}if(routes.oauth2Callback!==!1&&pathname===routes.oauth2Callback){if(request.method!=="GET")return methodNotAllowed("GET");let cookies=createRequestCookieAdapter(request,{secure}),result=await(await createServerClient({...common,origin:appOrigin(request,options),cookies})).completeOAuth2(request.url),target=new URL(result.redirectTo,appOrigin(request,options));if(!result.ok)target.searchParams.set("oauth2_error",result.error instanceof OAuth2Error?result.error.kind:"server");return redirectWithCookies(target.href,cookies)}return}function methodNotAllowed(allow){let response=jsonError(405,"Method not allowed.");return response.headers.set("Allow",allow),response}function sameSiteOf(value){if(typeof value==="boolean")return value;if(value==="lax"||value==="strict"||value==="none")return value;return}function svelteKitOptions(options){return{path:options?.path??"/",httpOnly:options?.httpOnly,sameSite:sameSiteOf(options?.sameSite),secure:options?.secure,maxAge:options?.maxAge,expires:options?.expires,domain:options?.domain}}function createSvelteKitAuth(options){let resolved=resolveAdapterOptions(options);return{handle:async({event,resolve})=>{let request=event.request,policy=noStorePolicy(request,resolved),denied=csrfRejection(request,resolved);if(denied)return policy.apply(denied);let routed=await handleRoute(request,resolved);if(routed)return policy.apply(routed);let cookies={getAll:()=>event.cookies.getAll(),isSecure:createRequestCookieAdapter2(request).isSecure,setAll:(toSet)=>{for(let c of toSet)event.cookies.set(c.name,c.value,svelteKitOptions(c.options))}},session=await resumeSession(cookies,request,resolved,policy);return Object.assign(event.locals,{pb:session.server,pbBrowserToken:session.browserToken}),policy.apply(await resolve(event))}}}export{createSvelteKitAuth};
2
+
3
+ //# debugId=8D0BCD03813113BD64756E2164756E21
4
+ //# sourceMappingURL=ssr-sveltekit.es.mjs.map
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [".js/src/ssr/sveltekit.js", ".js/src/ssr/adapter.js", ".js/src/ssr/cookies.js"],
4
+ "sourcesContent": [
5
+ "import { createRequestCookieAdapter, } from \"@sdk/ssr/index\";\nimport { csrfRejection, handleRoute, noStorePolicy, resolveAdapterOptions, resumeSession, } from \"@sdk/ssr/adapter\";\nfunction sameSiteOf(value) {\n if (typeof value === \"boolean\") {\n return value;\n }\n if (value === \"lax\" || value === \"strict\" || value === \"none\") {\n return value;\n }\n return undefined;\n}\n/** The serialize options of a core cookie write, in SvelteKit's `cookies.set` shape. */\nfunction svelteKitOptions(options) {\n return {\n path: options?.path ?? \"/\",\n httpOnly: options?.httpOnly,\n sameSite: sameSiteOf(options?.sameSite),\n secure: options?.secure,\n maxAge: options?.maxAge,\n expires: options?.expires,\n domain: options?.domain,\n };\n}\n/**\n * Creates the SvelteKit auth. Throws a `TypeError` without a superuser client and without\n * `readableCookie: true` (see `resolveAdapterOptions`).\n */\nexport function createSvelteKitAuth(options) {\n const resolved = resolveAdapterOptions(options);\n return {\n handle: async ({ event, resolve }) => {\n const request = event.request;\n // the no-cache headers go on every response of this request once it carries user data\n const policy = noStorePolicy(request, resolved);\n const denied = csrfRejection(request, resolved);\n if (denied) {\n return policy.apply(denied);\n }\n // the routes run before the session is resumed: they make their own client, so a\n // request answered by a route is not refreshed twice\n const routed = await handleRoute(request, resolved);\n if (routed) {\n return policy.apply(routed);\n }\n const cookies = {\n getAll: () => event.cookies.getAll(),\n isSecure: createRequestCookieAdapter(request).isSecure,\n setAll: (toSet) => {\n for (const c of toSet) {\n event.cookies.set(c.name, c.value, svelteKitOptions(c.options));\n }\n },\n };\n const session = await resumeSession(cookies, request, resolved, policy);\n Object.assign(event.locals, {\n pb: session.server,\n pbBrowserToken: session.browserToken,\n });\n // read after resolve: the app's loads and actions may write cookies or mint a token\n return policy.apply(await resolve(event));\n },\n };\n}\n",
6
+ "// docs/design/13-ssr.md (\"Framework adapters\", \"OAuth2\", \"CSRF\", \"Browser client and realtime\").\n//\n// The part every framework adapter shares: the route table, the per-request session, the CSRF\n// guard and the browser-token mint. Each adapter (next.ts, sveltekit.ts, nuxt.ts, astro.ts,\n// react-router.ts) maps its framework's request, cookies and locals onto these functions. Nothing\n// here keeps state between requests: the session cache and the superuser client belong to the\n// app, and the app passes them in the options.\nimport { ClientResponseError } from \"@sdk/ClientResponseError\";\nimport { DEFAULT_COOKIE_KEY, hasSessionCookieName, isHttpLocalUrl } from \"@sdk/ssr/cookies\";\nimport { checkCsrf, createRequestCookieAdapter, createServerClient, handleBrowserTokenRenew, handlePopupHandOff, mintBrowserToken, OAuth2Error, SSR_NO_CACHE_HEADERS, } from \"@sdk/ssr/index\";\nexport const DEFAULT_ROUTES = Object.freeze({\n renew: \"/api/pb/renew\",\n popup: \"/api/pb/popup\",\n oauth2Start: \"/api/pb/oauth2/start\",\n oauth2Callback: \"/api/pb/oauth2/callback\",\n});\n/**\n * Checks the configuration once, when the app creates its adapter. Without superuser\n * credentials the adapter can only serve pages in fallback mode, so it must say so here.\n *\n * @throws {TypeError} without a superuser client and without `readableCookie: true`\n */\nexport function resolveAdapterOptions(options) {\n if (!options.superuser && options.readableCookie !== true) {\n throw new TypeError(\"the adapter needs a superuser client, or readableCookie: true\");\n }\n const routes = {\n renew: options.routes?.renew ?? DEFAULT_ROUTES.renew,\n popup: options.routes?.popup ?? DEFAULT_ROUTES.popup,\n oauth2Start: options.routes?.oauth2Start ?? DEFAULT_ROUTES.oauth2Start,\n oauth2Callback: options.routes?.oauth2Callback ?? DEFAULT_ROUTES.oauth2Callback,\n };\n return { ...options, routes };\n}\n/** The cookie security this request's app uses: the option, else the app origin, else the request. */\nfunction secureFor(request, options) {\n if (options.secure !== undefined) {\n return options.secure;\n }\n if (options.origin !== undefined) {\n return !isHttpLocalUrl(options.origin);\n }\n return undefined;\n}\n/** The app's public origin: the option, or the origin of the request. */\nexport function appOrigin(request, options) {\n return options.origin ?? new URL(request.url).origin;\n}\n/**\n * The policy of one request. `onActive` runs once, when the policy turns on: at once when the\n * request carries a session cookie, otherwise at the first write or mint. For a framework that\n * sets headers before the handler's response is sent (Nitro) it sets them as they become needed.\n */\nexport function noStorePolicy(request, options, onActive) {\n const baseName = options.cookieName ?? DEFAULT_COOKIE_KEY;\n let active = hasSessionCookieName(createRequestCookieAdapter(request).getAll(), baseName);\n const policy = {\n get active() {\n return active;\n },\n markUserData() {\n if (!active) {\n active = true;\n onActive?.();\n }\n },\n apply(response) {\n // a network error (Response.error()) has no status to send and immutable headers: it goes out\n // as it is, as withMutableHeaders returns it\n if (!active || response.type === \"error\") {\n return response;\n }\n const mutable = withMutableHeaders(response);\n setNoStoreHeaders(mutable.headers);\n return mutable;\n },\n };\n if (active) {\n onActive?.();\n }\n return policy;\n}\n/**\n * Sets SSR_NO_CACHE_HEADERS, replacing any the app set: a user's response must not be stored with\n * a public `Cache-Control`. The headers must be mutable: see `withMutableHeaders`.\n */\nexport function setNoStoreHeaders(headers) {\n for (const [name, value] of Object.entries(SSR_NO_CACHE_HEADERS)) {\n headers.set(name, value);\n }\n}\n/** A name no response carries: deleting it changes nothing, and it throws on immutable headers. */\nconst MUTABILITY_PROBE = \"x-iglo-base-probe\";\n/**\n * The response with headers that can be changed. In Node, the runtime the SSR apps run on, the\n * headers of a `fetch()` response and of `Response.redirect()` are immutable, and so the no-cache\n * headers could not be set on them. Such a response (an endpoint that returns a fetched response\n * unchanged, say) is rebuilt with a copy of its headers, body and status. The copy does not keep\n * `url` or `redirected`, which are read-only. Callers send the response this returns.\n *\n * A network error (`Response.error()`, status 0) cannot be copied: `new Response` rejects status 0.\n * It is returned as it is, and so are its headers.\n */\nexport function withMutableHeaders(response) {\n if (response.type === \"error\") {\n return response;\n }\n try {\n response.headers.delete(MUTABILITY_PROBE);\n return response;\n }\n catch {\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: new Headers(response.headers),\n });\n }\n}\n/**\n * Resumes the session of one request with `createServerClient`. `cookies` is the framework's\n * cookie access, mapped to `{ getAll, setAll, isSecure }`: writes made by the session check\n * (a refresh, a revoked session) go through its `setAll`. Each write and each minted browser token\n * turns `policy` on, so the adapter sets the no-cache headers on the response it returns.\n */\nexport async function resumeSession(cookies, request, options, policy = noStorePolicy(request, options)) {\n const tracked = {\n ...cookies,\n setAll: cookies.setAll\n ? (toSet, headers) => {\n if (toSet.length > 0) {\n policy.markUserData();\n }\n return cookies.setAll?.(toSet, headers);\n }\n : undefined,\n };\n const server = await createServerClient({\n url: options.url,\n cookies: tracked,\n origin: appOrigin(request, options),\n secure: secureFor(request, options),\n sessionCache: options.sessionCache,\n cookieName: options.cookieName,\n cookieOptions: options.cookieOptions,\n readableCookie: options.readableCookie,\n lang: options.lang,\n clock: options.clock,\n fetch: options.fetch,\n });\n return {\n server,\n browserToken: async () => {\n const token = await mintBrowserToken(server, options.superuser, {\n duration: options.browserTokenDuration,\n });\n if (token) {\n policy.markUserData();\n }\n return token;\n },\n };\n}\n/**\n * The origins the CSRF guard accepts besides the request's own: the app's public origin (when it\n * is configured, since behind a proxy the request's URL can name another origin) and the\n * `allowedOrigins` the app lists.\n */\nfunction allowedOriginsOf(options) {\n const listed = options.csrf === false ? [] : (options.csrf?.allowedOrigins ?? []);\n return options.origin ? [new URL(options.origin).origin, ...listed] : [...listed];\n}\n/**\n * The CSRF guard for a request, before the app's handler runs: `undefined` lets it through, and\n * a response is the ready 403. It only looks at requests that carry the session cookie, so\n * the guard never rejects a sign-in form or a public page (see `checkCsrf`).\n */\nexport function csrfRejection(request, options) {\n if (options.csrf === false) {\n return undefined;\n }\n const result = checkCsrf(request, {\n allowedOrigins: allowedOriginsOf(options),\n cookieName: options.cookieName,\n });\n return result.ok ? undefined : result.response;\n}\n/** A `302` to `location` that carries the cookies and headers queued by `cookies`. */\nexport function redirectWithCookies(location, cookies) {\n const response = new Response(null, {\n status: 302,\n headers: { Location: location, ...SSR_NO_CACHE_HEADERS },\n });\n return cookies.applyToResponse(response);\n}\nfunction jsonError(status, message) {\n return new Response(JSON.stringify({ status, message, data: {} }), {\n status,\n headers: { \"Content-Type\": \"application/json\", ...SSR_NO_CACHE_HEADERS },\n });\n}\n/** The provider name in `{oauth2Start}/{provider}`, or `null` when the path has none. */\nfunction providerOf(pathname, start) {\n const rest = pathname.slice(start.length + 1);\n if (rest === \"\" || rest.includes(\"/\")) {\n return null;\n }\n try {\n return decodeURIComponent(rest);\n }\n catch {\n return null;\n }\n}\n/**\n * The adapter's routes. Returns the route's response, or `undefined` when the request is not for\n * one of them (the app then handles it). The renew and popup routes run their own origin checks.\n */\nexport async function handleRoute(request, options) {\n const { pathname, searchParams } = new URL(request.url);\n const { routes } = options;\n const secure = secureFor(request, options);\n const common = {\n url: options.url,\n sessionCache: options.sessionCache,\n cookieName: options.cookieName,\n cookieOptions: options.cookieOptions,\n readableCookie: options.readableCookie,\n lang: options.lang,\n clock: options.clock,\n fetch: options.fetch,\n secure,\n };\n if (routes.renew !== false && pathname === routes.renew) {\n return handleBrowserTokenRenew(request, {\n ...common,\n superuser: options.superuser,\n duration: options.browserTokenDuration,\n allowedOrigins: allowedOriginsOf(options),\n });\n }\n if (routes.popup !== false && pathname === routes.popup) {\n return handlePopupHandOff(request, {\n ...common,\n allowedOrigins: allowedOriginsOf(options),\n });\n }\n if (routes.oauth2Start !== false && pathname.startsWith(`${routes.oauth2Start}/`)) {\n if (request.method !== \"GET\") {\n return methodNotAllowed(\"GET\");\n }\n const provider = providerOf(pathname, routes.oauth2Start);\n // without a callback route there is no redirect URI to send the provider to\n if (provider === null || routes.oauth2Callback === false) {\n return jsonError(404, \"Not found.\");\n }\n const cookies = createRequestCookieAdapter(request, { secure });\n const origin = appOrigin(request, options);\n const server = await createServerClient({ ...common, origin, cookies });\n try {\n const location = await server.startOAuth2(provider, {\n callbackUrl: new URL(routes.oauth2Callback, origin).href,\n redirectTo: searchParams.get(\"redirectTo\") ?? \"/\",\n });\n return redirectWithCookies(location, cookies);\n }\n catch (err) {\n if (err instanceof OAuth2Error) {\n const status = err.kind === \"unknown-provider\" ? 404 : err.kind === \"unsupported-provider\" ? 400 : 500;\n return jsonError(status, err.message);\n }\n if (err instanceof ClientResponseError) {\n return jsonError(502, \"iglo.base refused the OAuth2 start.\");\n }\n throw err;\n }\n }\n if (routes.oauth2Callback !== false && pathname === routes.oauth2Callback) {\n if (request.method !== \"GET\") {\n return methodNotAllowed(\"GET\");\n }\n const cookies = createRequestCookieAdapter(request, { secure });\n const server = await createServerClient({\n ...common,\n origin: appOrigin(request, options),\n cookies,\n });\n const result = await server.completeOAuth2(request.url);\n const target = new URL(result.redirectTo, appOrigin(request, options));\n if (!result.ok) {\n target.searchParams.set(\"oauth2_error\", result.error instanceof OAuth2Error ? result.error.kind : \"server\");\n }\n // absolute: Next.js parses a middleware's Location header without a base URL\n return redirectWithCookies(target.href, cookies);\n }\n return undefined;\n}\nfunction methodNotAllowed(allow) {\n const response = jsonError(405, \"Method not allowed.\");\n response.headers.set(\"Allow\", allow);\n return response;\n}\n",
7
+ "// docs/design/13-ssr.md (\"Session cookie\", \"Clients\")\nimport { BaseAuthStore } from \"@sdk/stores/BaseAuthStore\";\nimport { cookieParse, cookieSerialize } from \"@sdk/tools/cookie\";\nimport { getTokenPayload } from \"@sdk/tools/jwt\";\nexport const SSR_NO_CACHE_HEADERS = Object.freeze({\n \"Cache-Control\": \"private, no-cache, no-store, must-revalidate, max-age=0\",\n Expires: \"0\",\n Pragma: \"no-cache\",\n});\nexport const DEFAULT_COOKIE_KEY = \"pb_auth\";\n/** The temporary cookie of the OAuth2 redirect flow (docs/design/13-ssr.md, \"OAuth2\"). */\nexport const OAUTH2_COOKIE_KEY = \"pb_oauth2\";\nexport const OAUTH2_COOKIE_MAX_AGE_SEC = 600;\nexport function isLocalhost(hostname) {\n const clean = hostname.replace(/^\\[|\\]$/g, \"\").split(\":\")[0] ?? \"\";\n return clean === \"localhost\" || clean === \"127.0.0.1\" || clean === \"::1\";\n}\nexport function isHttpLocalUrl(urlStr) {\n try {\n const parsed = new URL(urlStr);\n return parsed.protocol === \"http:\" && isLocalhost(parsed.hostname);\n }\n catch {\n return false;\n }\n}\nexport function normalizeCookies(raw) {\n if (!raw) {\n return [];\n }\n if (Array.isArray(raw)) {\n return raw;\n }\n return Object.entries(raw).map(([name, value]) => ({ name, value }));\n}\n/** The name a cookie is written under: `__Host-` prefixed on secure origins, plain otherwise. */\nexport function cookieWireName(baseName, secure) {\n return secure ? `__Host-${baseName}` : baseName;\n}\n/**\n * The cookie the SDK reads, by the one name it is written under for `secure` (see\n * `cookieWireName`). On a secure origin the plain name is never read: a sibling subdomain can set\n * a cookie for the parent domain under any name, but only the host itself can set a `__Host-` one.\n */\nexport function findCookie(cookies, baseName, secure) {\n const name = cookieWireName(baseName, secure);\n return cookies.find((c) => c.name === name);\n}\n/**\n * Whether a session cookie is present under either name. The CSRF guard does not know how the\n * app named its cookie (`secure` may come from the app's settings), so both names count: the\n * guard checks more requests, never fewer. Reading a value is still `findCookie`'s job.\n */\nexport function hasSessionCookieName(cookies, baseName) {\n return cookies.some((c) => c.name === `__Host-${baseName}` || c.name === baseName);\n}\n/**\n * The record that belongs to a token, or null: a record for another user is dropped, since a\n * cookie can be edited by its user. Shared by the server and the browser client.\n */\nexport function recordForToken(token, record) {\n const payload = getTokenPayload(token);\n if (record && payload.id && record.id !== payload.id) {\n return null;\n }\n return record;\n}\nexport function safeDecode(value) {\n try {\n return decodeURIComponent(value);\n }\n catch {\n return value;\n }\n}\n/**\n * Reads the token and user record from a session cookie value. Adapters may hand over\n * the value decoded (the request adapter does) or as sent by the browser (Next.js does),\n * so the raw text is tried first and the percent-decoded text second, each decoded once.\n */\nexport function parseSessionValue(raw) {\n const candidates = raw === safeDecode(raw) ? [raw] : [raw, safeDecode(raw)];\n for (const candidate of candidates) {\n let parsed;\n try {\n parsed = JSON.parse(candidate);\n }\n catch {\n continue;\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return undefined;\n }\n const obj = parsed;\n const token = typeof obj[\"token\"] === \"string\" ? obj[\"token\"] : \"\";\n if (!token) {\n return undefined;\n }\n const record = (obj[\"record\"] ?? obj[\"model\"] ?? null);\n return { token, record };\n }\n return undefined;\n}\nexport function formatSessionCookieValue(wireName, token, record, options) {\n const tempStore = new BaseAuthStore();\n tempStore.save(token, record);\n const cookieStr = tempStore.exportToCookie(options, wireName);\n const parsed = cookieParse(cookieStr);\n return parsed[wireName] ?? \"\";\n}\n/** `cookieOptions.secure` overrides the flag; the default is secure (`__Host-`). */\nexport function isSecureSession(settings) {\n return settings.cookieOptions?.secure ?? settings.secure ?? true;\n}\n/**\n * Serialize options shared by writes and clears. The app's `cookieOptions` come first,\n * then the name rules: `__Host-` requires `Secure`, `Path=/` and no `Domain`.\n */\nfunction serializeBase(settings, secure) {\n const opts = {\n path: \"/\",\n sameSite: \"lax\",\n httpOnly: true,\n ...settings.cookieOptions,\n secure,\n };\n if (settings.readable) {\n // the browser client reads this cookie (no superuser credentials; see 13-ssr.md, \"Browser client\")\n opts.httpOnly = false;\n }\n if (secure) {\n delete opts.domain;\n opts.path = \"/\";\n }\n return opts;\n}\nexport function buildSessionCookieToSet(token, record, options) {\n const secure = isSecureSession(options);\n const wireName = secure\n ? `__Host-${options.cookieName ?? DEFAULT_COOKIE_KEY}`\n : (options.cookieName ?? DEFAULT_COOKIE_KEY);\n const nowSec = options.nowSeconds ?? Math.floor(Date.now() / 1000);\n const exp = options.exp;\n const serializeOpts = {\n ...serializeBase(options, secure),\n maxAge: exp !== undefined ? Math.max(0, exp - nowSec) : undefined,\n expires: exp !== undefined ? new Date(exp * 1000) : undefined,\n };\n return {\n name: wireName,\n value: formatSessionCookieValue(wireName, token, record, serializeOpts),\n options: serializeOpts,\n };\n}\n/**\n * Expiring cookies for a cookie's end, one per name the cookie may have been written under.\n * When `secure`, the plain name is cleared too. It is never read on a secure origin, so this\n * only removes what some other writer or an older build left under it. `optionsFor` gives the\n * serialize options of each name's write.\n */\nfunction buildClearedCookies(baseName, secure, optionsFor) {\n const clear = (name, cookieSecure) => ({\n name,\n value: \"\",\n options: { ...optionsFor(cookieSecure), maxAge: 0, expires: new Date(0) },\n });\n if (secure) {\n return [clear(`__Host-${baseName}`, true), clear(baseName, false)];\n }\n return [clear(baseName, false)];\n}\n/** Expiring cookies for a session end. */\nexport function buildClearedSessionCookies(settings) {\n return buildClearedCookies(settings.cookieName ?? DEFAULT_COOKIE_KEY, isSecureSession(settings), (cookieSecure) => serializeBase(settings, cookieSecure));\n}\n/**\n * The temporary OAuth2 cookie: HttpOnly, `SameSite=Lax` (the provider's redirect is a top-level\n * GET), `Path=/`, `Max-Age` 600 and the same `Secure`/`__Host-` rule as the session cookie.\n * The app's `cookieOptions` are not applied (apart from `secure`), so an app cannot make the\n * cookie unreachable from the provider's redirect.\n */\nexport function buildOAuth2CookieToSet(value, settings) {\n const secure = isSecureSession(settings);\n return {\n name: secure ? `__Host-${OAUTH2_COOKIE_KEY}` : OAUTH2_COOKIE_KEY,\n value,\n options: {\n path: \"/\",\n sameSite: \"lax\",\n httpOnly: true,\n secure,\n maxAge: OAUTH2_COOKIE_MAX_AGE_SEC,\n },\n };\n}\n/** Expiring cookies for the temporary OAuth2 cookie, written with the same name rule. */\nexport function buildClearedOAuth2Cookies(settings) {\n return buildClearedCookies(OAUTH2_COOKIE_KEY, isSecureSession(settings), (cookieSecure) => ({\n path: \"/\",\n sameSite: \"lax\",\n httpOnly: true,\n secure: cookieSecure,\n }));\n}\nexport function createRequestCookieAdapter(request, options) {\n const queuedCookies = [];\n const queuedHeaders = {};\n const reqUrl = new URL(request.url);\n const isLocal = reqUrl.protocol === \"http:\" && isLocalhost(reqUrl.hostname);\n const isSecure = options?.secure ?? !isLocal;\n return {\n isSecure,\n getAll() {\n const cookieHeader = request.headers.get(\"cookie\") || \"\";\n const parsed = cookieParse(cookieHeader);\n return Object.entries(parsed).map(([name, value]) => ({\n name,\n value: typeof value === \"string\" ? value : String(value),\n }));\n },\n setAll(cookiesToSet, headers) {\n for (const c of cookiesToSet) {\n queuedCookies.push(c);\n }\n if (headers) {\n for (const [k, v] of Object.entries(headers)) {\n queuedHeaders[k] = v;\n }\n }\n },\n applyToResponse(response) {\n for (const [k, v] of Object.entries(queuedHeaders)) {\n response.headers.set(k, v);\n }\n for (const c of queuedCookies) {\n const serialized = cookieSerialize(c.name, c.value, c.options);\n response.headers.append(\"Set-Cookie\", serialized);\n }\n return response;\n },\n get queuedCookies() {\n return queuedCookies;\n },\n get queuedHeaders() {\n return queuedHeaders;\n },\n };\n}\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "8D0BCD03813113BD64756E2164756E21",
11
+ "names": []
12
+ }
@@ -0,0 +1,4 @@
1
+ import Client from"./iglo-base.es.mjs";import{ClientResponseError}from"./iglo-base.es.mjs";import{BaseAuthStore as BaseAuthStore2}from"./iglo-base.es.mjs";var isReactNative=typeof navigator<"u"&&navigator.product==="ReactNative"||typeof global<"u"&&global.HermesInternal,atobPolyfill;if(typeof atob==="function"&&!isReactNative)atobPolyfill=atob;else atobPolyfill=(input)=>{let str=String(input).replace(/=+$/,"");if(str.length%4==1)throw Error("'atob' failed: The string to be decoded is not correctly encoded.");for(var bc=0,bs,buffer,idx=0,output="";buffer=str.charAt(idx++);~buffer&&(bs=bc%4?bs*64+buffer:buffer,bc++%4)?output+=String.fromCharCode(255&bs>>(-2*bc&6)):0)buffer="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(buffer);return output};function getTokenPayload(token){if(token)try{let encodedPayload=decodeURIComponent(atobPolyfill(token.split(".")[1]).split("").map(function(c){return"%"+("00"+c.charCodeAt(0).toString(16)).slice(-2)}).join(""));return JSON.parse(encodedPayload)||{}}catch{}return{}}function isTokenExpired(token,expirationThreshold=0){let payload=getTokenPayload(token);if(Object.keys(payload).length>0&&(!payload.exp||payload.exp-expirationThreshold>Date.now()/1000))return!1;return!0}import{BaseAuthStore}from"./iglo-base.es.mjs";var fieldContentRegExp=/^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;function cookieParse(str,options){let result={};if(typeof str!=="string")return result;let decode=Object.assign({},options||{}).decode||defaultDecode,index=0;while(index<str.length){let eqIdx=str.indexOf("=",index);if(eqIdx===-1)break;let endIdx=str.indexOf(";",index);if(endIdx===-1)endIdx=str.length;else if(endIdx<eqIdx){index=str.lastIndexOf(";",eqIdx-1)+1;continue}let key=str.slice(index,eqIdx).trim();if(result[key]===void 0){let val=str.slice(eqIdx+1,endIdx).trim();if(val.charCodeAt(0)===34)val=val.slice(1,-1);try{result[key]=decode(val)}catch{result[key]=val}}index=endIdx+1}return result}function cookieSerialize(name,val,options){let opt=Object.assign({},options||{}),encode=opt.encode||defaultEncode;if(!fieldContentRegExp.test(name))throw TypeError("argument name is invalid");let value=encode(val);if(value&&!fieldContentRegExp.test(value))throw TypeError("argument val is invalid");let result=name+"="+value;if(opt.maxAge!=null){let maxAge=opt.maxAge-0;if(isNaN(maxAge)||!isFinite(maxAge))throw TypeError("option maxAge is invalid");result+="; Max-Age="+Math.floor(maxAge)}if(opt.domain){if(!fieldContentRegExp.test(opt.domain))throw TypeError("option domain is invalid");result+="; Domain="+opt.domain}if(opt.path){if(!fieldContentRegExp.test(opt.path))throw TypeError("option path is invalid");result+="; Path="+opt.path}if(opt.expires){if(!isDate(opt.expires)||isNaN(opt.expires.valueOf()))throw TypeError("option expires is invalid");result+="; Expires="+opt.expires.toUTCString()}if(opt.httpOnly)result+="; HttpOnly";if(opt.secure)result+="; Secure";if(opt.priority)switch(typeof opt.priority==="string"?opt.priority.toLowerCase():opt.priority){case"low":result+="; Priority=Low";break;case"medium":result+="; Priority=Medium";break;case"high":result+="; Priority=High";break;default:throw TypeError("option priority is invalid")}if(opt.sameSite)switch(typeof opt.sameSite==="string"?opt.sameSite.toLowerCase():opt.sameSite){case!0:result+="; SameSite=Strict";break;case"lax":result+="; SameSite=Lax";break;case"strict":result+="; SameSite=Strict";break;case"none":result+="; SameSite=None";break;default:throw TypeError("option sameSite is invalid")}return result}function defaultDecode(val){return val.indexOf("%")!==-1?decodeURIComponent(val):val}function defaultEncode(val){return encodeURIComponent(val)}function isDate(val){return Object.prototype.toString.call(val)==="[object Date]"||val instanceof Date}var SSR_NO_CACHE_HEADERS=Object.freeze({"Cache-Control":"private, no-cache, no-store, must-revalidate, max-age=0",Expires:"0",Pragma:"no-cache"}),DEFAULT_COOKIE_KEY="pb_auth",OAUTH2_COOKIE_KEY="pb_oauth2",OAUTH2_COOKIE_MAX_AGE_SEC=600;function isLocalhost(hostname){let clean=hostname.replace(/^\[|\]$/g,"").split(":")[0]??"";return clean==="localhost"||clean==="127.0.0.1"||clean==="::1"}function isHttpLocalUrl(urlStr){try{let parsed=new URL(urlStr);return parsed.protocol==="http:"&&isLocalhost(parsed.hostname)}catch{return!1}}function normalizeCookies(raw){if(!raw)return[];if(Array.isArray(raw))return raw;return Object.entries(raw).map(([name,value])=>({name,value}))}function cookieWireName(baseName,secure){return secure?`__Host-${baseName}`:baseName}function findCookie(cookies,baseName,secure){let name=cookieWireName(baseName,secure);return cookies.find((c)=>c.name===name)}function hasSessionCookieName(cookies,baseName){return cookies.some((c)=>c.name===`__Host-${baseName}`||c.name===baseName)}function recordForToken(token,record){let payload=getTokenPayload(token);if(record&&payload.id&&record.id!==payload.id)return null;return record}function safeDecode(value){try{return decodeURIComponent(value)}catch{return value}}function parseSessionValue(raw){let candidates=raw===safeDecode(raw)?[raw]:[raw,safeDecode(raw)];for(let candidate of candidates){let parsed;try{parsed=JSON.parse(candidate)}catch{continue}if(!parsed||typeof parsed!=="object"||Array.isArray(parsed))return;let obj=parsed,token=typeof obj.token==="string"?obj.token:"";if(!token)return;let record=obj.record??obj.model??null;return{token,record}}return}function formatSessionCookieValue(wireName,token,record,options){let tempStore=new BaseAuthStore;tempStore.save(token,record);let cookieStr=tempStore.exportToCookie(options,wireName);return cookieParse(cookieStr)[wireName]??""}function isSecureSession(settings){return settings.cookieOptions?.secure??settings.secure??!0}function serializeBase(settings,secure){let opts={path:"/",sameSite:"lax",httpOnly:!0,...settings.cookieOptions,secure};if(settings.readable)opts.httpOnly=!1;if(secure)delete opts.domain,opts.path="/";return opts}function buildSessionCookieToSet(token,record,options){let secure=isSecureSession(options),wireName=secure?`__Host-${options.cookieName??DEFAULT_COOKIE_KEY}`:options.cookieName??DEFAULT_COOKIE_KEY,nowSec=options.nowSeconds??Math.floor(Date.now()/1000),exp=options.exp,serializeOpts={...serializeBase(options,secure),maxAge:exp!==void 0?Math.max(0,exp-nowSec):void 0,expires:exp!==void 0?new Date(exp*1000):void 0};return{name:wireName,value:formatSessionCookieValue(wireName,token,record,serializeOpts),options:serializeOpts}}function buildClearedCookies(baseName,secure,optionsFor){let clear=(name,cookieSecure)=>({name,value:"",options:{...optionsFor(cookieSecure),maxAge:0,expires:new Date(0)}});if(secure)return[clear(`__Host-${baseName}`,!0),clear(baseName,!1)];return[clear(baseName,!1)]}function buildClearedSessionCookies(settings){return buildClearedCookies(settings.cookieName??DEFAULT_COOKIE_KEY,isSecureSession(settings),(cookieSecure)=>serializeBase(settings,cookieSecure))}function buildOAuth2CookieToSet(value,settings){let secure=isSecureSession(settings);return{name:secure?`__Host-${OAUTH2_COOKIE_KEY}`:OAUTH2_COOKIE_KEY,value,options:{path:"/",sameSite:"lax",httpOnly:!0,secure,maxAge:OAUTH2_COOKIE_MAX_AGE_SEC}}}function buildClearedOAuth2Cookies(settings){return buildClearedCookies(OAUTH2_COOKIE_KEY,isSecureSession(settings),(cookieSecure)=>({path:"/",sameSite:"lax",httpOnly:!0,secure:cookieSecure}))}function createRequestCookieAdapter(request,options){let queuedCookies=[],queuedHeaders={},reqUrl=new URL(request.url),isLocal=reqUrl.protocol==="http:"&&isLocalhost(reqUrl.hostname);return{isSecure:options?.secure??!isLocal,getAll(){let cookieHeader=request.headers.get("cookie")||"",parsed=cookieParse(cookieHeader);return Object.entries(parsed).map(([name,value])=>({name,value:typeof value==="string"?value:String(value)}))},setAll(cookiesToSet,headers){for(let c of cookiesToSet)queuedCookies.push(c);if(headers)for(let[k,v]of Object.entries(headers))queuedHeaders[k]=v},applyToResponse(response){for(let[k,v]of Object.entries(queuedHeaders))response.headers.set(k,v);for(let c of queuedCookies){let serialized=cookieSerialize(c.name,c.value,c.options);response.headers.append("Set-Cookie",serialized)}return response},get queuedCookies(){return queuedCookies},get queuedHeaders(){return queuedHeaders}}}function noStoreFetch(inner,onResponse){return async(url,config)=>{let response=await(inner??globalThis.fetch)(url,{...config,cache:"no-store"});return onResponse?.(response),response}}var OAUTH2_STATE_TTL_MS=600000,DEFAULT_OAUTH2_COLLECTION="users";class OAuth2Error extends Error{constructor(kind,message,providerError){super(message);this.name="OAuth2Error",this.kind=kind,this.providerError=providerError}}var FALLBACK_REDIRECT="/",PROBE_ORIGIN="http://app.invalid";function hasBackslashOrControl(value){for(let i=0;i<value.length;i++){let code=value.charCodeAt(i);if(code===92||code<32||code===127)return!0}return!1}function safeRedirectPath(value){if(typeof value!=="string"||value.length===0||value[0]!=="/")return FALLBACK_REDIRECT;if(value.startsWith("//")||hasBackslashOrControl(value))return FALLBACK_REDIRECT;try{if(new URL(value,PROBE_ORIGIN).origin!==PROBE_ORIGIN)return FALLBACK_REDIRECT}catch{return FALLBACK_REDIRECT}return value}function constantTimeEqual(a,b){if(a.length!==b.length)return!1;let diff=0;for(let i=0;i<a.length;i++)diff|=a.charCodeAt(i)^b.charCodeAt(i);return diff===0}function validateCallbackUrl(callbackUrl){let parsed;try{parsed=new URL(callbackUrl)}catch{throw new OAuth2Error("invalid-callback","callbackUrl must be an absolute URL.")}if(parsed.protocol!=="https:"&&parsed.protocol!=="http:")throw new OAuth2Error("invalid-callback","callbackUrl must be an http or https URL.");return parsed.href}var SAFE_COLLECTION=/^[A-Za-z0-9_-]{1,100}$/;function isString(value,max){return typeof value==="string"&&value.length<=max}function parseOAuth2Cookie(raw){let candidates=raw===safeDecode(raw)?[raw]:[raw,safeDecode(raw)];for(let candidate of candidates){let parsed;try{parsed=JSON.parse(candidate)}catch{continue}if(!parsed||typeof parsed!=="object"||Array.isArray(parsed))return;let obj=parsed;if(!isString(obj.state,256)||obj.state===""||!isString(obj.codeVerifier,256)||!isString(obj.provider,100)||obj.provider===""||!isString(obj.collection,100)||!SAFE_COLLECTION.test(obj.collection)||!isString(obj.redirectURL,2048)||obj.redirectURL===""||!isString(obj.redirectTo,2048)||typeof obj.exp!=="number"||!Number.isFinite(obj.exp))return;return{state:obj.state,codeVerifier:obj.codeVerifier,provider:obj.provider,collection:obj.collection,redirectURL:obj.redirectURL,redirectTo:obj.redirectTo,exp:obj.exp}}return}function withRedirectUri(authURL,callbackUrl){return authURL+encodeURIComponent(callbackUrl)}function returnsToQueryString(authURL){let mode=new URL(authURL,PROBE_ORIGIN).searchParams.get("response_mode");return mode===null||mode==="query"}var CLOCK_SKEW_SEC=60,SESSION_RESPONSE_PATH=/\/auth-(?:with-password|with-otp|with-oauth2|refresh)$/;function unauthorized(message){return new ClientResponseError({status:401,data:{message}})}function collectionKey(payload,record){return payload.collectionId||record?.collectionId||record?.collectionName||""}function resolveSecure(options){if(options.secure!==void 0)return options.secure;if(options.cookies.isSecure!==void 0)return options.cookies.isSecure;if(options.origin)return!isHttpLocalUrl(options.origin);if(options.request)return!isHttpLocalUrl(options.request.url);return!0}class ServerClient extends Client{constructor(options){let authStore=new BaseAuthStore2;super(options.url,authStore,options.lang??"en-US");this._clockOffsetMs=0,this._initialized=!1,this._applying=!1,this._lastToken="",this._options=options,this.cookies=options.cookies,this._cookieName=options.cookieName??DEFAULT_COOKIE_KEY,this._clock=options.clock??Date.now,this._clockOffsetMs=options.sessionCache?.clockOffsetMs??0,this._isSecure=resolveSecure(options),this._readable=options.readableCookie===!0,this.autoCancellation(!1),this.authStore.onChange((token,record)=>{if(!this._initialized||this._applying)return;if(token)this._saveSession(token,record,!0);else this._dropSession(this._lastToken)}),this.auth={verify:()=>this._checkSession(!0)}}get readableCookie(){return this._readable}serverNow(){return this._clock()+this._clockOffsetMs}async send(path,options){let seen={};options.fetch=noStoreFetch(options.fetch??this._options.fetch,(response)=>{seen.date=response.headers.get("date")});let data=await super.send(path,options);return this._observeResponse(path,seen.date,data),data}async init(){return await this._loadCookieAndCheck(),this._initialized=!0,this}async startOAuth2(provider,options){let callbackUrl=validateCallbackUrl(options.callbackUrl),collection=options.collection??DEFAULT_OAUTH2_COLLECTION;if(!this.cookies.setAll)throw new OAuth2Error("no-cookie-writer","The cookie adapter cannot write cookies.");let methods=await this.collection(collection).listAuthMethods(),info=methods.oauth2?.enabled?methods.oauth2.providers.find((p)=>p.name===provider):void 0;if(!info)throw new OAuth2Error("unknown-provider",`OAuth2 provider "${provider}" is not enabled for the ${collection} collection.`);if(!returnsToQueryString(info.authURL))throw new OAuth2Error("unsupported-provider",`OAuth2 provider "${provider}" returns to the callback by POST, which the redirect flow cannot read. Use the popup flow (authWithOAuth2 and handlePopupHandOff) for it.`);let pending={state:info.state,codeVerifier:info.codeVerifier??"",provider,collection,redirectURL:callbackUrl,redirectTo:safeRedirectPath(options.redirectTo??"/"),exp:this._clock()+OAUTH2_STATE_TTL_MS};return await this.cookies.setAll([buildOAuth2CookieToSet(JSON.stringify(pending),this._cookieSettings())],{...SSR_NO_CACHE_HEADERS}),withRedirectUri(info.authURL,callbackUrl)}async completeOAuth2(input){let callback=new URL(typeof input==="string"?input:input instanceof URL?input.href:input.url);try{let pending=await this._readOAuth2Cookie();return await this._finishOAuth2(callback,pending)}finally{this._writeCookies(buildClearedOAuth2Cookies(this._cookieSettings()))}}async _readOAuth2Cookie(){let rawCookies;try{rawCookies=await this.cookies.getAll()}catch{return}let matched=findCookie(normalizeCookies(rawCookies),OAUTH2_COOKIE_KEY,isSecureSession(this._cookieSettings()));return matched?.value?parseOAuth2Cookie(matched.value):void 0}async _finishOAuth2(callback,pending){let redirectTo=pending?safeRedirectPath(pending.redirectTo):"/",fail=(error)=>({ok:!1,redirectTo,error});if(!pending)return fail(new OAuth2Error("missing-cookie","The OAuth2 sign-in was not started here."));if(this._clock()>pending.exp)return fail(new OAuth2Error("expired","The OAuth2 sign-in took too long. Start it again."));let state=callback.searchParams.get("state")??"";if(state===""||!constantTimeEqual(state,pending.state))return fail(new OAuth2Error("state-mismatch","The OAuth2 state does not match."));let providerError=callback.searchParams.get("error");if(providerError!==null)return fail(new OAuth2Error("provider-error",`The OAuth2 provider returned an error: ${providerError}`,providerError));let code=callback.searchParams.get("code")??"";if(code==="")return fail(new OAuth2Error("missing-code","The OAuth2 callback has no code."));try{let auth=await this.collection(pending.collection).authWithOAuth2Code(pending.provider,code,pending.codeVerifier,pending.redirectURL);return{ok:!0,redirectTo,auth}}catch(err){if(err instanceof ClientResponseError)return fail(err);throw err}}_cookieSettings(){return{secure:this._isSecure,cookieName:this._cookieName,cookieOptions:this._options.cookieOptions,readable:this._readable}}async _loadCookieAndCheck(){let rawCookies;try{rawCookies=await this.cookies.getAll()}catch{return}let matched=findCookie(normalizeCookies(rawCookies),this._cookieName,isSecureSession(this._cookieSettings())),session=matched?.value?parseSessionValue(matched.value):void 0;if(!session){this._setLocalSession("",null);return}this._lastToken=session.token,this._setLocalSession(session.token,recordForToken(session.token,session.record)),await this._checkSession(!1)}async _checkSession(force){let token=this.authStore.token;if(!token){if(force)throw unauthorized("The request requires valid authentication token.");return null}let payload=getTokenPayload(token),nowMs=this._clock(),nowSec=Math.floor((nowMs+this._clockOffsetMs)/1000);if(payload.exp&&nowSec>payload.exp+CLOCK_SKEW_SEC){if(this._dropSession(token),force)throw unauthorized("The token is expired.");return null}let cache=this._options.sessionCache;if(cache&&!force){let cached=await cache.get(token),lifetime=cache.getCollectionLifetime(collectionKey(payload,this.authStore.record));if(cached&&lifetime&&nowMs-cached.checkedAt<cache.ttl){if((payload.exp?payload.exp-nowSec:0)>=lifetime/2)return this._setLocalSession(token,cached.record),cached.record}}let outcome=cache?await cache.runSingleFlight(token,()=>this._requestRefresh(token)):await this._requestRefresh(token);return this._applyOutcome(token,outcome,force,cache,nowMs)}async _requestRefresh(token){let payload=getTokenPayload(token),collection=this.authStore.record?.collectionName||this.authStore.record?.collectionId||payload.collectionId||(this.authStore.isSuperuser?"_superusers":"users");try{let res=await this.send(`/api/collections/${encodeURIComponent(collection)}/auth-refresh`,{method:"POST",headers:{Authorization:token}});return{kind:"ok",token:res?.token||token,record:res?.record??this.authStore.record}}catch(err){if(err instanceof ClientResponseError&&(err.status===401||err.status===403||err.status===404))return{kind:"revoked",error:err};return{kind:"transient",error:err}}}async _applyOutcome(token,outcome,force,cache,checkedAtMs){switch(outcome.kind){case"ok":{let record=recordForToken(outcome.token,outcome.record),changed=outcome.token!==token;if(cache){if(changed)await cache.delete(token);await cache.set(outcome.token,record,checkedAtMs)}return this._setLocalSession(outcome.token,record,changed),record}case"revoked":if(this._dropSession(token),force)throw outcome.error;return null;case"transient":if(force)throw outcome.error;return this.authStore.record}}_setLocalSession(token,record,writeCookie=!1){if(this._lastToken=token,this._applyLocally(()=>{if(token)this.authStore.save(token,record);else this.authStore.clear()}),token&&writeCookie)this._writeCookie(token,record)}_saveSession(token,record,writeCookie){let matched=recordForToken(token,record);this._setLocalSession(token,matched,writeCookie),this._options.sessionCache?.set(token,matched,this._clock()).catch(()=>{})}_dropSession(token){if(this._setLocalSession("",null),token)this._options.sessionCache?.delete(token).catch(()=>{});this._writeCookies(buildClearedSessionCookies(this._cookieSettings()))}_applyLocally(fn){let was=this._applying;this._applying=!0;try{fn()}finally{this._applying=was}}_writeCookie(token,record){let payload=getTokenPayload(token),nowSec=Math.floor((this._clock()+this._clockOffsetMs)/1000);this._writeCookies([buildSessionCookieToSet(token,record,{...this._cookieSettings(),nowSeconds:nowSec,exp:payload.exp})])}_writeCookies(cookies){if(!this.cookies.setAll)return;try{Promise.resolve(this.cookies.setAll(cookies,{...SSR_NO_CACHE_HEADERS})).catch(()=>{})}catch{}}_observeResponse(path,dateHeader,data){let serverMs=dateHeader?Date.parse(dateHeader):NaN;if(Number.isNaN(serverMs))return;if(this._clockOffsetMs=serverMs-this._clock(),this._options.sessionCache)this._options.sessionCache.clockOffsetMs=this._clockOffsetMs;let body=data&&typeof data==="object"?data:{},token=typeof body.token==="string"?body.token:"";if(!token||!SESSION_RESPONSE_PATH.test(path))return;let payload=getTokenPayload(token);if(payload.type!=="auth"||!payload.exp||payload.refreshable===!1)return;let record=body.record;if(!record||record.id!==payload.id)return;let lifetime=payload.exp-Math.floor(serverMs/1000),key=collectionKey(payload,record);if(lifetime>0&&key)this._options.sessionCache?.setCollectionLifetime(key,lifetime)}}async function createServerClient(options){let client=new ServerClient(options);return await client.init(),client}import Client3 from"./iglo-base.es.mjs";import{BaseAuthStore as BaseAuthStore3}from"./iglo-base.es.mjs";import Client2 from"./iglo-base.es.mjs";function resetAutoRefresh(client){client._resetAutoRefresh?.()}function registerAutoRefresh(client,threshold,refreshFunc,reauthenticateFunc){resetAutoRefresh(client);let oldBeforeSend=client.beforeSend,oldModel=client.authStore.record,unsubStoreChange=client.authStore.onChange((newToken,model)=>{if(!newToken||model?.id!=oldModel?.id||(model?.collectionId||oldModel?.collectionId)&&model?.collectionId!=oldModel?.collectionId)resetAutoRefresh(client)});client._resetAutoRefresh=function(){unsubStoreChange(),client.beforeSend=oldBeforeSend,delete client._resetAutoRefresh},client.beforeSend=async(url,sendOptions)=>{let oldToken=client.authStore.token;if(sendOptions.query?.autoRefresh)return oldBeforeSend?oldBeforeSend(url,sendOptions):{url,sendOptions};let isValid=client.authStore.isValid;if(isValid&&isTokenExpired(client.authStore.token,threshold))try{await refreshFunc()}catch{isValid=!1}if(!isValid)await reauthenticateFunc();let headers=sendOptions.headers||{};for(let key in headers)if(key.toLowerCase()=="authorization"&&oldToken==headers[key]&&client.authStore.token){headers[key]=client.authStore.token;break}return sendOptions.headers=headers,oldBeforeSend?oldBeforeSend(url,sendOptions):{url,sendOptions}}}var DEFAULT_BROWSER_TOKEN_DURATION_SEC=900,DEFAULT_AUTO_REFRESH_THRESHOLD_SEC=300;class SuperuserClient extends Client3{constructor(options){let authStore=new BaseAuthStore3;super(options.url,authStore,options.lang??"en-US");this._clockOffsetMs=0,this._options=options,this._clock=options.clock??Date.now,this.autoCancellation(!1)}serverNow(){return this._clock()+this._clockOffsetMs}async send(path,options){return options.fetch=noStoreFetch(options.fetch??this._options.fetch,(response)=>{let serverMs=Date.parse(response.headers.get("date")??"");if(!Number.isNaN(serverMs))this._clockOffsetMs=serverMs-this._clock()}),super.send(path,options)}async init(){let threshold=this._options.autoRefreshThreshold??DEFAULT_AUTO_REFRESH_THRESHOLD_SEC;if(this._options.token){let payload=getTokenPayload(this._options.token),initialRecord={id:payload.id||"",collectionId:payload.collectionId||"_superusers",collectionName:"_superusers"};if(this.authStore.save(this._options.token,initialRecord),threshold>0&&payload.refreshable!==!1)registerAutoRefresh(this,threshold,()=>this.collection("_superusers").authRefresh({autoRefresh:!0}),async()=>{})}else if(this._options.email&&this._options.password)await this.collection("_superusers").authWithPassword(this._options.email,this._options.password,{autoRefreshThreshold:threshold});return this}async impersonate(collectionOrRecord,recordIdOrDuration,duration){let collection,recordId,dur;if(typeof collectionOrRecord==="object"&&collectionOrRecord!==null)collection=collectionOrRecord.collectionName||collectionOrRecord.collectionId,recordId=collectionOrRecord.id,dur=typeof recordIdOrDuration==="number"?recordIdOrDuration:duration??DEFAULT_BROWSER_TOKEN_DURATION_SEC;else collection=collectionOrRecord,recordId=typeof recordIdOrDuration==="string"?recordIdOrDuration:"",dur=duration??DEFAULT_BROWSER_TOKEN_DURATION_SEC;let sendOpts={method:"POST",body:{duration:dur},headers:{Authorization:this.authStore.token}};if(this._options.fetch)sendOpts.fetch=this._options.fetch;let res=await this.send(`/api/collections/${encodeURIComponent(collection)}/impersonate/${encodeURIComponent(recordId)}`,sendOpts);return{token:res.token,record:res.record}}}async function createSuperuserClient(options){let client=new SuperuserClient(options);return await client.init(),client}var DEFAULT_TTL_MS=300000,DEFAULT_MAX_ENTRIES=1e4;async function hashToken(token){let data=new TextEncoder().encode(token),digest=await globalThis.crypto.subtle.digest("SHA-256",data),bytes=new Uint8Array(digest),hex="";for(let i=0;i<bytes.length;i++){let b=bytes[i];if(b!==void 0)hex+=b.toString(16).padStart(2,"0")}return hex}function createSessionCache(options){let ttl=options?.ttl??300000,maxEntries=options?.maxEntries??1e4,clock=options?.clock??Date.now,map=new Map,inFlight=new Map,collectionLifetimes=new Map,queue=Promise.resolve(),serial=(task)=>{let run=queue.then(task);return queue=run.catch(()=>{}),run};return{get(token){if(!token)return Promise.resolve(void 0);return serial(async()=>{let key=await hashToken(token),entry=map.get(key);if(!entry)return;return map.delete(key),map.set(key,entry),entry})},set(token,record,checkedAt){if(!token)return Promise.resolve();let timestamp=checkedAt??clock();return serial(async()=>{let key=await hashToken(token);if(map.has(key))map.delete(key);else if(map.size>=maxEntries){let oldestKey=map.keys().next().value;if(oldestKey!==void 0)map.delete(oldestKey)}map.set(key,{record,checkedAt:timestamp})})},delete(token){if(!token)return Promise.resolve();return serial(async()=>{let key=await hashToken(token);map.delete(key)})},clear(){map.clear(),inFlight.clear(),collectionLifetimes.clear()},async runSingleFlight(token,fn){let key=await hashToken(token),existing=inFlight.get(key);if(existing)return existing;let promise=(async()=>{try{return await fn()}finally{inFlight.delete(key)}})();return inFlight.set(key,promise),promise},get size(){return map.size},get ttl(){return ttl},get maxEntries(){return maxEntries},getCollectionLifetime(collectionId){if(!collectionId)return;return collectionLifetimes.get(collectionId)},setCollectionLifetime(collectionId,lifetimeSec){if(!collectionId||lifetimeSec<=0)return;collectionLifetimes.set(collectionId,lifetimeSec)},clockOffsetMs:0}}var STATE_CHANGING_METHODS=new Set(["POST","PUT","PATCH","DELETE"]);function normalizeAllowed(allowedOrigins){return(allowedOrigins??[]).map((entry)=>{let parsed;try{parsed=new URL(entry)}catch{throw TypeError(`allowedOrigins entry is not a URL: ${entry}`)}if(parsed.origin==="null"||parsed.pathname!=="/"||parsed.search||parsed.hash)throw TypeError(`allowedOrigins entry is not an origin: ${entry}`);return parsed.origin})}function checkOrigin(request,allowedOrigins){let allowed=normalizeAllowed(allowedOrigins),source=request.headers.get("origin")??request.headers.get("referer");if(source===null||source==="")return{ok:!1,reason:"missing-origin"};let sourceOrigin;try{sourceOrigin=new URL(source).origin}catch{return{ok:!1,reason:"foreign-origin"}}if(sourceOrigin==="null")return{ok:!1,reason:"foreign-origin"};let own=new URL(request.url).origin;if(sourceOrigin===own||allowed.includes(sourceOrigin))return{ok:!0};return{ok:!1,reason:"foreign-origin"}}function checkCsrf(request,options={}){if(normalizeAllowed(options.allowedOrigins),!STATE_CHANGING_METHODS.has(request.method.toUpperCase()))return{ok:!0};let cookies=createRequestCookieAdapter(request).getAll();if(!hasSessionCookieName(cookies,options.cookieName??DEFAULT_COOKIE_KEY))return{ok:!0};let check=checkOrigin(request,options.allowedOrigins);if(check.ok)return{ok:!0};return{ok:!1,reason:check.reason,response:new Response(JSON.stringify({status:403,message:"Cross-site request rejected.",data:{}}),{status:403,headers:{"Content-Type":"application/json"}})}}import{ClientResponseError as ClientResponseError2}from"./iglo-base.es.mjs";function jsonResponse(status,body){return new Response(JSON.stringify(body),{status,headers:{"Content-Type":"application/json",...SSR_NO_CACHE_HEADERS}})}function errorResponse(status,message){return jsonResponse(status,{status,message,data:{}})}async function handlePopupHandOff(request,{allowedOrigins,...options}){if(request.method!=="POST"){let response=errorResponse(405,"Method not allowed.");return response.headers.set("Allow","POST"),response}if(!checkOrigin(request,allowedOrigins).ok)return errorResponse(403,"Cross-site request rejected.");let body;try{body=await request.json()}catch{return errorResponse(400,"The request body must be JSON.")}let token=body&&typeof body==="object"&&typeof body.token==="string"?body.token:"",payload=token?getTokenPayload(token):{};if(!token||payload.type!=="auth"||typeof payload.collectionId!=="string")return errorResponse(400,"The token is not an auth token.");let cookies=createRequestCookieAdapter(request,{secure:options.secure}),client=await createServerClient({...options,cookies:{getAll:()=>[],setAll:cookies.setAll,isSecure:cookies.isSecure}}),refreshed;try{refreshed=await client.send(`/api/collections/${encodeURIComponent(payload.collectionId)}/auth-refresh`,{method:"POST",headers:{Authorization:token}})}catch(err){if(err instanceof ClientResponseError2&&err.status>=400&&err.status<500)return errorResponse(err.status===401||err.status===403?401:err.status,err.message);return errorResponse(503,"The sign-in could not be checked. Try again.")}let record=refreshed?.record;if(!record||record.id!==payload.id)return errorResponse(503,"The sign-in could not be checked. Try again.");client.authStore.save(refreshed.token||token,record);let response=jsonResponse(200,{record});return cookies.applyToResponse(response),response}var DEFAULT_BROWSER_TOKEN_DURATION_SEC2=900;async function mintBrowserToken(server,superuser,options={}){let session=server.authStore.token,record=server.authStore.record;if(!session||!record)return null;let token,serverTime;if(superuser){let collection=record.collectionName||record.collectionId;token=(await superuser.impersonate(collection,record.id,options.duration??DEFAULT_BROWSER_TOKEN_DURATION_SEC2)).token,serverTime=superuser.serverNow()}else if(server.readableCookie)token=session,serverTime=server.serverNow();else throw TypeError("mintBrowserToken needs a superuser client, or a server client with readableCookie");let exp=getTokenPayload(token).exp;if(!exp)throw Error("the token has no expiry");return{token,record,expiresAt:exp*1000,serverTime}}import{ClientResponseError as ClientResponseError3}from"./iglo-base.es.mjs";function jsonResponse2(status,body,headers){return new Response(JSON.stringify(body),{status,headers:{"Content-Type":"application/json",...headers,...SSR_NO_CACHE_HEADERS}})}function errorResponse2(status,message){let response=jsonResponse2(status,{status,message,data:{}});if(status===405)response.headers.set("Allow","POST");return response}async function handleBrowserTokenRenew(request,{allowedOrigins,superuser,duration,...options}){if(request.method!=="POST")return errorResponse2(405,"Method not allowed.");if(!checkOrigin(request,allowedOrigins).ok)return errorResponse2(403,"Cross-site request rejected.");if(!superuser&&options.readableCookie!==!0)throw TypeError("handleBrowserTokenRenew needs a superuser client, or readableCookie: true");let cookies=createRequestCookieAdapter(request,{secure:options.secure}),client;try{client=await createServerClient({...options,cookies}),await client.auth.verify()}catch(err){if(!(err instanceof ClientResponseError3))throw err;if(err.status===401||err.status===403||err.status===404)return withCookies(errorResponse2(401,"The session is not valid."),cookies);return errorResponse2(503,"The session could not be checked. Try again.")}let minted;try{minted=await mintBrowserToken(client,superuser,{duration})}catch(err){if(!(err instanceof ClientResponseError3))throw err;if(err.status===404)return client.authStore.clear(),withCookies(errorResponse2(401,"The session is not valid."),cookies);return withCookies(errorResponse2(503,"The browser token could not be minted. Try again."),cookies)}if(!minted)return withCookies(errorResponse2(401,"The session is not valid."),cookies);return withCookies(jsonResponse2(200,minted),cookies)}function withCookies(response,cookies){return cookies.applyToResponse(response),response}export{DEFAULT_BROWSER_TOKEN_DURATION_SEC2 as DEFAULT_BROWSER_TOKEN_DURATION_SEC,DEFAULT_COOKIE_KEY,DEFAULT_MAX_ENTRIES,DEFAULT_TTL_MS,OAuth2Error,SSR_NO_CACHE_HEADERS,ServerClient,SuperuserClient,checkCsrf,checkOrigin,createRequestCookieAdapter,createServerClient,createSessionCache,createSuperuserClient,handleBrowserTokenRenew,handlePopupHandOff,mintBrowserToken};
2
+
3
+ //# debugId=C1F88B2C03C1857464756E2164756E21
4
+ //# sourceMappingURL=ssr.es.mjs.map
@@ -0,0 +1,22 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [".js/src/ssr/server-client.js", ".js/src/tools/jwt.js", ".js/src/ssr/cookies.js", ".js/src/tools/cookie.js", ".js/src/ssr/no-store-fetch.js", ".js/src/ssr/oauth2.js", ".js/src/ssr/superuser-client.js", ".js/src/tools/refresh.js", ".js/src/ssr/cache.js", ".js/src/ssr/csrf.js", ".js/src/ssr/popup.js", ".js/src/ssr/browser-token.js", ".js/src/ssr/renew.js"],
4
+ "sourcesContent": [
5
+ "// docs/design/13-ssr.md (\"Clients\", \"Session cookie\", \"Session cache, check and refresh\")\nimport Client from \"@sdk/Client\";\nimport { ClientResponseError } from \"@sdk/ClientResponseError\";\nimport { BaseAuthStore } from \"@sdk/stores/BaseAuthStore\";\nimport { getTokenPayload } from \"@sdk/tools/jwt\";\nimport { DEFAULT_COOKIE_KEY, OAUTH2_COOKIE_KEY, SSR_NO_CACHE_HEADERS, buildClearedOAuth2Cookies, buildClearedSessionCookies, buildOAuth2CookieToSet, buildSessionCookieToSet, findCookie, isSecureSession, isHttpLocalUrl, normalizeCookies, parseSessionValue, recordForToken, } from \"@sdk/ssr/cookies\";\nimport { noStoreFetch } from \"@sdk/ssr/no-store-fetch\";\nimport { DEFAULT_OAUTH2_COLLECTION, OAUTH2_STATE_TTL_MS, OAuth2Error, constantTimeEqual, parseOAuth2Cookie, returnsToQueryString, safeRedirectPath, validateCallbackUrl, withRedirectUri, } from \"@sdk/ssr/oauth2\";\n/** Tokens past their `exp` by up to this many seconds still count as valid (clock skew). */\nconst CLOCK_SKEW_SEC = 60;\n/**\n * Only the responses that create or renew the caller's own session teach a collection's\n * token lifetime. Other endpoints can return tokens with other lifetimes (`files/token`\n * returns a 180-second file token; `impersonate` returns a non-refreshable one).\n */\nconst SESSION_RESPONSE_PATH = /\\/auth-(?:with-password|with-otp|with-oauth2|refresh)$/;\nfunction unauthorized(message) {\n return new ClientResponseError({ status: 401, data: { message } });\n}\n/** The key that ties a token to its collection's learned lifetime. */\nfunction collectionKey(payload, record) {\n return payload.collectionId || record?.collectionId || record?.collectionName || \"\";\n}\nfunction resolveSecure(options) {\n if (options.secure !== undefined) {\n return options.secure;\n }\n if (options.cookies.isSecure !== undefined) {\n return options.cookies.isSecure;\n }\n if (options.origin) {\n return !isHttpLocalUrl(options.origin);\n }\n if (options.request) {\n return !isHttpLocalUrl(options.request.url);\n }\n // Unknown app origin: secure by default. Apps on http://localhost pass `origin` or `request`.\n return true;\n}\nexport class ServerClient extends Client {\n constructor(options) {\n const authStore = new BaseAuthStore();\n super(options.url, authStore, options.lang ?? \"en-US\");\n this._clockOffsetMs = 0;\n this._initialized = false;\n // true while this client applies a change it made itself; the auth-store listener skips it\n this._applying = false;\n this._lastToken = \"\";\n this._options = options;\n this.cookies = options.cookies;\n this._cookieName = options.cookieName ?? DEFAULT_COOKIE_KEY;\n this._clock = options.clock ?? Date.now;\n this._clockOffsetMs = options.sessionCache?.clockOffsetMs ?? 0;\n this._isSecure = resolveSecure(options);\n this._readable = options.readableCookie === true;\n this.autoCancellation(false);\n // Auth changes the app makes on this client (authWithPassword, authStore.clear(), ...)\n // write or clear the cookie and update the cache. Changes made by this client during a\n // check go through `_applying` and write the cookie once, in `_applyOutcome`.\n this.authStore.onChange((token, record) => {\n if (!this._initialized || this._applying) {\n return;\n }\n if (token) {\n this._saveSession(token, record, true);\n }\n else {\n this._dropSession(this._lastToken);\n }\n });\n this.auth = {\n verify: () => this._checkSession(true),\n };\n }\n /** Whether the session cookie is readable by page JavaScript (`readableCookie`, the fallback). */\n get readableCookie() {\n return this._readable;\n }\n /**\n * iglo.base's clock in milliseconds, as the last response's `Date` header read it (this\n * process's clock when no response has said yet).\n */\n serverNow() {\n return this._clock() + this._clockOffsetMs;\n }\n /**\n * Every request goes through here: `cache: \"no-store\"` is applied at the fetch layer,\n * and the server's clock and the collection lifetime are read from each response.\n */\n async send(path, options) {\n const seen = {};\n options.fetch = noStoreFetch(options.fetch ?? this._options.fetch, (response) => {\n seen.date = response.headers.get(\"date\");\n });\n const data = await super.send(path, options);\n this._observeResponse(path, seen.date, data);\n return data;\n }\n /** Reads the cookie, then checks or refreshes the session. */\n async init() {\n await this._loadCookieAndCheck();\n this._initialized = true;\n return this;\n }\n /**\n * OAuth2 redirect flow, step 1 (docs/design/13-ssr.md, \"OAuth2\"). Reads the provider's\n * authorization URL, which carries the server's `state` and PKCE challenge, and stores the\n * state, the PKCE verifier, the provider and `redirectTo` in the temporary cookie (10 minutes).\n * Returns the URL with `callbackUrl` as its `redirect_uri`. The caller redirects to it and\n * applies the adapter's cookies to that response.\n *\n * @throws {OAuth2Error} `unknown-provider`, `unsupported-provider` (a POST callback, such as\n * Sign in with Apple), `invalid-callback` or `no-cookie-writer`\n * @throws {ClientResponseError} when iglo.base refuses the auth methods\n */\n async startOAuth2(provider, options) {\n const callbackUrl = validateCallbackUrl(options.callbackUrl);\n const collection = options.collection ?? DEFAULT_OAUTH2_COLLECTION;\n if (!this.cookies.setAll) {\n throw new OAuth2Error(\"no-cookie-writer\", \"The cookie adapter cannot write cookies.\");\n }\n const methods = await this.collection(collection).listAuthMethods();\n const info = methods.oauth2?.enabled\n ? methods.oauth2.providers.find((p) => p.name === provider)\n : undefined;\n if (!info) {\n throw new OAuth2Error(\"unknown-provider\", `OAuth2 provider \"${provider}\" is not enabled for the ${collection} collection.`);\n }\n if (!returnsToQueryString(info.authURL)) {\n // refused before any cookie is written: the callback could not complete (see 13-ssr.md)\n throw new OAuth2Error(\"unsupported-provider\", `OAuth2 provider \"${provider}\" returns to the callback by POST, which the redirect flow ` +\n \"cannot read. Use the popup flow (authWithOAuth2 and handlePopupHandOff) for it.\");\n }\n const pending = {\n state: info.state,\n codeVerifier: info.codeVerifier ?? \"\",\n provider,\n collection,\n redirectURL: callbackUrl,\n redirectTo: safeRedirectPath(options.redirectTo ?? \"/\"),\n exp: this._clock() + OAUTH2_STATE_TTL_MS,\n };\n await this.cookies.setAll([buildOAuth2CookieToSet(JSON.stringify(pending), this._cookieSettings())], { ...SSR_NO_CACHE_HEADERS });\n return withRedirectUri(info.authURL, callbackUrl);\n }\n /**\n * OAuth2 redirect flow, step 2: the callback route. Reads `code`, `state` and `error` from the\n * callback URL and checks `state` against the temporary cookie. On success it exchanges the\n * code, which writes the session cookie as any sign-in does. The temporary cookie is deleted\n * on every outcome. Failures are returned, not thrown, with `redirectTo` for the caller.\n *\n * @throws only for failures that are not about this sign-in (a network error, a bug)\n */\n async completeOAuth2(input) {\n const callback = new URL(typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url);\n try {\n const pending = await this._readOAuth2Cookie();\n return await this._finishOAuth2(callback, pending);\n }\n finally {\n this._writeCookies(buildClearedOAuth2Cookies(this._cookieSettings()));\n }\n }\n async _readOAuth2Cookie() {\n let rawCookies;\n try {\n rawCookies = await this.cookies.getAll();\n }\n catch {\n return undefined;\n }\n const matched = findCookie(normalizeCookies(rawCookies), OAUTH2_COOKIE_KEY, isSecureSession(this._cookieSettings()));\n return matched?.value ? parseOAuth2Cookie(matched.value) : undefined;\n }\n async _finishOAuth2(callback, pending) {\n const redirectTo = pending ? safeRedirectPath(pending.redirectTo) : \"/\";\n const fail = (error) => ({\n ok: false,\n redirectTo,\n error,\n });\n if (!pending) {\n return fail(new OAuth2Error(\"missing-cookie\", \"The OAuth2 sign-in was not started here.\"));\n }\n if (this._clock() > pending.exp) {\n return fail(new OAuth2Error(\"expired\", \"The OAuth2 sign-in took too long. Start it again.\"));\n }\n const state = callback.searchParams.get(\"state\") ?? \"\";\n if (state === \"\" || !constantTimeEqual(state, pending.state)) {\n return fail(new OAuth2Error(\"state-mismatch\", \"The OAuth2 state does not match.\"));\n }\n const providerError = callback.searchParams.get(\"error\");\n if (providerError !== null) {\n return fail(new OAuth2Error(\"provider-error\", `The OAuth2 provider returned an error: ${providerError}`, providerError));\n }\n const code = callback.searchParams.get(\"code\") ?? \"\";\n if (code === \"\") {\n return fail(new OAuth2Error(\"missing-code\", \"The OAuth2 callback has no code.\"));\n }\n try {\n const auth = await this.collection(pending.collection).authWithOAuth2Code(pending.provider, code, pending.codeVerifier, pending.redirectURL);\n return { ok: true, redirectTo, auth };\n }\n catch (err) {\n if (err instanceof ClientResponseError) {\n return fail(err);\n }\n throw err;\n }\n }\n /** The cookie rules of this client's session cookie, for the cookies that follow it. */\n _cookieSettings() {\n return {\n secure: this._isSecure,\n cookieName: this._cookieName,\n cookieOptions: this._options.cookieOptions,\n readable: this._readable,\n };\n }\n async _loadCookieAndCheck() {\n let rawCookies;\n try {\n rawCookies = await this.cookies.getAll();\n }\n catch {\n return;\n }\n const matched = findCookie(normalizeCookies(rawCookies), this._cookieName, isSecureSession(this._cookieSettings()));\n const session = matched?.value ? parseSessionValue(matched.value) : undefined;\n if (!session) {\n this._setLocalSession(\"\", null);\n return;\n }\n this._lastToken = session.token;\n this._setLocalSession(session.token, recordForToken(session.token, session.record));\n await this._checkSession(false);\n }\n async _checkSession(force) {\n const token = this.authStore.token;\n if (!token) {\n if (force) {\n throw unauthorized(\"The request requires valid authentication token.\");\n }\n return null;\n }\n const payload = getTokenPayload(token);\n const nowMs = this._clock();\n const nowSec = Math.floor((nowMs + this._clockOffsetMs) / 1000);\n if (payload.exp && nowSec > payload.exp + CLOCK_SKEW_SEC) {\n this._dropSession(token);\n if (force) {\n throw unauthorized(\"The token is expired.\");\n }\n return null;\n }\n const cache = this._options.sessionCache;\n if (cache && !force) {\n const cached = await cache.get(token);\n const lifetime = cache.getCollectionLifetime(collectionKey(payload, this.authStore.record));\n // Lifetime unknown (first check in this process): refresh once to learn it.\n if (cached && lifetime && nowMs - cached.checkedAt < cache.ttl) {\n const remaining = payload.exp ? payload.exp - nowSec : 0;\n if (remaining >= lifetime / 2) {\n this._setLocalSession(token, cached.record);\n return cached.record;\n }\n }\n }\n const outcome = cache\n ? await cache.runSingleFlight(token, () => this._requestRefresh(token))\n : await this._requestRefresh(token);\n return this._applyOutcome(token, outcome, force, cache, nowMs);\n }\n /** One `auth-refresh` for the token. Runs once per flight; see `RefreshOutcome`. */\n async _requestRefresh(token) {\n const payload = getTokenPayload(token);\n const collection = this.authStore.record?.collectionName ||\n this.authStore.record?.collectionId ||\n payload.collectionId ||\n (this.authStore.isSuperuser ? \"_superusers\" : \"users\");\n try {\n const res = await this.send(`/api/collections/${encodeURIComponent(collection)}/auth-refresh`, { method: \"POST\", headers: { Authorization: token } });\n return {\n kind: \"ok\",\n token: res?.token || token,\n record: res?.record ?? this.authStore.record,\n };\n }\n catch (err) {\n if (err instanceof ClientResponseError &&\n (err.status === 401 || err.status === 403 || err.status === 404)) {\n return { kind: \"revoked\", error: err };\n }\n return { kind: \"transient\", error: err };\n }\n }\n async _applyOutcome(token, outcome, force, cache, checkedAtMs) {\n switch (outcome.kind) {\n case \"ok\": {\n const record = recordForToken(outcome.token, outcome.record);\n const changed = outcome.token !== token;\n if (cache) {\n if (changed) {\n await cache.delete(token);\n }\n await cache.set(outcome.token, record, checkedAtMs);\n }\n this._setLocalSession(outcome.token, record, changed);\n return record;\n }\n case \"revoked\":\n // 401/403/404: the session is over for this request and its cookie is cleared\n this._dropSession(token);\n if (force) {\n throw outcome.error;\n }\n return null;\n case \"transient\":\n // Network error or 5xx: keep the session, do not touch the cookie or the cache\n if (force) {\n throw outcome.error;\n }\n return this.authStore.record;\n }\n }\n /** Sets the auth store without triggering the listener; optionally writes the cookie. */\n _setLocalSession(token, record, writeCookie = false) {\n this._lastToken = token;\n this._applyLocally(() => {\n if (token) {\n this.authStore.save(token, record);\n }\n else {\n this.authStore.clear();\n }\n });\n if (token && writeCookie) {\n this._writeCookie(token, record);\n }\n }\n /** A change made by the app on this client: update the cache and the cookie. */\n _saveSession(token, record, writeCookie) {\n const matched = recordForToken(token, record);\n this._setLocalSession(token, matched, writeCookie);\n this._options.sessionCache?.set(token, matched, this._clock()).catch(() => { });\n }\n /** Ends the session on this client: the auth store, the cache entry and the cookie. */\n _dropSession(token) {\n this._setLocalSession(\"\", null);\n if (token) {\n this._options.sessionCache?.delete(token).catch(() => { });\n }\n this._writeCookies(buildClearedSessionCookies(this._cookieSettings()));\n }\n _applyLocally(fn) {\n const was = this._applying;\n this._applying = true;\n try {\n fn();\n }\n finally {\n this._applying = was;\n }\n }\n _writeCookie(token, record) {\n const payload = getTokenPayload(token);\n const nowSec = Math.floor((this._clock() + this._clockOffsetMs) / 1000);\n this._writeCookies([\n buildSessionCookieToSet(token, record, {\n ...this._cookieSettings(),\n nowSeconds: nowSec,\n exp: payload.exp,\n }),\n ]);\n }\n _writeCookies(cookies) {\n if (!this.cookies.setAll) {\n return;\n }\n try {\n // Next.js Server Components throw here (cookies are read-only); the request still answers\n Promise.resolve(this.cookies.setAll(cookies, { ...SSR_NO_CACHE_HEADERS })).catch(() => { });\n }\n catch {\n // see above\n }\n }\n /**\n * Learns the server's clock from every response, and the collection's token lifetime from\n * a session response (see `SESSION_RESPONSE_PATH`).\n */\n _observeResponse(path, dateHeader, data) {\n const serverMs = dateHeader ? Date.parse(dateHeader) : NaN;\n if (Number.isNaN(serverMs)) {\n return;\n }\n this._clockOffsetMs = serverMs - this._clock();\n if (this._options.sessionCache) {\n this._options.sessionCache.clockOffsetMs = this._clockOffsetMs;\n }\n const body = data && typeof data === \"object\" ? data : {};\n const token = typeof body[\"token\"] === \"string\" ? body[\"token\"] : \"\";\n if (!token || !SESSION_RESPONSE_PATH.test(path)) {\n return;\n }\n const payload = getTokenPayload(token);\n // Only a session token of an auth collection, for the record it names, describes the\n // collection's lifetime. Impersonate tokens are not refreshable (refreshable: false).\n if (payload.type !== \"auth\" || !payload.exp || payload.refreshable === false) {\n return;\n }\n const record = body[\"record\"];\n if (!record || record.id !== payload.id) {\n return;\n }\n const lifetime = payload.exp - Math.floor(serverMs / 1000);\n const key = collectionKey(payload, record);\n if (lifetime > 0 && key) {\n this._options.sessionCache?.setCollectionLifetime(key, lifetime);\n }\n }\n}\nexport async function createServerClient(options) {\n const client = new ServerClient(options);\n await client.init();\n return client;\n}\n",
6
+ "// @todo remove after https://github.com/reactwg/react-native-releases/issues/287\nconst isReactNative = (typeof navigator !== \"undefined\" && navigator.product === \"ReactNative\") ||\n (typeof global !== \"undefined\" && global.HermesInternal);\nlet atobPolyfill;\nif (typeof atob === \"function\" && !isReactNative) {\n atobPolyfill = atob;\n}\nelse {\n /**\n * The code was extracted from:\n * https://github.com/davidchambers/Base64.js\n */\n atobPolyfill = (input) => {\n const chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\";\n let str = String(input).replace(/=+$/, \"\");\n if (str.length % 4 == 1) {\n throw new Error(\"'atob' failed: The string to be decoded is not correctly encoded.\");\n }\n for (\n // initialize result and counters\n var bc = 0, bs, buffer, idx = 0, output = \"\"; \n // get next character\n (buffer = str.charAt(idx++)); \n // character found in table? initialize bit storage and add its ascii value;\n ~buffer &&\n ((bs = bc % 4 ? bs * 64 + buffer : buffer),\n // and if not first of each 4 characters,\n // convert the first 8 bits to one ascii character\n bc++ % 4)\n ? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6))))\n : 0) {\n // try to find character in table (0-63, not found => -1)\n buffer = chars.indexOf(buffer);\n }\n // oxlint-disable-next-line block-scoped-var -- upstream atob polyfill loop variable\n return output;\n };\n}\n/**\n * Returns JWT token's payload data.\n */\nexport function getTokenPayload(token) {\n if (token) {\n try {\n const encodedPayload = decodeURIComponent(atobPolyfill(token.split(\".\")[1])\n .split(\"\")\n .map(function (c) {\n return \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2);\n })\n .join(\"\"));\n return JSON.parse(encodedPayload) || {};\n }\n catch { }\n }\n return {};\n}\n/**\n * Checks whether a JWT token is expired or not.\n * Tokens without `exp` payload key are considered valid.\n * Tokens with empty payload (eg. invalid token strings) are considered expired.\n *\n * @param token The token to check.\n * @param [expirationThreshold] Time in seconds that will be subtracted from the token `exp` property.\n */\nexport function isTokenExpired(token, expirationThreshold = 0) {\n let payload = getTokenPayload(token);\n if (Object.keys(payload).length > 0 &&\n (!payload.exp || payload.exp - expirationThreshold > Date.now() / 1000)) {\n return false;\n }\n return true;\n}\n",
7
+ "// docs/design/13-ssr.md (\"Session cookie\", \"Clients\")\nimport { BaseAuthStore } from \"@sdk/stores/BaseAuthStore\";\nimport { cookieParse, cookieSerialize } from \"@sdk/tools/cookie\";\nimport { getTokenPayload } from \"@sdk/tools/jwt\";\nexport const SSR_NO_CACHE_HEADERS = Object.freeze({\n \"Cache-Control\": \"private, no-cache, no-store, must-revalidate, max-age=0\",\n Expires: \"0\",\n Pragma: \"no-cache\",\n});\nexport const DEFAULT_COOKIE_KEY = \"pb_auth\";\n/** The temporary cookie of the OAuth2 redirect flow (docs/design/13-ssr.md, \"OAuth2\"). */\nexport const OAUTH2_COOKIE_KEY = \"pb_oauth2\";\nexport const OAUTH2_COOKIE_MAX_AGE_SEC = 600;\nexport function isLocalhost(hostname) {\n const clean = hostname.replace(/^\\[|\\]$/g, \"\").split(\":\")[0] ?? \"\";\n return clean === \"localhost\" || clean === \"127.0.0.1\" || clean === \"::1\";\n}\nexport function isHttpLocalUrl(urlStr) {\n try {\n const parsed = new URL(urlStr);\n return parsed.protocol === \"http:\" && isLocalhost(parsed.hostname);\n }\n catch {\n return false;\n }\n}\nexport function normalizeCookies(raw) {\n if (!raw) {\n return [];\n }\n if (Array.isArray(raw)) {\n return raw;\n }\n return Object.entries(raw).map(([name, value]) => ({ name, value }));\n}\n/** The name a cookie is written under: `__Host-` prefixed on secure origins, plain otherwise. */\nexport function cookieWireName(baseName, secure) {\n return secure ? `__Host-${baseName}` : baseName;\n}\n/**\n * The cookie the SDK reads, by the one name it is written under for `secure` (see\n * `cookieWireName`). On a secure origin the plain name is never read: a sibling subdomain can set\n * a cookie for the parent domain under any name, but only the host itself can set a `__Host-` one.\n */\nexport function findCookie(cookies, baseName, secure) {\n const name = cookieWireName(baseName, secure);\n return cookies.find((c) => c.name === name);\n}\n/**\n * Whether a session cookie is present under either name. The CSRF guard does not know how the\n * app named its cookie (`secure` may come from the app's settings), so both names count: the\n * guard checks more requests, never fewer. Reading a value is still `findCookie`'s job.\n */\nexport function hasSessionCookieName(cookies, baseName) {\n return cookies.some((c) => c.name === `__Host-${baseName}` || c.name === baseName);\n}\n/**\n * The record that belongs to a token, or null: a record for another user is dropped, since a\n * cookie can be edited by its user. Shared by the server and the browser client.\n */\nexport function recordForToken(token, record) {\n const payload = getTokenPayload(token);\n if (record && payload.id && record.id !== payload.id) {\n return null;\n }\n return record;\n}\nexport function safeDecode(value) {\n try {\n return decodeURIComponent(value);\n }\n catch {\n return value;\n }\n}\n/**\n * Reads the token and user record from a session cookie value. Adapters may hand over\n * the value decoded (the request adapter does) or as sent by the browser (Next.js does),\n * so the raw text is tried first and the percent-decoded text second, each decoded once.\n */\nexport function parseSessionValue(raw) {\n const candidates = raw === safeDecode(raw) ? [raw] : [raw, safeDecode(raw)];\n for (const candidate of candidates) {\n let parsed;\n try {\n parsed = JSON.parse(candidate);\n }\n catch {\n continue;\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return undefined;\n }\n const obj = parsed;\n const token = typeof obj[\"token\"] === \"string\" ? obj[\"token\"] : \"\";\n if (!token) {\n return undefined;\n }\n const record = (obj[\"record\"] ?? obj[\"model\"] ?? null);\n return { token, record };\n }\n return undefined;\n}\nexport function formatSessionCookieValue(wireName, token, record, options) {\n const tempStore = new BaseAuthStore();\n tempStore.save(token, record);\n const cookieStr = tempStore.exportToCookie(options, wireName);\n const parsed = cookieParse(cookieStr);\n return parsed[wireName] ?? \"\";\n}\n/** `cookieOptions.secure` overrides the flag; the default is secure (`__Host-`). */\nexport function isSecureSession(settings) {\n return settings.cookieOptions?.secure ?? settings.secure ?? true;\n}\n/**\n * Serialize options shared by writes and clears. The app's `cookieOptions` come first,\n * then the name rules: `__Host-` requires `Secure`, `Path=/` and no `Domain`.\n */\nfunction serializeBase(settings, secure) {\n const opts = {\n path: \"/\",\n sameSite: \"lax\",\n httpOnly: true,\n ...settings.cookieOptions,\n secure,\n };\n if (settings.readable) {\n // the browser client reads this cookie (no superuser credentials; see 13-ssr.md, \"Browser client\")\n opts.httpOnly = false;\n }\n if (secure) {\n delete opts.domain;\n opts.path = \"/\";\n }\n return opts;\n}\nexport function buildSessionCookieToSet(token, record, options) {\n const secure = isSecureSession(options);\n const wireName = secure\n ? `__Host-${options.cookieName ?? DEFAULT_COOKIE_KEY}`\n : (options.cookieName ?? DEFAULT_COOKIE_KEY);\n const nowSec = options.nowSeconds ?? Math.floor(Date.now() / 1000);\n const exp = options.exp;\n const serializeOpts = {\n ...serializeBase(options, secure),\n maxAge: exp !== undefined ? Math.max(0, exp - nowSec) : undefined,\n expires: exp !== undefined ? new Date(exp * 1000) : undefined,\n };\n return {\n name: wireName,\n value: formatSessionCookieValue(wireName, token, record, serializeOpts),\n options: serializeOpts,\n };\n}\n/**\n * Expiring cookies for a cookie's end, one per name the cookie may have been written under.\n * When `secure`, the plain name is cleared too. It is never read on a secure origin, so this\n * only removes what some other writer or an older build left under it. `optionsFor` gives the\n * serialize options of each name's write.\n */\nfunction buildClearedCookies(baseName, secure, optionsFor) {\n const clear = (name, cookieSecure) => ({\n name,\n value: \"\",\n options: { ...optionsFor(cookieSecure), maxAge: 0, expires: new Date(0) },\n });\n if (secure) {\n return [clear(`__Host-${baseName}`, true), clear(baseName, false)];\n }\n return [clear(baseName, false)];\n}\n/** Expiring cookies for a session end. */\nexport function buildClearedSessionCookies(settings) {\n return buildClearedCookies(settings.cookieName ?? DEFAULT_COOKIE_KEY, isSecureSession(settings), (cookieSecure) => serializeBase(settings, cookieSecure));\n}\n/**\n * The temporary OAuth2 cookie: HttpOnly, `SameSite=Lax` (the provider's redirect is a top-level\n * GET), `Path=/`, `Max-Age` 600 and the same `Secure`/`__Host-` rule as the session cookie.\n * The app's `cookieOptions` are not applied (apart from `secure`), so an app cannot make the\n * cookie unreachable from the provider's redirect.\n */\nexport function buildOAuth2CookieToSet(value, settings) {\n const secure = isSecureSession(settings);\n return {\n name: secure ? `__Host-${OAUTH2_COOKIE_KEY}` : OAUTH2_COOKIE_KEY,\n value,\n options: {\n path: \"/\",\n sameSite: \"lax\",\n httpOnly: true,\n secure,\n maxAge: OAUTH2_COOKIE_MAX_AGE_SEC,\n },\n };\n}\n/** Expiring cookies for the temporary OAuth2 cookie, written with the same name rule. */\nexport function buildClearedOAuth2Cookies(settings) {\n return buildClearedCookies(OAUTH2_COOKIE_KEY, isSecureSession(settings), (cookieSecure) => ({\n path: \"/\",\n sameSite: \"lax\",\n httpOnly: true,\n secure: cookieSecure,\n }));\n}\nexport function createRequestCookieAdapter(request, options) {\n const queuedCookies = [];\n const queuedHeaders = {};\n const reqUrl = new URL(request.url);\n const isLocal = reqUrl.protocol === \"http:\" && isLocalhost(reqUrl.hostname);\n const isSecure = options?.secure ?? !isLocal;\n return {\n isSecure,\n getAll() {\n const cookieHeader = request.headers.get(\"cookie\") || \"\";\n const parsed = cookieParse(cookieHeader);\n return Object.entries(parsed).map(([name, value]) => ({\n name,\n value: typeof value === \"string\" ? value : String(value),\n }));\n },\n setAll(cookiesToSet, headers) {\n for (const c of cookiesToSet) {\n queuedCookies.push(c);\n }\n if (headers) {\n for (const [k, v] of Object.entries(headers)) {\n queuedHeaders[k] = v;\n }\n }\n },\n applyToResponse(response) {\n for (const [k, v] of Object.entries(queuedHeaders)) {\n response.headers.set(k, v);\n }\n for (const c of queuedCookies) {\n const serialized = cookieSerialize(c.name, c.value, c.options);\n response.headers.append(\"Set-Cookie\", serialized);\n }\n return response;\n },\n get queuedCookies() {\n return queuedCookies;\n },\n get queuedHeaders() {\n return queuedHeaders;\n },\n };\n}\n",
8
+ "/**\n * -------------------------------------------------------------------\n * Simple cookie parse and serialize utilities mostly based on the\n * node module https://github.com/jshttp/cookie.\n * -------------------------------------------------------------------\n */\n/**\n * RegExp to match field-content in RFC 7230 sec 3.2\n *\n * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]\n * field-vchar = VCHAR / obs-text\n * obs-text = %x80-FF\n */\n// oxlint-disable-next-line no-control-regex -- RFC 6265 cookie spec allows horizontal tab (U+0009)\nconst fieldContentRegExp = /^[\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+$/;\n/**\n * Parses the given cookie header string into an object\n * The object has the various cookies as keys(names) => values\n */\nexport function cookieParse(str, options) {\n const result = {};\n if (typeof str !== \"string\") {\n return result;\n }\n const opt = Object.assign({}, options || {});\n const decode = opt.decode || defaultDecode;\n let index = 0;\n while (index < str.length) {\n const eqIdx = str.indexOf(\"=\", index);\n // no more cookie pairs\n if (eqIdx === -1) {\n break;\n }\n let endIdx = str.indexOf(\";\", index);\n if (endIdx === -1) {\n endIdx = str.length;\n }\n else if (endIdx < eqIdx) {\n // backtrack on prior semicolon\n index = str.lastIndexOf(\";\", eqIdx - 1) + 1;\n continue;\n }\n const key = str.slice(index, eqIdx).trim();\n // only assign once\n if (undefined === result[key]) {\n let val = str.slice(eqIdx + 1, endIdx).trim();\n // quoted values\n if (val.charCodeAt(0) === 0x22) {\n val = val.slice(1, -1);\n }\n try {\n result[key] = decode(val);\n }\n catch {\n result[key] = val; // no decoding\n }\n }\n index = endIdx + 1;\n }\n return result;\n}\n/**\n * Serialize data into a cookie header.\n *\n * Serialize the a name value pair into a cookie string suitable for\n * http headers. An optional options object specified cookie parameters.\n *\n * ```js\n * cookieSerialize('foo', 'bar', { httpOnly: true }) // \"foo=bar; httpOnly\"\n * ```\n */\nexport function cookieSerialize(name, val, options) {\n const opt = Object.assign({}, options || {});\n const encode = opt.encode || defaultEncode;\n if (!fieldContentRegExp.test(name)) {\n throw new TypeError(\"argument name is invalid\");\n }\n const value = encode(val);\n if (value && !fieldContentRegExp.test(value)) {\n throw new TypeError(\"argument val is invalid\");\n }\n let result = name + \"=\" + value;\n if (opt.maxAge != null) {\n const maxAge = opt.maxAge - 0;\n if (isNaN(maxAge) || !isFinite(maxAge)) {\n throw new TypeError(\"option maxAge is invalid\");\n }\n result += \"; Max-Age=\" + Math.floor(maxAge);\n }\n if (opt.domain) {\n if (!fieldContentRegExp.test(opt.domain)) {\n throw new TypeError(\"option domain is invalid\");\n }\n result += \"; Domain=\" + opt.domain;\n }\n if (opt.path) {\n if (!fieldContentRegExp.test(opt.path)) {\n throw new TypeError(\"option path is invalid\");\n }\n result += \"; Path=\" + opt.path;\n }\n if (opt.expires) {\n if (!isDate(opt.expires) || isNaN(opt.expires.valueOf())) {\n throw new TypeError(\"option expires is invalid\");\n }\n result += \"; Expires=\" + opt.expires.toUTCString();\n }\n if (opt.httpOnly) {\n result += \"; HttpOnly\";\n }\n if (opt.secure) {\n result += \"; Secure\";\n }\n if (opt.priority) {\n const priority = typeof opt.priority === \"string\" ? opt.priority.toLowerCase() : opt.priority;\n switch (priority) {\n case \"low\":\n result += \"; Priority=Low\";\n break;\n case \"medium\":\n result += \"; Priority=Medium\";\n break;\n case \"high\":\n result += \"; Priority=High\";\n break;\n default:\n throw new TypeError(\"option priority is invalid\");\n }\n }\n if (opt.sameSite) {\n const sameSite = typeof opt.sameSite === \"string\" ? opt.sameSite.toLowerCase() : opt.sameSite;\n switch (sameSite) {\n case true:\n result += \"; SameSite=Strict\";\n break;\n case \"lax\":\n result += \"; SameSite=Lax\";\n break;\n case \"strict\":\n result += \"; SameSite=Strict\";\n break;\n case \"none\":\n result += \"; SameSite=None\";\n break;\n default:\n throw new TypeError(\"option sameSite is invalid\");\n }\n }\n return result;\n}\n/**\n * Default URL-decode string value function.\n * Optimized to skip native call when no `%`.\n */\nfunction defaultDecode(val) {\n return val.indexOf(\"%\") !== -1 ? decodeURIComponent(val) : val;\n}\n/**\n * Default URL-encode value function.\n */\nfunction defaultEncode(val) {\n return encodeURIComponent(val);\n}\n/**\n * Determines if value is a Date.\n */\nfunction isDate(val) {\n return Object.prototype.toString.call(val) === \"[object Date]\" || val instanceof Date;\n}\n",
9
+ "/**\n * Wraps the fetch that sends a server client's requests. It runs after `beforeSend`, so an\n * app that replaces the send options cannot drop `cache: \"no-store\"` (Next.js would cache\n * the response in its data cache otherwise).\n */\nexport function noStoreFetch(inner, onResponse) {\n return async (url, config) => {\n const response = await (inner ?? globalThis.fetch)(url, { ...config, cache: \"no-store\" });\n onResponse?.(response);\n return response;\n };\n}\n",
10
+ "// docs/design/13-ssr.md (\"OAuth2\"). Upstream: apis/record_auth_methods.go (state, PKCE verifier,\n// authURL ending in `&redirect_uri=`) and apis/record_auth_with_oauth2.go (the code exchange).\nimport { safeDecode } from \"@sdk/ssr/cookies\";\n/** The temporary cookie's lifetime, as its `Max-Age` (see `buildOAuth2CookieToSet`). */\nexport const OAUTH2_STATE_TTL_MS = 600 * 1000;\nexport const DEFAULT_OAUTH2_COLLECTION = \"users\";\n/**\n * A failure of the OAuth2 redirect flow that the SDK detects itself. A failure the server\n * reports is its `ClientResponseError`, unchanged.\n */\nexport class OAuth2Error extends Error {\n constructor(kind, message, providerError) {\n super(message);\n this.name = \"OAuth2Error\";\n this.kind = kind;\n this.providerError = providerError;\n }\n}\nconst FALLBACK_REDIRECT = \"/\";\nconst PROBE_ORIGIN = \"http://app.invalid\";\n/** Backslashes and control characters (URL parsers strip some of them, browsers read others). */\nfunction hasBackslashOrControl(value) {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code === 0x5c || code < 0x20 || code === 0x7f) {\n return true;\n }\n }\n return false;\n}\n/**\n * A same-origin path for `redirectTo`, or `/`. Only a path that starts with one slash is kept:\n * no `//host`, no backslash (browsers read `/\\host` as `//host`), no control characters, no\n * scheme, and nothing that resolves to another origin.\n */\nexport function safeRedirectPath(value) {\n if (typeof value !== \"string\" || value.length === 0 || value[0] !== \"/\") {\n return FALLBACK_REDIRECT;\n }\n if (value.startsWith(\"//\") || hasBackslashOrControl(value)) {\n return FALLBACK_REDIRECT;\n }\n try {\n if (new URL(value, PROBE_ORIGIN).origin !== PROBE_ORIGIN) {\n return FALLBACK_REDIRECT;\n }\n }\n catch {\n return FALLBACK_REDIRECT;\n }\n return value;\n}\n/** Equal strings compare in time that depends only on their length (the state is public). */\nexport function constantTimeEqual(a, b) {\n if (a.length !== b.length) {\n return false;\n }\n let diff = 0;\n for (let i = 0; i < a.length; i++) {\n diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return diff === 0;\n}\n/**\n * The callback URL the provider redirects to. It is registered with the provider, so it must\n * be an absolute http(s) URL; a query or fragment would not match the registration.\n */\nexport function validateCallbackUrl(callbackUrl) {\n let parsed;\n try {\n parsed = new URL(callbackUrl);\n }\n catch {\n throw new OAuth2Error(\"invalid-callback\", \"callbackUrl must be an absolute URL.\");\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n throw new OAuth2Error(\"invalid-callback\", \"callbackUrl must be an http or https URL.\");\n }\n return parsed.href;\n}\nconst SAFE_COLLECTION = /^[A-Za-z0-9_-]{1,100}$/;\nfunction isString(value, max) {\n return typeof value === \"string\" && value.length <= max;\n}\n/**\n * Reads the temporary cookie's value. Adapters may hand it over decoded or as the browser sent\n * it, so the raw text is tried first and the percent-decoded text second (as the session cookie).\n * Anything malformed is `undefined`, which the caller treats as a missing cookie.\n */\nexport function parseOAuth2Cookie(raw) {\n const candidates = raw === safeDecode(raw) ? [raw] : [raw, safeDecode(raw)];\n for (const candidate of candidates) {\n let parsed;\n try {\n parsed = JSON.parse(candidate);\n }\n catch {\n continue;\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return undefined;\n }\n const obj = parsed;\n if (!isString(obj[\"state\"], 256) ||\n obj[\"state\"] === \"\" ||\n !isString(obj[\"codeVerifier\"], 256) ||\n !isString(obj[\"provider\"], 100) ||\n obj[\"provider\"] === \"\" ||\n !isString(obj[\"collection\"], 100) ||\n !SAFE_COLLECTION.test(obj[\"collection\"]) ||\n !isString(obj[\"redirectURL\"], 2048) ||\n obj[\"redirectURL\"] === \"\" ||\n !isString(obj[\"redirectTo\"], 2048) ||\n typeof obj[\"exp\"] !== \"number\" ||\n !Number.isFinite(obj[\"exp\"])) {\n return undefined;\n }\n return {\n state: obj[\"state\"],\n codeVerifier: obj[\"codeVerifier\"],\n provider: obj[\"provider\"],\n collection: obj[\"collection\"],\n redirectURL: obj[\"redirectURL\"],\n redirectTo: obj[\"redirectTo\"],\n exp: obj[\"exp\"],\n };\n }\n return undefined;\n}\n/** The provider's authorization URL with the callback as its `redirect_uri`. */\nexport function withRedirectUri(authURL, callbackUrl) {\n return authURL + encodeURIComponent(callbackUrl);\n}\n/**\n * Whether the provider returns to the callback as a GET with `code`, `state` and `error` in the\n * query string. Upstream asks Apple for `response_mode=form_post`, so Apple returns with a\n * cross-site POST whose form body this flow does not read, and whose `SameSite=Lax` cookie the\n * browser does not send. Only `response_mode=query`, or no `response_mode`, passes; any other\n * mode is refused. The URL may be relative: upstream returns `?client_id=...` for a provider with\n * no authorization endpoint, and the caller appends `redirect_uri` to it.\n */\nexport function returnsToQueryString(authURL) {\n const mode = new URL(authURL, PROBE_ORIGIN).searchParams.get(\"response_mode\");\n return mode === null || mode === \"query\";\n}\n",
11
+ "// docs/design/13-ssr.md (\"Clients\", \"Browser client and realtime\")\nimport Client from \"@sdk/Client\";\nimport { BaseAuthStore } from \"@sdk/stores/BaseAuthStore\";\nimport { getTokenPayload } from \"@sdk/tools/jwt\";\nimport { registerAutoRefresh } from \"@sdk/tools/refresh\";\nimport { noStoreFetch } from \"@sdk/ssr/no-store-fetch\";\nconst DEFAULT_BROWSER_TOKEN_DURATION_SEC = 15 * 60; // 15 minutes (900 seconds)\nconst DEFAULT_AUTO_REFRESH_THRESHOLD_SEC = 300; // 5 minutes\nexport class SuperuserClient extends Client {\n constructor(options) {\n const authStore = new BaseAuthStore();\n super(options.url, authStore, options.lang ?? \"en-US\");\n // iglo.base's clock minus this process's, as the last response's Date header said\n this._clockOffsetMs = 0;\n this._options = options;\n this._clock = options.clock ?? Date.now;\n this.autoCancellation(false);\n }\n /** iglo.base's clock in milliseconds, as the last response's `Date` header read it. */\n serverNow() {\n return this._clock() + this._clockOffsetMs;\n }\n /**\n * Overriding send ensures cache: 'no-store' and custom fetch stick\n * even if application code assigns pb.beforeSend.\n */\n async send(path, options) {\n options.fetch = noStoreFetch(options.fetch ?? this._options.fetch, (response) => {\n const serverMs = Date.parse(response.headers.get(\"date\") ?? \"\");\n if (!Number.isNaN(serverMs)) {\n this._clockOffsetMs = serverMs - this._clock();\n }\n });\n return super.send(path, options);\n }\n async init() {\n const threshold = this._options.autoRefreshThreshold ?? DEFAULT_AUTO_REFRESH_THRESHOLD_SEC;\n if (this._options.token) {\n const payload = getTokenPayload(this._options.token);\n const initialRecord = {\n id: payload.id || \"\",\n collectionId: payload.collectionId || \"_superusers\",\n collectionName: \"_superusers\",\n };\n this.authStore.save(this._options.token, initialRecord);\n // A non-refreshable token (an impersonate API key) cannot be renewed: the server answers\n // its auth-refresh with 403. No refresh is registered, so its requests carry it until it\n // expires and then fail with the server's 401.\n if (threshold > 0 && payload.refreshable !== false) {\n registerAutoRefresh(this, threshold, () => this.collection(\"_superusers\").authRefresh({ autoRefresh: true }), async () => { });\n }\n }\n else if (this._options.email && this._options.password) {\n await this.collection(\"_superusers\").authWithPassword(this._options.email, this._options.password, { autoRefreshThreshold: threshold });\n }\n return this;\n }\n async impersonate(collectionOrRecord, recordIdOrDuration, duration) {\n let collection;\n let recordId;\n let dur;\n if (typeof collectionOrRecord === \"object\" && collectionOrRecord !== null) {\n collection = collectionOrRecord.collectionName || collectionOrRecord.collectionId;\n recordId = collectionOrRecord.id;\n dur =\n typeof recordIdOrDuration === \"number\"\n ? recordIdOrDuration\n : (duration ?? DEFAULT_BROWSER_TOKEN_DURATION_SEC);\n }\n else {\n collection = collectionOrRecord;\n recordId = typeof recordIdOrDuration === \"string\" ? recordIdOrDuration : \"\";\n dur = duration ?? DEFAULT_BROWSER_TOKEN_DURATION_SEC;\n }\n const sendOpts = {\n method: \"POST\",\n body: { duration: dur },\n headers: {\n Authorization: this.authStore.token,\n },\n };\n if (this._options.fetch) {\n sendOpts.fetch = this._options.fetch;\n }\n const res = await this.send(`/api/collections/${encodeURIComponent(collection)}/impersonate/${encodeURIComponent(recordId)}`, sendOpts);\n return {\n token: res.token,\n record: res.record,\n };\n }\n}\nexport async function createSuperuserClient(options) {\n const client = new SuperuserClient(options);\n await client.init();\n return client;\n}\n",
12
+ "// js-sdk v0.28.1 src/tools/refresh.ts\nimport Client from \"@sdk/Client\";\nimport { isTokenExpired } from \"@sdk/tools/jwt\";\n// reset previous auto refresh registrations\nexport function resetAutoRefresh(client) {\n client._resetAutoRefresh?.();\n}\nexport function registerAutoRefresh(client, threshold, refreshFunc, reauthenticateFunc) {\n resetAutoRefresh(client);\n const oldBeforeSend = client.beforeSend;\n const oldModel = client.authStore.record;\n // unset the auto refresh in case the auth store was cleared\n // OR a new model was authenticated\n const unsubStoreChange = client.authStore.onChange((newToken, model) => {\n if (!newToken ||\n model?.id != oldModel?.id ||\n ((model?.collectionId || oldModel?.collectionId) &&\n model?.collectionId != oldModel?.collectionId)) {\n resetAutoRefresh(client);\n }\n });\n // initialize a reset function and attach it dynamically to the client\n client._resetAutoRefresh = function () {\n unsubStoreChange();\n client.beforeSend = oldBeforeSend;\n delete client._resetAutoRefresh;\n };\n client.beforeSend = async (url, sendOptions) => {\n const oldToken = client.authStore.token;\n if (sendOptions.query?.autoRefresh) {\n return oldBeforeSend ? oldBeforeSend(url, sendOptions) : { url, sendOptions };\n }\n let isValid = client.authStore.isValid;\n if (\n // is loosely valid\n isValid &&\n // but it is going to expire in the next \"threshold\" seconds\n isTokenExpired(client.authStore.token, threshold)) {\n try {\n await refreshFunc();\n }\n catch {\n isValid = false;\n }\n }\n // still invalid -> reauthenticate\n if (!isValid) {\n await reauthenticateFunc();\n }\n // the request wasn't sent with a custom token\n const headers = sendOptions.headers || {};\n for (let key in headers) {\n if (key.toLowerCase() == \"authorization\" &&\n // the request wasn't sent with a custom token\n oldToken == headers[key] &&\n client.authStore.token) {\n // set the latest store token\n headers[key] = client.authStore.token;\n break;\n }\n }\n sendOptions.headers = headers;\n return oldBeforeSend ? oldBeforeSend(url, sendOptions) : { url, sendOptions };\n };\n}\n",
13
+ "export const DEFAULT_TTL_MS = 5 * 60 * 1000; // 5 minutes\nexport const DEFAULT_MAX_ENTRIES = 10_000;\nexport async function hashToken(token) {\n const encoder = new TextEncoder();\n const data = encoder.encode(token);\n const digest = await globalThis.crypto.subtle.digest(\"SHA-256\", data);\n const bytes = new Uint8Array(digest);\n let hex = \"\";\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i];\n if (b !== undefined) {\n hex += b.toString(16).padStart(2, \"0\");\n }\n }\n return hex;\n}\n/**\n * Creates the per-process session cache. Pass one instance to every `createServerClient`\n * of the process; it is never a module global.\n */\nexport function createSessionCache(options) {\n const ttl = options?.ttl ?? DEFAULT_TTL_MS;\n const maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;\n const clock = options?.clock ?? Date.now;\n const map = new Map();\n const inFlight = new Map();\n const collectionLifetimes = new Map();\n // Hashing is asynchronous, so the map operations are queued in call order. A `set`\n // started by an auth change is then always visible to a `get` that follows it.\n let queue = Promise.resolve();\n const serial = (task) => {\n const run = queue.then(task);\n queue = run.catch(() => { });\n return run;\n };\n return {\n get(token) {\n if (!token) {\n return Promise.resolve(undefined);\n }\n return serial(async () => {\n const key = await hashToken(token);\n const entry = map.get(key);\n if (!entry) {\n return undefined;\n }\n // LRU refresh: re-insert at tail\n map.delete(key);\n map.set(key, entry);\n return entry;\n });\n },\n set(token, record, checkedAt) {\n if (!token) {\n return Promise.resolve();\n }\n const timestamp = checkedAt ?? clock();\n return serial(async () => {\n const key = await hashToken(token);\n if (map.has(key)) {\n map.delete(key);\n }\n else if (map.size >= maxEntries) {\n const oldestKey = map.keys().next().value;\n if (oldestKey !== undefined) {\n map.delete(oldestKey);\n }\n }\n map.set(key, { record, checkedAt: timestamp });\n });\n },\n delete(token) {\n if (!token) {\n return Promise.resolve();\n }\n return serial(async () => {\n const key = await hashToken(token);\n map.delete(key);\n });\n },\n clear() {\n map.clear();\n inFlight.clear();\n collectionLifetimes.clear();\n },\n async runSingleFlight(token, fn) {\n const key = await hashToken(token);\n const existing = inFlight.get(key);\n if (existing) {\n return existing;\n }\n const promise = (async () => {\n try {\n return await fn();\n }\n finally {\n inFlight.delete(key);\n }\n })();\n inFlight.set(key, promise);\n return promise;\n },\n get size() {\n return map.size;\n },\n get ttl() {\n return ttl;\n },\n get maxEntries() {\n return maxEntries;\n },\n getCollectionLifetime(collectionId) {\n if (!collectionId) {\n return undefined;\n }\n return collectionLifetimes.get(collectionId);\n },\n setCollectionLifetime(collectionId, lifetimeSec) {\n if (!collectionId || lifetimeSec <= 0) {\n return;\n }\n collectionLifetimes.set(collectionId, lifetimeSec);\n },\n clockOffsetMs: 0,\n };\n}\n",
14
+ "// docs/design/13-ssr.md (\"CSRF\"). Convex Auth, Lucia and Better Auth check the Origin the same way.\nimport { createRequestCookieAdapter, DEFAULT_COOKIE_KEY, hasSessionCookieName, } from \"@sdk/ssr/cookies\";\nconst STATE_CHANGING_METHODS = new Set([\"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\nfunction normalizeAllowed(allowedOrigins) {\n return (allowedOrigins ?? []).map((entry) => {\n let parsed;\n try {\n parsed = new URL(entry);\n }\n catch {\n throw new TypeError(`allowedOrigins entry is not a URL: ${entry}`);\n }\n if (parsed.origin === \"null\" || parsed.pathname !== \"/\" || parsed.search || parsed.hash) {\n throw new TypeError(`allowedOrigins entry is not an origin: ${entry}`);\n }\n return parsed.origin;\n });\n}\n/**\n * Whether the request comes from its own origin or an allowed one. `Origin` decides; `Referer`\n * is read only when `Origin` is absent. A request with neither, or with an `Origin` the browser\n * sent as `null`, is rejected. The request's own origin comes from its URL: behind a proxy that\n * changes the public origin, list the public origin in `allowedOrigins`.\n */\nexport function checkOrigin(request, allowedOrigins) {\n // a configuration error is reported on every request, not only on those that reach the list\n const allowed = normalizeAllowed(allowedOrigins);\n const origin = request.headers.get(\"origin\");\n const source = origin ?? request.headers.get(\"referer\");\n if (source === null || source === \"\") {\n return { ok: false, reason: \"missing-origin\" };\n }\n let sourceOrigin;\n try {\n sourceOrigin = new URL(source).origin;\n }\n catch {\n return { ok: false, reason: \"foreign-origin\" };\n }\n if (sourceOrigin === \"null\") {\n return { ok: false, reason: \"foreign-origin\" };\n }\n const own = new URL(request.url).origin;\n if (sourceOrigin === own || allowed.includes(sourceOrigin)) {\n return { ok: true };\n }\n return { ok: false, reason: \"foreign-origin\" };\n}\n/**\n * The CSRF guard for an adapter's request handler, called before the handler runs. It rejects\n * `POST`, `PUT`, `PATCH` and `DELETE` requests that carry the session cookie unless their origin\n * passes `checkOrigin`. Requests without the session cookie (under either name), and `GET`, `HEAD`\n * and `OPTIONS`, pass. A rejection carries a ready 403 `Response`. Frameworks with their own check\n * (Next.js Server Actions) may skip this.\n */\nexport function checkCsrf(request, options = {}) {\n // the configuration is validated first, so an invalid entry throws on every request\n normalizeAllowed(options.allowedOrigins);\n if (!STATE_CHANGING_METHODS.has(request.method.toUpperCase())) {\n return { ok: true };\n }\n const cookies = createRequestCookieAdapter(request).getAll();\n if (!hasSessionCookieName(cookies, options.cookieName ?? DEFAULT_COOKIE_KEY)) {\n return { ok: true };\n }\n const check = checkOrigin(request, options.allowedOrigins);\n if (check.ok) {\n return { ok: true };\n }\n return {\n ok: false,\n reason: check.reason,\n response: new Response(JSON.stringify({ status: 403, message: \"Cross-site request rejected.\", data: {} }), { status: 403, headers: { \"Content-Type\": \"application/json\" } }),\n };\n}\n",
15
+ "// docs/design/13-ssr.md (\"OAuth2\": popup flow; \"Browser client and realtime\"). The popup's token\n// is checked with one auth-refresh, and the session cookie is written from the server's answer.\nimport { ClientResponseError } from \"@sdk/ClientResponseError\";\nimport { getTokenPayload } from \"@sdk/tools/jwt\";\nimport { SSR_NO_CACHE_HEADERS, createRequestCookieAdapter } from \"@sdk/ssr/cookies\";\nimport { checkOrigin } from \"@sdk/ssr/csrf\";\nimport { createServerClient } from \"@sdk/ssr/server-client\";\nfunction jsonResponse(status, body) {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\", ...SSR_NO_CACHE_HEADERS },\n });\n}\nfunction errorResponse(status, message) {\n return jsonResponse(status, { status, message, data: {} });\n}\n/**\n * The adapter route that completes a popup sign-in. It accepts `POST` with the JSON body\n * `{ \"token\": \"...\" }` from the same origin (always checked, with or without a session cookie),\n * and checks the token with one `auth-refresh`. On success the HttpOnly session cookie is written\n * from the refreshed token and record, and the response carries the record only: the token stays\n * in the cookie. The posted token is the only input trusted; nothing else the browser sends is.\n * A revoked token gets 401 and sets no cookie.\n */\nexport async function handlePopupHandOff(request, { allowedOrigins, ...options }) {\n if (request.method !== \"POST\") {\n const response = errorResponse(405, \"Method not allowed.\");\n response.headers.set(\"Allow\", \"POST\");\n return response;\n }\n const origin = checkOrigin(request, allowedOrigins);\n if (!origin.ok) {\n return errorResponse(403, \"Cross-site request rejected.\");\n }\n let body;\n try {\n body = await request.json();\n }\n catch {\n return errorResponse(400, \"The request body must be JSON.\");\n }\n const token = body && typeof body === \"object\" && typeof body.token === \"string\"\n ? body.token\n : \"\";\n const payload = token ? getTokenPayload(token) : {};\n if (!token || payload[\"type\"] !== \"auth\" || typeof payload[\"collectionId\"] !== \"string\") {\n return errorResponse(400, \"The token is not an auth token.\");\n }\n const cookies = createRequestCookieAdapter(request, { secure: options.secure });\n // No cookie is read: the posted token replaces whatever the browser sent before.\n const client = await createServerClient({\n ...options,\n cookies: { getAll: () => [], setAll: cookies.setAll, isSecure: cookies.isSecure },\n });\n let refreshed;\n try {\n refreshed = await client.send(`/api/collections/${encodeURIComponent(payload[\"collectionId\"])}/auth-refresh`, { method: \"POST\", headers: { Authorization: token } });\n }\n catch (err) {\n if (err instanceof ClientResponseError && err.status >= 400 && err.status < 500) {\n return errorResponse(err.status === 401 || err.status === 403 ? 401 : err.status, err.message);\n }\n return errorResponse(503, \"The sign-in could not be checked. Try again.\");\n }\n const record = refreshed?.record;\n if (!record || record.id !== payload[\"id\"]) {\n return errorResponse(503, \"The sign-in could not be checked. Try again.\");\n }\n // authStore.save writes the session cookie and updates the session cache (see ServerClient)\n client.authStore.save(refreshed.token || token, record);\n const response = jsonResponse(200, { record });\n cookies.applyToResponse(response);\n return response;\n}\n",
16
+ "import { getTokenPayload } from \"@sdk/tools/jwt\";\n/** The lifetime of a browser token when the app does not set one (15 minutes). */\nexport const DEFAULT_BROWSER_TOKEN_DURATION_SEC = 900;\n/**\n * What a page needs for its browser client, for the user the server client resumed: a token\n * from the superuser client's `impersonate` (a non-refreshable one, 15 minutes by default), the\n * server's clock at minting (`serverTime`), and the server client's validated record. The session\n * token never leaves the server.\n *\n * The result is per-user: the response that carries it must be `Cache-Control: no-store` and\n * private (`SSR_NO_CACHE_HEADERS`, from `@iglo.tech/iglo.base/ssr`), or a shared cache can hand one user's\n * token to another. The renew route already answers with those headers.\n *\n * Returns `null` when nobody is signed in. Without a superuser client, the server client must\n * be in fallback mode (`readableCookie`): the page then gets the session token, which the session\n * cookie already exposes to page JavaScript.\n *\n * Errors from `impersonate` reach the caller as `ClientResponseError`, with the server's status.\n * They mean different things, and the caller decides what each means for the session\n * (`handleBrowserTokenRenew` does): `404` means the user is gone, while `401` (the superuser token\n * is missing, expired or revoked) and `403` (it is not a superuser's) mean the superuser\n * credentials were refused, which says nothing about the user's session.\n *\n * @throws {TypeError} without a superuser client on a server client that is not in fallback mode\n * @throws {ClientResponseError} when `impersonate` fails (see above)\n */\nexport async function mintBrowserToken(server, superuser, options = {}) {\n const session = server.authStore.token;\n const record = server.authStore.record;\n if (!session || !record) {\n return null;\n }\n let token;\n let serverTime;\n if (superuser) {\n const collection = record.collectionName || record.collectionId;\n const minted = await superuser.impersonate(collection, record.id, options.duration ?? DEFAULT_BROWSER_TOKEN_DURATION_SEC);\n token = minted.token;\n serverTime = superuser.serverNow();\n }\n else if (server.readableCookie) {\n token = session;\n serverTime = server.serverNow();\n }\n else {\n throw new TypeError(\"mintBrowserToken needs a superuser client, or a server client with readableCookie\");\n }\n const exp = getTokenPayload(token).exp;\n if (!exp) {\n throw new Error(\"the token has no expiry\");\n }\n // the record is the server client's own, validated by its session check, never the minted one\n return { token, record, expiresAt: exp * 1000, serverTime };\n}\n",
17
+ "// docs/design/13-ssr.md (\"Browser client and realtime\", \"CSRF\"). The route's shape follows\n// handlePopupHandOff (popup.ts): POST only, the Origin check always runs, and every answer\n// carries SSR_NO_CACHE_HEADERS.\nimport { ClientResponseError } from \"@sdk/ClientResponseError\";\nimport { SSR_NO_CACHE_HEADERS, createRequestCookieAdapter } from \"@sdk/ssr/cookies\";\nimport { checkOrigin } from \"@sdk/ssr/csrf\";\nimport { mintBrowserToken } from \"@sdk/ssr/browser-token\";\nimport { createServerClient } from \"@sdk/ssr/server-client\";\nfunction jsonResponse(status, body, headers) {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\", ...headers, ...SSR_NO_CACHE_HEADERS },\n });\n}\nfunction errorResponse(status, message) {\n const response = jsonResponse(status, { status, message, data: {} });\n if (status === 405) {\n response.headers.set(\"Allow\", \"POST\");\n }\n return response;\n}\n/**\n * The adapter route that renews a browser token: `POST` from the same origin, with the session\n * cookie. It resumes the session through `createServerClient` and checks it with iglo.base\n * (`auth.verify()`): a session revoked since its last check is refused here, though the cache\n * still holds it. On success it answers `200 { token, record, expiresAt, serverTime }` with a\n * freshly minted token, and the session cookie is rewritten if the session was refreshed.\n *\n * Answers: `401` when there is no valid session (the cookie is cleared when iglo.base revoked it,\n * or when impersonate answers `404`: the user was deleted after the check, so the session is\n * over), `403` for a foreign origin, `405` for another method, and `503` when iglo.base cannot be\n * reached, when the session check fails for another reason, or when impersonate answers `401` or\n * `403` (the superuser credentials were refused: missing, expired, revoked, or not a superuser's)\n * or fails for another reason. A `503` keeps the session cookie: the user's session is fine, and\n * an operator's credential problem must not sign every user out. A missing configuration (no\n * superuser client and no `readableCookie`) throws before any request to iglo.base is made.\n */\nexport async function handleBrowserTokenRenew(request, { allowedOrigins, superuser, duration, ...options }) {\n if (request.method !== \"POST\") {\n return errorResponse(405, \"Method not allowed.\");\n }\n const origin = checkOrigin(request, allowedOrigins);\n if (!origin.ok) {\n return errorResponse(403, \"Cross-site request rejected.\");\n }\n if (!superuser && options.readableCookie !== true) {\n // a configuration error: thrown before the session is checked or refreshed\n throw new TypeError(\"handleBrowserTokenRenew needs a superuser client, or readableCookie: true\");\n }\n const cookies = createRequestCookieAdapter(request, { secure: options.secure });\n let client;\n try {\n client = await createServerClient({ ...options, cookies });\n await client.auth.verify();\n }\n catch (err) {\n if (!(err instanceof ClientResponseError)) {\n throw err;\n }\n if (err.status === 401 || err.status === 403 || err.status === 404) {\n return withCookies(errorResponse(401, \"The session is not valid.\"), cookies);\n }\n return errorResponse(503, \"The session could not be checked. Try again.\");\n }\n let minted;\n try {\n minted = await mintBrowserToken(client, superuser, { duration });\n }\n catch (err) {\n if (!(err instanceof ClientResponseError)) {\n throw err; // a programming error, not a sign-in problem\n }\n if (err.status === 404) {\n // impersonate found no such record (deleted since the check): the session is over.\n // Clearing the client's auth store clears the session cookie through the adapter.\n client.authStore.clear();\n return withCookies(errorResponse(401, \"The session is not valid.\"), cookies);\n }\n // 401 and 403 are the superuser credentials refused (missing, expired, revoked, or not a\n // superuser's), not the user's session: the cookie is kept. The session may have been\n // refreshed by the check, so its cookie is written either way.\n return withCookies(errorResponse(503, \"The browser token could not be minted. Try again.\"), cookies);\n }\n if (!minted) {\n return withCookies(errorResponse(401, \"The session is not valid.\"), cookies);\n }\n return withCookies(jsonResponse(200, minted), cookies);\n}\nfunction withCookies(response, cookies) {\n cookies.applyToResponse(response);\n return response;\n}\n"
18
+ ],
19
+ "mappings": "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",
20
+ "debugId": "C1F88B2C03C1857464756E2164756E21",
21
+ "names": []
22
+ }