@pontive/pontkit-server 1.0.4 → 1.1.1
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/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +51 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const _=require("jose"),Y=10080*60;function Z(t){const e=["issuer","clientId","sessionSecret"];for(const r of e)if(!t[r])throw new Error(`@pontive/pontkit-server: ${r} is required`);if(t.sessionSecret.length<32)throw new Error("@pontive/pontkit-server: sessionSecret must be at least 32 characters");if(t.baseUrl)try{new URL(t.baseUrl)}catch{throw new Error(`@pontive/pontkit-server: baseUrl "${t.baseUrl}" is not an absolute URL`)}return{issuer:t.issuer.replace(/\/+$/,""),clientId:t.clientId,clientSecret:t.clientSecret||void 0,sessionSecret:t.sessionSecret,baseUrl:t.baseUrl?new URL(t.baseUrl).origin:void 0,scopes:t.scopes??"openid email profile offline_access",basePath:(t.basePath??"/api/auth").replace(/\/+$/,""),redirectUri:t.redirectUri,signInPath:t.signInPath??"/login",afterSignInPath:t.afterSignInPath??"/",prompt:t.prompt,cookieName:t.cookieName??"pontive_session",secureCookies:t.secureCookies,sessionMaxAgeSeconds:t.sessionMaxAgeSeconds??Y,refreshSkewSeconds:t.refreshSkewSeconds??60,fetch:t.fetch??((...r)=>fetch(...r)),logger:t.logger??console}}const T=3500,$=16;function C(t){const e=new Map;if(!t)return e;for(const r of t.split(";")){const n=r.indexOf("=");if(n<0)continue;const s=r.slice(0,n).trim();!s||e.has(s)||e.set(s,r.slice(n+1).trim())}return e}function I(t){return[...t].map(([e,r])=>`${e}=${r}`).join("; ")}function U(t,e,r){const n=[`${t}=${e}`,"Path=/","HttpOnly","SameSite=Lax",`Max-Age=${r.maxAge}`];return r.secure&&n.push("Secure"),n.join("; ")}function y(t,e){return e===0?t:`${t}.${e}`}function Q(t,e){const r=t.get(e);if(r===void 0)return null;let n=r;for(let s=1;s<$;s++){const a=t.get(y(e,s));if(a===void 0)break;n+=a}return n}function q(t,e,r,n){const s=[],a=Math.max(1,Math.ceil(r.length/T));for(let u=0;u<a;u++){const d=r.slice(u*T,(u+1)*T);t.set(y(e,u),d),s.push(U(y(e,u),d,n))}for(let u=a;u<$;u++){const d=y(e,u);if(!t.has(d))break;t.delete(d),s.push(U(d,"",{...n,maxAge:0}))}return s}function R(t,e,r){const n=[];for(let s=0;s<$;s++){const a=y(e,s);if(!t.has(a)){if(s>0)break;continue}t.delete(a),n.push(U(a,"",{maxAge:0,secure:r}))}return n}class W extends Error{constructor(e,r,n){super(`token endpoint ${e} ${r}: ${n}`),this.status=e,this.code=r}status;code}const ee=600*1e3,E=new Map,M=new Map;function te(t){return t.replace(/\/+$/,"")}function A(t,e){const r=te(t);let n=E.get(r);return n||(n=e(`${r}/.well-known/openid-configuration`).then(async s=>{if(!s.ok)throw new Error(`OIDC discovery failed for ${r}: ${s.status}`);return await s.json()}).catch(s=>{throw E.delete(r),s}),E.set(r,n)),n}async function L(t,e,r){const n=M.get(t);if(n&&!r&&Date.now()-n.at<ee)return n.set;const s=await e(t);if(!s.ok)throw new Error(`JWKS fetch failed for ${t}: ${s.status}`);const a=_.createLocalJWKSet(await s.json());return M.set(t,{at:Date.now(),set:a}),a}function F(t){let e="";for(const r of t)e+=String.fromCharCode(r);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function v(){return F(crypto.getRandomValues(new Uint8Array(32)))}async function re(t){const e=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t));return F(new Uint8Array(e))}async function ne(t){return[...new Uint8Array(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t)))].map(r=>r.toString(16).padStart(2,"0")).join("")}async function se(t,e,r){const n=await A(t.issuer,e),s=new URL(n.authorization_endpoint);return s.searchParams.set("response_type","code"),s.searchParams.set("client_id",t.clientId),s.searchParams.set("redirect_uri",r.redirectUri),s.searchParams.set("scope",r.scopes),s.searchParams.set("state",r.state),s.searchParams.set("nonce",r.nonce),s.searchParams.set("code_challenge",r.codeChallenge),s.searchParams.set("code_challenge_method","S256"),r.prompt&&s.searchParams.set("prompt",r.prompt),s.toString()}async function K(t,e,r){const n=await A(t.issuer,e),s=new URLSearchParams(r);s.set("client_id",t.clientId);const a={"content-type":"application/x-www-form-urlencoded"};if(t.clientSecret){const d=`${encodeURIComponent(t.clientId)}:${encodeURIComponent(t.clientSecret)}`;a.authorization=`Basic ${btoa(d)}`}const u=await e(n.token_endpoint,{method:"POST",headers:a,body:s});if(!u.ok){let d="unknown_error",k="";try{const S=await u.json();d=S.error??d,k=S.error_description??""}catch{}throw new W(u.status,d,k)}return await u.json()}function oe(t,e,r){return K(t,e,{grant_type:"authorization_code",code:r.code,code_verifier:r.codeVerifier,redirect_uri:r.redirectUri})}function ie(t,e,r){return K(t,e,{grant_type:"refresh_token",refresh_token:r})}async function ae(t,e,r,n){const s=await A(t.issuer,e),a={issuer:s.issuer,audience:t.clientId};let u;try{u=await _.jwtVerify(r,await L(s.jwks_uri,e,!1),a)}catch(d){if(d.code!=="ERR_JWKS_NO_MATCHING_KEY")throw d;u=await _.jwtVerify(r,await L(s.jwks_uri,e,!0),a)}if(u.payload.nonce!==n)throw new Error("id_token nonce does not match the sign-in request");return u.payload}async function ce(t,e,r){const n=await A(t.issuer,e);if(!n.end_session_endpoint)return null;const s=new URL(n.end_session_endpoint);return s.searchParams.set("client_id",t.clientId),s.searchParams.set("post_logout_redirect_uri",r.postLogoutRedirectUri),r.sessionId&&s.searchParams.set("logout_hint",r.sessionId),s.toString()}const ue=3e4;class de{constructor(e=ue){this.recentMs=e}recentMs;inFlight=new Map;recent=new Map;async run(e,r){const n=await ne(e);this.sweep();const s=this.recent.get(n);if(s)return s.result;let a=this.inFlight.get(n);return a||(a=r().then(u=>(this.recent.set(n,{at:Date.now(),result:u}),u)).finally(()=>this.inFlight.delete(n)),this.inFlight.set(n,a)),a}sweep(){const e=Date.now()-this.recentMs;for(const[r,n]of this.recent)n.at<e&&this.recent.delete(r)}}const N=new Map;function V(t){let e=N.get(t);return e||(e=crypto.subtle.digest("SHA-256",new TextEncoder().encode(t)).then(r=>new Uint8Array(r)),N.set(t,e)),e}async function j(t,e,r){return new _.EncryptJWT(t).setProtectedHeader({alg:"dir",enc:"A256GCM"}).setIssuedAt().setExpirationTime(`${r}s`).encrypt(await V(e))}async function D(t,e){try{const{payload:r}=await _.jwtDecrypt(t,await V(e));return r}catch{return null}}const le=900,H=600;function he(t,e){return t&&t.startsWith("/")&&!t.startsWith("//")&&!t.startsWith("/\\")?t:e}function m(t,e=[]){const r=new Headers({location:t,"cache-control":"no-store"});for(const n of e)r.append("set-cookie",n);return new Response(null,{status:302,headers:r})}function x(t,e){if(t.baseUrl)return t.baseUrl;const r=e.headers.get("x-forwarded-host")?.split(",")[0]?.trim()||e.headers.get("host")?.trim();if(r){const n=e.headers.get("x-forwarded-proto")?.split(",")[0]?.trim()||new URL(e.url).protocol.replace(":","");try{return new URL(`${n}://${r}`).origin}catch{}}return new URL(e.url).origin}function fe(t){const e=n=>typeof t[n]=="string"?t[n]:void 0,r=e("name")??([e("given_name"),e("family_name")].filter(Boolean).join(" ")||void 0)??e("email");return{id:t.sub??"",email:e("email"),name:r,username:e("preferred_username"),picture:e("picture"),emailVerified:t.email_verified===!0}}function O(t){return Date.now()+(t.expires_in??le)*1e3}function we(t){const e=Z(t),r={issuer:e.issuer,clientId:e.clientId,clientSecret:e.clientSecret},n=`${e.cookieName}_tx`,s=new de,a=o=>e.secureCookies??x(e,o).startsWith("https:"),u=o=>e.redirectUri??`${x(e,o)}${e.basePath}/callback`,d=(o,i)=>{const c=new URL(e.signInPath,x(e,o));return i&&c.searchParams.set("error",i),c.toString()};async function k(o){const i=Q(o,e.cookieName);if(i===null)return null;const c=await D(i,e.sessionSecret);return c||e.logger.warn("[pontive] session cookie could not be read (sealed with another secret, or truncated)"),c}async function S(o,i,c){const l=await j({...i},e.sessionSecret,e.sessionMaxAgeSeconds);return q(o,e.cookieName,l,{maxAge:e.sessionMaxAgeSeconds,secure:a(c)})}function b(o){const{refreshToken:i,...c}=o;return c}async function G(o){const i=new URL(o.url),c={state:v(),verifier:v(),nonce:v(),returnTo:he(i.searchParams.get("returnTo")??i.searchParams.get("next"),e.afterSignInPath)},l=await se(r,e.fetch,{redirectUri:u(o),scopes:e.scopes,state:c.state,nonce:c.nonce,codeChallenge:await re(c.verifier),prompt:e.prompt}),h=await j({...c},e.sessionSecret,H);return m(l,[U(n,h,{maxAge:H,secure:a(o)})])}async function z(o){const i=new URL(o.url),c=C(o.headers.get("cookie")),l=[U(n,"",{maxAge:0,secure:a(o)})],h=i.searchParams.get("error");if(h)return m(d(o,h),l);const f=c.get(n),g=f?await D(f,e.sessionSecret):null,w=i.searchParams.get("code");if(!g||!w||i.searchParams.get("state")!==g.state)return m(d(o,"invalid_state"),l);try{const p=await oe(r,e.fetch,{code:w,codeVerifier:g.verifier,redirectUri:u(o)});if(!p.id_token)throw new Error("the token response carried no id_token; is the openid scope requested?");const P=await ae(r,e.fetch,p.id_token,g.nonce),X={user:fe(P),accessToken:p.access_token,refreshToken:p.refresh_token,expiresAt:O(p),sessionId:typeof P.sid=="string"?P.sid:void 0};l.push(...await S(c,X,o))}catch(p){return e.logger.warn("[pontive] sign-in failed:",p instanceof Error?p.message:String(p)),m(d(o,"exchange_failed"),l)}return m(new URL(g.returnTo,x(e,o)).toString(),l)}async function J(o){const i=C(o.headers.get("cookie")),c=await k(i),l=R(i,e.cookieName,a(o));let h=d(o);try{h=await ce(r,e.fetch,{postLogoutRedirectUri:d(o),sessionId:c?.sessionId})??h}catch(f){e.logger.warn("[pontive] could not reach the issuer to end its session:",f instanceof Error?f.message:String(f))}return m(h,l)}async function B(o){try{const i=await ie(r,e.fetch,o.refreshToken);return{...o,accessToken:i.access_token,refreshToken:i.refresh_token??o.refreshToken,expiresAt:O(i)}}catch(i){if(i instanceof W&&i.status>=400&&i.status<500)return e.logger.warn("[pontive] refresh refused; signing out:",i.message),null;throw i}}return{async handle(o){const{pathname:i}=new URL(o.url);if(!i.startsWith(`${e.basePath}/`))return null;const c=i.slice(e.basePath.length+1);return c==="login"&&o.method==="GET"?G(o):c==="callback"&&o.method==="GET"?z(o):c==="logout"&&(o.method==="GET"||o.method==="POST")?J(o):null},async getSession(o){const i=await k(C(o));return i?Date.now()>=i.expiresAt?(e.logger.warn("[pontive] the session's access token has expired and getSession cannot refresh it. Run the SDK's middleware on this path so it refreshes before the request is handled."),null):b(i):null},async refresh(o){const i=C(o.headers.get("cookie")),c=w=>({session:w,setCookies:[],cookieHeader:o.headers.get("cookie")??""}),l=await k(i);if(!l)return c(null);if(Date.now()<l.expiresAt-e.refreshSkewSeconds*1e3)return c(b(l));const h=()=>({session:null,setCookies:R(i,e.cookieName,a(o)),cookieHeader:I(i)});if(!l.refreshToken)return h();let f;try{f=await s.run(l.refreshToken,()=>B(l))}catch(w){return e.logger.warn("[pontive] refresh failed, will retry:",w instanceof Error?w.message:String(w)),c(Date.now()<l.expiresAt?b(l):null)}if(!f)return h();const g=await S(i,f,o);return{session:b(f),setCookies:g,cookieHeader:I(i)}}}}exports.createServerAuth=we;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const U=require("jose"),Y=10080*60;function Z(t){const e=["issuer","clientId","sessionSecret"];for(const r of e)if(!t[r])throw new Error(`@pontive/pontkit-server: ${r} is required`);if(t.sessionSecret.length<32)throw new Error("@pontive/pontkit-server: sessionSecret must be at least 32 characters");if(t.baseUrl)try{new URL(t.baseUrl)}catch{throw new Error(`@pontive/pontkit-server: baseUrl "${t.baseUrl}" is not an absolute URL`)}return{issuer:t.issuer.replace(/\/+$/,""),clientId:t.clientId,clientSecret:t.clientSecret||void 0,sessionSecret:t.sessionSecret,baseUrl:t.baseUrl?new URL(t.baseUrl).origin:void 0,scopes:t.scopes??"openid email profile offline_access",basePath:(t.basePath??"/api/auth").replace(/\/+$/,""),redirectUri:t.redirectUri,signInPath:t.signInPath??"/login",afterSignInPath:t.afterSignInPath??"/",prompt:t.prompt,cookieName:t.cookieName??"pontive_session",secureCookies:t.secureCookies,sessionMaxAgeSeconds:t.sessionMaxAgeSeconds??Y,refreshSkewSeconds:t.refreshSkewSeconds??60,fetch:t.fetch??((...r)=>fetch(...r)),logger:t.logger??console}}const T=3500,v=16;function x(t){const e=new Map;if(!t)return e;for(const r of t.split(";")){const n=r.indexOf("=");if(n<0)continue;const s=r.slice(0,n).trim();!s||e.has(s)||e.set(s,r.slice(n+1).trim())}return e}function R(t){return[...t].map(([e,r])=>`${e}=${r}`).join("; ")}function b(t,e,r){const n=[`${t}=${e}`,"Path=/","HttpOnly","SameSite=Lax",`Max-Age=${r.maxAge}`];return r.secure&&n.push("Secure"),n.join("; ")}function _(t,e){return e===0?t:`${t}.${e}`}function Q(t,e){const r=t.get(e);if(r===void 0)return null;let n=r;for(let s=1;s<v;s++){const a=t.get(_(e,s));if(a===void 0)break;n+=a}return n}function q(t,e,r,n){const s=[],a=Math.max(1,Math.ceil(r.length/T));for(let u=0;u<a;u++){const l=r.slice(u*T,(u+1)*T);t.set(_(e,u),l),s.push(b(_(e,u),l,n))}for(let u=a;u<v;u++){const l=_(e,u);if(!t.has(l))break;t.delete(l),s.push(b(l,"",{...n,maxAge:0}))}return s}function I(t,e,r){const n=[];for(let s=0;s<v;s++){const a=_(e,s);if(!t.has(a)){if(s>0)break;continue}t.delete(a),n.push(b(a,"",{maxAge:0,secure:r}))}return n}class W extends Error{constructor(e,r,n){super(`token endpoint ${e} ${r}: ${n}`),this.status=e,this.code=r}status;code}const ee=600*1e3,E=new Map,L=new Map;function te(t){return t.replace(/\/+$/,"")}function C(t,e){const r=te(t);let n=E.get(r);return n||(n=e(`${r}/.well-known/openid-configuration`).then(async s=>{if(!s.ok)throw new Error(`OIDC discovery failed for ${r}: ${s.status}`);return await s.json()}).catch(s=>{throw E.delete(r),s}),E.set(r,n)),n}async function M(t,e,r){const n=L.get(t);if(n&&!r&&Date.now()-n.at<ee)return n.set;const s=await e(t);if(!s.ok)throw new Error(`JWKS fetch failed for ${t}: ${s.status}`);const a=U.createLocalJWKSet(await s.json());return L.set(t,{at:Date.now(),set:a}),a}function F(t){let e="";for(const r of t)e+=String.fromCharCode(r);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function $(){return F(crypto.getRandomValues(new Uint8Array(32)))}async function re(t){const e=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t));return F(new Uint8Array(e))}async function ne(t){return[...new Uint8Array(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t)))].map(r=>r.toString(16).padStart(2,"0")).join("")}async function se(t,e,r){const n=await C(t.issuer,e),s=new URL(n.authorization_endpoint);return s.searchParams.set("response_type","code"),s.searchParams.set("client_id",t.clientId),s.searchParams.set("redirect_uri",r.redirectUri),s.searchParams.set("scope",r.scopes),s.searchParams.set("state",r.state),s.searchParams.set("nonce",r.nonce),s.searchParams.set("code_challenge",r.codeChallenge),s.searchParams.set("code_challenge_method","S256"),r.prompt&&s.searchParams.set("prompt",r.prompt),s.toString()}async function K(t,e,r){const n=await C(t.issuer,e),s=new URLSearchParams(r);s.set("client_id",t.clientId);const a={"content-type":"application/x-www-form-urlencoded"};if(t.clientSecret){const l=`${encodeURIComponent(t.clientId)}:${encodeURIComponent(t.clientSecret)}`;a.authorization=`Basic ${btoa(l)}`}const u=await e(n.token_endpoint,{method:"POST",headers:a,body:s});if(!u.ok){let l="unknown_error",k="";try{const S=await u.json();l=S.error??l,k=S.error_description??""}catch{}throw new W(u.status,l,k)}return await u.json()}function oe(t,e,r){return K(t,e,{grant_type:"authorization_code",code:r.code,code_verifier:r.codeVerifier,redirect_uri:r.redirectUri})}function ie(t,e,r){return K(t,e,{grant_type:"refresh_token",refresh_token:r})}async function ae(t,e,r,n){const s=await C(t.issuer,e),a={issuer:s.issuer,audience:t.clientId};let u;try{u=await U.jwtVerify(r,await M(s.jwks_uri,e,!1),a)}catch(l){if(l.code!=="ERR_JWKS_NO_MATCHING_KEY")throw l;u=await U.jwtVerify(r,await M(s.jwks_uri,e,!0),a)}if(u.payload.nonce!==n)throw new Error("id_token nonce does not match the sign-in request");return u.payload}async function ce(t,e,r){const n=await C(t.issuer,e);if(!n.end_session_endpoint)return null;const s=new URL(n.end_session_endpoint);return s.searchParams.set("client_id",t.clientId),s.searchParams.set("post_logout_redirect_uri",r.postLogoutRedirectUri),r.sessionId&&s.searchParams.set("logout_hint",r.sessionId),s.toString()}const ue=3e4;class de{constructor(e=ue){this.recentMs=e}recentMs;inFlight=new Map;recent=new Map;async run(e,r){const n=await ne(e);this.sweep();const s=this.recent.get(n);if(s)return s.result;let a=this.inFlight.get(n);return a||(a=r().then(u=>(this.recent.set(n,{at:Date.now(),result:u}),u)).finally(()=>this.inFlight.delete(n)),this.inFlight.set(n,a)),a}sweep(){const e=Date.now()-this.recentMs;for(const[r,n]of this.recent)n.at<e&&this.recent.delete(r)}}const N=new Map;function V(t){let e=N.get(t);return e||(e=crypto.subtle.digest("SHA-256",new TextEncoder().encode(t)).then(r=>new Uint8Array(r)),N.set(t,e)),e}async function j(t,e,r){return new U.EncryptJWT(t).setProtectedHeader({alg:"dir",enc:"A256GCM"}).setIssuedAt().setExpirationTime(`${r}s`).encrypt(await V(e))}async function D(t,e){try{const{payload:r}=await U.jwtDecrypt(t,await V(e));return r}catch{return null}}const le=900,H=600;function he(t,e){return t&&t.startsWith("/")&&!t.startsWith("//")&&!t.startsWith("/\\")?t:e}function m(t,e=[]){const r=new Headers({location:t,"cache-control":"no-store"});for(const n of e)r.append("set-cookie",n);return new Response(null,{status:302,headers:r})}function A(t,e){if(t.baseUrl)return t.baseUrl;const r=e.headers.get("x-forwarded-host")?.split(",")[0]?.trim()||e.headers.get("host")?.trim();if(r){const n=e.headers.get("x-forwarded-proto")?.split(",")[0]?.trim()||new URL(e.url).protocol.replace(":","");try{return new URL(`${n}://${r}`).origin}catch{}}return new URL(e.url).origin}function fe(t){const e=n=>typeof t[n]=="string"?t[n]:void 0,r=e("name")??([e("given_name"),e("family_name")].filter(Boolean).join(" ")||void 0)??e("email");return{id:t.sub??"",email:e("email"),name:r,username:e("preferred_username"),picture:e("picture"),emailVerified:t.email_verified===!0}}function O(t){return Date.now()+(t.expires_in??le)*1e3}function we(t){const e=Z(t),r={issuer:e.issuer,clientId:e.clientId,clientSecret:e.clientSecret},n=`${e.cookieName}_tx`,s=new de,a=o=>e.secureCookies??A(e,o).startsWith("https:"),u=o=>e.redirectUri??`${A(e,o)}${e.basePath}/callback`,l=(o,i)=>{const c=new URL(e.signInPath,A(e,o));return i&&c.searchParams.set("error",i),c.toString()};async function k(o){const i=Q(o,e.cookieName);if(i===null)return null;const c=await D(i,e.sessionSecret);return c||e.logger.warn("[pontive] session cookie could not be read (sealed with another secret, or truncated)"),c}async function S(o,i,c){const d=await j({...i},e.sessionSecret,e.sessionMaxAgeSeconds);return q(o,e.cookieName,d,{maxAge:e.sessionMaxAgeSeconds,secure:a(c)})}function y(o){const{refreshToken:i,...c}=o;return c}async function G(o){const i=new URL(o.url),c={state:$(),verifier:$(),nonce:$(),returnTo:he(i.searchParams.get("returnTo")??i.searchParams.get("next"),e.afterSignInPath)},d=await se(r,e.fetch,{redirectUri:u(o),scopes:e.scopes,state:c.state,nonce:c.nonce,codeChallenge:await re(c.verifier),prompt:e.prompt}),h=await j({...c},e.sessionSecret,H);return m(d,[b(n,h,{maxAge:H,secure:a(o)})])}async function z(o){const i=new URL(o.url),c=x(o.headers.get("cookie")),d=[b(n,"",{maxAge:0,secure:a(o)})],h=i.searchParams.get("error");if(h)return m(l(o,h),d);const f=c.get(n),g=f?await D(f,e.sessionSecret):null,w=i.searchParams.get("code");if(!g||!w||i.searchParams.get("state")!==g.state)return m(l(o,"invalid_state"),d);try{const p=await oe(r,e.fetch,{code:w,codeVerifier:g.verifier,redirectUri:u(o)});if(!p.id_token)throw new Error("the token response carried no id_token; is the openid scope requested?");const P=await ae(r,e.fetch,p.id_token,g.nonce),X={user:fe(P),accessToken:p.access_token,refreshToken:p.refresh_token,expiresAt:O(p),sessionId:typeof P.sid=="string"?P.sid:void 0};d.push(...await S(c,X,o))}catch(p){return e.logger.warn("[pontive] sign-in failed:",p instanceof Error?p.message:String(p)),m(l(o,"exchange_failed"),d)}return m(new URL(g.returnTo,A(e,o)).toString(),d)}async function J(o){const i=x(o.headers.get("cookie")),c=await k(i),d=I(i,e.cookieName,a(o));let h=l(o);try{h=await ce(r,e.fetch,{postLogoutRedirectUri:l(o),sessionId:c?.sessionId})??h}catch(f){e.logger.warn("[pontive] could not reach the issuer to end its session:",f instanceof Error?f.message:String(f))}return m(h,d)}async function B(o){try{const i=await ie(r,e.fetch,o.refreshToken);return{...o,accessToken:i.access_token,refreshToken:i.refresh_token??o.refreshToken,expiresAt:O(i)}}catch(i){if(i instanceof W&&i.status>=400&&i.status<500)return e.logger.warn("[pontive] refresh refused; signing out:",i.message),null;throw i}}return{async handle(o){const{pathname:i}=new URL(o.url);if(!i.startsWith(`${e.basePath}/`))return null;const c=i.slice(e.basePath.length+1);return c==="login"&&o.method==="GET"?G(o):c==="callback"&&o.method==="GET"?z(o):c==="logout"&&(o.method==="GET"||o.method==="POST")?J(o):null},async getSession(o){const i=await k(x(o));return i?Date.now()>=i.expiresAt?(e.logger.warn("[pontive] the session's access token has expired and getSession cannot refresh it. Run the SDK's middleware on this path so it refreshes before the request is handled."),null):y(i):null},async refresh(o){const i=x(o.headers.get("cookie")),c=w=>({session:w,setCookies:[],cookieHeader:o.headers.get("cookie")??""}),d=await k(i);if(!d)return c(null);if(new URL(o.url).pathname.startsWith(`${e.basePath}/`))return c(Date.now()<d.expiresAt?y(d):null);if(Date.now()<d.expiresAt-e.refreshSkewSeconds*1e3)return c(y(d));const h=()=>({session:null,setCookies:I(i,e.cookieName,a(o)),cookieHeader:R(i)});if(!d.refreshToken)return h();let f;try{f=await s.run(d.refreshToken,()=>B(d))}catch(w){return e.logger.warn("[pontive] refresh failed, will retry:",w instanceof Error?w.message:String(w)),c(Date.now()<d.expiresAt?y(d):null)}if(!f)return h();const g=await S(i,f,o);return{session:y(f),setCookies:g,cookieHeader:R(i)}}}}exports.createServerAuth=we;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/config.ts","../src/cookies.ts","../src/oidc.ts","../src/refresh.ts","../src/seal.ts","../src/server-auth.ts"],"sourcesContent":["/**\n * What an app tells the SDK, and the defaults for everything it doesn't.\n *\n * Only the first five matter in practice. Everything else exists for the rare\n * app that must differ, and its default is the decision the SDK makes for\n * everyone else.\n */\nexport interface ServerAuthConfig {\n /** The Pontive auth domain, e.g. `https://acme.auth.us.pontive.com`. */\n issuer: string;\n /** The app's id. */\n clientId: string;\n /**\n * Set for a confidential app. It is sent on every token call, and is what\n * lets the app hold its refresh token on its server at all.\n */\n clientSecret?: string;\n /** Encrypts the session cookie. At least 32 characters; rotating it signs everyone out. */\n sessionSecret: string;\n /**\n * The app's public origin, e.g. `https://app.acme.com`. Set it for any\n * deployment behind a proxy or in a container, where the request's own URL\n * names the address the server binds to rather than the one a browser uses.\n * Without it the origin comes from `x-forwarded-proto`/`x-forwarded-host`,\n * then the request URL.\n */\n baseUrl?: string;\n\n /** Default `openid email profile offline_access`. */\n scopes?: string;\n /** Where the SDK's sign-in routes live. Default `/api/auth`. */\n basePath?: string;\n /** Default `${origin}${basePath}/callback`; must be registered on the app verbatim. */\n redirectUri?: string;\n /** The app's own sign-in page: failed sign-ins and signed-out users land here. Default `/login`. */\n signInPath?: string;\n /** Where a sign-in lands when it did not start from a page. Default `/`. */\n afterSignInPath?: string;\n /** An OIDC `prompt` to send, e.g. `login`. */\n prompt?: string;\n /** Default `pontive_session`. */\n cookieName?: string;\n /** Default: whether the public origin is https. */\n secureCookies?: boolean;\n /** The session cookie's lifetime. Default 7 days. */\n sessionMaxAgeSeconds?: number;\n /** Refresh when fewer than this many seconds remain. Default 60. */\n refreshSkewSeconds?: number;\n /** Defaults to the global `fetch`. */\n fetch?: typeof fetch;\n /** Where the SDK reports a failed refresh or sign-in. Defaults to `console`. */\n logger?: Pick<Console, \"warn\">;\n}\n\nexport interface ResolvedConfig {\n issuer: string;\n clientId: string;\n clientSecret?: string;\n sessionSecret: string;\n baseUrl?: string;\n scopes: string;\n basePath: string;\n redirectUri?: string;\n signInPath: string;\n afterSignInPath: string;\n prompt?: string;\n cookieName: string;\n secureCookies?: boolean;\n sessionMaxAgeSeconds: number;\n refreshSkewSeconds: number;\n fetch: typeof fetch;\n logger: Pick<Console, \"warn\">;\n}\n\nconst SEVEN_DAYS = 7 * 24 * 60 * 60;\n\nexport function resolveConfig(config: ServerAuthConfig): ResolvedConfig {\n const required: (keyof ServerAuthConfig)[] = [\"issuer\", \"clientId\", \"sessionSecret\"];\n for (const key of required) {\n if (!config[key]) throw new Error(`@pontive/pontkit-server: ${key} is required`);\n }\n\n if (config.sessionSecret.length < 32) {\n throw new Error(\"@pontive/pontkit-server: sessionSecret must be at least 32 characters\");\n }\n\n if (config.baseUrl) {\n try {\n new URL(config.baseUrl);\n } catch {\n throw new Error(`@pontive/pontkit-server: baseUrl \"${config.baseUrl}\" is not an absolute URL`);\n }\n }\n\n return {\n issuer: config.issuer.replace(/\\/+$/, \"\"),\n clientId: config.clientId,\n clientSecret: config.clientSecret || undefined,\n sessionSecret: config.sessionSecret,\n baseUrl: config.baseUrl ? new URL(config.baseUrl).origin : undefined,\n scopes: config.scopes ?? \"openid email profile offline_access\",\n basePath: (config.basePath ?? \"/api/auth\").replace(/\\/+$/, \"\"),\n redirectUri: config.redirectUri,\n signInPath: config.signInPath ?? \"/login\",\n afterSignInPath: config.afterSignInPath ?? \"/\",\n prompt: config.prompt,\n cookieName: config.cookieName ?? \"pontive_session\",\n secureCookies: config.secureCookies,\n sessionMaxAgeSeconds: config.sessionMaxAgeSeconds ?? SEVEN_DAYS,\n refreshSkewSeconds: config.refreshSkewSeconds ?? 60,\n fetch: config.fetch ?? ((...args) => fetch(...args)),\n logger: config.logger ?? console,\n };\n}\n","/**\n * Cookie reading and writing, on plain header strings so it runs anywhere a\n * Web `Request`/`Response` does.\n *\n * A sealed session outgrows one cookie (browsers cap each at about 4 KB, and an\n * access token, a refresh token and a user fit in about 3–5), so it is split\n * across numbered chunks: `name`, `name.1`, `name.2`, … and joined on read.\n */\n\n/** Characters per chunk, leaving room under the ~4096-byte cap for the name and attributes. */\nexport const CHUNK_SIZE = 3500;\n/** Upper bound on chunks probed when reading or clearing. */\nexport const MAX_CHUNKS = 16;\n\nexport interface CookieAttributes {\n /** Seconds; 0 deletes the cookie. */\n maxAge: number;\n secure: boolean;\n}\n\n/** Parses a `Cookie` request header into name → value. */\nexport function parseCookies(header: string | null | undefined): Map<string, string> {\n const out = new Map<string, string>();\n if (!header) return out;\n\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq < 0) continue;\n\n const name = part.slice(0, eq).trim();\n if (!name || out.has(name)) continue;\n\n out.set(name, part.slice(eq + 1).trim());\n }\n\n return out;\n}\n\n/** Serialises a `Cookie` request header from name → value. */\nexport function formatCookieHeader(cookies: Map<string, string>): string {\n return [...cookies].map(([name, value]) => `${name}=${value}`).join(\"; \");\n}\n\n/**\n * A `Set-Cookie` value. Always HttpOnly, host-only, `Path=/` and\n * `SameSite=Lax`: Lax is what lets the cookie ride the top-level redirect back\n * from the sign-in page, and nothing here is ever for script.\n */\nexport function serializeCookie(name: string, value: string, attrs: CookieAttributes): string {\n const parts = [`${name}=${value}`, \"Path=/\", \"HttpOnly\", \"SameSite=Lax\", `Max-Age=${attrs.maxAge}`];\n if (attrs.secure) parts.push(\"Secure\");\n\n return parts.join(\"; \");\n}\n\nexport function chunkName(name: string, index: number): string {\n return index === 0 ? name : `${name}.${index}`;\n}\n\n/** Joins a chunked value, or null when its first chunk is absent. */\nexport function readChunked(cookies: Map<string, string>, name: string): string | null {\n const first = cookies.get(name);\n if (first === undefined) return null;\n\n let value = first;\n for (let i = 1; i < MAX_CHUNKS; i++) {\n const part = cookies.get(chunkName(name, i));\n if (part === undefined) break;\n value += part;\n }\n\n return value;\n}\n\n/**\n * The `Set-Cookie` values that store `value` in chunks, and delete any chunk\n * the request carried beyond what it now needs. Also updates `cookies` in\n * place, so a caller can forward the new state on the same request.\n */\nexport function writeChunked(\n cookies: Map<string, string>,\n name: string,\n value: string,\n attrs: CookieAttributes,\n): string[] {\n const out: string[] = [];\n const count = Math.max(1, Math.ceil(value.length / CHUNK_SIZE));\n\n for (let i = 0; i < count; i++) {\n const chunk = value.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);\n cookies.set(chunkName(name, i), chunk);\n out.push(serializeCookie(chunkName(name, i), chunk, attrs));\n }\n\n for (let i = count; i < MAX_CHUNKS; i++) {\n const stale = chunkName(name, i);\n if (!cookies.has(stale)) break;\n cookies.delete(stale);\n out.push(serializeCookie(stale, \"\", { ...attrs, maxAge: 0 }));\n }\n\n return out;\n}\n\n/** The `Set-Cookie` values that delete every chunk the request carried. */\nexport function clearChunked(cookies: Map<string, string>, name: string, secure: boolean): string[] {\n const out: string[] = [];\n\n for (let i = 0; i < MAX_CHUNKS; i++) {\n const chunk = chunkName(name, i);\n if (!cookies.has(chunk)) {\n if (i > 0) break;\n continue;\n }\n cookies.delete(chunk);\n out.push(serializeCookie(chunk, \"\", { maxAge: 0, secure }));\n }\n\n return out;\n}\n","import { createLocalJWKSet, jwtVerify, type JSONWebKeySet, type JWTPayload } from \"jose\";\n\n/**\n * The OIDC relying-party calls: discovery, PKCE, the two token grants and\n * id_token verification. Standard OIDC only, so any compliant issuer works.\n */\n\nexport interface Discovery {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n jwks_uri: string;\n end_session_endpoint?: string;\n}\n\nexport interface TokenResponse {\n access_token: string;\n token_type: string;\n expires_in?: number;\n refresh_token?: string;\n id_token?: string;\n scope?: string;\n}\n\nexport interface ClientCredentials {\n issuer: string;\n clientId: string;\n clientSecret?: string;\n}\n\n/** An error from the token endpoint, carrying its OAuth code. */\nexport class TokenEndpointError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n description: string,\n ) {\n super(`token endpoint ${status} ${code}: ${description}`);\n }\n}\n\ntype Fetch = typeof fetch;\n\nconst JWKS_TTL_MS = 10 * 60 * 1000;\n\nconst discoveries = new Map<string, Promise<Discovery>>();\nconst jwksCache = new Map<string, { at: number; set: ReturnType<typeof createLocalJWKSet> }>();\n\nfunction trimSlash(value: string): string {\n return value.replace(/\\/+$/, \"\");\n}\n\nexport function discover(issuer: string, fetchImpl: Fetch): Promise<Discovery> {\n const key = trimSlash(issuer);\n let pending = discoveries.get(key);\n\n if (!pending) {\n pending = fetchImpl(`${key}/.well-known/openid-configuration`)\n .then(async (res) => {\n if (!res.ok) throw new Error(`OIDC discovery failed for ${key}: ${res.status}`);\n return (await res.json()) as Discovery;\n })\n .catch((err: unknown) => {\n // A failure is not cached: the next request retries.\n discoveries.delete(key);\n throw err;\n });\n discoveries.set(key, pending);\n }\n\n return pending;\n}\n\nasync function jwks(uri: string, fetchImpl: Fetch, force: boolean): Promise<ReturnType<typeof createLocalJWKSet>> {\n const cached = jwksCache.get(uri);\n if (cached && !force && Date.now() - cached.at < JWKS_TTL_MS) return cached.set;\n\n const res = await fetchImpl(uri);\n if (!res.ok) throw new Error(`JWKS fetch failed for ${uri}: ${res.status}`);\n\n const set = createLocalJWKSet((await res.json()) as JSONWebKeySet);\n jwksCache.set(uri, { at: Date.now(), set });\n\n return set;\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const b of bytes) binary += String.fromCharCode(b);\n\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** A random, URL-safe value for state, nonce and the PKCE verifier. */\nexport function randomValue(): string {\n return base64url(crypto.getRandomValues(new Uint8Array(32)));\n}\n\nexport async function pkceChallenge(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(verifier));\n\n return base64url(new Uint8Array(digest));\n}\n\n/** Hex SHA-256, for keying state by a secret without holding the secret. */\nexport async function sha256Hex(value: string): Promise<string> {\n const digest = new Uint8Array(await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(value)));\n\n return [...digest].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport async function authorizationUrl(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { redirectUri: string; scopes: string; state: string; nonce: string; codeChallenge: string; prompt?: string },\n): Promise<string> {\n const disc = await discover(client.issuer, fetchImpl);\n const url = new URL(disc.authorization_endpoint);\n\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", client.clientId);\n url.searchParams.set(\"redirect_uri\", input.redirectUri);\n url.searchParams.set(\"scope\", input.scopes);\n url.searchParams.set(\"state\", input.state);\n url.searchParams.set(\"nonce\", input.nonce);\n url.searchParams.set(\"code_challenge\", input.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n if (input.prompt) url.searchParams.set(\"prompt\", input.prompt);\n\n return url.toString();\n}\n\n/**\n * A token-endpoint call. A confidential client authenticates with HTTP Basic\n * (RFC 6749 §2.3.1); `client_id` is always in the form too, which a public\n * client needs and a confidential one is allowed.\n */\nasync function tokenRequest(\n client: ClientCredentials,\n fetchImpl: Fetch,\n params: Record<string, string>,\n): Promise<TokenResponse> {\n const disc = await discover(client.issuer, fetchImpl);\n const body = new URLSearchParams(params);\n body.set(\"client_id\", client.clientId);\n\n const headers: Record<string, string> = { \"content-type\": \"application/x-www-form-urlencoded\" };\n if (client.clientSecret) {\n const credentials = `${encodeURIComponent(client.clientId)}:${encodeURIComponent(client.clientSecret)}`;\n headers.authorization = `Basic ${btoa(credentials)}`;\n }\n\n const res = await fetchImpl(disc.token_endpoint, { method: \"POST\", headers, body });\n if (!res.ok) {\n let code = \"unknown_error\";\n let description = \"\";\n\n try {\n const err = (await res.json()) as { error?: string; error_description?: string };\n code = err.error ?? code;\n description = err.error_description ?? \"\";\n } catch {\n // Not JSON; the status says enough.\n }\n\n throw new TokenEndpointError(res.status, code, description);\n }\n\n return (await res.json()) as TokenResponse;\n}\n\nexport function exchangeCode(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { code: string; codeVerifier: string; redirectUri: string },\n): Promise<TokenResponse> {\n return tokenRequest(client, fetchImpl, {\n grant_type: \"authorization_code\",\n code: input.code,\n code_verifier: input.codeVerifier,\n redirect_uri: input.redirectUri,\n });\n}\n\nexport function refreshTokens(client: ClientCredentials, fetchImpl: Fetch, refreshToken: string): Promise<TokenResponse> {\n return tokenRequest(client, fetchImpl, { grant_type: \"refresh_token\", refresh_token: refreshToken });\n}\n\n/**\n * Verifies an id_token's signature, issuer, audience and nonce. A key the\n * cached set does not know refetches the set once, which is how a key rotation\n * at the issuer is picked up without waiting out the cache.\n */\nexport async function verifyIdToken(\n client: ClientCredentials,\n fetchImpl: Fetch,\n idToken: string,\n nonce: string,\n): Promise<JWTPayload> {\n const disc = await discover(client.issuer, fetchImpl);\n const options = { issuer: disc.issuer, audience: client.clientId };\n\n let result;\n try {\n result = await jwtVerify(idToken, await jwks(disc.jwks_uri, fetchImpl, false), options);\n } catch (err) {\n if ((err as { code?: string }).code !== \"ERR_JWKS_NO_MATCHING_KEY\") throw err;\n result = await jwtVerify(idToken, await jwks(disc.jwks_uri, fetchImpl, true), options);\n }\n\n if (result.payload.nonce !== nonce) throw new Error(\"id_token nonce does not match the sign-in request\");\n\n return result.payload;\n}\n\n/** RP-initiated logout (OIDC RP-Initiated Logout 1.0), or null when the issuer offers none. */\nexport async function endSessionUrl(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { postLogoutRedirectUri: string; sessionId?: string },\n): Promise<string | null> {\n const disc = await discover(client.issuer, fetchImpl);\n if (!disc.end_session_endpoint) return null;\n\n const url = new URL(disc.end_session_endpoint);\n url.searchParams.set(\"client_id\", client.clientId);\n url.searchParams.set(\"post_logout_redirect_uri\", input.postLogoutRedirectUri);\n // logout_hint is what lets the issuer end its session without asking \"Sign\n // out?\": only an app holding tokens from that session knows the sid.\n if (input.sessionId) url.searchParams.set(\"logout_hint\", input.sessionId);\n\n return url.toString();\n}\n\n/** For tests: forget cached discovery documents and key sets. */\nexport function resetOidcCaches(): void {\n discoveries.clear();\n jwksCache.clear();\n}\n","import { sha256Hex } from \"./oidc\";\n\n/**\n * One refresh per session, however many requests ask for it.\n *\n * With rotation on, every refresh spends the token it presents. A page that\n * fires several requests the moment its access token expires would otherwise\n * refresh once per request, and every request after the first would present a\n * token the first had already spent — which the issuer reads as theft.\n *\n * Two layers, in this process:\n *\n * - In flight: requests for the same refresh token share one call.\n * - Recently done: a request that arrives a moment later still holding the old\n * cookie (its browser had not yet stored the new one) is handed the same\n * result rather than spending the old token again.\n *\n * Across processes — several replicas of the app — the issuer's reuse grace\n * window covers the same race, so no shared lock is needed.\n */\n\n/** How long a result is handed to late arrivals. Matches the issuer's grace window. */\nexport const RECENT_RESULT_MS = 30_000;\n\nexport class RefreshCoordinator<T> {\n private readonly inFlight = new Map<string, Promise<T>>();\n private readonly recent = new Map<string, { at: number; result: T }>();\n\n constructor(private readonly recentMs: number = RECENT_RESULT_MS) {}\n\n async run(refreshToken: string, refresh: () => Promise<T>): Promise<T> {\n const key = await sha256Hex(refreshToken);\n this.sweep();\n\n const done = this.recent.get(key);\n if (done) return done.result;\n\n let pending = this.inFlight.get(key);\n if (!pending) {\n pending = refresh()\n .then((result) => {\n this.recent.set(key, { at: Date.now(), result });\n return result;\n })\n .finally(() => this.inFlight.delete(key));\n this.inFlight.set(key, pending);\n }\n\n return pending;\n }\n\n private sweep(): void {\n const cutoff = Date.now() - this.recentMs;\n for (const [key, entry] of this.recent) {\n if (entry.at < cutoff) this.recent.delete(key);\n }\n }\n}\n","import { EncryptJWT, jwtDecrypt } from \"jose\";\n\n/**\n * Sealing: authenticated encryption (JWE, `dir` + A256GCM) of whatever the\n * server stores in a cookie. The browser holds ciphertext it can neither read\n * nor alter, which is what lets a refresh token live in a cookie at all.\n */\n\nconst keys = new Map<string, Promise<Uint8Array>>();\n\n/** A 256-bit key derived from the configured secret, whatever its length. */\nfunction keyFor(secret: string): Promise<Uint8Array> {\n let key = keys.get(secret);\n if (!key) {\n key = crypto.subtle\n .digest(\"SHA-256\", new TextEncoder().encode(secret))\n .then((digest) => new Uint8Array(digest));\n keys.set(secret, key);\n }\n\n return key;\n}\n\nexport async function seal(payload: Record<string, unknown>, secret: string, maxAgeSeconds: number): Promise<string> {\n return new EncryptJWT(payload)\n .setProtectedHeader({ alg: \"dir\", enc: \"A256GCM\" })\n .setIssuedAt()\n .setExpirationTime(`${maxAgeSeconds}s`)\n .encrypt(await keyFor(secret));\n}\n\n/** The payload, or null when the value is not one this secret sealed, or has expired. */\nexport async function unseal<T>(value: string, secret: string): Promise<T | null> {\n try {\n const { payload } = await jwtDecrypt(value, await keyFor(secret));\n return payload as T;\n } catch {\n return null;\n }\n}\n","import type { JWTPayload } from \"jose\";\n\nimport { resolveConfig, type ResolvedConfig, type ServerAuthConfig } from \"./config\";\nimport {\n clearChunked,\n formatCookieHeader,\n parseCookies,\n readChunked,\n serializeCookie,\n writeChunked,\n} from \"./cookies\";\nimport {\n authorizationUrl,\n endSessionUrl,\n exchangeCode,\n pkceChallenge,\n randomValue,\n refreshTokens,\n TokenEndpointError,\n verifyIdToken,\n type ClientCredentials,\n type TokenResponse,\n} from \"./oidc\";\nimport { RefreshCoordinator } from \"./refresh\";\nimport { seal, unseal } from \"./seal\";\n\nexport interface SessionUser {\n id: string;\n email?: string;\n name?: string;\n username?: string;\n picture?: string;\n emailVerified: boolean;\n}\n\n/** What `getSession` hands the app. */\nexport interface Session {\n user: SessionUser;\n /** A valid access token, for calling APIs as this user. */\n accessToken: string;\n /** When the access token expires, epoch milliseconds. */\n expiresAt: number;\n /** The issuer's session id (`sid`), when it sent one. */\n sessionId?: string;\n}\n\n/** What the cookie holds: the session plus the refresh token, which the app never sees. */\ninterface StoredSession extends Session {\n refreshToken?: string;\n}\n\n/** A sign-in in progress, sealed into its own short-lived cookie. */\ninterface SignInTransaction {\n state: string;\n verifier: string;\n nonce: string;\n returnTo: string;\n}\n\n/** What a refresh produced, for the middleware to apply. */\nexport interface RefreshOutcome {\n /** The session after the refresh, or null when there is none (or it could not be refreshed). */\n session: Session | null;\n /** `Set-Cookie` values for the response. Empty when nothing changed. */\n setCookies: string[];\n /**\n * The request's `Cookie` header as it should be seen by whatever handles\n * this request next, carrying the refreshed session. Forward it, so a page\n * rendered on this very request reads the new token rather than the expired\n * one the browser sent.\n */\n cookieHeader: string;\n}\n\nexport interface ServerAuth {\n /**\n * Handles the SDK's routes under `basePath`: `/login`, `/callback` and\n * `/logout`. Null for any other path.\n */\n handle(request: Request): Promise<Response | null>;\n /**\n * The session in a `Cookie` header, or null. Read-only and never refreshes:\n * any server code can call it, including code that cannot set cookies.\n * Returns null for a session whose access token has expired — `refresh`,\n * run in middleware ahead of the request, is what keeps it current.\n */\n getSession(cookieHeader: string | null | undefined): Promise<Session | null>;\n /**\n * Refreshes the session when its access token is about to expire. For\n * middleware: it runs before anything renders, and its response can always\n * carry the new cookie. Concurrent calls for one session share one refresh.\n */\n refresh(request: Request): Promise<RefreshOutcome>;\n}\n\nconst DEFAULT_ACCESS_TTL_SECONDS = 900;\nconst TRANSACTION_MAX_AGE = 600;\n\n/** Only a same-origin path may be returned to after sign-in: no open redirect. */\nfunction safePath(value: string | null | undefined, fallback: string): string {\n if (value && value.startsWith(\"/\") && !value.startsWith(\"//\") && !value.startsWith(\"/\\\\\")) return value;\n return fallback;\n}\n\nfunction redirect(location: string, setCookies: string[] = []): Response {\n const headers = new Headers({ location, \"cache-control\": \"no-store\" });\n for (const cookie of setCookies) headers.append(\"set-cookie\", cookie);\n\n return new Response(null, { status: 302, headers });\n}\n\nfunction publicOrigin(cfg: ResolvedConfig, request: Request): string {\n if (cfg.baseUrl) return cfg.baseUrl;\n\n const host = request.headers.get(\"x-forwarded-host\")?.split(\",\")[0]?.trim() || request.headers.get(\"host\")?.trim();\n if (host) {\n const proto =\n request.headers.get(\"x-forwarded-proto\")?.split(\",\")[0]?.trim() || new URL(request.url).protocol.replace(\":\", \"\");\n try {\n return new URL(`${proto}://${host}`).origin;\n } catch {\n // Fall through to the request URL.\n }\n }\n\n return new URL(request.url).origin;\n}\n\nfunction userFrom(claims: JWTPayload): SessionUser {\n const str = (key: string) => (typeof claims[key] === \"string\" ? (claims[key] as string) : undefined);\n const name =\n str(\"name\") ?? ([str(\"given_name\"), str(\"family_name\")].filter(Boolean).join(\" \") || undefined) ?? str(\"email\");\n\n return {\n id: claims.sub ?? \"\",\n email: str(\"email\"),\n name,\n username: str(\"preferred_username\"),\n picture: str(\"picture\"),\n emailVerified: claims.email_verified === true,\n };\n}\n\nfunction expiresAtFrom(token: TokenResponse): number {\n return Date.now() + (token.expires_in ?? DEFAULT_ACCESS_TTL_SECONDS) * 1000;\n}\n\nexport function createServerAuth(config: ServerAuthConfig): ServerAuth {\n const cfg = resolveConfig(config);\n const client: ClientCredentials = { issuer: cfg.issuer, clientId: cfg.clientId, clientSecret: cfg.clientSecret };\n const txCookie = `${cfg.cookieName}_tx`;\n const coordinator = new RefreshCoordinator<StoredSession | null>();\n\n const secure = (request: Request) => cfg.secureCookies ?? publicOrigin(cfg, request).startsWith(\"https:\");\n const redirectUri = (request: Request) => cfg.redirectUri ?? `${publicOrigin(cfg, request)}${cfg.basePath}/callback`;\n const toSignIn = (request: Request, error?: string) => {\n const url = new URL(cfg.signInPath, publicOrigin(cfg, request));\n if (error) url.searchParams.set(\"error\", error);\n return url.toString();\n };\n\n async function readStored(cookies: Map<string, string>): Promise<StoredSession | null> {\n const raw = readChunked(cookies, cfg.cookieName);\n if (raw === null) return null;\n\n const stored = await unseal<StoredSession>(raw, cfg.sessionSecret);\n if (!stored) cfg.logger.warn(\"[pontive] session cookie could not be read (sealed with another secret, or truncated)\");\n\n return stored;\n }\n\n async function writeStored(cookies: Map<string, string>, stored: StoredSession, request: Request): Promise<string[]> {\n const sealed = await seal({ ...stored }, cfg.sessionSecret, cfg.sessionMaxAgeSeconds);\n\n return writeChunked(cookies, cfg.cookieName, sealed, { maxAge: cfg.sessionMaxAgeSeconds, secure: secure(request) });\n }\n\n function publicSession(stored: StoredSession): Session {\n const { refreshToken: _refreshToken, ...session } = stored;\n return session;\n }\n\n async function signIn(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const tx: SignInTransaction = {\n state: randomValue(),\n verifier: randomValue(),\n nonce: randomValue(),\n returnTo: safePath(url.searchParams.get(\"returnTo\") ?? url.searchParams.get(\"next\"), cfg.afterSignInPath),\n };\n\n const location = await authorizationUrl(client, cfg.fetch, {\n redirectUri: redirectUri(request),\n scopes: cfg.scopes,\n state: tx.state,\n nonce: tx.nonce,\n codeChallenge: await pkceChallenge(tx.verifier),\n prompt: cfg.prompt,\n });\n\n const sealed = await seal({ ...tx }, cfg.sessionSecret, TRANSACTION_MAX_AGE);\n\n return redirect(location, [serializeCookie(txCookie, sealed, { maxAge: TRANSACTION_MAX_AGE, secure: secure(request) })]);\n }\n\n async function callback(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n // Single use, whatever happens next.\n const setCookies = [serializeCookie(txCookie, \"\", { maxAge: 0, secure: secure(request) })];\n\n const issuerError = url.searchParams.get(\"error\");\n if (issuerError) return redirect(toSignIn(request, issuerError), setCookies);\n\n const raw = cookies.get(txCookie);\n const tx = raw ? await unseal<SignInTransaction>(raw, cfg.sessionSecret) : null;\n const code = url.searchParams.get(\"code\");\n\n if (!tx || !code || url.searchParams.get(\"state\") !== tx.state) {\n return redirect(toSignIn(request, \"invalid_state\"), setCookies);\n }\n\n try {\n const tokens = await exchangeCode(client, cfg.fetch, {\n code,\n codeVerifier: tx.verifier,\n redirectUri: redirectUri(request),\n });\n if (!tokens.id_token) throw new Error(\"the token response carried no id_token; is the openid scope requested?\");\n\n const claims = await verifyIdToken(client, cfg.fetch, tokens.id_token, tx.nonce);\n const stored: StoredSession = {\n user: userFrom(claims),\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresAt: expiresAtFrom(tokens),\n sessionId: typeof claims.sid === \"string\" ? claims.sid : undefined,\n };\n\n setCookies.push(...(await writeStored(cookies, stored, request)));\n } catch (err) {\n cfg.logger.warn(\"[pontive] sign-in failed:\", err instanceof Error ? err.message : String(err));\n return redirect(toSignIn(request, \"exchange_failed\"), setCookies);\n }\n\n return redirect(new URL(tx.returnTo, publicOrigin(cfg, request)).toString(), setCookies);\n }\n\n async function signOut(request: Request): Promise<Response> {\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n // Read before clearing: the session holds the sid the issuer's logout needs.\n const stored = await readStored(cookies);\n const setCookies = clearChunked(cookies, cfg.cookieName, secure(request));\n\n let location = toSignIn(request);\n try {\n location =\n (await endSessionUrl(client, cfg.fetch, {\n postLogoutRedirectUri: toSignIn(request),\n sessionId: stored?.sessionId,\n })) ?? location;\n } catch (err) {\n // An unreachable issuer must not stop the local sign-out.\n cfg.logger.warn(\"[pontive] could not reach the issuer to end its session:\", err instanceof Error ? err.message : String(err));\n }\n\n return redirect(location, setCookies);\n }\n\n /**\n * Spends the refresh token once, returning the refreshed session, or null\n * when the issuer refused it for good. Anything transient throws instead, so\n * the coordinator does not hand a passing failure to other requests.\n */\n async function refreshStored(stored: StoredSession): Promise<StoredSession | null> {\n try {\n const tokens = await refreshTokens(client, cfg.fetch, stored.refreshToken!);\n\n return {\n ...stored,\n accessToken: tokens.access_token,\n // A response without one means rotation is off: the token in hand is still current.\n refreshToken: tokens.refresh_token ?? stored.refreshToken,\n expiresAt: expiresAtFrom(tokens),\n };\n } catch (err) {\n if (err instanceof TokenEndpointError && err.status >= 400 && err.status < 500) {\n cfg.logger.warn(\"[pontive] refresh refused; signing out:\", err.message);\n return null;\n }\n\n throw err;\n }\n }\n\n return {\n async handle(request) {\n const { pathname } = new URL(request.url);\n if (!pathname.startsWith(`${cfg.basePath}/`)) return null;\n\n const route = pathname.slice(cfg.basePath.length + 1);\n if (route === \"login\" && request.method === \"GET\") return signIn(request);\n if (route === \"callback\" && request.method === \"GET\") return callback(request);\n if (route === \"logout\" && (request.method === \"GET\" || request.method === \"POST\")) return signOut(request);\n\n return null;\n },\n\n async getSession(cookieHeader) {\n const stored = await readStored(parseCookies(cookieHeader));\n if (!stored) return null;\n\n if (Date.now() >= stored.expiresAt) {\n cfg.logger.warn(\n \"[pontive] the session's access token has expired and getSession cannot refresh it. \" +\n \"Run the SDK's middleware on this path so it refreshes before the request is handled.\",\n );\n return null;\n }\n\n return publicSession(stored);\n },\n\n async refresh(request) {\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n const unchanged = (session: Session | null): RefreshOutcome => ({\n session,\n setCookies: [],\n cookieHeader: request.headers.get(\"cookie\") ?? \"\",\n });\n\n const stored = await readStored(cookies);\n if (!stored) return unchanged(null);\n\n if (Date.now() < stored.expiresAt - cfg.refreshSkewSeconds * 1000) return unchanged(publicSession(stored));\n\n const signOutNow = (): RefreshOutcome => {\n const setCookies = clearChunked(cookies, cfg.cookieName, secure(request));\n return { session: null, setCookies, cookieHeader: formatCookieHeader(cookies) };\n };\n\n if (!stored.refreshToken) return signOutNow();\n\n let refreshed: StoredSession | null;\n try {\n refreshed = await coordinator.run(stored.refreshToken, () => refreshStored(stored));\n } catch (err) {\n // Transient: keep the session and let the next request try again. The\n // token may still have a few seconds of the skew left.\n cfg.logger.warn(\"[pontive] refresh failed, will retry:\", err instanceof Error ? err.message : String(err));\n return unchanged(Date.now() < stored.expiresAt ? publicSession(stored) : null);\n }\n\n if (!refreshed) return signOutNow();\n\n const setCookies = await writeStored(cookies, refreshed, request);\n\n return { session: publicSession(refreshed), setCookies, cookieHeader: formatCookieHeader(cookies) };\n },\n };\n}\n"],"names":["SEVEN_DAYS","resolveConfig","config","required","key","args","CHUNK_SIZE","MAX_CHUNKS","parseCookies","header","out","part","eq","name","formatCookieHeader","cookies","value","serializeCookie","attrs","parts","chunkName","index","readChunked","first","i","writeChunked","count","chunk","stale","clearChunked","secure","TokenEndpointError","status","code","description","JWKS_TTL_MS","discoveries","jwksCache","trimSlash","discover","issuer","fetchImpl","pending","res","err","jwks","uri","force","cached","set","createLocalJWKSet","base64url","bytes","binary","b","randomValue","pkceChallenge","verifier","digest","sha256Hex","authorizationUrl","client","input","disc","url","tokenRequest","params","body","headers","credentials","exchangeCode","refreshTokens","refreshToken","verifyIdToken","idToken","nonce","options","result","jwtVerify","endSessionUrl","RECENT_RESULT_MS","RefreshCoordinator","recentMs","refresh","done","cutoff","entry","keys","keyFor","secret","seal","payload","maxAgeSeconds","EncryptJWT","unseal","jwtDecrypt","DEFAULT_ACCESS_TTL_SECONDS","TRANSACTION_MAX_AGE","safePath","fallback","redirect","location","setCookies","cookie","publicOrigin","cfg","request","host","proto","userFrom","claims","str","expiresAtFrom","token","createServerAuth","txCookie","coordinator","redirectUri","toSignIn","error","readStored","raw","stored","writeStored","sealed","publicSession","_refreshToken","session","signIn","tx","callback","issuerError","tokens","signOut","refreshStored","pathname","route","cookieHeader","unchanged","signOutNow","refreshed"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/config.ts","../src/cookies.ts","../src/oidc.ts","../src/refresh.ts","../src/seal.ts","../src/server-auth.ts"],"sourcesContent":["/**\n * What an app tells the SDK, and the defaults for everything it doesn't.\n *\n * Only the first five matter in practice. Everything else exists for the rare\n * app that must differ, and its default is the decision the SDK makes for\n * everyone else.\n */\nexport interface ServerAuthConfig {\n /** The Pontive auth domain, e.g. `https://acme.auth.us.pontive.com`. */\n issuer: string;\n /** The app's id. */\n clientId: string;\n /**\n * Set for a confidential app. It is sent on every token call, and is what\n * lets the app hold its refresh token on its server at all.\n */\n clientSecret?: string;\n /** Encrypts the session cookie. At least 32 characters; rotating it signs everyone out. */\n sessionSecret: string;\n /**\n * The app's public origin, e.g. `https://app.acme.com`. Set it for any\n * deployment behind a proxy or in a container, where the request's own URL\n * names the address the server binds to rather than the one a browser uses.\n * Without it the origin comes from `x-forwarded-proto`/`x-forwarded-host`,\n * then the request URL.\n */\n baseUrl?: string;\n\n /** Default `openid email profile offline_access`. */\n scopes?: string;\n /** Where the SDK's sign-in routes live. Default `/api/auth`. */\n basePath?: string;\n /** Default `${origin}${basePath}/callback`; must be registered on the app verbatim. */\n redirectUri?: string;\n /** The app's own sign-in page: failed sign-ins and signed-out users land here. Default `/login`. */\n signInPath?: string;\n /** Where a sign-in lands when it did not start from a page. Default `/`. */\n afterSignInPath?: string;\n /** An OIDC `prompt` to send, e.g. `login`. */\n prompt?: string;\n /** Default `pontive_session`. */\n cookieName?: string;\n /** Default: whether the public origin is https. */\n secureCookies?: boolean;\n /** The session cookie's lifetime. Default 7 days. */\n sessionMaxAgeSeconds?: number;\n /** Refresh when fewer than this many seconds remain. Default 60. */\n refreshSkewSeconds?: number;\n /** Defaults to the global `fetch`. */\n fetch?: typeof fetch;\n /** Where the SDK reports a failed refresh or sign-in. Defaults to `console`. */\n logger?: Pick<Console, \"warn\">;\n}\n\nexport interface ResolvedConfig {\n issuer: string;\n clientId: string;\n clientSecret?: string;\n sessionSecret: string;\n baseUrl?: string;\n scopes: string;\n basePath: string;\n redirectUri?: string;\n signInPath: string;\n afterSignInPath: string;\n prompt?: string;\n cookieName: string;\n secureCookies?: boolean;\n sessionMaxAgeSeconds: number;\n refreshSkewSeconds: number;\n fetch: typeof fetch;\n logger: Pick<Console, \"warn\">;\n}\n\nconst SEVEN_DAYS = 7 * 24 * 60 * 60;\n\nexport function resolveConfig(config: ServerAuthConfig): ResolvedConfig {\n const required: (keyof ServerAuthConfig)[] = [\"issuer\", \"clientId\", \"sessionSecret\"];\n for (const key of required) {\n if (!config[key]) throw new Error(`@pontive/pontkit-server: ${key} is required`);\n }\n\n if (config.sessionSecret.length < 32) {\n throw new Error(\"@pontive/pontkit-server: sessionSecret must be at least 32 characters\");\n }\n\n if (config.baseUrl) {\n try {\n new URL(config.baseUrl);\n } catch {\n throw new Error(`@pontive/pontkit-server: baseUrl \"${config.baseUrl}\" is not an absolute URL`);\n }\n }\n\n return {\n issuer: config.issuer.replace(/\\/+$/, \"\"),\n clientId: config.clientId,\n clientSecret: config.clientSecret || undefined,\n sessionSecret: config.sessionSecret,\n baseUrl: config.baseUrl ? new URL(config.baseUrl).origin : undefined,\n scopes: config.scopes ?? \"openid email profile offline_access\",\n basePath: (config.basePath ?? \"/api/auth\").replace(/\\/+$/, \"\"),\n redirectUri: config.redirectUri,\n signInPath: config.signInPath ?? \"/login\",\n afterSignInPath: config.afterSignInPath ?? \"/\",\n prompt: config.prompt,\n cookieName: config.cookieName ?? \"pontive_session\",\n secureCookies: config.secureCookies,\n sessionMaxAgeSeconds: config.sessionMaxAgeSeconds ?? SEVEN_DAYS,\n refreshSkewSeconds: config.refreshSkewSeconds ?? 60,\n fetch: config.fetch ?? ((...args) => fetch(...args)),\n logger: config.logger ?? console,\n };\n}\n","/**\n * Cookie reading and writing, on plain header strings so it runs anywhere a\n * Web `Request`/`Response` does.\n *\n * A sealed session outgrows one cookie (browsers cap each at about 4 KB, and an\n * access token, a refresh token and a user fit in about 3–5), so it is split\n * across numbered chunks: `name`, `name.1`, `name.2`, … and joined on read.\n */\n\n/** Characters per chunk, leaving room under the ~4096-byte cap for the name and attributes. */\nexport const CHUNK_SIZE = 3500;\n/** Upper bound on chunks probed when reading or clearing. */\nexport const MAX_CHUNKS = 16;\n\nexport interface CookieAttributes {\n /** Seconds; 0 deletes the cookie. */\n maxAge: number;\n secure: boolean;\n}\n\n/** Parses a `Cookie` request header into name → value. */\nexport function parseCookies(header: string | null | undefined): Map<string, string> {\n const out = new Map<string, string>();\n if (!header) return out;\n\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq < 0) continue;\n\n const name = part.slice(0, eq).trim();\n if (!name || out.has(name)) continue;\n\n out.set(name, part.slice(eq + 1).trim());\n }\n\n return out;\n}\n\n/** Serialises a `Cookie` request header from name → value. */\nexport function formatCookieHeader(cookies: Map<string, string>): string {\n return [...cookies].map(([name, value]) => `${name}=${value}`).join(\"; \");\n}\n\n/**\n * A `Set-Cookie` value. Always HttpOnly, host-only, `Path=/` and\n * `SameSite=Lax`: Lax is what lets the cookie ride the top-level redirect back\n * from the sign-in page, and nothing here is ever for script.\n */\nexport function serializeCookie(name: string, value: string, attrs: CookieAttributes): string {\n const parts = [`${name}=${value}`, \"Path=/\", \"HttpOnly\", \"SameSite=Lax\", `Max-Age=${attrs.maxAge}`];\n if (attrs.secure) parts.push(\"Secure\");\n\n return parts.join(\"; \");\n}\n\nexport function chunkName(name: string, index: number): string {\n return index === 0 ? name : `${name}.${index}`;\n}\n\n/** Joins a chunked value, or null when its first chunk is absent. */\nexport function readChunked(cookies: Map<string, string>, name: string): string | null {\n const first = cookies.get(name);\n if (first === undefined) return null;\n\n let value = first;\n for (let i = 1; i < MAX_CHUNKS; i++) {\n const part = cookies.get(chunkName(name, i));\n if (part === undefined) break;\n value += part;\n }\n\n return value;\n}\n\n/**\n * The `Set-Cookie` values that store `value` in chunks, and delete any chunk\n * the request carried beyond what it now needs. Also updates `cookies` in\n * place, so a caller can forward the new state on the same request.\n */\nexport function writeChunked(\n cookies: Map<string, string>,\n name: string,\n value: string,\n attrs: CookieAttributes,\n): string[] {\n const out: string[] = [];\n const count = Math.max(1, Math.ceil(value.length / CHUNK_SIZE));\n\n for (let i = 0; i < count; i++) {\n const chunk = value.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);\n cookies.set(chunkName(name, i), chunk);\n out.push(serializeCookie(chunkName(name, i), chunk, attrs));\n }\n\n for (let i = count; i < MAX_CHUNKS; i++) {\n const stale = chunkName(name, i);\n if (!cookies.has(stale)) break;\n cookies.delete(stale);\n out.push(serializeCookie(stale, \"\", { ...attrs, maxAge: 0 }));\n }\n\n return out;\n}\n\n/** The `Set-Cookie` values that delete every chunk the request carried. */\nexport function clearChunked(cookies: Map<string, string>, name: string, secure: boolean): string[] {\n const out: string[] = [];\n\n for (let i = 0; i < MAX_CHUNKS; i++) {\n const chunk = chunkName(name, i);\n if (!cookies.has(chunk)) {\n if (i > 0) break;\n continue;\n }\n cookies.delete(chunk);\n out.push(serializeCookie(chunk, \"\", { maxAge: 0, secure }));\n }\n\n return out;\n}\n","import { createLocalJWKSet, jwtVerify, type JSONWebKeySet, type JWTPayload } from \"jose\";\n\n/**\n * The OIDC relying-party calls: discovery, PKCE, the two token grants and\n * id_token verification. Standard OIDC only, so any compliant issuer works.\n */\n\nexport interface Discovery {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n jwks_uri: string;\n end_session_endpoint?: string;\n}\n\nexport interface TokenResponse {\n access_token: string;\n token_type: string;\n expires_in?: number;\n refresh_token?: string;\n id_token?: string;\n scope?: string;\n}\n\nexport interface ClientCredentials {\n issuer: string;\n clientId: string;\n clientSecret?: string;\n}\n\n/** An error from the token endpoint, carrying its OAuth code. */\nexport class TokenEndpointError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n description: string,\n ) {\n super(`token endpoint ${status} ${code}: ${description}`);\n }\n}\n\ntype Fetch = typeof fetch;\n\nconst JWKS_TTL_MS = 10 * 60 * 1000;\n\nconst discoveries = new Map<string, Promise<Discovery>>();\nconst jwksCache = new Map<string, { at: number; set: ReturnType<typeof createLocalJWKSet> }>();\n\nfunction trimSlash(value: string): string {\n return value.replace(/\\/+$/, \"\");\n}\n\nexport function discover(issuer: string, fetchImpl: Fetch): Promise<Discovery> {\n const key = trimSlash(issuer);\n let pending = discoveries.get(key);\n\n if (!pending) {\n pending = fetchImpl(`${key}/.well-known/openid-configuration`)\n .then(async (res) => {\n if (!res.ok) throw new Error(`OIDC discovery failed for ${key}: ${res.status}`);\n return (await res.json()) as Discovery;\n })\n .catch((err: unknown) => {\n // A failure is not cached: the next request retries.\n discoveries.delete(key);\n throw err;\n });\n discoveries.set(key, pending);\n }\n\n return pending;\n}\n\nasync function jwks(uri: string, fetchImpl: Fetch, force: boolean): Promise<ReturnType<typeof createLocalJWKSet>> {\n const cached = jwksCache.get(uri);\n if (cached && !force && Date.now() - cached.at < JWKS_TTL_MS) return cached.set;\n\n const res = await fetchImpl(uri);\n if (!res.ok) throw new Error(`JWKS fetch failed for ${uri}: ${res.status}`);\n\n const set = createLocalJWKSet((await res.json()) as JSONWebKeySet);\n jwksCache.set(uri, { at: Date.now(), set });\n\n return set;\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const b of bytes) binary += String.fromCharCode(b);\n\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** A random, URL-safe value for state, nonce and the PKCE verifier. */\nexport function randomValue(): string {\n return base64url(crypto.getRandomValues(new Uint8Array(32)));\n}\n\nexport async function pkceChallenge(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(verifier));\n\n return base64url(new Uint8Array(digest));\n}\n\n/** Hex SHA-256, for keying state by a secret without holding the secret. */\nexport async function sha256Hex(value: string): Promise<string> {\n const digest = new Uint8Array(await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(value)));\n\n return [...digest].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport async function authorizationUrl(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { redirectUri: string; scopes: string; state: string; nonce: string; codeChallenge: string; prompt?: string },\n): Promise<string> {\n const disc = await discover(client.issuer, fetchImpl);\n const url = new URL(disc.authorization_endpoint);\n\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", client.clientId);\n url.searchParams.set(\"redirect_uri\", input.redirectUri);\n url.searchParams.set(\"scope\", input.scopes);\n url.searchParams.set(\"state\", input.state);\n url.searchParams.set(\"nonce\", input.nonce);\n url.searchParams.set(\"code_challenge\", input.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n if (input.prompt) url.searchParams.set(\"prompt\", input.prompt);\n\n return url.toString();\n}\n\n/**\n * A token-endpoint call. A confidential client authenticates with HTTP Basic\n * (RFC 6749 §2.3.1); `client_id` is always in the form too, which a public\n * client needs and a confidential one is allowed.\n */\nasync function tokenRequest(\n client: ClientCredentials,\n fetchImpl: Fetch,\n params: Record<string, string>,\n): Promise<TokenResponse> {\n const disc = await discover(client.issuer, fetchImpl);\n const body = new URLSearchParams(params);\n body.set(\"client_id\", client.clientId);\n\n const headers: Record<string, string> = { \"content-type\": \"application/x-www-form-urlencoded\" };\n if (client.clientSecret) {\n const credentials = `${encodeURIComponent(client.clientId)}:${encodeURIComponent(client.clientSecret)}`;\n headers.authorization = `Basic ${btoa(credentials)}`;\n }\n\n const res = await fetchImpl(disc.token_endpoint, { method: \"POST\", headers, body });\n if (!res.ok) {\n let code = \"unknown_error\";\n let description = \"\";\n\n try {\n const err = (await res.json()) as { error?: string; error_description?: string };\n code = err.error ?? code;\n description = err.error_description ?? \"\";\n } catch {\n // Not JSON; the status says enough.\n }\n\n throw new TokenEndpointError(res.status, code, description);\n }\n\n return (await res.json()) as TokenResponse;\n}\n\nexport function exchangeCode(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { code: string; codeVerifier: string; redirectUri: string },\n): Promise<TokenResponse> {\n return tokenRequest(client, fetchImpl, {\n grant_type: \"authorization_code\",\n code: input.code,\n code_verifier: input.codeVerifier,\n redirect_uri: input.redirectUri,\n });\n}\n\nexport function refreshTokens(client: ClientCredentials, fetchImpl: Fetch, refreshToken: string): Promise<TokenResponse> {\n return tokenRequest(client, fetchImpl, { grant_type: \"refresh_token\", refresh_token: refreshToken });\n}\n\n/**\n * Verifies an id_token's signature, issuer, audience and nonce. A key the\n * cached set does not know refetches the set once, which is how a key rotation\n * at the issuer is picked up without waiting out the cache.\n */\nexport async function verifyIdToken(\n client: ClientCredentials,\n fetchImpl: Fetch,\n idToken: string,\n nonce: string,\n): Promise<JWTPayload> {\n const disc = await discover(client.issuer, fetchImpl);\n const options = { issuer: disc.issuer, audience: client.clientId };\n\n let result;\n try {\n result = await jwtVerify(idToken, await jwks(disc.jwks_uri, fetchImpl, false), options);\n } catch (err) {\n if ((err as { code?: string }).code !== \"ERR_JWKS_NO_MATCHING_KEY\") throw err;\n result = await jwtVerify(idToken, await jwks(disc.jwks_uri, fetchImpl, true), options);\n }\n\n if (result.payload.nonce !== nonce) throw new Error(\"id_token nonce does not match the sign-in request\");\n\n return result.payload;\n}\n\n/** RP-initiated logout (OIDC RP-Initiated Logout 1.0), or null when the issuer offers none. */\nexport async function endSessionUrl(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { postLogoutRedirectUri: string; sessionId?: string },\n): Promise<string | null> {\n const disc = await discover(client.issuer, fetchImpl);\n if (!disc.end_session_endpoint) return null;\n\n const url = new URL(disc.end_session_endpoint);\n url.searchParams.set(\"client_id\", client.clientId);\n url.searchParams.set(\"post_logout_redirect_uri\", input.postLogoutRedirectUri);\n // logout_hint is what lets the issuer end its session without asking \"Sign\n // out?\": only an app holding tokens from that session knows the sid.\n if (input.sessionId) url.searchParams.set(\"logout_hint\", input.sessionId);\n\n return url.toString();\n}\n\n/** For tests: forget cached discovery documents and key sets. */\nexport function resetOidcCaches(): void {\n discoveries.clear();\n jwksCache.clear();\n}\n","import { sha256Hex } from \"./oidc\";\n\n/**\n * One refresh per session, however many requests ask for it.\n *\n * With rotation on, every refresh spends the token it presents. A page that\n * fires several requests the moment its access token expires would otherwise\n * refresh once per request, and every request after the first would present a\n * token the first had already spent — which the issuer reads as theft.\n *\n * Two layers, in this process:\n *\n * - In flight: requests for the same refresh token share one call.\n * - Recently done: a request that arrives a moment later still holding the old\n * cookie (its browser had not yet stored the new one) is handed the same\n * result rather than spending the old token again.\n *\n * Across processes — several replicas of the app — the issuer's reuse grace\n * window covers the same race, so no shared lock is needed.\n */\n\n/** How long a result is handed to late arrivals. Matches the issuer's grace window. */\nexport const RECENT_RESULT_MS = 30_000;\n\nexport class RefreshCoordinator<T> {\n private readonly inFlight = new Map<string, Promise<T>>();\n private readonly recent = new Map<string, { at: number; result: T }>();\n\n constructor(private readonly recentMs: number = RECENT_RESULT_MS) {}\n\n async run(refreshToken: string, refresh: () => Promise<T>): Promise<T> {\n const key = await sha256Hex(refreshToken);\n this.sweep();\n\n const done = this.recent.get(key);\n if (done) return done.result;\n\n let pending = this.inFlight.get(key);\n if (!pending) {\n pending = refresh()\n .then((result) => {\n this.recent.set(key, { at: Date.now(), result });\n return result;\n })\n .finally(() => this.inFlight.delete(key));\n this.inFlight.set(key, pending);\n }\n\n return pending;\n }\n\n private sweep(): void {\n const cutoff = Date.now() - this.recentMs;\n for (const [key, entry] of this.recent) {\n if (entry.at < cutoff) this.recent.delete(key);\n }\n }\n}\n","import { EncryptJWT, jwtDecrypt } from \"jose\";\n\n/**\n * Sealing: authenticated encryption (JWE, `dir` + A256GCM) of whatever the\n * server stores in a cookie. The browser holds ciphertext it can neither read\n * nor alter, which is what lets a refresh token live in a cookie at all.\n */\n\nconst keys = new Map<string, Promise<Uint8Array>>();\n\n/** A 256-bit key derived from the configured secret, whatever its length. */\nfunction keyFor(secret: string): Promise<Uint8Array> {\n let key = keys.get(secret);\n if (!key) {\n key = crypto.subtle\n .digest(\"SHA-256\", new TextEncoder().encode(secret))\n .then((digest) => new Uint8Array(digest));\n keys.set(secret, key);\n }\n\n return key;\n}\n\nexport async function seal(payload: Record<string, unknown>, secret: string, maxAgeSeconds: number): Promise<string> {\n return new EncryptJWT(payload)\n .setProtectedHeader({ alg: \"dir\", enc: \"A256GCM\" })\n .setIssuedAt()\n .setExpirationTime(`${maxAgeSeconds}s`)\n .encrypt(await keyFor(secret));\n}\n\n/** The payload, or null when the value is not one this secret sealed, or has expired. */\nexport async function unseal<T>(value: string, secret: string): Promise<T | null> {\n try {\n const { payload } = await jwtDecrypt(value, await keyFor(secret));\n return payload as T;\n } catch {\n return null;\n }\n}\n","import type { JWTPayload } from \"jose\";\n\nimport { resolveConfig, type ResolvedConfig, type ServerAuthConfig } from \"./config\";\nimport {\n clearChunked,\n formatCookieHeader,\n parseCookies,\n readChunked,\n serializeCookie,\n writeChunked,\n} from \"./cookies\";\nimport {\n authorizationUrl,\n endSessionUrl,\n exchangeCode,\n pkceChallenge,\n randomValue,\n refreshTokens,\n TokenEndpointError,\n verifyIdToken,\n type ClientCredentials,\n type TokenResponse,\n} from \"./oidc\";\nimport { RefreshCoordinator } from \"./refresh\";\nimport { seal, unseal } from \"./seal\";\n\nexport interface SessionUser {\n id: string;\n email?: string;\n name?: string;\n username?: string;\n picture?: string;\n emailVerified: boolean;\n}\n\n/** What `getSession` hands the app. */\nexport interface Session {\n user: SessionUser;\n /** A valid access token, for calling APIs as this user. */\n accessToken: string;\n /** When the access token expires, epoch milliseconds. */\n expiresAt: number;\n /** The issuer's session id (`sid`), when it sent one. */\n sessionId?: string;\n}\n\n/** What the cookie holds: the session plus the refresh token, which the app never sees. */\ninterface StoredSession extends Session {\n refreshToken?: string;\n}\n\n/** A sign-in in progress, sealed into its own short-lived cookie. */\ninterface SignInTransaction {\n state: string;\n verifier: string;\n nonce: string;\n returnTo: string;\n}\n\n/** What a refresh produced, for the middleware to apply. */\nexport interface RefreshOutcome {\n /** The session after the refresh, or null when there is none (or it could not be refreshed). */\n session: Session | null;\n /** `Set-Cookie` values for the response. Empty when nothing changed. */\n setCookies: string[];\n /**\n * The request's `Cookie` header as it should be seen by whatever handles\n * this request next, carrying the refreshed session. Forward it, so a page\n * rendered on this very request reads the new token rather than the expired\n * one the browser sent.\n */\n cookieHeader: string;\n}\n\nexport interface ServerAuth {\n /**\n * Handles the SDK's routes under `basePath`: `/login`, `/callback` and\n * `/logout`. Null for any other path.\n */\n handle(request: Request): Promise<Response | null>;\n /**\n * The session in a `Cookie` header, or null. Read-only and never refreshes:\n * any server code can call it, including code that cannot set cookies.\n * Returns null for a session whose access token has expired — `refresh`,\n * run in middleware ahead of the request, is what keeps it current.\n */\n getSession(cookieHeader: string | null | undefined): Promise<Session | null>;\n /**\n * Refreshes the session when its access token is about to expire. For\n * middleware: it runs before anything renders, and its response can always\n * carry the new cookie. Concurrent calls for one session share one refresh.\n */\n refresh(request: Request): Promise<RefreshOutcome>;\n}\n\nconst DEFAULT_ACCESS_TTL_SECONDS = 900;\nconst TRANSACTION_MAX_AGE = 600;\n\n/** Only a same-origin path may be returned to after sign-in: no open redirect. */\nfunction safePath(value: string | null | undefined, fallback: string): string {\n if (value && value.startsWith(\"/\") && !value.startsWith(\"//\") && !value.startsWith(\"/\\\\\")) return value;\n return fallback;\n}\n\nfunction redirect(location: string, setCookies: string[] = []): Response {\n const headers = new Headers({ location, \"cache-control\": \"no-store\" });\n for (const cookie of setCookies) headers.append(\"set-cookie\", cookie);\n\n return new Response(null, { status: 302, headers });\n}\n\nfunction publicOrigin(cfg: ResolvedConfig, request: Request): string {\n if (cfg.baseUrl) return cfg.baseUrl;\n\n const host = request.headers.get(\"x-forwarded-host\")?.split(\",\")[0]?.trim() || request.headers.get(\"host\")?.trim();\n if (host) {\n const proto =\n request.headers.get(\"x-forwarded-proto\")?.split(\",\")[0]?.trim() || new URL(request.url).protocol.replace(\":\", \"\");\n try {\n return new URL(`${proto}://${host}`).origin;\n } catch {\n // Fall through to the request URL.\n }\n }\n\n return new URL(request.url).origin;\n}\n\nfunction userFrom(claims: JWTPayload): SessionUser {\n const str = (key: string) => (typeof claims[key] === \"string\" ? (claims[key] as string) : undefined);\n const name =\n str(\"name\") ?? ([str(\"given_name\"), str(\"family_name\")].filter(Boolean).join(\" \") || undefined) ?? str(\"email\");\n\n return {\n id: claims.sub ?? \"\",\n email: str(\"email\"),\n name,\n username: str(\"preferred_username\"),\n picture: str(\"picture\"),\n emailVerified: claims.email_verified === true,\n };\n}\n\nfunction expiresAtFrom(token: TokenResponse): number {\n return Date.now() + (token.expires_in ?? DEFAULT_ACCESS_TTL_SECONDS) * 1000;\n}\n\nexport function createServerAuth(config: ServerAuthConfig): ServerAuth {\n const cfg = resolveConfig(config);\n const client: ClientCredentials = { issuer: cfg.issuer, clientId: cfg.clientId, clientSecret: cfg.clientSecret };\n const txCookie = `${cfg.cookieName}_tx`;\n const coordinator = new RefreshCoordinator<StoredSession | null>();\n\n const secure = (request: Request) => cfg.secureCookies ?? publicOrigin(cfg, request).startsWith(\"https:\");\n const redirectUri = (request: Request) => cfg.redirectUri ?? `${publicOrigin(cfg, request)}${cfg.basePath}/callback`;\n const toSignIn = (request: Request, error?: string) => {\n const url = new URL(cfg.signInPath, publicOrigin(cfg, request));\n if (error) url.searchParams.set(\"error\", error);\n return url.toString();\n };\n\n async function readStored(cookies: Map<string, string>): Promise<StoredSession | null> {\n const raw = readChunked(cookies, cfg.cookieName);\n if (raw === null) return null;\n\n const stored = await unseal<StoredSession>(raw, cfg.sessionSecret);\n if (!stored) cfg.logger.warn(\"[pontive] session cookie could not be read (sealed with another secret, or truncated)\");\n\n return stored;\n }\n\n async function writeStored(cookies: Map<string, string>, stored: StoredSession, request: Request): Promise<string[]> {\n const sealed = await seal({ ...stored }, cfg.sessionSecret, cfg.sessionMaxAgeSeconds);\n\n return writeChunked(cookies, cfg.cookieName, sealed, { maxAge: cfg.sessionMaxAgeSeconds, secure: secure(request) });\n }\n\n function publicSession(stored: StoredSession): Session {\n const { refreshToken: _refreshToken, ...session } = stored;\n return session;\n }\n\n async function signIn(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const tx: SignInTransaction = {\n state: randomValue(),\n verifier: randomValue(),\n nonce: randomValue(),\n returnTo: safePath(url.searchParams.get(\"returnTo\") ?? url.searchParams.get(\"next\"), cfg.afterSignInPath),\n };\n\n const location = await authorizationUrl(client, cfg.fetch, {\n redirectUri: redirectUri(request),\n scopes: cfg.scopes,\n state: tx.state,\n nonce: tx.nonce,\n codeChallenge: await pkceChallenge(tx.verifier),\n prompt: cfg.prompt,\n });\n\n const sealed = await seal({ ...tx }, cfg.sessionSecret, TRANSACTION_MAX_AGE);\n\n return redirect(location, [serializeCookie(txCookie, sealed, { maxAge: TRANSACTION_MAX_AGE, secure: secure(request) })]);\n }\n\n async function callback(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n // Single use, whatever happens next.\n const setCookies = [serializeCookie(txCookie, \"\", { maxAge: 0, secure: secure(request) })];\n\n const issuerError = url.searchParams.get(\"error\");\n if (issuerError) return redirect(toSignIn(request, issuerError), setCookies);\n\n const raw = cookies.get(txCookie);\n const tx = raw ? await unseal<SignInTransaction>(raw, cfg.sessionSecret) : null;\n const code = url.searchParams.get(\"code\");\n\n if (!tx || !code || url.searchParams.get(\"state\") !== tx.state) {\n return redirect(toSignIn(request, \"invalid_state\"), setCookies);\n }\n\n try {\n const tokens = await exchangeCode(client, cfg.fetch, {\n code,\n codeVerifier: tx.verifier,\n redirectUri: redirectUri(request),\n });\n if (!tokens.id_token) throw new Error(\"the token response carried no id_token; is the openid scope requested?\");\n\n const claims = await verifyIdToken(client, cfg.fetch, tokens.id_token, tx.nonce);\n const stored: StoredSession = {\n user: userFrom(claims),\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresAt: expiresAtFrom(tokens),\n sessionId: typeof claims.sid === \"string\" ? claims.sid : undefined,\n };\n\n setCookies.push(...(await writeStored(cookies, stored, request)));\n } catch (err) {\n cfg.logger.warn(\"[pontive] sign-in failed:\", err instanceof Error ? err.message : String(err));\n return redirect(toSignIn(request, \"exchange_failed\"), setCookies);\n }\n\n return redirect(new URL(tx.returnTo, publicOrigin(cfg, request)).toString(), setCookies);\n }\n\n async function signOut(request: Request): Promise<Response> {\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n // Read before clearing: the session holds the sid the issuer's logout needs.\n const stored = await readStored(cookies);\n const setCookies = clearChunked(cookies, cfg.cookieName, secure(request));\n\n let location = toSignIn(request);\n try {\n location =\n (await endSessionUrl(client, cfg.fetch, {\n postLogoutRedirectUri: toSignIn(request),\n sessionId: stored?.sessionId,\n })) ?? location;\n } catch (err) {\n // An unreachable issuer must not stop the local sign-out.\n cfg.logger.warn(\"[pontive] could not reach the issuer to end its session:\", err instanceof Error ? err.message : String(err));\n }\n\n return redirect(location, setCookies);\n }\n\n /**\n * Spends the refresh token once, returning the refreshed session, or null\n * when the issuer refused it for good. Anything transient throws instead, so\n * the coordinator does not hand a passing failure to other requests.\n */\n async function refreshStored(stored: StoredSession): Promise<StoredSession | null> {\n try {\n const tokens = await refreshTokens(client, cfg.fetch, stored.refreshToken!);\n\n return {\n ...stored,\n accessToken: tokens.access_token,\n // A response without one means rotation is off: the token in hand is still current.\n refreshToken: tokens.refresh_token ?? stored.refreshToken,\n expiresAt: expiresAtFrom(tokens),\n };\n } catch (err) {\n if (err instanceof TokenEndpointError && err.status >= 400 && err.status < 500) {\n cfg.logger.warn(\"[pontive] refresh refused; signing out:\", err.message);\n return null;\n }\n\n throw err;\n }\n }\n\n return {\n async handle(request) {\n const { pathname } = new URL(request.url);\n if (!pathname.startsWith(`${cfg.basePath}/`)) return null;\n\n const route = pathname.slice(cfg.basePath.length + 1);\n if (route === \"login\" && request.method === \"GET\") return signIn(request);\n if (route === \"callback\" && request.method === \"GET\") return callback(request);\n if (route === \"logout\" && (request.method === \"GET\" || request.method === \"POST\")) return signOut(request);\n\n return null;\n },\n\n async getSession(cookieHeader) {\n const stored = await readStored(parseCookies(cookieHeader));\n if (!stored) return null;\n\n if (Date.now() >= stored.expiresAt) {\n cfg.logger.warn(\n \"[pontive] the session's access token has expired and getSession cannot refresh it. \" +\n \"Run the SDK's middleware on this path so it refreshes before the request is handled.\",\n );\n return null;\n }\n\n return publicSession(stored);\n },\n\n async refresh(request) {\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n const unchanged = (session: Session | null): RefreshOutcome => ({\n session,\n setCookies: [],\n cookieHeader: request.headers.get(\"cookie\") ?? \"\",\n });\n\n const stored = await readStored(cookies);\n if (!stored) return unchanged(null);\n\n // The SDK's own routes manage the session themselves. Refreshing ahead of\n // them only spends a refresh token that sign-out is about to discard, and\n // puts a second session cookie on the response it clears.\n if (new URL(request.url).pathname.startsWith(`${cfg.basePath}/`)) {\n return unchanged(Date.now() < stored.expiresAt ? publicSession(stored) : null);\n }\n\n if (Date.now() < stored.expiresAt - cfg.refreshSkewSeconds * 1000) return unchanged(publicSession(stored));\n\n const signOutNow = (): RefreshOutcome => {\n const setCookies = clearChunked(cookies, cfg.cookieName, secure(request));\n return { session: null, setCookies, cookieHeader: formatCookieHeader(cookies) };\n };\n\n if (!stored.refreshToken) return signOutNow();\n\n let refreshed: StoredSession | null;\n try {\n refreshed = await coordinator.run(stored.refreshToken, () => refreshStored(stored));\n } catch (err) {\n // Transient: keep the session and let the next request try again. The\n // token may still have a few seconds of the skew left.\n cfg.logger.warn(\"[pontive] refresh failed, will retry:\", err instanceof Error ? err.message : String(err));\n return unchanged(Date.now() < stored.expiresAt ? publicSession(stored) : null);\n }\n\n if (!refreshed) return signOutNow();\n\n const setCookies = await writeStored(cookies, refreshed, request);\n\n return { session: publicSession(refreshed), setCookies, cookieHeader: formatCookieHeader(cookies) };\n },\n };\n}\n"],"names":["SEVEN_DAYS","resolveConfig","config","required","key","args","CHUNK_SIZE","MAX_CHUNKS","parseCookies","header","out","part","eq","name","formatCookieHeader","cookies","value","serializeCookie","attrs","parts","chunkName","index","readChunked","first","i","writeChunked","count","chunk","stale","clearChunked","secure","TokenEndpointError","status","code","description","JWKS_TTL_MS","discoveries","jwksCache","trimSlash","discover","issuer","fetchImpl","pending","res","err","jwks","uri","force","cached","set","createLocalJWKSet","base64url","bytes","binary","b","randomValue","pkceChallenge","verifier","digest","sha256Hex","authorizationUrl","client","input","disc","url","tokenRequest","params","body","headers","credentials","exchangeCode","refreshTokens","refreshToken","verifyIdToken","idToken","nonce","options","result","jwtVerify","endSessionUrl","RECENT_RESULT_MS","RefreshCoordinator","recentMs","refresh","done","cutoff","entry","keys","keyFor","secret","seal","payload","maxAgeSeconds","EncryptJWT","unseal","jwtDecrypt","DEFAULT_ACCESS_TTL_SECONDS","TRANSACTION_MAX_AGE","safePath","fallback","redirect","location","setCookies","cookie","publicOrigin","cfg","request","host","proto","userFrom","claims","str","expiresAtFrom","token","createServerAuth","txCookie","coordinator","redirectUri","toSignIn","error","readStored","raw","stored","writeStored","sealed","publicSession","_refreshToken","session","signIn","tx","callback","issuerError","tokens","signOut","refreshStored","pathname","route","cookieHeader","unchanged","signOutNow","refreshed"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -44,14 +44,14 @@ function x(t) {
|
|
|
44
44
|
}
|
|
45
45
|
return e;
|
|
46
46
|
}
|
|
47
|
-
function
|
|
47
|
+
function R(t) {
|
|
48
48
|
return [...t].map(([e, r]) => `${e}=${r}`).join("; ");
|
|
49
49
|
}
|
|
50
|
-
function
|
|
50
|
+
function U(t, e, r) {
|
|
51
51
|
const n = [`${t}=${e}`, "Path=/", "HttpOnly", "SameSite=Lax", `Max-Age=${r.maxAge}`];
|
|
52
52
|
return r.secure && n.push("Secure"), n.join("; ");
|
|
53
53
|
}
|
|
54
|
-
function
|
|
54
|
+
function _(t, e) {
|
|
55
55
|
return e === 0 ? t : `${t}.${e}`;
|
|
56
56
|
}
|
|
57
57
|
function te(t, e) {
|
|
@@ -59,7 +59,7 @@ function te(t, e) {
|
|
|
59
59
|
if (r === void 0) return null;
|
|
60
60
|
let n = r;
|
|
61
61
|
for (let s = 1; s < $; s++) {
|
|
62
|
-
const a = t.get(
|
|
62
|
+
const a = t.get(_(e, s));
|
|
63
63
|
if (a === void 0) break;
|
|
64
64
|
n += a;
|
|
65
65
|
}
|
|
@@ -68,25 +68,25 @@ function te(t, e) {
|
|
|
68
68
|
function re(t, e, r, n) {
|
|
69
69
|
const s = [], a = Math.max(1, Math.ceil(r.length / P));
|
|
70
70
|
for (let u = 0; u < a; u++) {
|
|
71
|
-
const
|
|
72
|
-
t.set(
|
|
71
|
+
const l = r.slice(u * P, (u + 1) * P);
|
|
72
|
+
t.set(_(e, u), l), s.push(U(_(e, u), l, n));
|
|
73
73
|
}
|
|
74
74
|
for (let u = a; u < $; u++) {
|
|
75
|
-
const
|
|
76
|
-
if (!t.has(
|
|
77
|
-
t.delete(
|
|
75
|
+
const l = _(e, u);
|
|
76
|
+
if (!t.has(l)) break;
|
|
77
|
+
t.delete(l), s.push(U(l, "", { ...n, maxAge: 0 }));
|
|
78
78
|
}
|
|
79
79
|
return s;
|
|
80
80
|
}
|
|
81
|
-
function
|
|
81
|
+
function I(t, e, r) {
|
|
82
82
|
const n = [];
|
|
83
83
|
for (let s = 0; s < $; s++) {
|
|
84
|
-
const a =
|
|
84
|
+
const a = _(e, s);
|
|
85
85
|
if (!t.has(a)) {
|
|
86
86
|
if (s > 0) break;
|
|
87
87
|
continue;
|
|
88
88
|
}
|
|
89
|
-
t.delete(a), n.push(
|
|
89
|
+
t.delete(a), n.push(U(a, "", { maxAge: 0, secure: r }));
|
|
90
90
|
}
|
|
91
91
|
return n;
|
|
92
92
|
}
|
|
@@ -143,18 +143,18 @@ async function K(t, e, r) {
|
|
|
143
143
|
s.set("client_id", t.clientId);
|
|
144
144
|
const a = { "content-type": "application/x-www-form-urlencoded" };
|
|
145
145
|
if (t.clientSecret) {
|
|
146
|
-
const
|
|
147
|
-
a.authorization = `Basic ${btoa(
|
|
146
|
+
const l = `${encodeURIComponent(t.clientId)}:${encodeURIComponent(t.clientSecret)}`;
|
|
147
|
+
a.authorization = `Basic ${btoa(l)}`;
|
|
148
148
|
}
|
|
149
149
|
const u = await e(n.token_endpoint, { method: "POST", headers: a, body: s });
|
|
150
150
|
if (!u.ok) {
|
|
151
|
-
let
|
|
151
|
+
let l = "unknown_error", k = "";
|
|
152
152
|
try {
|
|
153
153
|
const S = await u.json();
|
|
154
|
-
|
|
154
|
+
l = S.error ?? l, k = S.error_description ?? "";
|
|
155
155
|
} catch {
|
|
156
156
|
}
|
|
157
|
-
throw new W(u.status,
|
|
157
|
+
throw new W(u.status, l, k);
|
|
158
158
|
}
|
|
159
159
|
return await u.json();
|
|
160
160
|
}
|
|
@@ -174,8 +174,8 @@ async function de(t, e, r, n) {
|
|
|
174
174
|
let u;
|
|
175
175
|
try {
|
|
176
176
|
u = await v(r, await M(s.jwks_uri, e, !1), a);
|
|
177
|
-
} catch (
|
|
178
|
-
if (
|
|
177
|
+
} catch (l) {
|
|
178
|
+
if (l.code !== "ERR_JWKS_NO_MATCHING_KEY") throw l;
|
|
179
179
|
u = await v(r, await M(s.jwks_uri, e, !0), a);
|
|
180
180
|
}
|
|
181
181
|
if (u.payload.nonce !== n) throw new Error("id_token nonce does not match the sign-in request");
|
|
@@ -214,10 +214,10 @@ function V(t) {
|
|
|
214
214
|
let e = N.get(t);
|
|
215
215
|
return e || (e = crypto.subtle.digest("SHA-256", new TextEncoder().encode(t)).then((r) => new Uint8Array(r)), N.set(t, e)), e;
|
|
216
216
|
}
|
|
217
|
-
async function
|
|
217
|
+
async function D(t, e, r) {
|
|
218
218
|
return new Q(t).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${r}s`).encrypt(await V(e));
|
|
219
219
|
}
|
|
220
|
-
async function
|
|
220
|
+
async function j(t, e) {
|
|
221
221
|
try {
|
|
222
222
|
const { payload: r } = await Z(t, await V(e));
|
|
223
223
|
return r;
|
|
@@ -261,21 +261,21 @@ function O(t) {
|
|
|
261
261
|
return Date.now() + (t.expires_in ?? we) * 1e3;
|
|
262
262
|
}
|
|
263
263
|
function me(t) {
|
|
264
|
-
const e = ee(t), r = { issuer: e.issuer, clientId: e.clientId, clientSecret: e.clientSecret }, n = `${e.cookieName}_tx`, s = new fe(), a = (o) => e.secureCookies ?? C(e, o).startsWith("https:"), u = (o) => e.redirectUri ?? `${C(e, o)}${e.basePath}/callback`,
|
|
264
|
+
const e = ee(t), r = { issuer: e.issuer, clientId: e.clientId, clientSecret: e.clientSecret }, n = `${e.cookieName}_tx`, s = new fe(), a = (o) => e.secureCookies ?? C(e, o).startsWith("https:"), u = (o) => e.redirectUri ?? `${C(e, o)}${e.basePath}/callback`, l = (o, i) => {
|
|
265
265
|
const c = new URL(e.signInPath, C(e, o));
|
|
266
266
|
return i && c.searchParams.set("error", i), c.toString();
|
|
267
267
|
};
|
|
268
268
|
async function k(o) {
|
|
269
269
|
const i = te(o, e.cookieName);
|
|
270
270
|
if (i === null) return null;
|
|
271
|
-
const c = await
|
|
271
|
+
const c = await j(i, e.sessionSecret);
|
|
272
272
|
return c || e.logger.warn("[pontive] session cookie could not be read (sealed with another secret, or truncated)"), c;
|
|
273
273
|
}
|
|
274
274
|
async function S(o, i, c) {
|
|
275
|
-
const
|
|
276
|
-
return re(o, e.cookieName,
|
|
275
|
+
const d = await D({ ...i }, e.sessionSecret, e.sessionMaxAgeSeconds);
|
|
276
|
+
return re(o, e.cookieName, d, { maxAge: e.sessionMaxAgeSeconds, secure: a(c) });
|
|
277
277
|
}
|
|
278
|
-
function
|
|
278
|
+
function y(o) {
|
|
279
279
|
const { refreshToken: i, ...c } = o;
|
|
280
280
|
return c;
|
|
281
281
|
}
|
|
@@ -285,22 +285,22 @@ function me(t) {
|
|
|
285
285
|
verifier: E(),
|
|
286
286
|
nonce: E(),
|
|
287
287
|
returnTo: pe(i.searchParams.get("returnTo") ?? i.searchParams.get("next"), e.afterSignInPath)
|
|
288
|
-
},
|
|
288
|
+
}, d = await ae(r, e.fetch, {
|
|
289
289
|
redirectUri: u(o),
|
|
290
290
|
scopes: e.scopes,
|
|
291
291
|
state: c.state,
|
|
292
292
|
nonce: c.nonce,
|
|
293
293
|
codeChallenge: await oe(c.verifier),
|
|
294
294
|
prompt: e.prompt
|
|
295
|
-
}), h = await
|
|
296
|
-
return m(
|
|
295
|
+
}), h = await D({ ...c }, e.sessionSecret, H);
|
|
296
|
+
return m(d, [U(n, h, { maxAge: H, secure: a(o) })]);
|
|
297
297
|
}
|
|
298
298
|
async function z(o) {
|
|
299
|
-
const i = new URL(o.url), c = x(o.headers.get("cookie")),
|
|
300
|
-
if (h) return m(
|
|
301
|
-
const f = c.get(n), g = f ? await
|
|
299
|
+
const i = new URL(o.url), c = x(o.headers.get("cookie")), d = [U(n, "", { maxAge: 0, secure: a(o) })], h = i.searchParams.get("error");
|
|
300
|
+
if (h) return m(l(o, h), d);
|
|
301
|
+
const f = c.get(n), g = f ? await j(f, e.sessionSecret) : null, w = i.searchParams.get("code");
|
|
302
302
|
if (!g || !w || i.searchParams.get("state") !== g.state)
|
|
303
|
-
return m(
|
|
303
|
+
return m(l(o, "invalid_state"), d);
|
|
304
304
|
try {
|
|
305
305
|
const p = await ce(r, e.fetch, {
|
|
306
306
|
code: w,
|
|
@@ -315,24 +315,24 @@ function me(t) {
|
|
|
315
315
|
expiresAt: O(p),
|
|
316
316
|
sessionId: typeof A.sid == "string" ? A.sid : void 0
|
|
317
317
|
};
|
|
318
|
-
|
|
318
|
+
d.push(...await S(c, X, o));
|
|
319
319
|
} catch (p) {
|
|
320
|
-
return e.logger.warn("[pontive] sign-in failed:", p instanceof Error ? p.message : String(p)), m(
|
|
320
|
+
return e.logger.warn("[pontive] sign-in failed:", p instanceof Error ? p.message : String(p)), m(l(o, "exchange_failed"), d);
|
|
321
321
|
}
|
|
322
|
-
return m(new URL(g.returnTo, C(e, o)).toString(),
|
|
322
|
+
return m(new URL(g.returnTo, C(e, o)).toString(), d);
|
|
323
323
|
}
|
|
324
324
|
async function J(o) {
|
|
325
|
-
const i = x(o.headers.get("cookie")), c = await k(i),
|
|
326
|
-
let h =
|
|
325
|
+
const i = x(o.headers.get("cookie")), c = await k(i), d = I(i, e.cookieName, a(o));
|
|
326
|
+
let h = l(o);
|
|
327
327
|
try {
|
|
328
328
|
h = await le(r, e.fetch, {
|
|
329
|
-
postLogoutRedirectUri:
|
|
329
|
+
postLogoutRedirectUri: l(o),
|
|
330
330
|
sessionId: c?.sessionId
|
|
331
331
|
}) ?? h;
|
|
332
332
|
} catch (f) {
|
|
333
333
|
e.logger.warn("[pontive] could not reach the issuer to end its session:", f instanceof Error ? f.message : String(f));
|
|
334
334
|
}
|
|
335
|
-
return m(h,
|
|
335
|
+
return m(h, d);
|
|
336
336
|
}
|
|
337
337
|
async function B(o) {
|
|
338
338
|
try {
|
|
@@ -361,27 +361,29 @@ function me(t) {
|
|
|
361
361
|
const i = await k(x(o));
|
|
362
362
|
return i ? Date.now() >= i.expiresAt ? (e.logger.warn(
|
|
363
363
|
"[pontive] the session's access token has expired and getSession cannot refresh it. Run the SDK's middleware on this path so it refreshes before the request is handled."
|
|
364
|
-
), null) :
|
|
364
|
+
), null) : y(i) : null;
|
|
365
365
|
},
|
|
366
366
|
async refresh(o) {
|
|
367
367
|
const i = x(o.headers.get("cookie")), c = (w) => ({
|
|
368
368
|
session: w,
|
|
369
369
|
setCookies: [],
|
|
370
370
|
cookieHeader: o.headers.get("cookie") ?? ""
|
|
371
|
-
}),
|
|
372
|
-
if (!
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
if (
|
|
371
|
+
}), d = await k(i);
|
|
372
|
+
if (!d) return c(null);
|
|
373
|
+
if (new URL(o.url).pathname.startsWith(`${e.basePath}/`))
|
|
374
|
+
return c(Date.now() < d.expiresAt ? y(d) : null);
|
|
375
|
+
if (Date.now() < d.expiresAt - e.refreshSkewSeconds * 1e3) return c(y(d));
|
|
376
|
+
const h = () => ({ session: null, setCookies: I(i, e.cookieName, a(o)), cookieHeader: R(i) });
|
|
377
|
+
if (!d.refreshToken) return h();
|
|
376
378
|
let f;
|
|
377
379
|
try {
|
|
378
|
-
f = await s.run(
|
|
380
|
+
f = await s.run(d.refreshToken, () => B(d));
|
|
379
381
|
} catch (w) {
|
|
380
|
-
return e.logger.warn("[pontive] refresh failed, will retry:", w instanceof Error ? w.message : String(w)), c(Date.now() <
|
|
382
|
+
return e.logger.warn("[pontive] refresh failed, will retry:", w instanceof Error ? w.message : String(w)), c(Date.now() < d.expiresAt ? y(d) : null);
|
|
381
383
|
}
|
|
382
384
|
if (!f) return h();
|
|
383
385
|
const g = await S(i, f, o);
|
|
384
|
-
return { session:
|
|
386
|
+
return { session: y(f), setCookies: g, cookieHeader: R(i) };
|
|
385
387
|
}
|
|
386
388
|
};
|
|
387
389
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/config.ts","../src/cookies.ts","../src/oidc.ts","../src/refresh.ts","../src/seal.ts","../src/server-auth.ts"],"sourcesContent":["/**\n * What an app tells the SDK, and the defaults for everything it doesn't.\n *\n * Only the first five matter in practice. Everything else exists for the rare\n * app that must differ, and its default is the decision the SDK makes for\n * everyone else.\n */\nexport interface ServerAuthConfig {\n /** The Pontive auth domain, e.g. `https://acme.auth.us.pontive.com`. */\n issuer: string;\n /** The app's id. */\n clientId: string;\n /**\n * Set for a confidential app. It is sent on every token call, and is what\n * lets the app hold its refresh token on its server at all.\n */\n clientSecret?: string;\n /** Encrypts the session cookie. At least 32 characters; rotating it signs everyone out. */\n sessionSecret: string;\n /**\n * The app's public origin, e.g. `https://app.acme.com`. Set it for any\n * deployment behind a proxy or in a container, where the request's own URL\n * names the address the server binds to rather than the one a browser uses.\n * Without it the origin comes from `x-forwarded-proto`/`x-forwarded-host`,\n * then the request URL.\n */\n baseUrl?: string;\n\n /** Default `openid email profile offline_access`. */\n scopes?: string;\n /** Where the SDK's sign-in routes live. Default `/api/auth`. */\n basePath?: string;\n /** Default `${origin}${basePath}/callback`; must be registered on the app verbatim. */\n redirectUri?: string;\n /** The app's own sign-in page: failed sign-ins and signed-out users land here. Default `/login`. */\n signInPath?: string;\n /** Where a sign-in lands when it did not start from a page. Default `/`. */\n afterSignInPath?: string;\n /** An OIDC `prompt` to send, e.g. `login`. */\n prompt?: string;\n /** Default `pontive_session`. */\n cookieName?: string;\n /** Default: whether the public origin is https. */\n secureCookies?: boolean;\n /** The session cookie's lifetime. Default 7 days. */\n sessionMaxAgeSeconds?: number;\n /** Refresh when fewer than this many seconds remain. Default 60. */\n refreshSkewSeconds?: number;\n /** Defaults to the global `fetch`. */\n fetch?: typeof fetch;\n /** Where the SDK reports a failed refresh or sign-in. Defaults to `console`. */\n logger?: Pick<Console, \"warn\">;\n}\n\nexport interface ResolvedConfig {\n issuer: string;\n clientId: string;\n clientSecret?: string;\n sessionSecret: string;\n baseUrl?: string;\n scopes: string;\n basePath: string;\n redirectUri?: string;\n signInPath: string;\n afterSignInPath: string;\n prompt?: string;\n cookieName: string;\n secureCookies?: boolean;\n sessionMaxAgeSeconds: number;\n refreshSkewSeconds: number;\n fetch: typeof fetch;\n logger: Pick<Console, \"warn\">;\n}\n\nconst SEVEN_DAYS = 7 * 24 * 60 * 60;\n\nexport function resolveConfig(config: ServerAuthConfig): ResolvedConfig {\n const required: (keyof ServerAuthConfig)[] = [\"issuer\", \"clientId\", \"sessionSecret\"];\n for (const key of required) {\n if (!config[key]) throw new Error(`@pontive/pontkit-server: ${key} is required`);\n }\n\n if (config.sessionSecret.length < 32) {\n throw new Error(\"@pontive/pontkit-server: sessionSecret must be at least 32 characters\");\n }\n\n if (config.baseUrl) {\n try {\n new URL(config.baseUrl);\n } catch {\n throw new Error(`@pontive/pontkit-server: baseUrl \"${config.baseUrl}\" is not an absolute URL`);\n }\n }\n\n return {\n issuer: config.issuer.replace(/\\/+$/, \"\"),\n clientId: config.clientId,\n clientSecret: config.clientSecret || undefined,\n sessionSecret: config.sessionSecret,\n baseUrl: config.baseUrl ? new URL(config.baseUrl).origin : undefined,\n scopes: config.scopes ?? \"openid email profile offline_access\",\n basePath: (config.basePath ?? \"/api/auth\").replace(/\\/+$/, \"\"),\n redirectUri: config.redirectUri,\n signInPath: config.signInPath ?? \"/login\",\n afterSignInPath: config.afterSignInPath ?? \"/\",\n prompt: config.prompt,\n cookieName: config.cookieName ?? \"pontive_session\",\n secureCookies: config.secureCookies,\n sessionMaxAgeSeconds: config.sessionMaxAgeSeconds ?? SEVEN_DAYS,\n refreshSkewSeconds: config.refreshSkewSeconds ?? 60,\n fetch: config.fetch ?? ((...args) => fetch(...args)),\n logger: config.logger ?? console,\n };\n}\n","/**\n * Cookie reading and writing, on plain header strings so it runs anywhere a\n * Web `Request`/`Response` does.\n *\n * A sealed session outgrows one cookie (browsers cap each at about 4 KB, and an\n * access token, a refresh token and a user fit in about 3–5), so it is split\n * across numbered chunks: `name`, `name.1`, `name.2`, … and joined on read.\n */\n\n/** Characters per chunk, leaving room under the ~4096-byte cap for the name and attributes. */\nexport const CHUNK_SIZE = 3500;\n/** Upper bound on chunks probed when reading or clearing. */\nexport const MAX_CHUNKS = 16;\n\nexport interface CookieAttributes {\n /** Seconds; 0 deletes the cookie. */\n maxAge: number;\n secure: boolean;\n}\n\n/** Parses a `Cookie` request header into name → value. */\nexport function parseCookies(header: string | null | undefined): Map<string, string> {\n const out = new Map<string, string>();\n if (!header) return out;\n\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq < 0) continue;\n\n const name = part.slice(0, eq).trim();\n if (!name || out.has(name)) continue;\n\n out.set(name, part.slice(eq + 1).trim());\n }\n\n return out;\n}\n\n/** Serialises a `Cookie` request header from name → value. */\nexport function formatCookieHeader(cookies: Map<string, string>): string {\n return [...cookies].map(([name, value]) => `${name}=${value}`).join(\"; \");\n}\n\n/**\n * A `Set-Cookie` value. Always HttpOnly, host-only, `Path=/` and\n * `SameSite=Lax`: Lax is what lets the cookie ride the top-level redirect back\n * from the sign-in page, and nothing here is ever for script.\n */\nexport function serializeCookie(name: string, value: string, attrs: CookieAttributes): string {\n const parts = [`${name}=${value}`, \"Path=/\", \"HttpOnly\", \"SameSite=Lax\", `Max-Age=${attrs.maxAge}`];\n if (attrs.secure) parts.push(\"Secure\");\n\n return parts.join(\"; \");\n}\n\nexport function chunkName(name: string, index: number): string {\n return index === 0 ? name : `${name}.${index}`;\n}\n\n/** Joins a chunked value, or null when its first chunk is absent. */\nexport function readChunked(cookies: Map<string, string>, name: string): string | null {\n const first = cookies.get(name);\n if (first === undefined) return null;\n\n let value = first;\n for (let i = 1; i < MAX_CHUNKS; i++) {\n const part = cookies.get(chunkName(name, i));\n if (part === undefined) break;\n value += part;\n }\n\n return value;\n}\n\n/**\n * The `Set-Cookie` values that store `value` in chunks, and delete any chunk\n * the request carried beyond what it now needs. Also updates `cookies` in\n * place, so a caller can forward the new state on the same request.\n */\nexport function writeChunked(\n cookies: Map<string, string>,\n name: string,\n value: string,\n attrs: CookieAttributes,\n): string[] {\n const out: string[] = [];\n const count = Math.max(1, Math.ceil(value.length / CHUNK_SIZE));\n\n for (let i = 0; i < count; i++) {\n const chunk = value.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);\n cookies.set(chunkName(name, i), chunk);\n out.push(serializeCookie(chunkName(name, i), chunk, attrs));\n }\n\n for (let i = count; i < MAX_CHUNKS; i++) {\n const stale = chunkName(name, i);\n if (!cookies.has(stale)) break;\n cookies.delete(stale);\n out.push(serializeCookie(stale, \"\", { ...attrs, maxAge: 0 }));\n }\n\n return out;\n}\n\n/** The `Set-Cookie` values that delete every chunk the request carried. */\nexport function clearChunked(cookies: Map<string, string>, name: string, secure: boolean): string[] {\n const out: string[] = [];\n\n for (let i = 0; i < MAX_CHUNKS; i++) {\n const chunk = chunkName(name, i);\n if (!cookies.has(chunk)) {\n if (i > 0) break;\n continue;\n }\n cookies.delete(chunk);\n out.push(serializeCookie(chunk, \"\", { maxAge: 0, secure }));\n }\n\n return out;\n}\n","import { createLocalJWKSet, jwtVerify, type JSONWebKeySet, type JWTPayload } from \"jose\";\n\n/**\n * The OIDC relying-party calls: discovery, PKCE, the two token grants and\n * id_token verification. Standard OIDC only, so any compliant issuer works.\n */\n\nexport interface Discovery {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n jwks_uri: string;\n end_session_endpoint?: string;\n}\n\nexport interface TokenResponse {\n access_token: string;\n token_type: string;\n expires_in?: number;\n refresh_token?: string;\n id_token?: string;\n scope?: string;\n}\n\nexport interface ClientCredentials {\n issuer: string;\n clientId: string;\n clientSecret?: string;\n}\n\n/** An error from the token endpoint, carrying its OAuth code. */\nexport class TokenEndpointError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n description: string,\n ) {\n super(`token endpoint ${status} ${code}: ${description}`);\n }\n}\n\ntype Fetch = typeof fetch;\n\nconst JWKS_TTL_MS = 10 * 60 * 1000;\n\nconst discoveries = new Map<string, Promise<Discovery>>();\nconst jwksCache = new Map<string, { at: number; set: ReturnType<typeof createLocalJWKSet> }>();\n\nfunction trimSlash(value: string): string {\n return value.replace(/\\/+$/, \"\");\n}\n\nexport function discover(issuer: string, fetchImpl: Fetch): Promise<Discovery> {\n const key = trimSlash(issuer);\n let pending = discoveries.get(key);\n\n if (!pending) {\n pending = fetchImpl(`${key}/.well-known/openid-configuration`)\n .then(async (res) => {\n if (!res.ok) throw new Error(`OIDC discovery failed for ${key}: ${res.status}`);\n return (await res.json()) as Discovery;\n })\n .catch((err: unknown) => {\n // A failure is not cached: the next request retries.\n discoveries.delete(key);\n throw err;\n });\n discoveries.set(key, pending);\n }\n\n return pending;\n}\n\nasync function jwks(uri: string, fetchImpl: Fetch, force: boolean): Promise<ReturnType<typeof createLocalJWKSet>> {\n const cached = jwksCache.get(uri);\n if (cached && !force && Date.now() - cached.at < JWKS_TTL_MS) return cached.set;\n\n const res = await fetchImpl(uri);\n if (!res.ok) throw new Error(`JWKS fetch failed for ${uri}: ${res.status}`);\n\n const set = createLocalJWKSet((await res.json()) as JSONWebKeySet);\n jwksCache.set(uri, { at: Date.now(), set });\n\n return set;\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const b of bytes) binary += String.fromCharCode(b);\n\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** A random, URL-safe value for state, nonce and the PKCE verifier. */\nexport function randomValue(): string {\n return base64url(crypto.getRandomValues(new Uint8Array(32)));\n}\n\nexport async function pkceChallenge(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(verifier));\n\n return base64url(new Uint8Array(digest));\n}\n\n/** Hex SHA-256, for keying state by a secret without holding the secret. */\nexport async function sha256Hex(value: string): Promise<string> {\n const digest = new Uint8Array(await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(value)));\n\n return [...digest].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport async function authorizationUrl(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { redirectUri: string; scopes: string; state: string; nonce: string; codeChallenge: string; prompt?: string },\n): Promise<string> {\n const disc = await discover(client.issuer, fetchImpl);\n const url = new URL(disc.authorization_endpoint);\n\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", client.clientId);\n url.searchParams.set(\"redirect_uri\", input.redirectUri);\n url.searchParams.set(\"scope\", input.scopes);\n url.searchParams.set(\"state\", input.state);\n url.searchParams.set(\"nonce\", input.nonce);\n url.searchParams.set(\"code_challenge\", input.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n if (input.prompt) url.searchParams.set(\"prompt\", input.prompt);\n\n return url.toString();\n}\n\n/**\n * A token-endpoint call. A confidential client authenticates with HTTP Basic\n * (RFC 6749 §2.3.1); `client_id` is always in the form too, which a public\n * client needs and a confidential one is allowed.\n */\nasync function tokenRequest(\n client: ClientCredentials,\n fetchImpl: Fetch,\n params: Record<string, string>,\n): Promise<TokenResponse> {\n const disc = await discover(client.issuer, fetchImpl);\n const body = new URLSearchParams(params);\n body.set(\"client_id\", client.clientId);\n\n const headers: Record<string, string> = { \"content-type\": \"application/x-www-form-urlencoded\" };\n if (client.clientSecret) {\n const credentials = `${encodeURIComponent(client.clientId)}:${encodeURIComponent(client.clientSecret)}`;\n headers.authorization = `Basic ${btoa(credentials)}`;\n }\n\n const res = await fetchImpl(disc.token_endpoint, { method: \"POST\", headers, body });\n if (!res.ok) {\n let code = \"unknown_error\";\n let description = \"\";\n\n try {\n const err = (await res.json()) as { error?: string; error_description?: string };\n code = err.error ?? code;\n description = err.error_description ?? \"\";\n } catch {\n // Not JSON; the status says enough.\n }\n\n throw new TokenEndpointError(res.status, code, description);\n }\n\n return (await res.json()) as TokenResponse;\n}\n\nexport function exchangeCode(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { code: string; codeVerifier: string; redirectUri: string },\n): Promise<TokenResponse> {\n return tokenRequest(client, fetchImpl, {\n grant_type: \"authorization_code\",\n code: input.code,\n code_verifier: input.codeVerifier,\n redirect_uri: input.redirectUri,\n });\n}\n\nexport function refreshTokens(client: ClientCredentials, fetchImpl: Fetch, refreshToken: string): Promise<TokenResponse> {\n return tokenRequest(client, fetchImpl, { grant_type: \"refresh_token\", refresh_token: refreshToken });\n}\n\n/**\n * Verifies an id_token's signature, issuer, audience and nonce. A key the\n * cached set does not know refetches the set once, which is how a key rotation\n * at the issuer is picked up without waiting out the cache.\n */\nexport async function verifyIdToken(\n client: ClientCredentials,\n fetchImpl: Fetch,\n idToken: string,\n nonce: string,\n): Promise<JWTPayload> {\n const disc = await discover(client.issuer, fetchImpl);\n const options = { issuer: disc.issuer, audience: client.clientId };\n\n let result;\n try {\n result = await jwtVerify(idToken, await jwks(disc.jwks_uri, fetchImpl, false), options);\n } catch (err) {\n if ((err as { code?: string }).code !== \"ERR_JWKS_NO_MATCHING_KEY\") throw err;\n result = await jwtVerify(idToken, await jwks(disc.jwks_uri, fetchImpl, true), options);\n }\n\n if (result.payload.nonce !== nonce) throw new Error(\"id_token nonce does not match the sign-in request\");\n\n return result.payload;\n}\n\n/** RP-initiated logout (OIDC RP-Initiated Logout 1.0), or null when the issuer offers none. */\nexport async function endSessionUrl(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { postLogoutRedirectUri: string; sessionId?: string },\n): Promise<string | null> {\n const disc = await discover(client.issuer, fetchImpl);\n if (!disc.end_session_endpoint) return null;\n\n const url = new URL(disc.end_session_endpoint);\n url.searchParams.set(\"client_id\", client.clientId);\n url.searchParams.set(\"post_logout_redirect_uri\", input.postLogoutRedirectUri);\n // logout_hint is what lets the issuer end its session without asking \"Sign\n // out?\": only an app holding tokens from that session knows the sid.\n if (input.sessionId) url.searchParams.set(\"logout_hint\", input.sessionId);\n\n return url.toString();\n}\n\n/** For tests: forget cached discovery documents and key sets. */\nexport function resetOidcCaches(): void {\n discoveries.clear();\n jwksCache.clear();\n}\n","import { sha256Hex } from \"./oidc\";\n\n/**\n * One refresh per session, however many requests ask for it.\n *\n * With rotation on, every refresh spends the token it presents. A page that\n * fires several requests the moment its access token expires would otherwise\n * refresh once per request, and every request after the first would present a\n * token the first had already spent — which the issuer reads as theft.\n *\n * Two layers, in this process:\n *\n * - In flight: requests for the same refresh token share one call.\n * - Recently done: a request that arrives a moment later still holding the old\n * cookie (its browser had not yet stored the new one) is handed the same\n * result rather than spending the old token again.\n *\n * Across processes — several replicas of the app — the issuer's reuse grace\n * window covers the same race, so no shared lock is needed.\n */\n\n/** How long a result is handed to late arrivals. Matches the issuer's grace window. */\nexport const RECENT_RESULT_MS = 30_000;\n\nexport class RefreshCoordinator<T> {\n private readonly inFlight = new Map<string, Promise<T>>();\n private readonly recent = new Map<string, { at: number; result: T }>();\n\n constructor(private readonly recentMs: number = RECENT_RESULT_MS) {}\n\n async run(refreshToken: string, refresh: () => Promise<T>): Promise<T> {\n const key = await sha256Hex(refreshToken);\n this.sweep();\n\n const done = this.recent.get(key);\n if (done) return done.result;\n\n let pending = this.inFlight.get(key);\n if (!pending) {\n pending = refresh()\n .then((result) => {\n this.recent.set(key, { at: Date.now(), result });\n return result;\n })\n .finally(() => this.inFlight.delete(key));\n this.inFlight.set(key, pending);\n }\n\n return pending;\n }\n\n private sweep(): void {\n const cutoff = Date.now() - this.recentMs;\n for (const [key, entry] of this.recent) {\n if (entry.at < cutoff) this.recent.delete(key);\n }\n }\n}\n","import { EncryptJWT, jwtDecrypt } from \"jose\";\n\n/**\n * Sealing: authenticated encryption (JWE, `dir` + A256GCM) of whatever the\n * server stores in a cookie. The browser holds ciphertext it can neither read\n * nor alter, which is what lets a refresh token live in a cookie at all.\n */\n\nconst keys = new Map<string, Promise<Uint8Array>>();\n\n/** A 256-bit key derived from the configured secret, whatever its length. */\nfunction keyFor(secret: string): Promise<Uint8Array> {\n let key = keys.get(secret);\n if (!key) {\n key = crypto.subtle\n .digest(\"SHA-256\", new TextEncoder().encode(secret))\n .then((digest) => new Uint8Array(digest));\n keys.set(secret, key);\n }\n\n return key;\n}\n\nexport async function seal(payload: Record<string, unknown>, secret: string, maxAgeSeconds: number): Promise<string> {\n return new EncryptJWT(payload)\n .setProtectedHeader({ alg: \"dir\", enc: \"A256GCM\" })\n .setIssuedAt()\n .setExpirationTime(`${maxAgeSeconds}s`)\n .encrypt(await keyFor(secret));\n}\n\n/** The payload, or null when the value is not one this secret sealed, or has expired. */\nexport async function unseal<T>(value: string, secret: string): Promise<T | null> {\n try {\n const { payload } = await jwtDecrypt(value, await keyFor(secret));\n return payload as T;\n } catch {\n return null;\n }\n}\n","import type { JWTPayload } from \"jose\";\n\nimport { resolveConfig, type ResolvedConfig, type ServerAuthConfig } from \"./config\";\nimport {\n clearChunked,\n formatCookieHeader,\n parseCookies,\n readChunked,\n serializeCookie,\n writeChunked,\n} from \"./cookies\";\nimport {\n authorizationUrl,\n endSessionUrl,\n exchangeCode,\n pkceChallenge,\n randomValue,\n refreshTokens,\n TokenEndpointError,\n verifyIdToken,\n type ClientCredentials,\n type TokenResponse,\n} from \"./oidc\";\nimport { RefreshCoordinator } from \"./refresh\";\nimport { seal, unseal } from \"./seal\";\n\nexport interface SessionUser {\n id: string;\n email?: string;\n name?: string;\n username?: string;\n picture?: string;\n emailVerified: boolean;\n}\n\n/** What `getSession` hands the app. */\nexport interface Session {\n user: SessionUser;\n /** A valid access token, for calling APIs as this user. */\n accessToken: string;\n /** When the access token expires, epoch milliseconds. */\n expiresAt: number;\n /** The issuer's session id (`sid`), when it sent one. */\n sessionId?: string;\n}\n\n/** What the cookie holds: the session plus the refresh token, which the app never sees. */\ninterface StoredSession extends Session {\n refreshToken?: string;\n}\n\n/** A sign-in in progress, sealed into its own short-lived cookie. */\ninterface SignInTransaction {\n state: string;\n verifier: string;\n nonce: string;\n returnTo: string;\n}\n\n/** What a refresh produced, for the middleware to apply. */\nexport interface RefreshOutcome {\n /** The session after the refresh, or null when there is none (or it could not be refreshed). */\n session: Session | null;\n /** `Set-Cookie` values for the response. Empty when nothing changed. */\n setCookies: string[];\n /**\n * The request's `Cookie` header as it should be seen by whatever handles\n * this request next, carrying the refreshed session. Forward it, so a page\n * rendered on this very request reads the new token rather than the expired\n * one the browser sent.\n */\n cookieHeader: string;\n}\n\nexport interface ServerAuth {\n /**\n * Handles the SDK's routes under `basePath`: `/login`, `/callback` and\n * `/logout`. Null for any other path.\n */\n handle(request: Request): Promise<Response | null>;\n /**\n * The session in a `Cookie` header, or null. Read-only and never refreshes:\n * any server code can call it, including code that cannot set cookies.\n * Returns null for a session whose access token has expired — `refresh`,\n * run in middleware ahead of the request, is what keeps it current.\n */\n getSession(cookieHeader: string | null | undefined): Promise<Session | null>;\n /**\n * Refreshes the session when its access token is about to expire. For\n * middleware: it runs before anything renders, and its response can always\n * carry the new cookie. Concurrent calls for one session share one refresh.\n */\n refresh(request: Request): Promise<RefreshOutcome>;\n}\n\nconst DEFAULT_ACCESS_TTL_SECONDS = 900;\nconst TRANSACTION_MAX_AGE = 600;\n\n/** Only a same-origin path may be returned to after sign-in: no open redirect. */\nfunction safePath(value: string | null | undefined, fallback: string): string {\n if (value && value.startsWith(\"/\") && !value.startsWith(\"//\") && !value.startsWith(\"/\\\\\")) return value;\n return fallback;\n}\n\nfunction redirect(location: string, setCookies: string[] = []): Response {\n const headers = new Headers({ location, \"cache-control\": \"no-store\" });\n for (const cookie of setCookies) headers.append(\"set-cookie\", cookie);\n\n return new Response(null, { status: 302, headers });\n}\n\nfunction publicOrigin(cfg: ResolvedConfig, request: Request): string {\n if (cfg.baseUrl) return cfg.baseUrl;\n\n const host = request.headers.get(\"x-forwarded-host\")?.split(\",\")[0]?.trim() || request.headers.get(\"host\")?.trim();\n if (host) {\n const proto =\n request.headers.get(\"x-forwarded-proto\")?.split(\",\")[0]?.trim() || new URL(request.url).protocol.replace(\":\", \"\");\n try {\n return new URL(`${proto}://${host}`).origin;\n } catch {\n // Fall through to the request URL.\n }\n }\n\n return new URL(request.url).origin;\n}\n\nfunction userFrom(claims: JWTPayload): SessionUser {\n const str = (key: string) => (typeof claims[key] === \"string\" ? (claims[key] as string) : undefined);\n const name =\n str(\"name\") ?? ([str(\"given_name\"), str(\"family_name\")].filter(Boolean).join(\" \") || undefined) ?? str(\"email\");\n\n return {\n id: claims.sub ?? \"\",\n email: str(\"email\"),\n name,\n username: str(\"preferred_username\"),\n picture: str(\"picture\"),\n emailVerified: claims.email_verified === true,\n };\n}\n\nfunction expiresAtFrom(token: TokenResponse): number {\n return Date.now() + (token.expires_in ?? DEFAULT_ACCESS_TTL_SECONDS) * 1000;\n}\n\nexport function createServerAuth(config: ServerAuthConfig): ServerAuth {\n const cfg = resolveConfig(config);\n const client: ClientCredentials = { issuer: cfg.issuer, clientId: cfg.clientId, clientSecret: cfg.clientSecret };\n const txCookie = `${cfg.cookieName}_tx`;\n const coordinator = new RefreshCoordinator<StoredSession | null>();\n\n const secure = (request: Request) => cfg.secureCookies ?? publicOrigin(cfg, request).startsWith(\"https:\");\n const redirectUri = (request: Request) => cfg.redirectUri ?? `${publicOrigin(cfg, request)}${cfg.basePath}/callback`;\n const toSignIn = (request: Request, error?: string) => {\n const url = new URL(cfg.signInPath, publicOrigin(cfg, request));\n if (error) url.searchParams.set(\"error\", error);\n return url.toString();\n };\n\n async function readStored(cookies: Map<string, string>): Promise<StoredSession | null> {\n const raw = readChunked(cookies, cfg.cookieName);\n if (raw === null) return null;\n\n const stored = await unseal<StoredSession>(raw, cfg.sessionSecret);\n if (!stored) cfg.logger.warn(\"[pontive] session cookie could not be read (sealed with another secret, or truncated)\");\n\n return stored;\n }\n\n async function writeStored(cookies: Map<string, string>, stored: StoredSession, request: Request): Promise<string[]> {\n const sealed = await seal({ ...stored }, cfg.sessionSecret, cfg.sessionMaxAgeSeconds);\n\n return writeChunked(cookies, cfg.cookieName, sealed, { maxAge: cfg.sessionMaxAgeSeconds, secure: secure(request) });\n }\n\n function publicSession(stored: StoredSession): Session {\n const { refreshToken: _refreshToken, ...session } = stored;\n return session;\n }\n\n async function signIn(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const tx: SignInTransaction = {\n state: randomValue(),\n verifier: randomValue(),\n nonce: randomValue(),\n returnTo: safePath(url.searchParams.get(\"returnTo\") ?? url.searchParams.get(\"next\"), cfg.afterSignInPath),\n };\n\n const location = await authorizationUrl(client, cfg.fetch, {\n redirectUri: redirectUri(request),\n scopes: cfg.scopes,\n state: tx.state,\n nonce: tx.nonce,\n codeChallenge: await pkceChallenge(tx.verifier),\n prompt: cfg.prompt,\n });\n\n const sealed = await seal({ ...tx }, cfg.sessionSecret, TRANSACTION_MAX_AGE);\n\n return redirect(location, [serializeCookie(txCookie, sealed, { maxAge: TRANSACTION_MAX_AGE, secure: secure(request) })]);\n }\n\n async function callback(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n // Single use, whatever happens next.\n const setCookies = [serializeCookie(txCookie, \"\", { maxAge: 0, secure: secure(request) })];\n\n const issuerError = url.searchParams.get(\"error\");\n if (issuerError) return redirect(toSignIn(request, issuerError), setCookies);\n\n const raw = cookies.get(txCookie);\n const tx = raw ? await unseal<SignInTransaction>(raw, cfg.sessionSecret) : null;\n const code = url.searchParams.get(\"code\");\n\n if (!tx || !code || url.searchParams.get(\"state\") !== tx.state) {\n return redirect(toSignIn(request, \"invalid_state\"), setCookies);\n }\n\n try {\n const tokens = await exchangeCode(client, cfg.fetch, {\n code,\n codeVerifier: tx.verifier,\n redirectUri: redirectUri(request),\n });\n if (!tokens.id_token) throw new Error(\"the token response carried no id_token; is the openid scope requested?\");\n\n const claims = await verifyIdToken(client, cfg.fetch, tokens.id_token, tx.nonce);\n const stored: StoredSession = {\n user: userFrom(claims),\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresAt: expiresAtFrom(tokens),\n sessionId: typeof claims.sid === \"string\" ? claims.sid : undefined,\n };\n\n setCookies.push(...(await writeStored(cookies, stored, request)));\n } catch (err) {\n cfg.logger.warn(\"[pontive] sign-in failed:\", err instanceof Error ? err.message : String(err));\n return redirect(toSignIn(request, \"exchange_failed\"), setCookies);\n }\n\n return redirect(new URL(tx.returnTo, publicOrigin(cfg, request)).toString(), setCookies);\n }\n\n async function signOut(request: Request): Promise<Response> {\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n // Read before clearing: the session holds the sid the issuer's logout needs.\n const stored = await readStored(cookies);\n const setCookies = clearChunked(cookies, cfg.cookieName, secure(request));\n\n let location = toSignIn(request);\n try {\n location =\n (await endSessionUrl(client, cfg.fetch, {\n postLogoutRedirectUri: toSignIn(request),\n sessionId: stored?.sessionId,\n })) ?? location;\n } catch (err) {\n // An unreachable issuer must not stop the local sign-out.\n cfg.logger.warn(\"[pontive] could not reach the issuer to end its session:\", err instanceof Error ? err.message : String(err));\n }\n\n return redirect(location, setCookies);\n }\n\n /**\n * Spends the refresh token once, returning the refreshed session, or null\n * when the issuer refused it for good. Anything transient throws instead, so\n * the coordinator does not hand a passing failure to other requests.\n */\n async function refreshStored(stored: StoredSession): Promise<StoredSession | null> {\n try {\n const tokens = await refreshTokens(client, cfg.fetch, stored.refreshToken!);\n\n return {\n ...stored,\n accessToken: tokens.access_token,\n // A response without one means rotation is off: the token in hand is still current.\n refreshToken: tokens.refresh_token ?? stored.refreshToken,\n expiresAt: expiresAtFrom(tokens),\n };\n } catch (err) {\n if (err instanceof TokenEndpointError && err.status >= 400 && err.status < 500) {\n cfg.logger.warn(\"[pontive] refresh refused; signing out:\", err.message);\n return null;\n }\n\n throw err;\n }\n }\n\n return {\n async handle(request) {\n const { pathname } = new URL(request.url);\n if (!pathname.startsWith(`${cfg.basePath}/`)) return null;\n\n const route = pathname.slice(cfg.basePath.length + 1);\n if (route === \"login\" && request.method === \"GET\") return signIn(request);\n if (route === \"callback\" && request.method === \"GET\") return callback(request);\n if (route === \"logout\" && (request.method === \"GET\" || request.method === \"POST\")) return signOut(request);\n\n return null;\n },\n\n async getSession(cookieHeader) {\n const stored = await readStored(parseCookies(cookieHeader));\n if (!stored) return null;\n\n if (Date.now() >= stored.expiresAt) {\n cfg.logger.warn(\n \"[pontive] the session's access token has expired and getSession cannot refresh it. \" +\n \"Run the SDK's middleware on this path so it refreshes before the request is handled.\",\n );\n return null;\n }\n\n return publicSession(stored);\n },\n\n async refresh(request) {\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n const unchanged = (session: Session | null): RefreshOutcome => ({\n session,\n setCookies: [],\n cookieHeader: request.headers.get(\"cookie\") ?? \"\",\n });\n\n const stored = await readStored(cookies);\n if (!stored) return unchanged(null);\n\n if (Date.now() < stored.expiresAt - cfg.refreshSkewSeconds * 1000) return unchanged(publicSession(stored));\n\n const signOutNow = (): RefreshOutcome => {\n const setCookies = clearChunked(cookies, cfg.cookieName, secure(request));\n return { session: null, setCookies, cookieHeader: formatCookieHeader(cookies) };\n };\n\n if (!stored.refreshToken) return signOutNow();\n\n let refreshed: StoredSession | null;\n try {\n refreshed = await coordinator.run(stored.refreshToken, () => refreshStored(stored));\n } catch (err) {\n // Transient: keep the session and let the next request try again. The\n // token may still have a few seconds of the skew left.\n cfg.logger.warn(\"[pontive] refresh failed, will retry:\", err instanceof Error ? err.message : String(err));\n return unchanged(Date.now() < stored.expiresAt ? publicSession(stored) : null);\n }\n\n if (!refreshed) return signOutNow();\n\n const setCookies = await writeStored(cookies, refreshed, request);\n\n return { session: publicSession(refreshed), setCookies, cookieHeader: formatCookieHeader(cookies) };\n },\n };\n}\n"],"names":["SEVEN_DAYS","resolveConfig","config","required","key","args","CHUNK_SIZE","MAX_CHUNKS","parseCookies","header","out","part","eq","name","formatCookieHeader","cookies","value","serializeCookie","attrs","parts","chunkName","index","readChunked","first","i","writeChunked","count","chunk","stale","clearChunked","secure","TokenEndpointError","status","code","description","JWKS_TTL_MS","discoveries","jwksCache","trimSlash","discover","issuer","fetchImpl","pending","res","err","jwks","uri","force","cached","set","createLocalJWKSet","base64url","bytes","binary","b","randomValue","pkceChallenge","verifier","digest","sha256Hex","authorizationUrl","client","input","disc","url","tokenRequest","params","body","headers","credentials","exchangeCode","refreshTokens","refreshToken","verifyIdToken","idToken","nonce","options","result","jwtVerify","endSessionUrl","RECENT_RESULT_MS","RefreshCoordinator","recentMs","refresh","done","cutoff","entry","keys","keyFor","secret","seal","payload","maxAgeSeconds","EncryptJWT","unseal","jwtDecrypt","DEFAULT_ACCESS_TTL_SECONDS","TRANSACTION_MAX_AGE","safePath","fallback","redirect","location","setCookies","cookie","publicOrigin","cfg","request","host","proto","userFrom","claims","str","expiresAtFrom","token","createServerAuth","txCookie","coordinator","redirectUri","toSignIn","error","readStored","raw","stored","writeStored","sealed","publicSession","_refreshToken","session","signIn","tx","callback","issuerError","tokens","signOut","refreshStored","pathname","route","cookieHeader","unchanged","signOutNow","refreshed"],"mappings":";AA0EA,MAAMA,IAAa,QAAc;AAE1B,SAASC,GAAcC,GAA0C;AACtE,QAAMC,IAAuC,CAAC,UAAU,YAAY,eAAe;AACnF,aAAWC,KAAOD;AAChB,QAAI,CAACD,EAAOE,CAAG,SAAS,IAAI,MAAM,4BAA4BA,CAAG,cAAc;AAGjF,MAAIF,EAAO,cAAc,SAAS;AAChC,UAAM,IAAI,MAAM,uEAAuE;AAGzF,MAAIA,EAAO;AACT,QAAI;AACF,UAAI,IAAIA,EAAO,OAAO;AAAA,IACxB,QAAQ;AACN,YAAM,IAAI,MAAM,qCAAqCA,EAAO,OAAO,0BAA0B;AAAA,IAC/F;AAGF,SAAO;AAAA,IACL,QAAQA,EAAO,OAAO,QAAQ,QAAQ,EAAE;AAAA,IACxC,UAAUA,EAAO;AAAA,IACjB,cAAcA,EAAO,gBAAgB;AAAA,IACrC,eAAeA,EAAO;AAAA,IACtB,SAASA,EAAO,UAAU,IAAI,IAAIA,EAAO,OAAO,EAAE,SAAS;AAAA,IAC3D,QAAQA,EAAO,UAAU;AAAA,IACzB,WAAWA,EAAO,YAAY,aAAa,QAAQ,QAAQ,EAAE;AAAA,IAC7D,aAAaA,EAAO;AAAA,IACpB,YAAYA,EAAO,cAAc;AAAA,IACjC,iBAAiBA,EAAO,mBAAmB;AAAA,IAC3C,QAAQA,EAAO;AAAA,IACf,YAAYA,EAAO,cAAc;AAAA,IACjC,eAAeA,EAAO;AAAA,IACtB,sBAAsBA,EAAO,wBAAwBF;AAAA,IACrD,oBAAoBE,EAAO,sBAAsB;AAAA,IACjD,OAAOA,EAAO,UAAU,IAAIG,MAAS,MAAM,GAAGA,CAAI;AAAA,IAClD,QAAQH,EAAO,UAAU;AAAA,EAAA;AAE7B;ACvGO,MAAMI,IAAa,MAEbC,IAAa;AASnB,SAASC,EAAaC,GAAwD;AACnF,QAAMC,wBAAU,IAAA;AAChB,MAAI,CAACD,EAAQ,QAAOC;AAEpB,aAAWC,KAAQF,EAAO,MAAM,GAAG,GAAG;AACpC,UAAMG,IAAKD,EAAK,QAAQ,GAAG;AAC3B,QAAIC,IAAK,EAAG;AAEZ,UAAMC,IAAOF,EAAK,MAAM,GAAGC,CAAE,EAAE,KAAA;AAC/B,IAAI,CAACC,KAAQH,EAAI,IAAIG,CAAI,KAEzBH,EAAI,IAAIG,GAAMF,EAAK,MAAMC,IAAK,CAAC,EAAE,MAAM;AAAA,EACzC;AAEA,SAAOF;AACT;AAGO,SAASI,EAAmBC,GAAsC;AACvE,SAAO,CAAC,GAAGA,CAAO,EAAE,IAAI,CAAC,CAACF,GAAMG,CAAK,MAAM,GAAGH,CAAI,IAAIG,CAAK,EAAE,EAAE,KAAK,IAAI;AAC1E;AAOO,SAASC,EAAgBJ,GAAcG,GAAeE,GAAiC;AAC5F,QAAMC,IAAQ,CAAC,GAAGN,CAAI,IAAIG,CAAK,IAAI,UAAU,YAAY,gBAAgB,WAAWE,EAAM,MAAM,EAAE;AAClG,SAAIA,EAAM,UAAQC,EAAM,KAAK,QAAQ,GAE9BA,EAAM,KAAK,IAAI;AACxB;AAEO,SAASC,EAAUP,GAAcQ,GAAuB;AAC7D,SAAOA,MAAU,IAAIR,IAAO,GAAGA,CAAI,IAAIQ,CAAK;AAC9C;AAGO,SAASC,GAAYP,GAA8BF,GAA6B;AACrF,QAAMU,IAAQR,EAAQ,IAAIF,CAAI;AAC9B,MAAIU,MAAU,OAAW,QAAO;AAEhC,MAAIP,IAAQO;AACZ,WAASC,IAAI,GAAGA,IAAIjB,GAAYiB,KAAK;AACnC,UAAMb,IAAOI,EAAQ,IAAIK,EAAUP,GAAMW,CAAC,CAAC;AAC3C,QAAIb,MAAS,OAAW;AACxB,IAAAK,KAASL;AAAA,EACX;AAEA,SAAOK;AACT;AAOO,SAASS,GACdV,GACAF,GACAG,GACAE,GACU;AACV,QAAMR,IAAgB,CAAA,GAChBgB,IAAQ,KAAK,IAAI,GAAG,KAAK,KAAKV,EAAM,SAASV,CAAU,CAAC;AAE9D,WAASkB,IAAI,GAAGA,IAAIE,GAAOF,KAAK;AAC9B,UAAMG,IAAQX,EAAM,MAAMQ,IAAIlB,IAAakB,IAAI,KAAKlB,CAAU;AAC9D,IAAAS,EAAQ,IAAIK,EAAUP,GAAMW,CAAC,GAAGG,CAAK,GACrCjB,EAAI,KAAKO,EAAgBG,EAAUP,GAAMW,CAAC,GAAGG,GAAOT,CAAK,CAAC;AAAA,EAC5D;AAEA,WAASM,IAAIE,GAAOF,IAAIjB,GAAYiB,KAAK;AACvC,UAAMI,IAAQR,EAAUP,GAAMW,CAAC;AAC/B,QAAI,CAACT,EAAQ,IAAIa,CAAK,EAAG;AACzB,IAAAb,EAAQ,OAAOa,CAAK,GACpBlB,EAAI,KAAKO,EAAgBW,GAAO,IAAI,EAAE,GAAGV,GAAO,QAAQ,EAAA,CAAG,CAAC;AAAA,EAC9D;AAEA,SAAOR;AACT;AAGO,SAASmB,EAAad,GAA8BF,GAAciB,GAA2B;AAClG,QAAMpB,IAAgB,CAAA;AAEtB,WAASc,IAAI,GAAGA,IAAIjB,GAAYiB,KAAK;AACnC,UAAMG,IAAQP,EAAUP,GAAMW,CAAC;AAC/B,QAAI,CAACT,EAAQ,IAAIY,CAAK,GAAG;AACvB,UAAIH,IAAI,EAAG;AACX;AAAA,IACF;AACA,IAAAT,EAAQ,OAAOY,CAAK,GACpBjB,EAAI,KAAKO,EAAgBU,GAAO,IAAI,EAAE,QAAQ,GAAG,QAAAG,EAAA,CAAQ,CAAC;AAAA,EAC5D;AAEA,SAAOpB;AACT;ACxFO,MAAMqB,UAA2B,MAAM;AAAA,EAC5C,YACWC,GACAC,GACTC,GACA;AACA,UAAM,kBAAkBF,CAAM,IAAIC,CAAI,KAAKC,CAAW,EAAE,GAJ/C,KAAA,SAAAF,GACA,KAAA,OAAAC;AAAA,EAIX;AAAA,EALW;AAAA,EACA;AAKb;AAIA,MAAME,KAAc,MAAU,KAExBC,wBAAkB,IAAA,GAClBC,wBAAgB,IAAA;AAEtB,SAASC,GAAUtB,GAAuB;AACxC,SAAOA,EAAM,QAAQ,QAAQ,EAAE;AACjC;AAEO,SAASuB,EAASC,GAAgBC,GAAsC;AAC7E,QAAMrC,IAAMkC,GAAUE,CAAM;AAC5B,MAAIE,IAAUN,EAAY,IAAIhC,CAAG;AAEjC,SAAKsC,MACHA,IAAUD,EAAU,GAAGrC,CAAG,mCAAmC,EAC1D,KAAK,OAAOuC,MAAQ;AACnB,QAAI,CAACA,EAAI,GAAI,OAAM,IAAI,MAAM,6BAA6BvC,CAAG,KAAKuC,EAAI,MAAM,EAAE;AAC9E,WAAQ,MAAMA,EAAI,KAAA;AAAA,EACpB,CAAC,EACA,MAAM,CAACC,MAAiB;AAEvB,UAAAR,EAAY,OAAOhC,CAAG,GAChBwC;AAAA,EACR,CAAC,GACHR,EAAY,IAAIhC,GAAKsC,CAAO,IAGvBA;AACT;AAEA,eAAeG,EAAKC,GAAaL,GAAkBM,GAA+D;AAChH,QAAMC,IAASX,EAAU,IAAIS,CAAG;AAChC,MAAIE,KAAU,CAACD,KAAS,KAAK,IAAA,IAAQC,EAAO,KAAKb,GAAa,QAAOa,EAAO;AAE5E,QAAML,IAAM,MAAMF,EAAUK,CAAG;AAC/B,MAAI,CAACH,EAAI,GAAI,OAAM,IAAI,MAAM,yBAAyBG,CAAG,KAAKH,EAAI,MAAM,EAAE;AAE1E,QAAMM,IAAMC,EAAmB,MAAMP,EAAI,MAAwB;AACjE,SAAAN,EAAU,IAAIS,GAAK,EAAE,IAAI,KAAK,IAAA,GAAO,KAAAG,GAAK,GAEnCA;AACT;AAEA,SAASE,EAAUC,GAA2B;AAC5C,MAAIC,IAAS;AACb,aAAWC,KAAKF,EAAO,CAAAC,KAAU,OAAO,aAAaC,CAAC;AAEtD,SAAO,KAAKD,CAAM,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AAC/E;AAGO,SAASE,IAAsB;AACpC,SAAOJ,EAAU,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC,CAAC;AAC7D;AAEA,eAAsBK,GAAcC,GAAmC;AACrE,QAAMC,IAAS,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI,YAAA,EAAc,OAAOD,CAAQ,CAAC;AAEvF,SAAON,EAAU,IAAI,WAAWO,CAAM,CAAC;AACzC;AAGA,eAAsBC,GAAU3C,GAAgC;AAG9D,SAAO,CAAC,GAFO,IAAI,WAAW,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI,YAAA,EAAc,OAAOA,CAAK,CAAC,CAAC,CAEnF,EAAE,IAAI,CAACsC,MAAMA,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACxE;AAEA,eAAsBM,GACpBC,GACApB,GACAqB,GACiB;AACjB,QAAMC,IAAO,MAAMxB,EAASsB,EAAO,QAAQpB,CAAS,GAC9CuB,IAAM,IAAI,IAAID,EAAK,sBAAsB;AAE/C,SAAAC,EAAI,aAAa,IAAI,iBAAiB,MAAM,GAC5CA,EAAI,aAAa,IAAI,aAAaH,EAAO,QAAQ,GACjDG,EAAI,aAAa,IAAI,gBAAgBF,EAAM,WAAW,GACtDE,EAAI,aAAa,IAAI,SAASF,EAAM,MAAM,GAC1CE,EAAI,aAAa,IAAI,SAASF,EAAM,KAAK,GACzCE,EAAI,aAAa,IAAI,SAASF,EAAM,KAAK,GACzCE,EAAI,aAAa,IAAI,kBAAkBF,EAAM,aAAa,GAC1DE,EAAI,aAAa,IAAI,yBAAyB,MAAM,GAChDF,EAAM,UAAQE,EAAI,aAAa,IAAI,UAAUF,EAAM,MAAM,GAEtDE,EAAI,SAAA;AACb;AAOA,eAAeC,EACbJ,GACApB,GACAyB,GACwB;AACxB,QAAMH,IAAO,MAAMxB,EAASsB,EAAO,QAAQpB,CAAS,GAC9C0B,IAAO,IAAI,gBAAgBD,CAAM;AACvC,EAAAC,EAAK,IAAI,aAAaN,EAAO,QAAQ;AAErC,QAAMO,IAAkC,EAAE,gBAAgB,oCAAA;AAC1D,MAAIP,EAAO,cAAc;AACvB,UAAMQ,IAAc,GAAG,mBAAmBR,EAAO,QAAQ,CAAC,IAAI,mBAAmBA,EAAO,YAAY,CAAC;AACrG,IAAAO,EAAQ,gBAAgB,SAAS,KAAKC,CAAW,CAAC;AAAA,EACpD;AAEA,QAAM1B,IAAM,MAAMF,EAAUsB,EAAK,gBAAgB,EAAE,QAAQ,QAAQ,SAAAK,GAAS,MAAAD,GAAM;AAClF,MAAI,CAACxB,EAAI,IAAI;AACX,QAAIV,IAAO,iBACPC,IAAc;AAElB,QAAI;AACF,YAAMU,IAAO,MAAMD,EAAI,KAAA;AACvB,MAAAV,IAAOW,EAAI,SAASX,GACpBC,IAAcU,EAAI,qBAAqB;AAAA,IACzC,QAAQ;AAAA,IAER;AAEA,UAAM,IAAIb,EAAmBY,EAAI,QAAQV,GAAMC,CAAW;AAAA,EAC5D;AAEA,SAAQ,MAAMS,EAAI,KAAA;AACpB;AAEO,SAAS2B,GACdT,GACApB,GACAqB,GACwB;AACxB,SAAOG,EAAaJ,GAAQpB,GAAW;AAAA,IACrC,YAAY;AAAA,IACZ,MAAMqB,EAAM;AAAA,IACZ,eAAeA,EAAM;AAAA,IACrB,cAAcA,EAAM;AAAA,EAAA,CACrB;AACH;AAEO,SAASS,GAAcV,GAA2BpB,GAAkB+B,GAA8C;AACvH,SAAOP,EAAaJ,GAAQpB,GAAW,EAAE,YAAY,iBAAiB,eAAe+B,GAAc;AACrG;AAOA,eAAsBC,GACpBZ,GACApB,GACAiC,GACAC,GACqB;AACrB,QAAMZ,IAAO,MAAMxB,EAASsB,EAAO,QAAQpB,CAAS,GAC9CmC,IAAU,EAAE,QAAQb,EAAK,QAAQ,UAAUF,EAAO,SAAA;AAExD,MAAIgB;AACJ,MAAI;AACF,IAAAA,IAAS,MAAMC,EAAUJ,GAAS,MAAM7B,EAAKkB,EAAK,UAAUtB,GAAW,EAAK,GAAGmC,CAAO;AAAA,EACxF,SAAShC,GAAK;AACZ,QAAKA,EAA0B,SAAS,2BAA4B,OAAMA;AAC1E,IAAAiC,IAAS,MAAMC,EAAUJ,GAAS,MAAM7B,EAAKkB,EAAK,UAAUtB,GAAW,EAAI,GAAGmC,CAAO;AAAA,EACvF;AAEA,MAAIC,EAAO,QAAQ,UAAUF,EAAO,OAAM,IAAI,MAAM,mDAAmD;AAEvG,SAAOE,EAAO;AAChB;AAGA,eAAsBE,GACpBlB,GACApB,GACAqB,GACwB;AACxB,QAAMC,IAAO,MAAMxB,EAASsB,EAAO,QAAQpB,CAAS;AACpD,MAAI,CAACsB,EAAK,qBAAsB,QAAO;AAEvC,QAAMC,IAAM,IAAI,IAAID,EAAK,oBAAoB;AAC7C,SAAAC,EAAI,aAAa,IAAI,aAAaH,EAAO,QAAQ,GACjDG,EAAI,aAAa,IAAI,4BAA4BF,EAAM,qBAAqB,GAGxEA,EAAM,aAAWE,EAAI,aAAa,IAAI,eAAeF,EAAM,SAAS,GAEjEE,EAAI,SAAA;AACb;AClNO,MAAMgB,KAAmB;AAEzB,MAAMC,GAAsB;AAAA,EAIjC,YAA6BC,IAAmBF,IAAkB;AAArC,SAAA,WAAAE;AAAA,EAAsC;AAAA,EAAtC;AAAA,EAHZ,+BAAe,IAAA;AAAA,EACf,6BAAa,IAAA;AAAA,EAI9B,MAAM,IAAIV,GAAsBW,GAAuC;AACrE,UAAM/E,IAAM,MAAMuD,GAAUa,CAAY;AACxC,SAAK,MAAA;AAEL,UAAMY,IAAO,KAAK,OAAO,IAAIhF,CAAG;AAChC,QAAIgF,UAAaA,EAAK;AAEtB,QAAI1C,IAAU,KAAK,SAAS,IAAItC,CAAG;AACnC,WAAKsC,MACHA,IAAUyC,EAAA,EACP,KAAK,CAACN,OACL,KAAK,OAAO,IAAIzE,GAAK,EAAE,IAAI,KAAK,OAAO,QAAAyE,GAAQ,GACxCA,EACR,EACA,QAAQ,MAAM,KAAK,SAAS,OAAOzE,CAAG,CAAC,GAC1C,KAAK,SAAS,IAAIA,GAAKsC,CAAO,IAGzBA;AAAA,EACT;AAAA,EAEQ,QAAc;AACpB,UAAM2C,IAAS,KAAK,IAAA,IAAQ,KAAK;AACjC,eAAW,CAACjF,GAAKkF,CAAK,KAAK,KAAK;AAC9B,MAAIA,EAAM,KAAKD,KAAQ,KAAK,OAAO,OAAOjF,CAAG;AAAA,EAEjD;AACF;ACjDA,MAAMmF,wBAAW,IAAA;AAGjB,SAASC,EAAOC,GAAqC;AACnD,MAAIrF,IAAMmF,EAAK,IAAIE,CAAM;AACzB,SAAKrF,MACHA,IAAM,OAAO,OACV,OAAO,WAAW,IAAI,cAAc,OAAOqF,CAAM,CAAC,EAClD,KAAK,CAAC/B,MAAW,IAAI,WAAWA,CAAM,CAAC,GAC1C6B,EAAK,IAAIE,GAAQrF,CAAG,IAGfA;AACT;AAEA,eAAsBsF,EAAKC,GAAkCF,GAAgBG,GAAwC;AACnH,SAAO,IAAIC,EAAWF,CAAO,EAC1B,mBAAmB,EAAE,KAAK,OAAO,KAAK,WAAW,EACjD,YAAA,EACA,kBAAkB,GAAGC,CAAa,GAAG,EACrC,QAAQ,MAAMJ,EAAOC,CAAM,CAAC;AACjC;AAGA,eAAsBK,EAAU9E,GAAeyE,GAAmC;AAChF,MAAI;AACF,UAAM,EAAE,SAAAE,MAAY,MAAMI,EAAW/E,GAAO,MAAMwE,EAAOC,CAAM,CAAC;AAChE,WAAOE;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;ACwDA,MAAMK,KAA6B,KAC7BC,IAAsB;AAG5B,SAASC,GAASlF,GAAkCmF,GAA0B;AAC5E,SAAInF,KAASA,EAAM,WAAW,GAAG,KAAK,CAACA,EAAM,WAAW,IAAI,KAAK,CAACA,EAAM,WAAW,KAAK,IAAUA,IAC3FmF;AACT;AAEA,SAASC,EAASC,GAAkBC,IAAuB,IAAc;AACvE,QAAMlC,IAAU,IAAI,QAAQ,EAAE,UAAAiC,GAAU,iBAAiB,YAAY;AACrE,aAAWE,KAAUD,EAAY,CAAAlC,EAAQ,OAAO,cAAcmC,CAAM;AAEpE,SAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,SAAAnC,GAAS;AACpD;AAEA,SAASoC,EAAaC,GAAqBC,GAA0B;AACnE,MAAID,EAAI,QAAS,QAAOA,EAAI;AAE5B,QAAME,IAAOD,EAAQ,QAAQ,IAAI,kBAAkB,GAAG,MAAM,GAAG,EAAE,CAAC,GAAG,UAAUA,EAAQ,QAAQ,IAAI,MAAM,GAAG,KAAA;AAC5G,MAAIC,GAAM;AACR,UAAMC,IACJF,EAAQ,QAAQ,IAAI,mBAAmB,GAAG,MAAM,GAAG,EAAE,CAAC,GAAG,UAAU,IAAI,IAAIA,EAAQ,GAAG,EAAE,SAAS,QAAQ,KAAK,EAAE;AAClH,QAAI;AACF,aAAO,IAAI,IAAI,GAAGE,CAAK,MAAMD,CAAI,EAAE,EAAE;AAAA,IACvC,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO,IAAI,IAAID,EAAQ,GAAG,EAAE;AAC9B;AAEA,SAASG,GAASC,GAAiC;AACjD,QAAMC,IAAM,CAAC3G,MAAiB,OAAO0G,EAAO1G,CAAG,KAAM,WAAY0G,EAAO1G,CAAG,IAAe,QACpFS,IACJkG,EAAI,MAAM,MAAM,CAACA,EAAI,YAAY,GAAGA,EAAI,aAAa,CAAC,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,KAAK,WAAcA,EAAI,OAAO;AAEhH,SAAO;AAAA,IACL,IAAID,EAAO,OAAO;AAAA,IAClB,OAAOC,EAAI,OAAO;AAAA,IAClB,MAAAlG;AAAA,IACA,UAAUkG,EAAI,oBAAoB;AAAA,IAClC,SAASA,EAAI,SAAS;AAAA,IACtB,eAAeD,EAAO,mBAAmB;AAAA,EAAA;AAE7C;AAEA,SAASE,EAAcC,GAA8B;AACnD,SAAO,KAAK,IAAA,KAASA,EAAM,cAAcjB,MAA8B;AACzE;AAEO,SAASkB,GAAiBhH,GAAsC;AACrE,QAAMuG,IAAMxG,GAAcC,CAAM,GAC1B2D,IAA4B,EAAE,QAAQ4C,EAAI,QAAQ,UAAUA,EAAI,UAAU,cAAcA,EAAI,aAAA,GAC5FU,IAAW,GAAGV,EAAI,UAAU,OAC5BW,IAAc,IAAInC,GAAA,GAElBnD,IAAS,CAAC4E,MAAqBD,EAAI,iBAAiBD,EAAaC,GAAKC,CAAO,EAAE,WAAW,QAAQ,GAClGW,IAAc,CAACX,MAAqBD,EAAI,eAAe,GAAGD,EAAaC,GAAKC,CAAO,CAAC,GAAGD,EAAI,QAAQ,aACnGa,IAAW,CAACZ,GAAkBa,MAAmB;AACrD,UAAMvD,IAAM,IAAI,IAAIyC,EAAI,YAAYD,EAAaC,GAAKC,CAAO,CAAC;AAC9D,WAAIa,KAAOvD,EAAI,aAAa,IAAI,SAASuD,CAAK,GACvCvD,EAAI,SAAA;AAAA,EACb;AAEA,iBAAewD,EAAWzG,GAA6D;AACrF,UAAM0G,IAAMnG,GAAYP,GAAS0F,EAAI,UAAU;AAC/C,QAAIgB,MAAQ,KAAM,QAAO;AAEzB,UAAMC,IAAS,MAAM5B,EAAsB2B,GAAKhB,EAAI,aAAa;AACjE,WAAKiB,KAAQjB,EAAI,OAAO,KAAK,uFAAuF,GAE7GiB;AAAA,EACT;AAEA,iBAAeC,EAAY5G,GAA8B2G,GAAuBhB,GAAqC;AACnH,UAAMkB,IAAS,MAAMlC,EAAK,EAAE,GAAGgC,KAAUjB,EAAI,eAAeA,EAAI,oBAAoB;AAEpF,WAAOhF,GAAaV,GAAS0F,EAAI,YAAYmB,GAAQ,EAAE,QAAQnB,EAAI,sBAAsB,QAAQ3E,EAAO4E,CAAO,GAAG;AAAA,EACpH;AAEA,WAASmB,EAAcH,GAAgC;AACrD,UAAM,EAAE,cAAcI,GAAe,GAAGC,MAAYL;AACpD,WAAOK;AAAA,EACT;AAEA,iBAAeC,EAAOtB,GAAqC;AACzD,UAAM1C,IAAM,IAAI,IAAI0C,EAAQ,GAAG,GACzBuB,IAAwB;AAAA,MAC5B,OAAO1E,EAAA;AAAA,MACP,UAAUA,EAAA;AAAA,MACV,OAAOA,EAAA;AAAA,MACP,UAAU2C,GAASlC,EAAI,aAAa,IAAI,UAAU,KAAKA,EAAI,aAAa,IAAI,MAAM,GAAGyC,EAAI,eAAe;AAAA,IAAA,GAGpGJ,IAAW,MAAMzC,GAAiBC,GAAQ4C,EAAI,OAAO;AAAA,MACzD,aAAaY,EAAYX,CAAO;AAAA,MAChC,QAAQD,EAAI;AAAA,MACZ,OAAOwB,EAAG;AAAA,MACV,OAAOA,EAAG;AAAA,MACV,eAAe,MAAMzE,GAAcyE,EAAG,QAAQ;AAAA,MAC9C,QAAQxB,EAAI;AAAA,IAAA,CACb,GAEKmB,IAAS,MAAMlC,EAAK,EAAE,GAAGuC,EAAA,GAAMxB,EAAI,eAAeR,CAAmB;AAE3E,WAAOG,EAASC,GAAU,CAACpF,EAAgBkG,GAAUS,GAAQ,EAAE,QAAQ3B,GAAqB,QAAQnE,EAAO4E,CAAO,EAAA,CAAG,CAAC,CAAC;AAAA,EACzH;AAEA,iBAAewB,EAASxB,GAAqC;AAC3D,UAAM1C,IAAM,IAAI,IAAI0C,EAAQ,GAAG,GACzB3F,IAAUP,EAAakG,EAAQ,QAAQ,IAAI,QAAQ,CAAC,GAEpDJ,IAAa,CAACrF,EAAgBkG,GAAU,IAAI,EAAE,QAAQ,GAAG,QAAQrF,EAAO4E,CAAO,EAAA,CAAG,CAAC,GAEnFyB,IAAcnE,EAAI,aAAa,IAAI,OAAO;AAChD,QAAImE,EAAa,QAAO/B,EAASkB,EAASZ,GAASyB,CAAW,GAAG7B,CAAU;AAE3E,UAAMmB,IAAM1G,EAAQ,IAAIoG,CAAQ,GAC1Bc,IAAKR,IAAM,MAAM3B,EAA0B2B,GAAKhB,EAAI,aAAa,IAAI,MACrExE,IAAO+B,EAAI,aAAa,IAAI,MAAM;AAExC,QAAI,CAACiE,KAAM,CAAChG,KAAQ+B,EAAI,aAAa,IAAI,OAAO,MAAMiE,EAAG;AACvD,aAAO7B,EAASkB,EAASZ,GAAS,eAAe,GAAGJ,CAAU;AAGhE,QAAI;AACF,YAAM8B,IAAS,MAAM9D,GAAaT,GAAQ4C,EAAI,OAAO;AAAA,QACnD,MAAAxE;AAAA,QACA,cAAcgG,EAAG;AAAA,QACjB,aAAaZ,EAAYX,CAAO;AAAA,MAAA,CACjC;AACD,UAAI,CAAC0B,EAAO,SAAU,OAAM,IAAI,MAAM,wEAAwE;AAE9G,YAAMtB,IAAS,MAAMrC,GAAcZ,GAAQ4C,EAAI,OAAO2B,EAAO,UAAUH,EAAG,KAAK,GACzEP,IAAwB;AAAA,QAC5B,MAAMb,GAASC,CAAM;AAAA,QACrB,aAAasB,EAAO;AAAA,QACpB,cAAcA,EAAO;AAAA,QACrB,WAAWpB,EAAcoB,CAAM;AAAA,QAC/B,WAAW,OAAOtB,EAAO,OAAQ,WAAWA,EAAO,MAAM;AAAA,MAAA;AAG3D,MAAAR,EAAW,KAAK,GAAI,MAAMqB,EAAY5G,GAAS2G,GAAQhB,CAAO,CAAE;AAAA,IAClE,SAAS9D,GAAK;AACZ,aAAA6D,EAAI,OAAO,KAAK,6BAA6B7D,aAAe,QAAQA,EAAI,UAAU,OAAOA,CAAG,CAAC,GACtFwD,EAASkB,EAASZ,GAAS,iBAAiB,GAAGJ,CAAU;AAAA,IAClE;AAEA,WAAOF,EAAS,IAAI,IAAI6B,EAAG,UAAUzB,EAAaC,GAAKC,CAAO,CAAC,EAAE,SAAA,GAAYJ,CAAU;AAAA,EACzF;AAEA,iBAAe+B,EAAQ3B,GAAqC;AAC1D,UAAM3F,IAAUP,EAAakG,EAAQ,QAAQ,IAAI,QAAQ,CAAC,GAEpDgB,IAAS,MAAMF,EAAWzG,CAAO,GACjCuF,IAAazE,EAAad,GAAS0F,EAAI,YAAY3E,EAAO4E,CAAO,CAAC;AAExE,QAAIL,IAAWiB,EAASZ,CAAO;AAC/B,QAAI;AACF,MAAAL,IACG,MAAMtB,GAAclB,GAAQ4C,EAAI,OAAO;AAAA,QACtC,uBAAuBa,EAASZ,CAAO;AAAA,QACvC,WAAWgB,GAAQ;AAAA,MAAA,CACpB,KAAMrB;AAAA,IACX,SAASzD,GAAK;AAEZ,MAAA6D,EAAI,OAAO,KAAK,4DAA4D7D,aAAe,QAAQA,EAAI,UAAU,OAAOA,CAAG,CAAC;AAAA,IAC9H;AAEA,WAAOwD,EAASC,GAAUC,CAAU;AAAA,EACtC;AAOA,iBAAegC,EAAcZ,GAAsD;AACjF,QAAI;AACF,YAAMU,IAAS,MAAM7D,GAAcV,GAAQ4C,EAAI,OAAOiB,EAAO,YAAa;AAE1E,aAAO;AAAA,QACL,GAAGA;AAAA,QACH,aAAaU,EAAO;AAAA;AAAA,QAEpB,cAAcA,EAAO,iBAAiBV,EAAO;AAAA,QAC7C,WAAWV,EAAcoB,CAAM;AAAA,MAAA;AAAA,IAEnC,SAASxF,GAAK;AACZ,UAAIA,aAAeb,KAAsBa,EAAI,UAAU,OAAOA,EAAI,SAAS;AACzE,eAAA6D,EAAI,OAAO,KAAK,2CAA2C7D,EAAI,OAAO,GAC/D;AAGT,YAAMA;AAAA,IACR;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,OAAO8D,GAAS;AACpB,YAAM,EAAE,UAAA6B,EAAA,IAAa,IAAI,IAAI7B,EAAQ,GAAG;AACxC,UAAI,CAAC6B,EAAS,WAAW,GAAG9B,EAAI,QAAQ,GAAG,EAAG,QAAO;AAErD,YAAM+B,IAAQD,EAAS,MAAM9B,EAAI,SAAS,SAAS,CAAC;AACpD,aAAI+B,MAAU,WAAW9B,EAAQ,WAAW,QAAcsB,EAAOtB,CAAO,IACpE8B,MAAU,cAAc9B,EAAQ,WAAW,QAAcwB,EAASxB,CAAO,IACzE8B,MAAU,aAAa9B,EAAQ,WAAW,SAASA,EAAQ,WAAW,UAAgB2B,EAAQ3B,CAAO,IAElG;AAAA,IACT;AAAA,IAEA,MAAM,WAAW+B,GAAc;AAC7B,YAAMf,IAAS,MAAMF,EAAWhH,EAAaiI,CAAY,CAAC;AAC1D,aAAKf,IAED,KAAK,SAASA,EAAO,aACvBjB,EAAI,OAAO;AAAA,QACT;AAAA,MAAA,GAGK,QAGFoB,EAAcH,CAAM,IAVP;AAAA,IAWtB;AAAA,IAEA,MAAM,QAAQhB,GAAS;AACrB,YAAM3F,IAAUP,EAAakG,EAAQ,QAAQ,IAAI,QAAQ,CAAC,GACpDgC,IAAY,CAACX,OAA6C;AAAA,QAC9D,SAAAA;AAAA,QACA,YAAY,CAAA;AAAA,QACZ,cAAcrB,EAAQ,QAAQ,IAAI,QAAQ,KAAK;AAAA,MAAA,IAG3CgB,IAAS,MAAMF,EAAWzG,CAAO;AACvC,UAAI,CAAC2G,EAAQ,QAAOgB,EAAU,IAAI;AAElC,UAAI,KAAK,QAAQhB,EAAO,YAAYjB,EAAI,qBAAqB,IAAM,QAAOiC,EAAUb,EAAcH,CAAM,CAAC;AAEzG,YAAMiB,IAAa,OAEV,EAAE,SAAS,MAAM,YADL9G,EAAad,GAAS0F,EAAI,YAAY3E,EAAO4E,CAAO,CAAC,GACpC,cAAc5F,EAAmBC,CAAO,EAAA;AAG9E,UAAI,CAAC2G,EAAO,aAAc,QAAOiB,EAAA;AAEjC,UAAIC;AACJ,UAAI;AACF,QAAAA,IAAY,MAAMxB,EAAY,IAAIM,EAAO,cAAc,MAAMY,EAAcZ,CAAM,CAAC;AAAA,MACpF,SAAS9E,GAAK;AAGZ,eAAA6D,EAAI,OAAO,KAAK,yCAAyC7D,aAAe,QAAQA,EAAI,UAAU,OAAOA,CAAG,CAAC,GAClG8F,EAAU,KAAK,QAAQhB,EAAO,YAAYG,EAAcH,CAAM,IAAI,IAAI;AAAA,MAC/E;AAEA,UAAI,CAACkB,EAAW,QAAOD,EAAA;AAEvB,YAAMrC,IAAa,MAAMqB,EAAY5G,GAAS6H,GAAWlC,CAAO;AAEhE,aAAO,EAAE,SAASmB,EAAce,CAAS,GAAG,YAAAtC,GAAY,cAAcxF,EAAmBC,CAAO,EAAA;AAAA,IAClG;AAAA,EAAA;AAEJ;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/config.ts","../src/cookies.ts","../src/oidc.ts","../src/refresh.ts","../src/seal.ts","../src/server-auth.ts"],"sourcesContent":["/**\n * What an app tells the SDK, and the defaults for everything it doesn't.\n *\n * Only the first five matter in practice. Everything else exists for the rare\n * app that must differ, and its default is the decision the SDK makes for\n * everyone else.\n */\nexport interface ServerAuthConfig {\n /** The Pontive auth domain, e.g. `https://acme.auth.us.pontive.com`. */\n issuer: string;\n /** The app's id. */\n clientId: string;\n /**\n * Set for a confidential app. It is sent on every token call, and is what\n * lets the app hold its refresh token on its server at all.\n */\n clientSecret?: string;\n /** Encrypts the session cookie. At least 32 characters; rotating it signs everyone out. */\n sessionSecret: string;\n /**\n * The app's public origin, e.g. `https://app.acme.com`. Set it for any\n * deployment behind a proxy or in a container, where the request's own URL\n * names the address the server binds to rather than the one a browser uses.\n * Without it the origin comes from `x-forwarded-proto`/`x-forwarded-host`,\n * then the request URL.\n */\n baseUrl?: string;\n\n /** Default `openid email profile offline_access`. */\n scopes?: string;\n /** Where the SDK's sign-in routes live. Default `/api/auth`. */\n basePath?: string;\n /** Default `${origin}${basePath}/callback`; must be registered on the app verbatim. */\n redirectUri?: string;\n /** The app's own sign-in page: failed sign-ins and signed-out users land here. Default `/login`. */\n signInPath?: string;\n /** Where a sign-in lands when it did not start from a page. Default `/`. */\n afterSignInPath?: string;\n /** An OIDC `prompt` to send, e.g. `login`. */\n prompt?: string;\n /** Default `pontive_session`. */\n cookieName?: string;\n /** Default: whether the public origin is https. */\n secureCookies?: boolean;\n /** The session cookie's lifetime. Default 7 days. */\n sessionMaxAgeSeconds?: number;\n /** Refresh when fewer than this many seconds remain. Default 60. */\n refreshSkewSeconds?: number;\n /** Defaults to the global `fetch`. */\n fetch?: typeof fetch;\n /** Where the SDK reports a failed refresh or sign-in. Defaults to `console`. */\n logger?: Pick<Console, \"warn\">;\n}\n\nexport interface ResolvedConfig {\n issuer: string;\n clientId: string;\n clientSecret?: string;\n sessionSecret: string;\n baseUrl?: string;\n scopes: string;\n basePath: string;\n redirectUri?: string;\n signInPath: string;\n afterSignInPath: string;\n prompt?: string;\n cookieName: string;\n secureCookies?: boolean;\n sessionMaxAgeSeconds: number;\n refreshSkewSeconds: number;\n fetch: typeof fetch;\n logger: Pick<Console, \"warn\">;\n}\n\nconst SEVEN_DAYS = 7 * 24 * 60 * 60;\n\nexport function resolveConfig(config: ServerAuthConfig): ResolvedConfig {\n const required: (keyof ServerAuthConfig)[] = [\"issuer\", \"clientId\", \"sessionSecret\"];\n for (const key of required) {\n if (!config[key]) throw new Error(`@pontive/pontkit-server: ${key} is required`);\n }\n\n if (config.sessionSecret.length < 32) {\n throw new Error(\"@pontive/pontkit-server: sessionSecret must be at least 32 characters\");\n }\n\n if (config.baseUrl) {\n try {\n new URL(config.baseUrl);\n } catch {\n throw new Error(`@pontive/pontkit-server: baseUrl \"${config.baseUrl}\" is not an absolute URL`);\n }\n }\n\n return {\n issuer: config.issuer.replace(/\\/+$/, \"\"),\n clientId: config.clientId,\n clientSecret: config.clientSecret || undefined,\n sessionSecret: config.sessionSecret,\n baseUrl: config.baseUrl ? new URL(config.baseUrl).origin : undefined,\n scopes: config.scopes ?? \"openid email profile offline_access\",\n basePath: (config.basePath ?? \"/api/auth\").replace(/\\/+$/, \"\"),\n redirectUri: config.redirectUri,\n signInPath: config.signInPath ?? \"/login\",\n afterSignInPath: config.afterSignInPath ?? \"/\",\n prompt: config.prompt,\n cookieName: config.cookieName ?? \"pontive_session\",\n secureCookies: config.secureCookies,\n sessionMaxAgeSeconds: config.sessionMaxAgeSeconds ?? SEVEN_DAYS,\n refreshSkewSeconds: config.refreshSkewSeconds ?? 60,\n fetch: config.fetch ?? ((...args) => fetch(...args)),\n logger: config.logger ?? console,\n };\n}\n","/**\n * Cookie reading and writing, on plain header strings so it runs anywhere a\n * Web `Request`/`Response` does.\n *\n * A sealed session outgrows one cookie (browsers cap each at about 4 KB, and an\n * access token, a refresh token and a user fit in about 3–5), so it is split\n * across numbered chunks: `name`, `name.1`, `name.2`, … and joined on read.\n */\n\n/** Characters per chunk, leaving room under the ~4096-byte cap for the name and attributes. */\nexport const CHUNK_SIZE = 3500;\n/** Upper bound on chunks probed when reading or clearing. */\nexport const MAX_CHUNKS = 16;\n\nexport interface CookieAttributes {\n /** Seconds; 0 deletes the cookie. */\n maxAge: number;\n secure: boolean;\n}\n\n/** Parses a `Cookie` request header into name → value. */\nexport function parseCookies(header: string | null | undefined): Map<string, string> {\n const out = new Map<string, string>();\n if (!header) return out;\n\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq < 0) continue;\n\n const name = part.slice(0, eq).trim();\n if (!name || out.has(name)) continue;\n\n out.set(name, part.slice(eq + 1).trim());\n }\n\n return out;\n}\n\n/** Serialises a `Cookie` request header from name → value. */\nexport function formatCookieHeader(cookies: Map<string, string>): string {\n return [...cookies].map(([name, value]) => `${name}=${value}`).join(\"; \");\n}\n\n/**\n * A `Set-Cookie` value. Always HttpOnly, host-only, `Path=/` and\n * `SameSite=Lax`: Lax is what lets the cookie ride the top-level redirect back\n * from the sign-in page, and nothing here is ever for script.\n */\nexport function serializeCookie(name: string, value: string, attrs: CookieAttributes): string {\n const parts = [`${name}=${value}`, \"Path=/\", \"HttpOnly\", \"SameSite=Lax\", `Max-Age=${attrs.maxAge}`];\n if (attrs.secure) parts.push(\"Secure\");\n\n return parts.join(\"; \");\n}\n\nexport function chunkName(name: string, index: number): string {\n return index === 0 ? name : `${name}.${index}`;\n}\n\n/** Joins a chunked value, or null when its first chunk is absent. */\nexport function readChunked(cookies: Map<string, string>, name: string): string | null {\n const first = cookies.get(name);\n if (first === undefined) return null;\n\n let value = first;\n for (let i = 1; i < MAX_CHUNKS; i++) {\n const part = cookies.get(chunkName(name, i));\n if (part === undefined) break;\n value += part;\n }\n\n return value;\n}\n\n/**\n * The `Set-Cookie` values that store `value` in chunks, and delete any chunk\n * the request carried beyond what it now needs. Also updates `cookies` in\n * place, so a caller can forward the new state on the same request.\n */\nexport function writeChunked(\n cookies: Map<string, string>,\n name: string,\n value: string,\n attrs: CookieAttributes,\n): string[] {\n const out: string[] = [];\n const count = Math.max(1, Math.ceil(value.length / CHUNK_SIZE));\n\n for (let i = 0; i < count; i++) {\n const chunk = value.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);\n cookies.set(chunkName(name, i), chunk);\n out.push(serializeCookie(chunkName(name, i), chunk, attrs));\n }\n\n for (let i = count; i < MAX_CHUNKS; i++) {\n const stale = chunkName(name, i);\n if (!cookies.has(stale)) break;\n cookies.delete(stale);\n out.push(serializeCookie(stale, \"\", { ...attrs, maxAge: 0 }));\n }\n\n return out;\n}\n\n/** The `Set-Cookie` values that delete every chunk the request carried. */\nexport function clearChunked(cookies: Map<string, string>, name: string, secure: boolean): string[] {\n const out: string[] = [];\n\n for (let i = 0; i < MAX_CHUNKS; i++) {\n const chunk = chunkName(name, i);\n if (!cookies.has(chunk)) {\n if (i > 0) break;\n continue;\n }\n cookies.delete(chunk);\n out.push(serializeCookie(chunk, \"\", { maxAge: 0, secure }));\n }\n\n return out;\n}\n","import { createLocalJWKSet, jwtVerify, type JSONWebKeySet, type JWTPayload } from \"jose\";\n\n/**\n * The OIDC relying-party calls: discovery, PKCE, the two token grants and\n * id_token verification. Standard OIDC only, so any compliant issuer works.\n */\n\nexport interface Discovery {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n jwks_uri: string;\n end_session_endpoint?: string;\n}\n\nexport interface TokenResponse {\n access_token: string;\n token_type: string;\n expires_in?: number;\n refresh_token?: string;\n id_token?: string;\n scope?: string;\n}\n\nexport interface ClientCredentials {\n issuer: string;\n clientId: string;\n clientSecret?: string;\n}\n\n/** An error from the token endpoint, carrying its OAuth code. */\nexport class TokenEndpointError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n description: string,\n ) {\n super(`token endpoint ${status} ${code}: ${description}`);\n }\n}\n\ntype Fetch = typeof fetch;\n\nconst JWKS_TTL_MS = 10 * 60 * 1000;\n\nconst discoveries = new Map<string, Promise<Discovery>>();\nconst jwksCache = new Map<string, { at: number; set: ReturnType<typeof createLocalJWKSet> }>();\n\nfunction trimSlash(value: string): string {\n return value.replace(/\\/+$/, \"\");\n}\n\nexport function discover(issuer: string, fetchImpl: Fetch): Promise<Discovery> {\n const key = trimSlash(issuer);\n let pending = discoveries.get(key);\n\n if (!pending) {\n pending = fetchImpl(`${key}/.well-known/openid-configuration`)\n .then(async (res) => {\n if (!res.ok) throw new Error(`OIDC discovery failed for ${key}: ${res.status}`);\n return (await res.json()) as Discovery;\n })\n .catch((err: unknown) => {\n // A failure is not cached: the next request retries.\n discoveries.delete(key);\n throw err;\n });\n discoveries.set(key, pending);\n }\n\n return pending;\n}\n\nasync function jwks(uri: string, fetchImpl: Fetch, force: boolean): Promise<ReturnType<typeof createLocalJWKSet>> {\n const cached = jwksCache.get(uri);\n if (cached && !force && Date.now() - cached.at < JWKS_TTL_MS) return cached.set;\n\n const res = await fetchImpl(uri);\n if (!res.ok) throw new Error(`JWKS fetch failed for ${uri}: ${res.status}`);\n\n const set = createLocalJWKSet((await res.json()) as JSONWebKeySet);\n jwksCache.set(uri, { at: Date.now(), set });\n\n return set;\n}\n\nfunction base64url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const b of bytes) binary += String.fromCharCode(b);\n\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** A random, URL-safe value for state, nonce and the PKCE verifier. */\nexport function randomValue(): string {\n return base64url(crypto.getRandomValues(new Uint8Array(32)));\n}\n\nexport async function pkceChallenge(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(verifier));\n\n return base64url(new Uint8Array(digest));\n}\n\n/** Hex SHA-256, for keying state by a secret without holding the secret. */\nexport async function sha256Hex(value: string): Promise<string> {\n const digest = new Uint8Array(await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(value)));\n\n return [...digest].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport async function authorizationUrl(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { redirectUri: string; scopes: string; state: string; nonce: string; codeChallenge: string; prompt?: string },\n): Promise<string> {\n const disc = await discover(client.issuer, fetchImpl);\n const url = new URL(disc.authorization_endpoint);\n\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", client.clientId);\n url.searchParams.set(\"redirect_uri\", input.redirectUri);\n url.searchParams.set(\"scope\", input.scopes);\n url.searchParams.set(\"state\", input.state);\n url.searchParams.set(\"nonce\", input.nonce);\n url.searchParams.set(\"code_challenge\", input.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n if (input.prompt) url.searchParams.set(\"prompt\", input.prompt);\n\n return url.toString();\n}\n\n/**\n * A token-endpoint call. A confidential client authenticates with HTTP Basic\n * (RFC 6749 §2.3.1); `client_id` is always in the form too, which a public\n * client needs and a confidential one is allowed.\n */\nasync function tokenRequest(\n client: ClientCredentials,\n fetchImpl: Fetch,\n params: Record<string, string>,\n): Promise<TokenResponse> {\n const disc = await discover(client.issuer, fetchImpl);\n const body = new URLSearchParams(params);\n body.set(\"client_id\", client.clientId);\n\n const headers: Record<string, string> = { \"content-type\": \"application/x-www-form-urlencoded\" };\n if (client.clientSecret) {\n const credentials = `${encodeURIComponent(client.clientId)}:${encodeURIComponent(client.clientSecret)}`;\n headers.authorization = `Basic ${btoa(credentials)}`;\n }\n\n const res = await fetchImpl(disc.token_endpoint, { method: \"POST\", headers, body });\n if (!res.ok) {\n let code = \"unknown_error\";\n let description = \"\";\n\n try {\n const err = (await res.json()) as { error?: string; error_description?: string };\n code = err.error ?? code;\n description = err.error_description ?? \"\";\n } catch {\n // Not JSON; the status says enough.\n }\n\n throw new TokenEndpointError(res.status, code, description);\n }\n\n return (await res.json()) as TokenResponse;\n}\n\nexport function exchangeCode(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { code: string; codeVerifier: string; redirectUri: string },\n): Promise<TokenResponse> {\n return tokenRequest(client, fetchImpl, {\n grant_type: \"authorization_code\",\n code: input.code,\n code_verifier: input.codeVerifier,\n redirect_uri: input.redirectUri,\n });\n}\n\nexport function refreshTokens(client: ClientCredentials, fetchImpl: Fetch, refreshToken: string): Promise<TokenResponse> {\n return tokenRequest(client, fetchImpl, { grant_type: \"refresh_token\", refresh_token: refreshToken });\n}\n\n/**\n * Verifies an id_token's signature, issuer, audience and nonce. A key the\n * cached set does not know refetches the set once, which is how a key rotation\n * at the issuer is picked up without waiting out the cache.\n */\nexport async function verifyIdToken(\n client: ClientCredentials,\n fetchImpl: Fetch,\n idToken: string,\n nonce: string,\n): Promise<JWTPayload> {\n const disc = await discover(client.issuer, fetchImpl);\n const options = { issuer: disc.issuer, audience: client.clientId };\n\n let result;\n try {\n result = await jwtVerify(idToken, await jwks(disc.jwks_uri, fetchImpl, false), options);\n } catch (err) {\n if ((err as { code?: string }).code !== \"ERR_JWKS_NO_MATCHING_KEY\") throw err;\n result = await jwtVerify(idToken, await jwks(disc.jwks_uri, fetchImpl, true), options);\n }\n\n if (result.payload.nonce !== nonce) throw new Error(\"id_token nonce does not match the sign-in request\");\n\n return result.payload;\n}\n\n/** RP-initiated logout (OIDC RP-Initiated Logout 1.0), or null when the issuer offers none. */\nexport async function endSessionUrl(\n client: ClientCredentials,\n fetchImpl: Fetch,\n input: { postLogoutRedirectUri: string; sessionId?: string },\n): Promise<string | null> {\n const disc = await discover(client.issuer, fetchImpl);\n if (!disc.end_session_endpoint) return null;\n\n const url = new URL(disc.end_session_endpoint);\n url.searchParams.set(\"client_id\", client.clientId);\n url.searchParams.set(\"post_logout_redirect_uri\", input.postLogoutRedirectUri);\n // logout_hint is what lets the issuer end its session without asking \"Sign\n // out?\": only an app holding tokens from that session knows the sid.\n if (input.sessionId) url.searchParams.set(\"logout_hint\", input.sessionId);\n\n return url.toString();\n}\n\n/** For tests: forget cached discovery documents and key sets. */\nexport function resetOidcCaches(): void {\n discoveries.clear();\n jwksCache.clear();\n}\n","import { sha256Hex } from \"./oidc\";\n\n/**\n * One refresh per session, however many requests ask for it.\n *\n * With rotation on, every refresh spends the token it presents. A page that\n * fires several requests the moment its access token expires would otherwise\n * refresh once per request, and every request after the first would present a\n * token the first had already spent — which the issuer reads as theft.\n *\n * Two layers, in this process:\n *\n * - In flight: requests for the same refresh token share one call.\n * - Recently done: a request that arrives a moment later still holding the old\n * cookie (its browser had not yet stored the new one) is handed the same\n * result rather than spending the old token again.\n *\n * Across processes — several replicas of the app — the issuer's reuse grace\n * window covers the same race, so no shared lock is needed.\n */\n\n/** How long a result is handed to late arrivals. Matches the issuer's grace window. */\nexport const RECENT_RESULT_MS = 30_000;\n\nexport class RefreshCoordinator<T> {\n private readonly inFlight = new Map<string, Promise<T>>();\n private readonly recent = new Map<string, { at: number; result: T }>();\n\n constructor(private readonly recentMs: number = RECENT_RESULT_MS) {}\n\n async run(refreshToken: string, refresh: () => Promise<T>): Promise<T> {\n const key = await sha256Hex(refreshToken);\n this.sweep();\n\n const done = this.recent.get(key);\n if (done) return done.result;\n\n let pending = this.inFlight.get(key);\n if (!pending) {\n pending = refresh()\n .then((result) => {\n this.recent.set(key, { at: Date.now(), result });\n return result;\n })\n .finally(() => this.inFlight.delete(key));\n this.inFlight.set(key, pending);\n }\n\n return pending;\n }\n\n private sweep(): void {\n const cutoff = Date.now() - this.recentMs;\n for (const [key, entry] of this.recent) {\n if (entry.at < cutoff) this.recent.delete(key);\n }\n }\n}\n","import { EncryptJWT, jwtDecrypt } from \"jose\";\n\n/**\n * Sealing: authenticated encryption (JWE, `dir` + A256GCM) of whatever the\n * server stores in a cookie. The browser holds ciphertext it can neither read\n * nor alter, which is what lets a refresh token live in a cookie at all.\n */\n\nconst keys = new Map<string, Promise<Uint8Array>>();\n\n/** A 256-bit key derived from the configured secret, whatever its length. */\nfunction keyFor(secret: string): Promise<Uint8Array> {\n let key = keys.get(secret);\n if (!key) {\n key = crypto.subtle\n .digest(\"SHA-256\", new TextEncoder().encode(secret))\n .then((digest) => new Uint8Array(digest));\n keys.set(secret, key);\n }\n\n return key;\n}\n\nexport async function seal(payload: Record<string, unknown>, secret: string, maxAgeSeconds: number): Promise<string> {\n return new EncryptJWT(payload)\n .setProtectedHeader({ alg: \"dir\", enc: \"A256GCM\" })\n .setIssuedAt()\n .setExpirationTime(`${maxAgeSeconds}s`)\n .encrypt(await keyFor(secret));\n}\n\n/** The payload, or null when the value is not one this secret sealed, or has expired. */\nexport async function unseal<T>(value: string, secret: string): Promise<T | null> {\n try {\n const { payload } = await jwtDecrypt(value, await keyFor(secret));\n return payload as T;\n } catch {\n return null;\n }\n}\n","import type { JWTPayload } from \"jose\";\n\nimport { resolveConfig, type ResolvedConfig, type ServerAuthConfig } from \"./config\";\nimport {\n clearChunked,\n formatCookieHeader,\n parseCookies,\n readChunked,\n serializeCookie,\n writeChunked,\n} from \"./cookies\";\nimport {\n authorizationUrl,\n endSessionUrl,\n exchangeCode,\n pkceChallenge,\n randomValue,\n refreshTokens,\n TokenEndpointError,\n verifyIdToken,\n type ClientCredentials,\n type TokenResponse,\n} from \"./oidc\";\nimport { RefreshCoordinator } from \"./refresh\";\nimport { seal, unseal } from \"./seal\";\n\nexport interface SessionUser {\n id: string;\n email?: string;\n name?: string;\n username?: string;\n picture?: string;\n emailVerified: boolean;\n}\n\n/** What `getSession` hands the app. */\nexport interface Session {\n user: SessionUser;\n /** A valid access token, for calling APIs as this user. */\n accessToken: string;\n /** When the access token expires, epoch milliseconds. */\n expiresAt: number;\n /** The issuer's session id (`sid`), when it sent one. */\n sessionId?: string;\n}\n\n/** What the cookie holds: the session plus the refresh token, which the app never sees. */\ninterface StoredSession extends Session {\n refreshToken?: string;\n}\n\n/** A sign-in in progress, sealed into its own short-lived cookie. */\ninterface SignInTransaction {\n state: string;\n verifier: string;\n nonce: string;\n returnTo: string;\n}\n\n/** What a refresh produced, for the middleware to apply. */\nexport interface RefreshOutcome {\n /** The session after the refresh, or null when there is none (or it could not be refreshed). */\n session: Session | null;\n /** `Set-Cookie` values for the response. Empty when nothing changed. */\n setCookies: string[];\n /**\n * The request's `Cookie` header as it should be seen by whatever handles\n * this request next, carrying the refreshed session. Forward it, so a page\n * rendered on this very request reads the new token rather than the expired\n * one the browser sent.\n */\n cookieHeader: string;\n}\n\nexport interface ServerAuth {\n /**\n * Handles the SDK's routes under `basePath`: `/login`, `/callback` and\n * `/logout`. Null for any other path.\n */\n handle(request: Request): Promise<Response | null>;\n /**\n * The session in a `Cookie` header, or null. Read-only and never refreshes:\n * any server code can call it, including code that cannot set cookies.\n * Returns null for a session whose access token has expired — `refresh`,\n * run in middleware ahead of the request, is what keeps it current.\n */\n getSession(cookieHeader: string | null | undefined): Promise<Session | null>;\n /**\n * Refreshes the session when its access token is about to expire. For\n * middleware: it runs before anything renders, and its response can always\n * carry the new cookie. Concurrent calls for one session share one refresh.\n */\n refresh(request: Request): Promise<RefreshOutcome>;\n}\n\nconst DEFAULT_ACCESS_TTL_SECONDS = 900;\nconst TRANSACTION_MAX_AGE = 600;\n\n/** Only a same-origin path may be returned to after sign-in: no open redirect. */\nfunction safePath(value: string | null | undefined, fallback: string): string {\n if (value && value.startsWith(\"/\") && !value.startsWith(\"//\") && !value.startsWith(\"/\\\\\")) return value;\n return fallback;\n}\n\nfunction redirect(location: string, setCookies: string[] = []): Response {\n const headers = new Headers({ location, \"cache-control\": \"no-store\" });\n for (const cookie of setCookies) headers.append(\"set-cookie\", cookie);\n\n return new Response(null, { status: 302, headers });\n}\n\nfunction publicOrigin(cfg: ResolvedConfig, request: Request): string {\n if (cfg.baseUrl) return cfg.baseUrl;\n\n const host = request.headers.get(\"x-forwarded-host\")?.split(\",\")[0]?.trim() || request.headers.get(\"host\")?.trim();\n if (host) {\n const proto =\n request.headers.get(\"x-forwarded-proto\")?.split(\",\")[0]?.trim() || new URL(request.url).protocol.replace(\":\", \"\");\n try {\n return new URL(`${proto}://${host}`).origin;\n } catch {\n // Fall through to the request URL.\n }\n }\n\n return new URL(request.url).origin;\n}\n\nfunction userFrom(claims: JWTPayload): SessionUser {\n const str = (key: string) => (typeof claims[key] === \"string\" ? (claims[key] as string) : undefined);\n const name =\n str(\"name\") ?? ([str(\"given_name\"), str(\"family_name\")].filter(Boolean).join(\" \") || undefined) ?? str(\"email\");\n\n return {\n id: claims.sub ?? \"\",\n email: str(\"email\"),\n name,\n username: str(\"preferred_username\"),\n picture: str(\"picture\"),\n emailVerified: claims.email_verified === true,\n };\n}\n\nfunction expiresAtFrom(token: TokenResponse): number {\n return Date.now() + (token.expires_in ?? DEFAULT_ACCESS_TTL_SECONDS) * 1000;\n}\n\nexport function createServerAuth(config: ServerAuthConfig): ServerAuth {\n const cfg = resolveConfig(config);\n const client: ClientCredentials = { issuer: cfg.issuer, clientId: cfg.clientId, clientSecret: cfg.clientSecret };\n const txCookie = `${cfg.cookieName}_tx`;\n const coordinator = new RefreshCoordinator<StoredSession | null>();\n\n const secure = (request: Request) => cfg.secureCookies ?? publicOrigin(cfg, request).startsWith(\"https:\");\n const redirectUri = (request: Request) => cfg.redirectUri ?? `${publicOrigin(cfg, request)}${cfg.basePath}/callback`;\n const toSignIn = (request: Request, error?: string) => {\n const url = new URL(cfg.signInPath, publicOrigin(cfg, request));\n if (error) url.searchParams.set(\"error\", error);\n return url.toString();\n };\n\n async function readStored(cookies: Map<string, string>): Promise<StoredSession | null> {\n const raw = readChunked(cookies, cfg.cookieName);\n if (raw === null) return null;\n\n const stored = await unseal<StoredSession>(raw, cfg.sessionSecret);\n if (!stored) cfg.logger.warn(\"[pontive] session cookie could not be read (sealed with another secret, or truncated)\");\n\n return stored;\n }\n\n async function writeStored(cookies: Map<string, string>, stored: StoredSession, request: Request): Promise<string[]> {\n const sealed = await seal({ ...stored }, cfg.sessionSecret, cfg.sessionMaxAgeSeconds);\n\n return writeChunked(cookies, cfg.cookieName, sealed, { maxAge: cfg.sessionMaxAgeSeconds, secure: secure(request) });\n }\n\n function publicSession(stored: StoredSession): Session {\n const { refreshToken: _refreshToken, ...session } = stored;\n return session;\n }\n\n async function signIn(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const tx: SignInTransaction = {\n state: randomValue(),\n verifier: randomValue(),\n nonce: randomValue(),\n returnTo: safePath(url.searchParams.get(\"returnTo\") ?? url.searchParams.get(\"next\"), cfg.afterSignInPath),\n };\n\n const location = await authorizationUrl(client, cfg.fetch, {\n redirectUri: redirectUri(request),\n scopes: cfg.scopes,\n state: tx.state,\n nonce: tx.nonce,\n codeChallenge: await pkceChallenge(tx.verifier),\n prompt: cfg.prompt,\n });\n\n const sealed = await seal({ ...tx }, cfg.sessionSecret, TRANSACTION_MAX_AGE);\n\n return redirect(location, [serializeCookie(txCookie, sealed, { maxAge: TRANSACTION_MAX_AGE, secure: secure(request) })]);\n }\n\n async function callback(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n // Single use, whatever happens next.\n const setCookies = [serializeCookie(txCookie, \"\", { maxAge: 0, secure: secure(request) })];\n\n const issuerError = url.searchParams.get(\"error\");\n if (issuerError) return redirect(toSignIn(request, issuerError), setCookies);\n\n const raw = cookies.get(txCookie);\n const tx = raw ? await unseal<SignInTransaction>(raw, cfg.sessionSecret) : null;\n const code = url.searchParams.get(\"code\");\n\n if (!tx || !code || url.searchParams.get(\"state\") !== tx.state) {\n return redirect(toSignIn(request, \"invalid_state\"), setCookies);\n }\n\n try {\n const tokens = await exchangeCode(client, cfg.fetch, {\n code,\n codeVerifier: tx.verifier,\n redirectUri: redirectUri(request),\n });\n if (!tokens.id_token) throw new Error(\"the token response carried no id_token; is the openid scope requested?\");\n\n const claims = await verifyIdToken(client, cfg.fetch, tokens.id_token, tx.nonce);\n const stored: StoredSession = {\n user: userFrom(claims),\n accessToken: tokens.access_token,\n refreshToken: tokens.refresh_token,\n expiresAt: expiresAtFrom(tokens),\n sessionId: typeof claims.sid === \"string\" ? claims.sid : undefined,\n };\n\n setCookies.push(...(await writeStored(cookies, stored, request)));\n } catch (err) {\n cfg.logger.warn(\"[pontive] sign-in failed:\", err instanceof Error ? err.message : String(err));\n return redirect(toSignIn(request, \"exchange_failed\"), setCookies);\n }\n\n return redirect(new URL(tx.returnTo, publicOrigin(cfg, request)).toString(), setCookies);\n }\n\n async function signOut(request: Request): Promise<Response> {\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n // Read before clearing: the session holds the sid the issuer's logout needs.\n const stored = await readStored(cookies);\n const setCookies = clearChunked(cookies, cfg.cookieName, secure(request));\n\n let location = toSignIn(request);\n try {\n location =\n (await endSessionUrl(client, cfg.fetch, {\n postLogoutRedirectUri: toSignIn(request),\n sessionId: stored?.sessionId,\n })) ?? location;\n } catch (err) {\n // An unreachable issuer must not stop the local sign-out.\n cfg.logger.warn(\"[pontive] could not reach the issuer to end its session:\", err instanceof Error ? err.message : String(err));\n }\n\n return redirect(location, setCookies);\n }\n\n /**\n * Spends the refresh token once, returning the refreshed session, or null\n * when the issuer refused it for good. Anything transient throws instead, so\n * the coordinator does not hand a passing failure to other requests.\n */\n async function refreshStored(stored: StoredSession): Promise<StoredSession | null> {\n try {\n const tokens = await refreshTokens(client, cfg.fetch, stored.refreshToken!);\n\n return {\n ...stored,\n accessToken: tokens.access_token,\n // A response without one means rotation is off: the token in hand is still current.\n refreshToken: tokens.refresh_token ?? stored.refreshToken,\n expiresAt: expiresAtFrom(tokens),\n };\n } catch (err) {\n if (err instanceof TokenEndpointError && err.status >= 400 && err.status < 500) {\n cfg.logger.warn(\"[pontive] refresh refused; signing out:\", err.message);\n return null;\n }\n\n throw err;\n }\n }\n\n return {\n async handle(request) {\n const { pathname } = new URL(request.url);\n if (!pathname.startsWith(`${cfg.basePath}/`)) return null;\n\n const route = pathname.slice(cfg.basePath.length + 1);\n if (route === \"login\" && request.method === \"GET\") return signIn(request);\n if (route === \"callback\" && request.method === \"GET\") return callback(request);\n if (route === \"logout\" && (request.method === \"GET\" || request.method === \"POST\")) return signOut(request);\n\n return null;\n },\n\n async getSession(cookieHeader) {\n const stored = await readStored(parseCookies(cookieHeader));\n if (!stored) return null;\n\n if (Date.now() >= stored.expiresAt) {\n cfg.logger.warn(\n \"[pontive] the session's access token has expired and getSession cannot refresh it. \" +\n \"Run the SDK's middleware on this path so it refreshes before the request is handled.\",\n );\n return null;\n }\n\n return publicSession(stored);\n },\n\n async refresh(request) {\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n const unchanged = (session: Session | null): RefreshOutcome => ({\n session,\n setCookies: [],\n cookieHeader: request.headers.get(\"cookie\") ?? \"\",\n });\n\n const stored = await readStored(cookies);\n if (!stored) return unchanged(null);\n\n // The SDK's own routes manage the session themselves. Refreshing ahead of\n // them only spends a refresh token that sign-out is about to discard, and\n // puts a second session cookie on the response it clears.\n if (new URL(request.url).pathname.startsWith(`${cfg.basePath}/`)) {\n return unchanged(Date.now() < stored.expiresAt ? publicSession(stored) : null);\n }\n\n if (Date.now() < stored.expiresAt - cfg.refreshSkewSeconds * 1000) return unchanged(publicSession(stored));\n\n const signOutNow = (): RefreshOutcome => {\n const setCookies = clearChunked(cookies, cfg.cookieName, secure(request));\n return { session: null, setCookies, cookieHeader: formatCookieHeader(cookies) };\n };\n\n if (!stored.refreshToken) return signOutNow();\n\n let refreshed: StoredSession | null;\n try {\n refreshed = await coordinator.run(stored.refreshToken, () => refreshStored(stored));\n } catch (err) {\n // Transient: keep the session and let the next request try again. The\n // token may still have a few seconds of the skew left.\n cfg.logger.warn(\"[pontive] refresh failed, will retry:\", err instanceof Error ? err.message : String(err));\n return unchanged(Date.now() < stored.expiresAt ? publicSession(stored) : null);\n }\n\n if (!refreshed) return signOutNow();\n\n const setCookies = await writeStored(cookies, refreshed, request);\n\n return { session: publicSession(refreshed), setCookies, cookieHeader: formatCookieHeader(cookies) };\n },\n };\n}\n"],"names":["SEVEN_DAYS","resolveConfig","config","required","key","args","CHUNK_SIZE","MAX_CHUNKS","parseCookies","header","out","part","eq","name","formatCookieHeader","cookies","value","serializeCookie","attrs","parts","chunkName","index","readChunked","first","i","writeChunked","count","chunk","stale","clearChunked","secure","TokenEndpointError","status","code","description","JWKS_TTL_MS","discoveries","jwksCache","trimSlash","discover","issuer","fetchImpl","pending","res","err","jwks","uri","force","cached","set","createLocalJWKSet","base64url","bytes","binary","b","randomValue","pkceChallenge","verifier","digest","sha256Hex","authorizationUrl","client","input","disc","url","tokenRequest","params","body","headers","credentials","exchangeCode","refreshTokens","refreshToken","verifyIdToken","idToken","nonce","options","result","jwtVerify","endSessionUrl","RECENT_RESULT_MS","RefreshCoordinator","recentMs","refresh","done","cutoff","entry","keys","keyFor","secret","seal","payload","maxAgeSeconds","EncryptJWT","unseal","jwtDecrypt","DEFAULT_ACCESS_TTL_SECONDS","TRANSACTION_MAX_AGE","safePath","fallback","redirect","location","setCookies","cookie","publicOrigin","cfg","request","host","proto","userFrom","claims","str","expiresAtFrom","token","createServerAuth","txCookie","coordinator","redirectUri","toSignIn","error","readStored","raw","stored","writeStored","sealed","publicSession","_refreshToken","session","signIn","tx","callback","issuerError","tokens","signOut","refreshStored","pathname","route","cookieHeader","unchanged","signOutNow","refreshed"],"mappings":"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"}
|
package/package.json
CHANGED