@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 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
@@ -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 I(t) {
47
+ function R(t) {
48
48
  return [...t].map(([e, r]) => `${e}=${r}`).join("; ");
49
49
  }
50
- function _(t, e, r) {
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 y(t, e) {
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(y(e, s));
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 d = r.slice(u * P, (u + 1) * P);
72
- t.set(y(e, u), d), s.push(_(y(e, u), d, n));
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 d = y(e, u);
76
- if (!t.has(d)) break;
77
- t.delete(d), s.push(_(d, "", { ...n, maxAge: 0 }));
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 R(t, e, r) {
81
+ function I(t, e, r) {
82
82
  const n = [];
83
83
  for (let s = 0; s < $; s++) {
84
- const a = y(e, s);
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(_(a, "", { maxAge: 0, secure: r }));
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 d = `${encodeURIComponent(t.clientId)}:${encodeURIComponent(t.clientSecret)}`;
147
- a.authorization = `Basic ${btoa(d)}`;
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 d = "unknown_error", k = "";
151
+ let l = "unknown_error", k = "";
152
152
  try {
153
153
  const S = await u.json();
154
- d = S.error ?? d, k = S.error_description ?? "";
154
+ l = S.error ?? l, k = S.error_description ?? "";
155
155
  } catch {
156
156
  }
157
- throw new W(u.status, d, k);
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 (d) {
178
- if (d.code !== "ERR_JWKS_NO_MATCHING_KEY") throw d;
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 j(t, e, r) {
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 D(t, e) {
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`, d = (o, i) => {
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 D(i, e.sessionSecret);
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 l = await j({ ...i }, e.sessionSecret, e.sessionMaxAgeSeconds);
276
- return re(o, e.cookieName, l, { maxAge: e.sessionMaxAgeSeconds, secure: a(c) });
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 U(o) {
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
- }, l = await ae(r, e.fetch, {
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 j({ ...c }, e.sessionSecret, H);
296
- return m(l, [_(n, h, { maxAge: H, secure: a(o) })]);
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")), l = [_(n, "", { maxAge: 0, secure: a(o) })], h = i.searchParams.get("error");
300
- if (h) return m(d(o, h), l);
301
- const f = c.get(n), g = f ? await D(f, e.sessionSecret) : null, w = i.searchParams.get("code");
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(d(o, "invalid_state"), l);
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
- l.push(...await S(c, X, o));
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(d(o, "exchange_failed"), l);
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(), l);
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), l = R(i, e.cookieName, a(o));
326
- let h = d(o);
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: d(o),
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, l);
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) : U(i) : 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
- }), l = await k(i);
372
- if (!l) return c(null);
373
- if (Date.now() < l.expiresAt - e.refreshSkewSeconds * 1e3) return c(U(l));
374
- const h = () => ({ session: null, setCookies: R(i, e.cookieName, a(o)), cookieHeader: I(i) });
375
- if (!l.refreshToken) return h();
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(l.refreshToken, () => B(l));
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() < l.expiresAt ? U(l) : null);
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: U(f), setCookies: g, cookieHeader: I(i) };
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":"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"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pontive/pontkit-server",
3
- "version": "1.0.4",
3
+ "version": "1.1.1",
4
4
  "license": "MIT",
5
5
  "description": "Server-side sessions for Pontive apps: sign-in, a sealed session cookie and safe token refresh, on Web Request/Response.",
6
6
  "type": "module",