@iglo.tech/iglo.base 0.0.0-stage → 1.0.1-rc.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.
- package/CHANGELOG.md +22 -0
- package/LICENSE +18 -0
- package/README.md +778 -2
- package/dist/iglo-base.es.mjs +4 -0
- package/dist/iglo-base.es.mjs.map +34 -0
- package/dist/ssr-astro.es.mjs +4 -0
- package/dist/ssr-astro.es.mjs.map +12 -0
- package/dist/ssr-browser-client.es.mjs +4 -0
- package/dist/ssr-browser-client.es.mjs.map +13 -0
- package/dist/ssr-browser.es.mjs +4 -0
- package/dist/ssr-browser.es.mjs.map +10 -0
- package/dist/ssr-next.es.mjs +4 -0
- package/dist/ssr-next.es.mjs.map +12 -0
- package/dist/ssr-nuxt.es.mjs +4 -0
- package/dist/ssr-nuxt.es.mjs.map +12 -0
- package/dist/ssr-react-router.es.mjs +4 -0
- package/dist/ssr-react-router.es.mjs.map +12 -0
- package/dist/ssr-sveltekit.es.mjs +4 -0
- package/dist/ssr-sveltekit.es.mjs.map +12 -0
- package/dist/ssr.es.mjs +4 -0
- package/dist/ssr.es.mjs.map +22 -0
- package/dist/types/src/Client.d.ts +233 -0
- package/dist/types/src/ClientResponseError.d.ts +22 -0
- package/dist/types/src/index.d.ts +18 -0
- package/dist/types/src/services/BackupService.d.ts +59 -0
- package/dist/types/src/services/BaseService.d.ts +8 -0
- package/dist/types/src/services/BatchService.d.ts +56 -0
- package/dist/types/src/services/CollectionService.d.ts +47 -0
- package/dist/types/src/services/CronService.d.ts +20 -0
- package/dist/types/src/services/CrudService.d.ts +85 -0
- package/dist/types/src/services/FileService.d.ts +19 -0
- package/dist/types/src/services/HealthService.d.ts +16 -0
- package/dist/types/src/services/LogService.d.ts +35 -0
- package/dist/types/src/services/RealtimeService.d.ts +124 -0
- package/dist/types/src/services/RecordService.d.ts +374 -0
- package/dist/types/src/services/SQLService.d.ts +21 -0
- package/dist/types/src/services/SettingsService.d.ts +46 -0
- package/dist/types/src/ssr/adapter.d.ts +133 -0
- package/dist/types/src/ssr/astro.d.ts +27 -0
- package/dist/types/src/ssr/browser-client.d.ts +100 -0
- package/dist/types/src/ssr/browser-token.d.ts +29 -0
- package/dist/types/src/ssr/browser.d.ts +1 -0
- package/dist/types/src/ssr/cache.d.ts +9 -0
- package/dist/types/src/ssr/cookies.d.ts +68 -0
- package/dist/types/src/ssr/csrf.d.ts +38 -0
- package/dist/types/src/ssr/index.d.ts +12 -0
- package/dist/types/src/ssr/next.d.ts +53 -0
- package/dist/types/src/ssr/no-store-fetch.d.ts +7 -0
- package/dist/types/src/ssr/nuxt.d.ts +41 -0
- package/dist/types/src/ssr/oauth2.d.ts +56 -0
- package/dist/types/src/ssr/popup.d.ts +14 -0
- package/dist/types/src/ssr/react-router.d.ts +53 -0
- package/dist/types/src/ssr/renew.d.ts +25 -0
- package/dist/types/src/ssr/server-client.d.ts +83 -0
- package/dist/types/src/ssr/superuser-client.d.ts +24 -0
- package/dist/types/src/ssr/sveltekit.d.ts +44 -0
- package/dist/types/src/ssr/types.d.ts +118 -0
- package/dist/types/src/stores/AsyncAuthStore.d.ts +52 -0
- package/dist/types/src/stores/BaseAuthStore.d.ts +104 -0
- package/dist/types/src/stores/LocalAuthStore.d.ts +48 -0
- package/dist/types/src/tools/cookie.d.ts +31 -0
- package/dist/types/src/tools/dtos.d.ts +122 -0
- package/dist/types/src/tools/formdata.d.ts +30 -0
- package/dist/types/src/tools/jwt.d.ts +14 -0
- package/dist/types/src/tools/legacy.d.ts +2 -0
- package/dist/types/src/tools/loose.d.ts +5 -0
- package/dist/types/src/tools/options.d.ts +91 -0
- package/dist/types/src/tools/refresh.d.ts +3 -0
- package/package.json +73 -4
|
@@ -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 astroOptions(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 createAstroAuth(options){let resolved=resolveAdapterOptions(options);return{onRequest:async(context,next)=>{let request=context.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 requestCookies=createRequestCookieAdapter2(request),cookies={getAll:()=>requestCookies.getAll(),isSecure:requestCookies.isSecure,setAll:(toSet)=>{for(let c of toSet)context.cookies.set(c.name,c.value,astroOptions(c.options))}},session=await resumeSession(cookies,request,resolved,policy);return Object.assign(context.locals,{pb:session.server,pbBrowserToken:session.browserToken}),policy.apply(await next())}}}export{createAstroAuth};
|
|
2
|
+
|
|
3
|
+
//# debugId=E9C65BF5446C2CA264756E2164756E21
|
|
4
|
+
//# sourceMappingURL=ssr-astro.es.mjs.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": [".js/src/ssr/astro.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 Astro's `cookies.set` shape. */\nfunction astroOptions(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 Astro auth. Throws a `TypeError` without a superuser client and without\n * `readableCookie: true` (see `resolveAdapterOptions`).\n */\nexport function createAstroAuth(options) {\n const resolved = resolveAdapterOptions(options);\n return {\n onRequest: async (context, next) => {\n const request = context.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 (see sveltekit.ts)\n const routed = await handleRoute(request, resolved);\n if (routed) {\n return policy.apply(routed);\n }\n const requestCookies = createRequestCookieAdapter(request);\n const cookies = {\n getAll: () => requestCookies.getAll(),\n isSecure: requestCookies.isSecure,\n setAll: (toSet) => {\n for (const c of toSet) {\n context.cookies.set(c.name, c.value, astroOptions(c.options));\n }\n },\n };\n const session = await resumeSession(cookies, request, resolved, policy);\n Object.assign(context.locals, {\n pb: session.server,\n pbBrowserToken: session.browserToken,\n });\n // read after next(): the page's render may write cookies or mint a token\n return policy.apply(await next());\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": "E9C65BF5446C2CA264756E2164756E21",
|
|
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";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 defaultDecode(val){return val.indexOf("%")!==-1?decodeURIComponent(val):val}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{}}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 cookieWireName(baseName,secure){return secure?`__Host-${baseName}`: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}var RENEW_FRACTION=0.5,RENEW_BEFORE_EXPIRY_MS=60000,MIN_RENEW_INTERVAL_MS=30000,RETRY_BASE_MS=1000,RETRY_MAX_MS=30000,MAX_RETRIES=10,MAX_TIMER_MS=2147483647,RENEW_REQUEST={method:"POST",cache:"no-store",credentials:"same-origin",headers:{Accept:"application/json"}},defaultTimers={setTimeout:(callback,ms)=>setTimeout(callback,ms),clearTimeout:(handle)=>clearTimeout(handle)};function networkError(url,err){return new ClientResponseError({url,status:0,data:{},originalError:err})}async function responseError(url,res){let data={};try{data=await res.json()}catch{}return new ClientResponseError({url,status:res.status,data})}function identityOf(token){if(!token)return"";let payload=getTokenPayload(token);return`${payload.collectionId??""}/${payload.id??""}`}function readSessionCookie(baseName){let scope=globalThis,raw=scope.document?.cookie;if(!raw)return;let wireName=cookieWireName(baseName,scope.location?.protocol==="https:"),value=cookieParse(raw)[wireName];if(typeof value!=="string"||value==="")return;let session=parseSessionValue(value);if(!session)return;return{token:session.token,record:recordForToken(session.token,session.record)}}class BrowserClient extends Client{constructor(options){super(options.url,new BaseAuthStore2,options.lang??"en-US");if(this._timer=void 0,this._retries=0,this._renewing=void 0,this._disposed=!1,this._offsetMs=0,this._lastRenewedAt=void 0,this._options=options,this._clock=options.clock??Date.now,this._timers=options.timers??defaultTimers,options.serverTime!==void 0&&Number.isFinite(options.serverTime))this._offsetMs=options.serverTime-this._clock();let seeded=options.token?{token:options.token,record:recordForToken(options.token,options.record??null)}:readSessionCookie(options.cookieName??DEFAULT_COOKIE_KEY);if(seeded)this.authStore.save(seeded.token,seeded.record);this._realtimeIdentity=identityOf(this.authStore.token),this.authStore.onChange(()=>this._authChanged()),this._schedule()}async send(path,options){if(options&&!options.fetch&&this._options.fetch)options.fetch=this._options.fetch;return super.send(path,options)}renew(){if(!this._options.renewUrl)return Promise.reject(TypeError("renewUrl is not set"));if(!this._renewing||this._renewing.token!==this.authStore.token){let token=this.authStore.token,promise=this._renewOnce().finally(()=>{if(this._renewing?.promise===promise)this._renewing=void 0});this._renewing={token,promise}}return this._renewing.promise}async handOff(){let url=this._options.handOffUrl;if(!url||!this._options.renewUrl)throw TypeError("handOffUrl and renewUrl are both needed");let token=this.authStore.token;if(!token)throw new ClientResponseError({status:401,data:{message:"There is no sign-in."}});try{await this._postHandOff(url,token),await this.renew()}catch(err){if(this.authStore.token===token)this.authStore.clear();throw err}}dispose(){this._disposed=!0,this._cancelTimer()}async _postHandOff(url,token){let res;try{res=await this._fetch(url,{method:"POST",cache:"no-store",credentials:"same-origin",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({token})})}catch(err){throw networkError(url,err)}if(!res.ok)throw await responseError(url,res)}_fetch(url,init){return(this._options.fetch??globalThis.fetch)(url,init)}_expiresAt(){let exp=getTokenPayload(this.authStore.token).exp;return typeof exp==="number"&&Number.isFinite(exp)&&exp>0?exp*1000:void 0}_serverNow(){return this._clock()+this._offsetMs}_authChanged(){this._retries=0,this._schedule();let identity=identityOf(this.authStore.token),sameRecord=identity===this._realtimeIdentity;this._realtimeIdentity=identity,(sameRecord?this.realtime.resubmit():this.realtime.reconnect()).catch(()=>{})}_schedule(){if(this._cancelTimer(),this._disposed||!this._options.renewUrl)return;let expiresAt=this._expiresAt();if(expiresAt===void 0)return;let remaining=expiresAt-this._serverNow(),delay=Math.max(0,Math.min(remaining*RENEW_FRACTION,remaining-RENEW_BEFORE_EXPIRY_MS));if(this._lastRenewedAt!==void 0)delay=Math.max(delay,this._lastRenewedAt+MIN_RENEW_INTERVAL_MS-this._clock());this._setTimer(delay,()=>{this.renew().catch(()=>{})})}_retryLaterIfSame(before){if(this.authStore.token===before)this._retryLater()}_retryLater(){let expiresAt=this._expiresAt();if(this._disposed||expiresAt===void 0)return;let attempt=this._retries+1,backoff=Math.min(RETRY_BASE_MS*2**(attempt-1),RETRY_MAX_MS),now=this._serverNow();if(attempt>MAX_RETRIES||now+backoff>=expiresAt){this._setTimer(Math.max(0,expiresAt-now),()=>{this.authStore.clear()});return}this._retries=attempt,this._setTimer(backoff,()=>{this.renew().catch(()=>{})})}async _renewOnce(){let url=this._options.renewUrl??"",before=this.authStore.token,res;try{res=await this._fetch(url,RENEW_REQUEST)}catch(err){throw this._retryLaterIfSame(before),networkError(url,err)}if(res.status===401){if(this.authStore.token===before)this.authStore.clear();throw await responseError(url,res)}if(res.status>=500)throw this._retryLaterIfSame(before),await responseError(url,res);if(!res.ok)throw await responseError(url,res);let body;try{body=await res.json()}catch(err){throw this._retryLaterIfSame(before),networkError(url,err)}if(typeof body.token!=="string"||body.token==="")throw new ClientResponseError({url,status:res.status,data:body});if(this.authStore.token!==before)return;if(typeof body.serverTime==="number"&&Number.isFinite(body.serverTime))this._offsetMs=body.serverTime-this._clock();let record=body.record??null;this._lastRenewedAt=this._clock(),this._retries=0,this.authStore.save(body.token,recordForToken(body.token,record))}_setTimer(ms,callback){this._cancelTimer();let wait=Math.min(Math.max(0,ms),MAX_TIMER_MS);this._timer=this._timers.setTimeout(()=>{if(this._timer=void 0,ms>MAX_TIMER_MS)this._setTimer(ms-MAX_TIMER_MS,callback);else callback()},wait)}_cancelTimer(){if(this._timer!==void 0)this._timers.clearTimeout(this._timer),this._timer=void 0}}function createBrowserClient(options){return new BrowserClient(options)}export{BrowserClient,createBrowserClient};
|
|
2
|
+
|
|
3
|
+
//# debugId=6FF509A0D3163E4664756E2164756E21
|
|
4
|
+
//# sourceMappingURL=ssr-browser-client.es.mjs.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": [".js/src/ssr/browser-client.js", ".js/src/tools/cookie.js", ".js/src/tools/jwt.js", ".js/src/ssr/cookies.js"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"// docs/design/13-ssr.md (\"Browser client and realtime\", \"OAuth2\": popup flow, \"CSRF\").\n//\n// The browser entry, `@iglo.tech/iglo.base/ssr/browser`. It is browser-safe: no server client, no\n// superuser client, no Bun-only API. It returns a normal SDK Client, so `instanceof` checks\n// and the realtime and collection services are the main entry's own.\nimport Client from \"@sdk/Client\";\nimport { ClientResponseError } from \"@sdk/ClientResponseError\";\nimport { BaseAuthStore } from \"@sdk/stores/BaseAuthStore\";\nimport { cookieParse } from \"@sdk/tools/cookie\";\nimport { getTokenPayload } from \"@sdk/tools/jwt\";\nimport { DEFAULT_COOKIE_KEY, cookieWireName, parseSessionValue, recordForToken, } from \"@sdk/ssr/cookies\";\n/** The token is renewed when this fraction of its remaining lifetime is left... */\nconst RENEW_FRACTION = 0.5;\n/** ...but never later than this before its expiry. */\nconst RENEW_BEFORE_EXPIRY_MS = 60_000;\n/** Successful renewals are at least this far apart, whatever the token's lifetime or the clocks say. */\nconst MIN_RENEW_INTERVAL_MS = 30_000;\n/** Retry backoff after a network error or a 5xx: doubles from the base, capped. */\nconst RETRY_BASE_MS = 1_000;\nconst RETRY_MAX_MS = 30_000;\n/** Retries in a row before the client stops trying; the store is then cleared at the token's expiry. */\nconst MAX_RETRIES = 10;\n/** The longest delay `setTimeout` takes (2^31 - 1 ms, about 24.8 days). Longer waits are re-armed. */\nconst MAX_TIMER_MS = 2 ** 31 - 1;\nconst RENEW_REQUEST = {\n method: \"POST\",\n cache: \"no-store\",\n credentials: \"same-origin\",\n headers: { Accept: \"application/json\" },\n};\nconst defaultTimers = {\n setTimeout: (callback, ms) => setTimeout(callback, ms),\n clearTimeout: (handle) => clearTimeout(handle),\n};\n/** A failed request that never got a response: a network error, status 0. */\nfunction networkError(url, err) {\n return new ClientResponseError({ url, status: 0, data: {}, originalError: err });\n}\n/** The error for an answer that is not a success. */\nasync function responseError(url, res) {\n let data = {};\n try {\n data = await res.json();\n }\n catch {\n // not JSON: the status is the error\n }\n return new ClientResponseError({ url, status: res.status, data });\n}\n/**\n * Who a token is for, as the realtime service's server sees it: `collectionId/id`, or `\"\"` for\n * no token (a guest). Two tokens with the same identity are the same record.\n */\nfunction identityOf(token) {\n if (!token) {\n return \"\";\n }\n const payload = getTokenPayload(token);\n return `${payload.collectionId ?? \"\"}/${payload.id ?? \"\"}`;\n}\n/**\n * The readable session cookie, read from `document.cookie` (fallback mode). Its name follows the\n * rule the server writes it under: `__Host-` on https, the plain name otherwise.\n */\nfunction readSessionCookie(baseName) {\n const scope = globalThis;\n const raw = scope.document?.cookie;\n if (!raw) {\n return undefined;\n }\n const wireName = cookieWireName(baseName, scope.location?.protocol === \"https:\");\n const value = cookieParse(raw)[wireName];\n if (typeof value !== \"string\" || value === \"\") {\n return undefined;\n }\n const session = parseSessionValue(value);\n if (!session) {\n return undefined;\n }\n return { token: session.token, record: recordForToken(session.token, session.record) };\n}\n/**\n * The SDK client for a page. It holds a short browser token, renews it before it expires,\n * keeps its realtime subscriptions authorized as the signed-in user changes, and hands a popup\n * token off to the server. Create it with `createBrowserClient`.\n */\nexport class BrowserClient extends Client {\n constructor(options) {\n super(options.url, new BaseAuthStore(), options.lang ?? \"en-US\");\n this._timer = undefined;\n this._retries = 0;\n // the renewal in flight, and the token it renews: a new session does not share an old one's request\n this._renewing = undefined;\n this._disposed = false;\n // iglo.base's clock minus this device's clock, learned from `serverTime`\n this._offsetMs = 0;\n // when the last successful renewal happened (this device's clock), for the renewal floor\n this._lastRenewedAt = undefined;\n this._options = options;\n this._clock = options.clock ?? Date.now;\n this._timers = options.timers ?? defaultTimers;\n if (options.serverTime !== undefined && Number.isFinite(options.serverTime)) {\n this._offsetMs = options.serverTime - this._clock();\n }\n const seeded = options.token\n ? {\n token: options.token,\n record: recordForToken(options.token, options.record ?? null),\n }\n : readSessionCookie(options.cookieName ?? DEFAULT_COOKIE_KEY);\n if (seeded) {\n this.authStore.save(seeded.token, seeded.record);\n }\n this._realtimeIdentity = identityOf(this.authStore.token);\n // every change of the token (renewal, sign-out, expiry, a new sign-in) reschedules the\n // renewal and brings the realtime connection in line with the new authorization\n this.authStore.onChange(() => this._authChanged());\n this._schedule();\n }\n /** Sends with the injected fetch (if any) unless the request names its own. */\n async send(path, options) {\n if (options && !options.fetch && this._options.fetch) {\n options.fetch = this._options.fetch;\n }\n return super.send(path, options);\n }\n /**\n * Renews the browser token now, through the renew route. Parallel callers, and the timers,\n * share one request. A 401 clears the auth store. A network error or 5xx keeps the token and\n * retries with a bounded backoff until it expires.\n *\n * @throws {ClientResponseError} when the request fails (status 0 for a network error), or\n * when no `renewUrl` is set\n */\n renew() {\n if (!this._options.renewUrl) {\n return Promise.reject(new TypeError(\"renewUrl is not set\"));\n }\n if (!this._renewing || this._renewing.token !== this.authStore.token) {\n const token = this.authStore.token;\n const promise = this._renewOnce().finally(() => {\n if (this._renewing?.promise === promise) {\n this._renewing = undefined;\n }\n });\n this._renewing = { token, promise };\n }\n return this._renewing.promise;\n }\n /**\n * After `authWithOAuth2()` in a popup: posts the popup's token to the hand-off route, which\n * writes the HttpOnly session cookie, then renews the token. The long-lived popup token is\n * replaced in page memory by the short minted one. On any failure the popup token is cleared\n * from the store rather than kept.\n *\n * @throws {ClientResponseError} when the hand-off or the renewal is refused or fails\n */\n async handOff() {\n const url = this._options.handOffUrl;\n if (!url || !this._options.renewUrl) {\n throw new TypeError(\"handOffUrl and renewUrl are both needed\");\n }\n const token = this.authStore.token;\n if (!token) {\n throw new ClientResponseError({ status: 401, data: { message: \"There is no sign-in.\" } });\n }\n try {\n await this._postHandOff(url, token);\n await this.renew();\n }\n catch (err) {\n // the popup token must not stay in page memory, whatever failed\n if (this.authStore.token === token) {\n this.authStore.clear();\n }\n throw err;\n }\n }\n /** Stops the renewal timers. The client keeps working; it just stops renewing. */\n dispose() {\n this._disposed = true;\n this._cancelTimer();\n }\n async _postHandOff(url, token) {\n let res;\n try {\n res = await this._fetch(url, {\n method: \"POST\",\n cache: \"no-store\",\n credentials: \"same-origin\",\n headers: { \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n body: JSON.stringify({ token }),\n });\n }\n catch (err) {\n throw networkError(url, err);\n }\n if (!res.ok) {\n throw await responseError(url, res);\n }\n }\n _fetch(url, init) {\n return (this._options.fetch ?? globalThis.fetch)(url, init);\n }\n /** The current token's expiry in the server's clock, in milliseconds. */\n _expiresAt() {\n const exp = getTokenPayload(this.authStore.token).exp;\n return typeof exp === \"number\" && Number.isFinite(exp) && exp > 0 ? exp * 1000 : undefined;\n }\n /** This device's clock, moved onto iglo.base's clock. */\n _serverNow() {\n return this._clock() + this._offsetMs;\n }\n _authChanged() {\n this._retries = 0;\n this._schedule();\n const identity = identityOf(this.authStore.token);\n const sameRecord = identity === this._realtimeIdentity;\n this._realtimeIdentity = identity;\n // Upstream accepts a new token for the same record on a live connection, and refuses any\n // other change with 403 (apis/realtime.go). A different user, or a sign-out, gets a new\n // connection, so its client ID is authorized with the current token or as a guest.\n const update = sameRecord ? this.realtime.resubmit() : this.realtime.reconnect();\n update.catch(() => { });\n }\n _schedule() {\n this._cancelTimer();\n if (this._disposed || !this._options.renewUrl) {\n return;\n }\n const expiresAt = this._expiresAt();\n if (expiresAt === undefined) {\n return;\n }\n const remaining = expiresAt - this._serverNow();\n let delay = Math.max(0, Math.min(remaining * RENEW_FRACTION, remaining - RENEW_BEFORE_EXPIRY_MS));\n if (this._lastRenewedAt !== undefined) {\n // a floor on how often successful renewals repeat (a short token, or a clock that is off)\n delay = Math.max(delay, this._lastRenewedAt + MIN_RENEW_INTERVAL_MS - this._clock());\n }\n this._setTimer(delay, () => {\n this.renew().catch(() => { });\n });\n }\n /**\n * After a network error or a 5xx: retries with a bounded backoff. Once the retries are used up,\n * or no further retry fits before the token ends, the store is cleared at the token's expiry.\n */\n _retryLaterIfSame(before) {\n if (this.authStore.token === before) {\n this._retryLater();\n }\n }\n _retryLater() {\n const expiresAt = this._expiresAt();\n if (this._disposed || expiresAt === undefined) {\n return;\n }\n const attempt = this._retries + 1;\n const backoff = Math.min(RETRY_BASE_MS * 2 ** (attempt - 1), RETRY_MAX_MS);\n const now = this._serverNow();\n if (attempt > MAX_RETRIES || now + backoff >= expiresAt) {\n this._setTimer(Math.max(0, expiresAt - now), () => {\n this.authStore.clear();\n });\n return;\n }\n this._retries = attempt;\n this._setTimer(backoff, () => {\n this.renew().catch(() => { });\n });\n }\n async _renewOnce() {\n const url = this._options.renewUrl ?? \"\";\n const before = this.authStore.token;\n // a failed renewal of a session that has since changed must not schedule the new session's\n // retries, or clear it at the expiry of the old one's token\n let res;\n try {\n res = await this._fetch(url, RENEW_REQUEST);\n }\n catch (err) {\n this._retryLaterIfSame(before);\n throw networkError(url, err);\n }\n if (res.status === 401) {\n // the session is over (revoked, expired or deleted): the client continues as a guest\n if (this.authStore.token === before) {\n this.authStore.clear();\n }\n throw await responseError(url, res);\n }\n if (res.status >= 500) {\n this._retryLaterIfSame(before);\n throw await responseError(url, res);\n }\n if (!res.ok) {\n // a route that is not there or refuses this origin: keep the token, do not retry\n throw await responseError(url, res);\n }\n let body;\n try {\n body = (await res.json());\n }\n catch (err) {\n this._retryLaterIfSame(before);\n throw networkError(url, err);\n }\n if (typeof body.token !== \"string\" || body.token === \"\") {\n throw new ClientResponseError({ url, status: res.status, data: body });\n }\n if (this.authStore.token !== before) {\n // the session changed while this renewal was in flight (a sign-out or a new sign-in)\n return;\n }\n if (typeof body.serverTime === \"number\" && Number.isFinite(body.serverTime)) {\n this._offsetMs = body.serverTime - this._clock();\n }\n const record = (body.record ?? null);\n this._lastRenewedAt = this._clock();\n this._retries = 0;\n // saving the token reschedules the next renewal and resubmits the realtime subscriptions\n this.authStore.save(body.token, recordForToken(body.token, record));\n }\n /**\n * Sets a timer. A delay above `setTimeout`'s maximum is waited for in steps: the timer is\n * re-armed when it fires, so no delay is ever cut to 1 ms.\n */\n _setTimer(ms, callback) {\n this._cancelTimer();\n const wait = Math.min(Math.max(0, ms), MAX_TIMER_MS);\n this._timer = this._timers.setTimeout(() => {\n this._timer = undefined;\n if (ms > MAX_TIMER_MS) {\n this._setTimer(ms - MAX_TIMER_MS, callback);\n }\n else {\n callback();\n }\n }, wait);\n }\n _cancelTimer() {\n if (this._timer !== undefined) {\n this._timers.clearTimeout(this._timer);\n this._timer = undefined;\n }\n }\n}\n/**\n * Creates the browser client for a page. Pass the token, its record and `serverTime` from\n * `mintBrowserToken`, which the page gets at render, and the renew route's URL. Without a token,\n * the readable session cookie (fallback mode) is read.\n */\nexport function createBrowserClient(options) {\n return new BrowserClient(options);\n}\n",
|
|
6
|
+
"/**\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",
|
|
7
|
+
"// @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",
|
|
8
|
+
"// 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"
|
|
9
|
+
],
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "6FF509A0D3163E4664756E2164756E21",
|
|
12
|
+
"names": []
|
|
13
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
throw Error('@iglo.tech/iglo.base/ssr is server-only and cannot be imported in browser code. On Next.js, the middleware must run on the Node.js runtime: proxy.ts (Next.js 16), or middleware.ts with export const runtime = "nodejs" (Next.js 15.5). The Edge runtime is not supported.');
|
|
2
|
+
|
|
3
|
+
//# debugId=D73EABB17A57BF2364756E2164756E21
|
|
4
|
+
//# sourceMappingURL=ssr-browser.es.mjs.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": [".js/src/ssr/browser.js"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"// docs/design/13-ssr.md (\"Clients\", \"Runtime (Next.js)\"). The browser condition of every @iglo.tech/iglo.base/ssr\n// subpath resolves here, so a bundle for the browser or for the Edge runtime loads this module and\n// nothing else. Its named exports are missing: Turbopack fails the build on the import, and webpack\n// builds it and the app answers every request with this error.\nthrow new Error('@iglo.tech/iglo.base/ssr is server-only and cannot be imported in browser code. On Next.js, the middleware must run on the Node.js runtime: proxy.ts (Next.js 16), or middleware.ts with export const runtime = \"nodejs\" (Next.js 15.5). The Edge runtime is not supported.');\nexport {};\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "AAIA,MAAU,MAAM,6QAA6Q",
|
|
8
|
+
"debugId": "D73EABB17A57BF2364756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
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}import{createRequestCookieAdapter as createRequestCookieAdapter2}from"./ssr.es.mjs";var SECURE_HEADER="x-iglo-base-secure",ADAPTER_HEADER_PREFIX="x-iglo-base-";function configuredSecure(options){if(options.secure!==void 0)return options.secure;if(options.origin!==void 0)return!isHttpLocalUrl(options.origin);return}function parseSecureFlag(value){if(value==="1")return!0;if(value==="0")return!1;return}function cookiePairs(header){let pairs=[];for(let part of header.split(";")){let pair=part.trim();if(pair==="")continue;let eq=pair.indexOf("=");pairs.push(eq===-1?{name:pair,raw:pair}:{name:pair.slice(0,eq),raw:pair})}return pairs}function cookieHeaderWith(header,written){let changes=new Map;for(let cookie of written)changes.set(cookie.name,cookie.value===""?null:encodeURIComponent(cookie.value));let parts=[],seen=new Set;for(let pair of cookiePairs(header)){if(!changes.has(pair.name)){parts.push(pair.raw);continue}seen.add(pair.name);let value=changes.get(pair.name);if(value!==null&&value!==void 0)parts.push(`${pair.name}=${value}`)}for(let[name,value]of changes)if(!seen.has(name)&&value!==null)parts.push(`${name}=${value}`);return parts.join("; ")}function forwardedHeaders(request,written,secure){let headers=new Headers;if(request.headers.forEach((value,name)=>{if(!name.startsWith(ADAPTER_HEADER_PREFIX))headers.append(name,value)}),headers.set(SECURE_HEADER,secure?"1":"0"),written.length>0)headers.set("cookie",cookieHeaderWith(headers.get("cookie")??"",written));return headers}function storeWriter(cookies){return(toSet)=>{for(let cookie of toSet)try{cookies.set?.(cookie.name,cookie.value,cookie.options)}catch{}}}function createNextAuth(options){let resolved=resolveAdapterOptions(options),passThrough=options.next;async function middleware(request){let policy=noStorePolicy(request,resolved),rejected=csrfRejection(request,resolved);if(rejected)return policy.apply(rejected);let routed=await handleRoute(request,resolved);if(routed)return policy.apply(routed);let cookies=createRequestCookieAdapter2(request,{secure:configuredSecure(resolved)}),secure=cookies.isSecure;await resumeSession(cookies,request,{...resolved,secure},policy);let response=passThrough({request:{headers:forwardedHeaders(request,cookies.queuedCookies,secure)}});if(cookies.queuedCookies.length>0)response=withMutableHeaders(response),cookies.applyToResponse(response);return policy.apply(response)}async function serverSession(input){let flag=parseSecureFlag(input.headers?.get(SECURE_HEADER)??null),secure=resolved.secure??flag??(resolved.origin!==void 0?!isHttpLocalUrl(resolved.origin):!0),cookies={getAll:()=>input.cookies.getAll(),setAll:storeWriter(input.cookies),isSecure:secure},request=new Request(resolved.origin??"http://localhost/");return resumeSession(cookies,request,{...resolved,secure})}return{middleware,serverSession}}export{createNextAuth};
|
|
2
|
+
|
|
3
|
+
//# debugId=CCA8352E506FFFB464756E2164756E21
|
|
4
|
+
//# sourceMappingURL=ssr-next.es.mjs.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": [".js/src/ssr/adapter.js", ".js/src/ssr/cookies.js", ".js/src/ssr/next.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/next; \"Session cookie\"; \"Session cache,\n// check and refresh\"; \"CSRF\"; \"OAuth2\"). The Next.js App Router adapter. proxy.ts (Next.js 16) or\n// middleware.ts (Next.js 15.5, Node.js runtime) resumes the session on every request and forwards\n// the refreshed cookies in the request it passes on, so Server Components (which cannot set cookies)\n// read the session the middleware prepared. Server Components have no response of their own, so the\n// no-cache headers come from the middleware's response and from the dynamic rendering of pages that\n// read cookies. Next.js sets no-store on Server Action responses itself, and a Route Handler that\n// changes the session sets the headers in its own response (docs/design/13-ssr.md, \"Next.js\").\nimport { csrfRejection, handleRoute, noStorePolicy, resolveAdapterOptions, resumeSession, withMutableHeaders, } from \"@sdk/ssr/adapter\";\nimport { isHttpLocalUrl } from \"@sdk/ssr/cookies\";\nimport { createRequestCookieAdapter } from \"@sdk/ssr/index\";\n/** The cookie security the middleware resolved, passed on to the page as `\"1\"` or `\"0\"`. */\nconst SECURE_HEADER = \"x-iglo-base-secure\";\n/** Every request header under this prefix belongs to the adapter: a client's copy is dropped. */\nconst ADAPTER_HEADER_PREFIX = \"x-iglo-base-\";\n/**\n * The cookie security from the options alone: `secure`, else the app origin's scheme. `undefined`\n * when neither is set, so the request decides (as the core does).\n */\nfunction configuredSecure(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}\nfunction parseSecureFlag(value) {\n if (value === \"1\") {\n return true;\n }\n if (value === \"0\") {\n return false;\n }\n return undefined;\n}\n/** The raw `name=value` pairs of a Cookie header, in order. */\nfunction cookiePairs(header) {\n const pairs = [];\n for (const part of header.split(\";\")) {\n const pair = part.trim();\n if (pair === \"\") {\n continue;\n }\n const eq = pair.indexOf(\"=\");\n pairs.push(eq === -1 ? { name: pair, raw: pair } : { name: pair.slice(0, eq), raw: pair });\n }\n return pairs;\n}\n/**\n * The Cookie header of the forwarded request: the browser's cookies with the written ones replaced,\n * removed (an empty value) or added. Values are percent-encoded, as the browser would send them.\n * Cookies the session did not touch are kept as the browser sent them.\n */\nfunction cookieHeaderWith(header, written) {\n const changes = new Map();\n for (const cookie of written) {\n changes.set(cookie.name, cookie.value === \"\" ? null : encodeURIComponent(cookie.value));\n }\n const parts = [];\n const seen = new Set();\n for (const pair of cookiePairs(header)) {\n if (!changes.has(pair.name)) {\n parts.push(pair.raw);\n continue;\n }\n seen.add(pair.name);\n const value = changes.get(pair.name);\n if (value !== null && value !== undefined) {\n parts.push(`${pair.name}=${value}`);\n }\n }\n for (const [name, value] of changes) {\n if (!seen.has(name) && value !== null) {\n parts.push(`${name}=${value}`);\n }\n }\n return parts.join(\"; \");\n}\n/**\n * The headers the page's request is forwarded with. Any `x-iglo-base-*` header the client sent is\n * dropped first, so only the middleware's own copy of the flag reaches the page.\n */\nfunction forwardedHeaders(request, written, secure) {\n const headers = new Headers();\n request.headers.forEach((value, name) => {\n if (!name.startsWith(ADAPTER_HEADER_PREFIX)) {\n headers.append(name, value);\n }\n });\n headers.set(SECURE_HEADER, secure ? \"1\" : \"0\");\n if (written.length > 0) {\n headers.set(\"cookie\", cookieHeaderWith(headers.get(\"cookie\") ?? \"\", written));\n }\n return headers;\n}\n/** A cookie store's writes, through its `set` (a read-only store has none, or it throws). */\nfunction storeWriter(cookies) {\n return (toSet) => {\n for (const cookie of toSet) {\n try {\n cookies.set?.(cookie.name, cookie.value, cookie.options);\n }\n catch {\n // a Server Component's store throws here: the refreshed session stays in memory only\n }\n }\n };\n}\n/**\n * The Next.js App Router adapter. The options carry the app's session cache and superuser client,\n * which it creates once; nothing here keeps state between requests.\n *\n * It runs on the Node.js runtime only: Next.js 16 `proxy.ts` always does, and Next.js 15.5\n * `middleware.ts` does with `export const runtime = \"nodejs\"`. The Edge runtime is not supported.\n * On Edge the package's browser condition resolves to a stub, so the import fails at build time\n * or the app answers every request with the stub's error (docs/design/13-ssr.md, \"Runtime\").\n *\n * @throws {TypeError} without a superuser client and without `readableCookie: true`\n */\nexport function createNextAuth(options) {\n const resolved = resolveAdapterOptions(options);\n const passThrough = options.next;\n async function middleware(request) {\n // Server Components cannot set headers, so only the middleware's own response gets them here\n const policy = noStorePolicy(request, resolved);\n const rejected = csrfRejection(request, resolved);\n if (rejected) {\n return policy.apply(rejected);\n }\n const routed = await handleRoute(request, resolved);\n if (routed) {\n return policy.apply(routed);\n }\n const cookies = createRequestCookieAdapter(request, { secure: configuredSecure(resolved) });\n const secure = cookies.isSecure;\n await resumeSession(cookies, request, { ...resolved, secure }, policy);\n let response = passThrough({\n request: { headers: forwardedHeaders(request, cookies.queuedCookies, secure) },\n });\n if (cookies.queuedCookies.length > 0) {\n // the cookies are written onto the response, so its headers must be mutable\n response = withMutableHeaders(response);\n cookies.applyToResponse(response);\n }\n return policy.apply(response);\n }\n async function serverSession(input) {\n // the middleware's flag names the cookie; the app's own setting wins over it, and the origin\n // decides only when the middleware did not run (no flag)\n const flag = parseSecureFlag(input.headers?.get(SECURE_HEADER) ?? null);\n const secure = resolved.secure ??\n flag ??\n (resolved.origin !== undefined ? !isHttpLocalUrl(resolved.origin) : true);\n const cookies = {\n getAll: () => input.cookies.getAll(),\n setAll: storeWriter(input.cookies),\n isSecure: secure,\n };\n // resumeSession wants the request for its origin, which the explicit `secure` makes unused\n const request = new Request(resolved.origin ?? \"http://localhost/\");\n return resumeSession(cookies, request, { ...resolved, secure });\n }\n return { middleware, serverSession };\n}\n"
|
|
8
|
+
],
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "CCA8352E506FFFB464756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{createRequestCookieAdapter as createRequestCookieAdapter2,SSR_NO_CACHE_HEADERS as SSR_NO_CACHE_HEADERS3}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 nuxtOptions(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 headerValue(value){return Array.isArray(value)?value.join(", "):value}function originOf(headers,options){if(options.origin!==void 0)return options.origin;let host=headers.get("host")??"localhost";return`${headers.get("x-forwarded-proto")?.split(",")[0]?.trim()||"http"}://${host}`}async function requestOf(event,options){let headers=new Headers;for(let[name,value]of Object.entries(event.headers)){let text=headerValue(value);if(text!==void 0)headers.set(name,text)}let url=new URL(`/${event.path.replace(/^\/+/,"")}`,originOf(headers,options)),body;if(options.routes.popup!==!1&&url.pathname===options.routes.popup&&event.method==="POST"&&event.readBody)try{body=JSON.stringify(await event.readBody())}catch{body=void 0}return new Request(url,{method:event.method,headers,body})}function createNuxtAuth(options){let resolved=resolveAdapterOptions(options);return{middleware:async(event)=>{let request=await requestOf(event,resolved),policy=noStorePolicy(request,resolved,()=>{for(let[name,value]of Object.entries(SSR_NO_CACHE_HEADERS3))event.setHeader(name,value)}),denied=csrfRejection(request,resolved);if(denied)return policy.apply(denied);let routed=await handleRoute(request,resolved);if(routed)return policy.apply(routed);let requestCookies=createRequestCookieAdapter2(request),cookies={getAll:()=>requestCookies.getAll(),isSecure:requestCookies.isSecure,setAll:(toSet)=>{for(let c of toSet)event.setCookie(c.name,c.value,nuxtOptions(c.options))}},session=await resumeSession(cookies,request,resolved,policy);event.context.pb=session.server,event.context.pbBrowserToken=session.browserToken;return}}}export{createNuxtAuth};
|
|
2
|
+
|
|
3
|
+
//# debugId=3EA9D0D637EC3A1064756E2164756E21
|
|
4
|
+
//# sourceMappingURL=ssr-nuxt.es.mjs.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": [".js/src/ssr/nuxt.js", ".js/src/ssr/adapter.js", ".js/src/ssr/cookies.js"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { createRequestCookieAdapter, SSR_NO_CACHE_HEADERS, } 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 h3's `setCookie` shape. */\nfunction nuxtOptions(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}\nfunction headerValue(value) {\n return Array.isArray(value) ? value.join(\", \") : value;\n}\n/** The request's origin, as the app's public origin or as the Host header tells it. */\nfunction originOf(headers, options) {\n if (options.origin !== undefined) {\n return options.origin;\n }\n const host = headers.get(\"host\") ?? \"localhost\";\n const proto = headers.get(\"x-forwarded-proto\")?.split(\",\")[0]?.trim() || \"http\";\n return `${proto}://${host}`;\n}\n/**\n * The Web request of a Nitro event. The popup route's JSON body is read here, once, since the\n * event's body can be read only once. A body that is not JSON becomes an unreadable body, which\n * the popup route answers with its own 400.\n */\nasync function requestOf(event, options) {\n const headers = new Headers();\n for (const [name, value] of Object.entries(event.headers)) {\n const text = headerValue(value);\n if (text !== undefined) {\n headers.set(name, text);\n }\n }\n // a path that starts with \"//\" must not become a URL on another host\n const url = new URL(`/${event.path.replace(/^\\/+/, \"\")}`, originOf(headers, options));\n let body;\n const isPopup = options.routes.popup !== false && url.pathname === options.routes.popup;\n if (isPopup && event.method === \"POST\" && event.readBody) {\n try {\n body = JSON.stringify(await event.readBody());\n }\n catch {\n body = undefined;\n }\n }\n return new Request(url, { method: event.method, headers, body });\n}\n/**\n * Creates the Nuxt auth. Throws a `TypeError` without a superuser client and without\n * `readableCookie: true` (see `resolveAdapterOptions`).\n */\nexport function createNuxtAuth(options) {\n const resolved = resolveAdapterOptions(options);\n return {\n middleware: async (event) => {\n const request = await requestOf(event, resolved);\n // Nitro sends the headers after the handler, so they are set as soon as the request turns\n // out to carry user data: at once when it has a session cookie, else at the first write or\n // mint (the handler runs after this middleware returns)\n const policy = noStorePolicy(request, resolved, () => {\n for (const [name, value] of Object.entries(SSR_NO_CACHE_HEADERS)) {\n event.setHeader(name, value);\n }\n });\n const denied = csrfRejection(request, resolved);\n if (denied) {\n return policy.apply(denied);\n }\n // the routes run before the session is resumed (see sveltekit.ts)\n const routed = await handleRoute(request, resolved);\n if (routed) {\n return policy.apply(routed);\n }\n const requestCookies = createRequestCookieAdapter(request);\n const cookies = {\n getAll: () => requestCookies.getAll(),\n isSecure: requestCookies.isSecure,\n setAll: (toSet) => {\n for (const c of toSet) {\n event.setCookie(c.name, c.value, nuxtOptions(c.options));\n }\n },\n };\n const session = await resumeSession(cookies, request, resolved, policy);\n event.context.pb = session.server;\n event.context.pbBrowserToken = session.browserToken;\n return undefined;\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": "3EA9D0D637EC3A1064756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
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}import{createRequestCookieAdapter as createRequestCookieAdapter2}from"./ssr.es.mjs";function sessionHeaders({loaderHeaders,actionHeaders,parentHeaders,errorHeaders}){let headers=new Headers(parentHeaders);if([loaderHeaders,actionHeaders,errorHeaders].some((source)=>source?.get("cache-control")?.includes("no-store")===!0))setNoStoreHeaders(headers);return headers}function createReactRouterAuth(options){let resolved=resolveAdapterOptions(options);return{async session(request){let policy=noStorePolicy(request,resolved),rejected=csrfRejection(request,resolved);if(rejected)return{response:policy.apply(rejected)};let routed=await handleRoute(request,resolved);if(routed)return{response:policy.apply(routed)};let cookies=createRequestCookieAdapter2(request,{secure:resolved.secure});return{session:await resumeSession(cookies,request,resolved,policy),headers:()=>{let response=cookies.applyToResponse(new Response(null));return policy.apply(response).headers}}}}}export{createReactRouterAuth,sessionHeaders};
|
|
2
|
+
|
|
3
|
+
//# debugId=F7C64D06518C224864756E2164756E21
|
|
4
|
+
//# sourceMappingURL=ssr-react-router.es.mjs.map
|