@authrim/core 0.1.0 → 0.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,3 +1,3 @@
1
- 'use strict';function U(s){return {...s,scopes:s.scopes??["openid","profile"],flowEngine:s.flowEngine??false,discoveryCacheTtlMs:s.discoveryCacheTtlMs??3600*1e3,refreshSkewSeconds:s.refreshSkewSeconds??30,stateTtlSeconds:s.stateTtlSeconds??600,hashOptions:{hashLength:s.hashOptions?.hashLength??16}}}var a=class s extends Error{constructor(e,t,n){super(t),this.name="AuthrimError",this.code=e,this.details=n?.details,this.errorUri=n?.errorUri,this.cause=n?.cause;}static fromOAuthError(e){let n=["invalid_request","unauthorized_client","access_denied","unsupported_response_type","invalid_scope","server_error","temporarily_unavailable","invalid_grant","invalid_token"].includes(e.error)?e.error:"invalid_request";return new s(n,e.error_description??e.error,{errorUri:e.error_uri,details:{originalError:e.error}})}isRetryable(){return this.code==="network_error"||this.code==="timeout_error"}get meta(){return j(this.code)}},F={invalid_request:{transient:false,retryable:false,userAction:"contact_support",severity:"error"},unauthorized_client:{transient:false,retryable:false,userAction:"contact_support",severity:"fatal"},access_denied:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},unsupported_response_type:{transient:false,retryable:false,userAction:"contact_support",severity:"fatal"},invalid_scope:{transient:false,retryable:false,userAction:"contact_support",severity:"error"},server_error:{transient:true,retryable:true,retryAfterMs:5e3,maxRetries:3,userAction:"retry",severity:"error"},temporarily_unavailable:{transient:true,retryable:true,retryAfterMs:1e4,maxRetries:3,userAction:"retry",severity:"warning"},invalid_grant:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},invalid_token:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},invalid_state:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},expired_state:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},invalid_nonce:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},nonce_mismatch:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},session_expired:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},session_check_failed:{transient:true,retryable:true,retryAfterMs:3e3,maxRetries:2,userAction:"retry",severity:"warning"},network_error:{transient:true,retryable:true,retryAfterMs:2e3,maxRetries:3,userAction:"check_network",severity:"error"},timeout_error:{transient:true,retryable:true,retryAfterMs:3e3,maxRetries:3,userAction:"retry",severity:"warning"},discovery_error:{transient:true,retryable:true,retryAfterMs:5e3,maxRetries:2,userAction:"retry",severity:"error"},discovery_mismatch:{transient:false,retryable:false,userAction:"contact_support",severity:"fatal"},configuration_error:{transient:false,retryable:false,userAction:"contact_support",severity:"fatal"},storage_error:{transient:true,retryable:true,retryAfterMs:1e3,maxRetries:2,userAction:"retry",severity:"error"},flow_engine_error:{transient:false,retryable:false,userAction:"contact_support",severity:"error"},no_tokens:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},token_expired:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},token_error:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},refresh_error:{transient:true,retryable:true,retryAfterMs:2e3,maxRetries:2,userAction:"retry",severity:"error"},token_exchange_error:{transient:true,retryable:true,retryAfterMs:2e3,maxRetries:2,userAction:"retry",severity:"error"},oauth_error:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},missing_code:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},missing_state:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},not_initialized:{transient:false,retryable:false,userAction:"none",severity:"fatal"},no_discovery:{transient:true,retryable:true,retryAfterMs:3e3,maxRetries:2,userAction:"retry",severity:"error"},no_userinfo_endpoint:{transient:false,retryable:false,userAction:"none",severity:"warning"},userinfo_error:{transient:true,retryable:true,retryAfterMs:2e3,maxRetries:2,userAction:"retry",severity:"error"},introspection_error:{transient:true,retryable:true,retryAfterMs:2e3,maxRetries:2,userAction:"retry",severity:"error"},revocation_error:{transient:true,retryable:true,retryAfterMs:2e3,maxRetries:2,userAction:"retry",severity:"error"},no_introspection_endpoint:{transient:false,retryable:false,userAction:"none",severity:"warning"},no_revocation_endpoint:{transient:false,retryable:false,userAction:"none",severity:"warning"},login_required:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},interaction_required:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},consent_required:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},account_selection_required:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"}};function j(s){return F[s]}function g(s){return s.replace(/\/+$/,"")}var O=class O{constructor(e){this.cache=new Map;this.http=e.http,this.cacheTtlMs=e.cacheTtlMs??O.DEFAULT_CACHE_TTL_MS;}async discover(e){let t=g(e),n=this.cache.get(t);if(n&&!this.isExpired(n))return n.doc;let r=`${t}/.well-known/openid-configuration`,i;try{let c=await this.http.fetch(r);if(!c.ok)throw new a("discovery_error",`Discovery request failed: ${c.status}`,{details:{status:c.status,statusText:c.statusText}});i=c.data;}catch(c){throw c instanceof a?c:new a("discovery_error","Failed to fetch discovery document",{cause:c instanceof Error?c:void 0,details:{url:r}})}let o=g(i.issuer);if(o!==t)throw new a("discovery_mismatch",`Issuer mismatch in discovery document: expected "${t}", got "${o}"`,{details:{expected:t,actual:o}});return this.cache.set(t,{doc:i,fetchedAt:Date.now()}),i}isExpired(e){return Date.now()-e.fetchedAt>this.cacheTtlMs}clearCache(){this.cache.clear();}clearIssuer(e){this.cache.delete(g(e));}};O.DEFAULT_CACHE_TTL_MS=3600*1e3;var v=O;var _=class{constructor(){this.listeners=new Map;}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{this.off(e,t);}}once(e,t){let n=(r=>{this.off(e,n),t(r);});return this.on(e,n)}off(e,t){let n=this.listeners.get(e);n&&(n.delete(t),n.size===0&&this.listeners.delete(e));}emit(e,t){let n=this.listeners.get(e);if(n)for(let r of n)try{r(t);}catch(i){console.error(`Error in event handler for '${e}':`,i);}}removeAllListeners(e){e?this.listeners.delete(e):this.listeners.clear();}listenerCount(e){return this.listeners.get(e)?.size??0}};var T=class{constructor(e){this.crypto=e;}async generatePKCE(){let e=await this.crypto.generateCodeVerifier(),t=await this.crypto.generateCodeChallenge(e);return {codeVerifier:e,codeChallenge:t,codeChallengeMethod:"S256"}}async generateCodeVerifier(){return this.crypto.generateCodeVerifier()}async generateCodeChallenge(e){return this.crypto.generateCodeChallenge(e)}};function y(s){let e="",t=s.length;for(let n=0;n<t;n+=3){let r=s[n],i=n+1<t?s[n+1]:0,o=n+2<t?s[n+2]:0,c=r<<16|i<<8|o;e+=k[c>>18&63],e+=k[c>>12&63],e+=n+1<t?k[c>>6&63]:"",e+=n+2<t?k[c&63]:"";}return e.replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function N(s){let e=s.replace(/-/g,"+").replace(/_/g,"/");for(;e.length%4;)e+="=";let t=e.length,n=e.endsWith("==")?2:e.endsWith("=")?1:0,r=t*3/4-n,i=new Uint8Array(r),o=0;for(let c=0;c<t;c+=4){let p=w[e.charCodeAt(c)],d=w[e.charCodeAt(c+1)],h=w[e.charCodeAt(c+2)],u=w[e.charCodeAt(c+3)],l=p<<18|d<<12|h<<6|u;o<r&&(i[o++]=l>>16&255),o<r&&(i[o++]=l>>8&255),o<r&&(i[o++]=l&255);}return i}function J(s){let e=new TextEncoder;return y(e.encode(s))}function b(s){return new TextDecoder().decode(N(s))}var k="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",w=new Uint8Array(256);for(let s=0;s<k.length;s++)w[k.charCodeAt(s)]=s;var f={authState:(s,e,t)=>`authrim:${s}:${e}:auth:${t}`,tokens:(s,e)=>`authrim:${s}:${e}:tokens`,idToken:(s,e)=>`authrim:${s}:${e}:id_token`,authStatePrefix:(s,e)=>`authrim:${s}:${e}:auth:`},D=class D{constructor(e,t,n,r){this.crypto=e;this.storage=t;this.issuerHash=n;this.clientIdHash=r;}async generateAuthState(e){let t=e.ttlSeconds??D.DEFAULT_TTL_SECONDS,n=await this.crypto.randomBytes(32),r=await this.crypto.randomBytes(32),i=y(n),o=y(r),c=Date.now(),p={state:i,nonce:o,codeVerifier:e.codeVerifier,redirectUri:e.redirectUri,createdAt:c,expiresAt:c+t*1e3},d=f.authState(this.issuerHash,this.clientIdHash,i);return await this.storage.set(d,JSON.stringify(p)),p}async validateAndConsumeState(e){let t=f.authState(this.issuerHash,this.clientIdHash,e);try{let n=await this.storage.get(t);if(!n)throw new a("invalid_state","State not found or already used");let r;try{r=JSON.parse(n);}catch{throw new a("invalid_state","Malformed state data")}if(Date.now()>r.expiresAt)throw new a("expired_state","State has expired");return r}finally{await this.storage.remove(t);}}async cleanupExpiredStates(){if(!this.storage.getAll)return;let e=f.authStatePrefix(this.issuerHash,this.clientIdHash),t=await this.storage.getAll(),n=Date.now();for(let[r,i]of Object.entries(t))if(r.startsWith(e))try{let o=JSON.parse(i);n>o.expiresAt&&await this.storage.remove(r);}catch{await this.storage.remove(r);}}};D.DEFAULT_TTL_SECONDS=600;var A=D;function q(s){let e=s.split(".");if(e.length!==3)throw new Error("Invalid JWT format: expected 3 parts");let[t,n,r]=e;try{let i=JSON.parse(b(t)),o=JSON.parse(b(n));return {header:i,payload:o,signature:r}}catch{throw new Error("Invalid JWT format: failed to decode")}}function K(s){return q(s).payload}function V(s,e=0){if(s.exp===void 0)return false;let t=Math.floor(Date.now()/1e3);return s.exp+e<t}function M(s){try{return K(s).nonce}catch{return}}var E=class{constructor(e,t){this.http=e;this.clientId=t;}buildAuthorizationUrl(e,t,n,r){let i=e.authorization_endpoint,o=new URLSearchParams;o.set("client_id",this.clientId),o.set("response_type","code"),o.set("redirect_uri",r.redirectUri),o.set("state",t.state),o.set("nonce",t.nonce),o.set("code_challenge",n.codeChallenge),o.set("code_challenge_method",n.codeChallengeMethod);let c=r.scope??"openid profile";if(o.set("scope",c),r.prompt&&o.set("prompt",r.prompt),r.loginHint&&o.set("login_hint",r.loginHint),r.acrValues&&o.set("acr_values",r.acrValues),r.extraParams){let h=new Set(["client_id","response_type","redirect_uri","state","nonce","code_challenge","code_challenge_method","scope"]);for(let[u,l]of Object.entries(r.extraParams))h.has(u.toLowerCase())||o.set(u,l);}let d={url:`${i}?${o.toString()}`};return r.exposeState&&(d.state=t.state,d.nonce=t.nonce),d}parseCallback(e){let t;e.includes("?")?t=(e.startsWith("http")?new URL(e):new URL(e,"https://dummy.local")).searchParams:t=new URLSearchParams(e);let n=t.get("error");if(n){let o=t.get("error_description")??"Authorization failed";throw new a("oauth_error",o,{details:{error:n,error_description:o,error_uri:t.get("error_uri")}})}let r=t.get("code"),i=t.get("state");if(!r)throw new a("missing_code","Authorization code not found in callback");if(!i)throw new a("missing_state","State parameter not found in callback");return {code:r,state:i}}async exchangeCode(e,t){let n=e.token_endpoint,r=new URLSearchParams({grant_type:"authorization_code",client_id:this.clientId,code:t.code,redirect_uri:t.redirectUri,code_verifier:t.codeVerifier}),i;try{i=await this.http.fetch(n,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r.toString()});}catch(h){throw new a("network_error","Token request failed",{cause:h instanceof Error?h:void 0})}if(!i.ok){let h=i.data;throw new a("token_error","Token exchange failed",{details:{status:i.status,error:h?.error,error_description:h?.error_description}})}let o=i.data;if(o.id_token&&M(o.id_token)!==t.nonce)throw new a("nonce_mismatch","ID token nonce does not match expected value");let c=Math.floor(Date.now()/1e3),p=o.expires_in?c+o.expires_in:c+3600;return {accessToken:o.access_token,tokenType:o.token_type??"Bearer",expiresAt:p,refreshToken:o.refresh_token,idToken:o.id_token,scope:o.scope}}};var L={access_token:"urn:ietf:params:oauth:token-type:access_token",refresh_token:"urn:ietf:params:oauth:token-type:refresh_token",id_token:"urn:ietf:params:oauth:token-type:id_token"};var H=class H{constructor(e){this.refreshPromise=null;this.discovery=null;this.http=e.http,this.storage=e.storage,this.clientId=e.clientId,this.issuerHash=e.issuerHash,this.clientIdHash=e.clientIdHash,this.refreshSkewSeconds=e.refreshSkewSeconds??H.DEFAULT_REFRESH_SKEW_SECONDS,this.eventEmitter=e.eventEmitter;}setDiscovery(e){this.discovery=e;}get tokenKey(){return f.tokens(this.issuerHash,this.clientIdHash)}get idTokenKey(){return f.idToken(this.issuerHash,this.clientIdHash)}async getTokens(){let e=await this.storage.get(this.tokenKey);if(!e)return null;try{return JSON.parse(e)}catch{return await this.clearTokens(),null}}async saveTokens(e){await this.storage.set(this.tokenKey,JSON.stringify(e)),e.idToken&&await this.storage.set(this.idTokenKey,e.idToken);}async clearTokens(){await this.storage.remove(this.tokenKey),await this.storage.remove(this.idTokenKey);}async getAccessToken(){let e=await this.getTokens();if(!e)throw new a("no_tokens","No tokens available. Please authenticate first.");if(this.shouldRefresh(e)){if(!e.refreshToken)throw new a("token_expired","Access token expired and no refresh token available");return this.refreshWithLock(e.refreshToken)}return e.accessToken}async getIdToken(){return (await this.getTokens())?.idToken??null}shouldRefresh(e){let t=Math.floor(Date.now()/1e3);return e.expiresAt-this.refreshSkewSeconds<=t}async refreshWithLock(e){if(this.refreshPromise)return (await this.refreshPromise).accessToken;this.refreshPromise=this.doRefreshWithRetry(e);try{return (await this.refreshPromise).accessToken}finally{this.refreshPromise=null;}}async doRefreshWithRetry(e,t=false){try{return await this.doRefresh(e)}catch(n){if(this.isRetryableError(n)&&!t)return this.doRefreshWithRetry(e,true);throw n}}async doRefresh(e){if(!this.discovery)throw new a("no_discovery","Discovery document not set");let t=this.discovery.token_endpoint,n=new URLSearchParams({grant_type:"refresh_token",client_id:this.clientId,refresh_token:e}),r;try{r=await this.http.fetch(t,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:n.toString()});}catch(d){let h=new a("network_error","Token refresh request failed",{cause:d instanceof Error?d:void 0});throw this.eventEmitter?.emit("token:error",{error:h}),h}if(!r.ok){let d=r.data,h=new a("refresh_error","Token refresh failed",{details:{status:r.status,error:d?.error,error_description:d?.error_description}});throw this.eventEmitter?.emit("token:error",{error:h}),h}let i=r.data,o=Math.floor(Date.now()/1e3),c=i.expires_in?o+i.expires_in:o+3600,p={accessToken:i.access_token,tokenType:i.token_type??"Bearer",expiresAt:c,refreshToken:i.refresh_token??e,idToken:i.id_token,scope:i.scope};return await this.saveTokens(p),this.eventEmitter?.emit("token:refreshed",{tokens:p}),p}isRetryableError(e){return e instanceof a?e.code==="network_error":false}async isAuthenticated(){let e=await this.getTokens();if(!e)return false;let t=Math.floor(Date.now()/1e3);return e.expiresAt<=t?!!e.refreshToken:true}async exchangeToken(e){if(!this.discovery)throw new a("no_discovery","Discovery document not set");let t=this.discovery.token_endpoint,n=this.mapTokenTypeToUri(e.subjectTokenType??"access_token"),r=new URLSearchParams({grant_type:"urn:ietf:params:oauth:grant-type:token-exchange",client_id:this.clientId,subject_token:e.subjectToken,subject_token_type:n});e.audience&&r.set("audience",e.audience),e.scope&&r.set("scope",e.scope),e.requestedTokenType&&r.set("requested_token_type",this.mapTokenTypeToUri(e.requestedTokenType)),e.actorToken&&(r.set("actor_token",e.actorToken),e.actorTokenType&&r.set("actor_token_type",this.mapTokenTypeToUri(e.actorTokenType)));let i;try{i=await this.http.fetch(t,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r.toString()});}catch(u){let l=new a("network_error","Token exchange request failed",{cause:u instanceof Error?u:void 0});throw this.eventEmitter?.emit("token:error",{error:l}),l}if(!i.ok){let u=i.data,l=new a("token_exchange_error","Token exchange failed",{details:{status:i.status,error:u?.error,error_description:u?.error_description}});throw this.eventEmitter?.emit("token:error",{error:l}),l}let o=i.data,c=Math.floor(Date.now()/1e3),p=o.expires_in?c+o.expires_in:c+3600,d={accessToken:o.access_token,tokenType:o.token_type??"Bearer",expiresAt:p,refreshToken:o.refresh_token,idToken:o.id_token,scope:o.scope},h={tokens:d,issuedTokenType:o.issued_token_type};return this.eventEmitter?.emit("token:exchanged",{tokens:d,issuedTokenType:o.issued_token_type}),h}mapTokenTypeToUri(e){return L[e]}};H.DEFAULT_REFRESH_SKEW_SECONDS=30;var x=H;var S=class{constructor(e){this.http=e.http,this.clientId=e.clientId;}async introspect(e,t){let n=e.introspection_endpoint;if(!n)throw new a("no_introspection_endpoint","Authorization server does not support token introspection");return this.introspectWithEndpoint(n,t)}async introspectWithEndpoint(e,t){let n=new URLSearchParams({client_id:this.clientId,token:t.token});t.tokenTypeHint&&n.set("token_type_hint",t.tokenTypeHint);let r;try{r=await this.http.fetch(e,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:n.toString()});}catch(i){throw new a("network_error","Token introspection request failed",{cause:i instanceof Error?i:void 0})}if(!r.ok){let i=r.data;throw new a("introspection_error","Token introspection failed",{details:{status:r.status,error:i?.error,error_description:i?.error_description}})}return r.data}async isActive(e,t){return (await this.introspect(e,{token:t})).active}};var m=class{constructor(e){this.http=e.http,this.clientId=e.clientId;}async revoke(e,t){let n=e.revocation_endpoint;if(!n)throw new a("no_revocation_endpoint","Authorization server does not support token revocation");let r=new URLSearchParams({client_id:this.clientId,token:t.token});t.tokenTypeHint&&r.set("token_type_hint",t.tokenTypeHint);let i;try{i=await this.http.fetch(n,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r.toString()});}catch(c){throw new a("network_error","Token revocation request failed",{cause:c instanceof Error?c:void 0})}if(i.ok)return;let o=i.data;throw new a("revocation_error","Token revocation failed",{details:{status:i.status,error:o?.error,error_description:o?.error_description}})}async revokeWithEndpoint(e,t){let n=new URLSearchParams({client_id:this.clientId,token:t.token});t.tokenTypeHint&&n.set("token_type_hint",t.tokenTypeHint);let r;try{r=await this.http.fetch(e,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:n.toString()});}catch(o){throw new a("network_error","Token revocation request failed",{cause:o instanceof Error?o:void 0})}if(r.ok)return;let i=r.data;throw new a("revocation_error","Token revocation failed",{details:{status:r.status,error:i?.error,error_description:i?.error_description}})}};var C=class{constructor(e){this.storage=e.storage,this.clientId=e.clientId,this.issuerHash=e.issuerHash,this.clientIdHash=e.clientIdHash,this.eventEmitter=e.eventEmitter,this.endpoints=e.endpoints,this.tokenRevoker=new m({http:e.http,clientId:e.clientId});}async logout(e,t){let n=await this.getStoredIdToken(),r=await this.getStoredTokens(),i;t?.revokeTokens&&e?.revocation_endpoint&&r&&(i=await this.revokeTokens(e,r)),await this.clearTokens(),this.eventEmitter?.emit("session:ended",{reason:"logout"});let o;if(this.endpoints?.endSession!==void 0?o=this.endpoints.endSession:e&&(o=e.end_session_endpoint),!o)return {localOnly:true,revocation:i};let c=t?.idTokenHint??n,p=new URLSearchParams({client_id:this.clientId});return c&&p.set("id_token_hint",c),t?.postLogoutRedirectUri&&p.set("post_logout_redirect_uri",t.postLogoutRedirectUri),t?.state&&p.set("state",t.state),{logoutUrl:`${o}?${p.toString()}`,localOnly:false,revocation:i}}async revokeTokens(e,t){let n={attempted:true};try{t.refreshToken&&(await this.tokenRevoker.revoke(e,{token:t.refreshToken,tokenTypeHint:"refresh_token"}),n.refreshTokenRevoked=!0),await this.tokenRevoker.revoke(e,{token:t.accessToken,tokenTypeHint:"access_token"}),n.accessTokenRevoked=!0;}catch(r){n.error=r instanceof Error?r:new Error(String(r));}return n}async clearTokens(){let e=f.tokens(this.issuerHash,this.clientIdHash),t=f.idToken(this.issuerHash,this.clientIdHash);await this.storage.remove(e),await this.storage.remove(t);}async getStoredIdToken(){let e=f.idToken(this.issuerHash,this.clientIdHash);return this.storage.get(e)}async getStoredTokens(){let e=f.tokens(this.issuerHash,this.clientIdHash),t=await this.storage.get(e);if(!t)return null;try{return JSON.parse(t)}catch{return null}}};var R=class{constructor(e){this.http=e.http;}async checkSession(e,t){let n=e.userinfo_endpoint;if(!n)return {valid:false,error:new a("no_userinfo_endpoint","UserInfo endpoint not available")};try{let r=await this.http.fetch(n,{method:"GET",headers:{Authorization:`Bearer ${t}`}});return r.ok?{valid:!0,user:r.data}:r.status===401?{valid:!1,error:new a("session_expired","Session has expired")}:{valid:!1,error:new a("session_check_failed","Session check failed",{details:{status:r.status}})}}catch(r){return {valid:false,error:new a("network_error","Failed to check session",{cause:r instanceof Error?r:void 0})}}}async getUserInfo(e,t){let n=e.userinfo_endpoint;if(!n)throw new a("no_userinfo_endpoint","UserInfo endpoint not available");let r;try{r=await this.http.fetch(n,{method:"GET",headers:{Authorization:`Bearer ${t}`}});}catch(i){throw new a("network_error","Failed to fetch user info",{cause:i instanceof Error?i:void 0})}if(!r.ok)throw new a("userinfo_error","Failed to get user info",{details:{status:r.status}});return r.data}};var I=class{constructor(e){this.discovery=null;this.tokenManager=e.tokenManager,this.tokenApiClient=e.tokenApiClient;}setDiscovery(e){this.discovery=e;}async isAuthenticated(){return this.tokenManager.isAuthenticated()}async checkSession(){if(!this.discovery)return {valid:false,error:new a("no_discovery","Discovery document not available")};try{let e=await this.tokenManager.getAccessToken();return this.tokenApiClient.checkSession(this.discovery,e)}catch(e){return e instanceof a?{valid:false,error:e}:{valid:false,error:new a("session_check_failed","Failed to check session",{cause:e instanceof Error?e:void 0})}}}async getUser(){if(!this.discovery)throw new a("no_discovery","Discovery document not available");let e=await this.tokenManager.getAccessToken();return this.tokenApiClient.getUserInfo(this.discovery,e)}};async function B(s,e,t=16){let n=await s.sha256(e);return y(n).slice(0,t)}var P=class{constructor(e){this.initialized=false;this.config=U(e),this.normalizedIssuer=g(e.issuer),this.events=new _,this.discoveryClient=new v({http:this.config.http,cacheTtlMs:this.config.discoveryCacheTtlMs}),this.pkce=new T(this.config.crypto),this.authCodeFlow=new E(this.config.http,this.config.clientId);}async initialize(){if(this.initialized)return;let e=this.config.hashOptions.hashLength;this.issuerHash=await B(this.config.crypto,this.normalizedIssuer,e),this.clientIdHash=await B(this.config.crypto,this.config.clientId,e),this.stateManager=new A(this.config.crypto,this.config.storage,this.issuerHash,this.clientIdHash),this.tokenManager=new x({http:this.config.http,storage:this.config.storage,clientId:this.config.clientId,issuerHash:this.issuerHash,clientIdHash:this.clientIdHash,refreshSkewSeconds:this.config.refreshSkewSeconds,eventEmitter:this.events}),this.logoutHandler=new C({storage:this.config.storage,http:this.config.http,clientId:this.config.clientId,issuerHash:this.issuerHash,clientIdHash:this.clientIdHash,eventEmitter:this.events,endpoints:this.config.endpoints}),this.tokenIntrospector=new S({http:this.config.http,clientId:this.config.clientId}),this.tokenRevoker=new m({http:this.config.http,clientId:this.config.clientId});let t=new R({http:this.config.http});this.sessionManager=new I({tokenManager:this.tokenManager,tokenApiClient:t}),await this.stateManager.cleanupExpiredStates(),this.initialized=true;}ensureInitialized(){if(!this.initialized)throw new a("not_initialized","Client not initialized. Use createAuthrimClient().")}async discover(){let e=await this.discoveryClient.discover(this.normalizedIssuer);return this.tokenManager.setDiscovery(e),this.sessionManager.setDiscovery(e),e}async buildAuthorizationUrl(e){this.ensureInitialized();let t=await this.discover(),n=await this.pkce.generatePKCE(),r=await this.stateManager.generateAuthState({redirectUri:e.redirectUri,codeVerifier:n.codeVerifier,ttlSeconds:this.config.stateTtlSeconds}),i=this.authCodeFlow.buildAuthorizationUrl(t,r,n,e);return this.events.emit("auth:redirecting",{url:i.url}),i}async handleCallback(e){this.ensureInitialized();let{code:t,state:n}=this.authCodeFlow.parseCallback(e);this.events.emit("auth:callback",{code:t,state:n});let r=await this.stateManager.validateAndConsumeState(n),i=await this.discover(),o=await this.authCodeFlow.exchangeCode(i,{code:t,state:n,redirectUri:r.redirectUri,codeVerifier:r.codeVerifier,nonce:r.nonce});return await this.tokenManager.saveTokens(o),o}get token(){return this.ensureInitialized(),{getAccessToken:()=>this.tokenManager.getAccessToken(),getTokens:()=>this.tokenManager.getTokens(),getIdToken:()=>this.tokenManager.getIdToken(),isAuthenticated:()=>this.tokenManager.isAuthenticated(),exchange:async e=>(await this.discover(),this.tokenManager.exchangeToken(e)),introspect:async e=>{let t=await this.discover();return this.tokenIntrospector.introspect(t,e)},revoke:async e=>{let t=await this.discover();return this.tokenRevoker.revoke(t,e)}}}get session(){return this.ensureInitialized(),{isAuthenticated:()=>this.sessionManager.isAuthenticated(),check:()=>this.sessionManager.checkSession()}}async isAuthenticated(){return this.ensureInitialized(),this.tokenManager.isAuthenticated()}async getUser(){return this.ensureInitialized(),this.sessionManager.getUser()}async logout(e){this.ensureInitialized();let t=null;try{t=await this.discover();}catch{}return this.logoutHandler.logout(t,e)}on(e,t){return this.events.on(e,t)}once(e,t){return this.events.once(e,t)}off(e,t){this.events.off(e,t);}};async function W(s){let e=new P(s);return await e.initialize(),e}var $=new Set(["login_required","interaction_required","consent_required","account_selection_required"]),z=class{constructor(e){this.clientId=e;}buildSilentAuthUrl(e,t,n,r){let i=e.authorization_endpoint,o=new URLSearchParams;o.set("client_id",this.clientId),o.set("response_type","code"),o.set("redirect_uri",r.redirectUri),o.set("state",t.state),o.set("nonce",t.nonce),o.set("prompt","none"),o.set("code_challenge",n.codeChallenge),o.set("code_challenge_method",n.codeChallengeMethod);let c=r.scope??"openid";if(o.set("scope",c),r.loginHint&&o.set("login_hint",r.loginHint),r.idTokenHint&&o.set("id_token_hint",r.idTokenHint),r.extraParams){let h=new Set(["client_id","response_type","redirect_uri","state","nonce","code_challenge","code_challenge_method","scope","prompt"]);for(let[u,l]of Object.entries(r.extraParams))h.has(u.toLowerCase())||o.set(u,l);}let d={url:`${i}?${o.toString()}`};return r.exposeState&&(d.state=t.state,d.nonce=t.nonce),d}parseSilentAuthResponse(e){let t;e.includes("?")?t=(e.startsWith("http")?new URL(e):new URL(e,"https://dummy.local")).searchParams:t=new URLSearchParams(e);let n=t.get("error");if(n){let o=t.get("error_description"),c=this.mapSilentAuthError(n);return {success:false,error:new a(c,o??this.getDefaultErrorMessage(n),{details:{error:n,error_description:o,error_uri:t.get("error_uri")}})}}let r=t.get("code"),i=t.get("state");return r?i?{success:true,code:r,state:i}:{success:false,error:new a("missing_state","State parameter not found in silent auth response")}:{success:false,error:new a("missing_code","Authorization code not found in silent auth response")}}isInteractiveLoginRequired(e){return $.has(e.code)}mapSilentAuthError(e){return $.has(e)?e:"oauth_error"}getDefaultErrorMessage(e){switch(e){case "login_required":return "User must log in - no active session found";case "interaction_required":return "User interaction required";case "consent_required":return "User consent required";case "account_selection_required":return "User must select an account";default:return "Silent authentication failed"}}};async function G(s,e){let t=await e.sha256(s),n=t.slice(0,t.length/2);return y(n)}
2
- exports.AuthorizationCodeFlow=E;exports.AuthrimClient=P;exports.AuthrimError=a;exports.DiscoveryClient=v;exports.EventEmitter=_;exports.LogoutHandler=C;exports.PKCEHelper=T;exports.STORAGE_KEYS=f;exports.SessionManager=I;exports.SilentAuthHandler=z;exports.StateManager=A;exports.TOKEN_TYPE_URIS=L;exports.TokenApiClient=R;exports.TokenIntrospector=S;exports.TokenManager=x;exports.TokenRevoker=m;exports.base64urlDecode=N;exports.base64urlEncode=y;exports.base64urlToString=b;exports.calculateDsHash=G;exports.createAuthrimClient=W;exports.decodeIdToken=K;exports.decodeJwt=q;exports.getErrorMeta=j;exports.getIdTokenNonce=M;exports.isJwtExpired=V;exports.normalizeIssuer=g;exports.resolveConfig=U;exports.stringToBase64url=J;//# sourceMappingURL=index.cjs.map
1
+ 'use strict';function U(s){return {...s,scopes:s.scopes??["openid","profile"],flowEngine:s.flowEngine??false,discoveryCacheTtlMs:s.discoveryCacheTtlMs??3600*1e3,refreshSkewSeconds:s.refreshSkewSeconds??30,stateTtlSeconds:s.stateTtlSeconds??600,hashOptions:{hashLength:s.hashOptions?.hashLength??16}}}var a=class s extends Error{constructor(e,t,n){super(t),this.name="AuthrimError",this.code=e,this.details=n?.details,this.errorUri=n?.errorUri,this.cause=n?.cause;}static fromOAuthError(e){let n=["invalid_request","unauthorized_client","access_denied","unsupported_response_type","invalid_scope","server_error","temporarily_unavailable","invalid_grant","invalid_token"].includes(e.error)?e.error:"invalid_request";return new s(n,e.error_description??e.error,{errorUri:e.error_uri,details:{originalError:e.error}})}isRetryable(){return this.code==="network_error"||this.code==="timeout_error"}get meta(){return j(this.code)}},F={invalid_request:{transient:false,retryable:false,userAction:"contact_support",severity:"error"},unauthorized_client:{transient:false,retryable:false,userAction:"contact_support",severity:"fatal"},access_denied:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},unsupported_response_type:{transient:false,retryable:false,userAction:"contact_support",severity:"fatal"},invalid_scope:{transient:false,retryable:false,userAction:"contact_support",severity:"error"},server_error:{transient:true,retryable:true,retryAfterMs:5e3,maxRetries:3,userAction:"retry",severity:"error"},temporarily_unavailable:{transient:true,retryable:true,retryAfterMs:1e4,maxRetries:3,userAction:"retry",severity:"warning"},invalid_grant:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},invalid_token:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},invalid_state:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},expired_state:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},invalid_nonce:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},nonce_mismatch:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},session_expired:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},session_check_failed:{transient:true,retryable:true,retryAfterMs:3e3,maxRetries:2,userAction:"retry",severity:"warning"},network_error:{transient:true,retryable:true,retryAfterMs:2e3,maxRetries:3,userAction:"check_network",severity:"error"},timeout_error:{transient:true,retryable:true,retryAfterMs:3e3,maxRetries:3,userAction:"retry",severity:"warning"},discovery_error:{transient:true,retryable:true,retryAfterMs:5e3,maxRetries:2,userAction:"retry",severity:"error"},discovery_mismatch:{transient:false,retryable:false,userAction:"contact_support",severity:"fatal"},configuration_error:{transient:false,retryable:false,userAction:"contact_support",severity:"fatal"},storage_error:{transient:true,retryable:true,retryAfterMs:1e3,maxRetries:2,userAction:"retry",severity:"error"},flow_engine_error:{transient:false,retryable:false,userAction:"contact_support",severity:"error"},no_tokens:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},token_expired:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},token_error:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},refresh_error:{transient:true,retryable:true,retryAfterMs:2e3,maxRetries:2,userAction:"retry",severity:"error"},token_exchange_error:{transient:true,retryable:true,retryAfterMs:2e3,maxRetries:2,userAction:"retry",severity:"error"},oauth_error:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},missing_code:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},missing_state:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},not_initialized:{transient:false,retryable:false,userAction:"none",severity:"fatal"},no_discovery:{transient:true,retryable:true,retryAfterMs:3e3,maxRetries:2,userAction:"retry",severity:"error"},no_userinfo_endpoint:{transient:false,retryable:false,userAction:"none",severity:"warning"},userinfo_error:{transient:true,retryable:true,retryAfterMs:2e3,maxRetries:2,userAction:"retry",severity:"error"},introspection_error:{transient:true,retryable:true,retryAfterMs:2e3,maxRetries:2,userAction:"retry",severity:"error"},revocation_error:{transient:true,retryable:true,retryAfterMs:2e3,maxRetries:2,userAction:"retry",severity:"error"},no_introspection_endpoint:{transient:false,retryable:false,userAction:"none",severity:"warning"},no_revocation_endpoint:{transient:false,retryable:false,userAction:"none",severity:"warning"},login_required:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},interaction_required:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},consent_required:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},account_selection_required:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},dom_not_ready:{transient:true,retryable:true,retryAfterMs:100,maxRetries:3,userAction:"retry",severity:"error"},state_mismatch:{transient:false,retryable:false,userAction:"reauthenticate",severity:"error"},popup_blocked:{transient:false,retryable:false,userAction:"none",severity:"warning"},popup_closed:{transient:false,retryable:false,userAction:"reauthenticate",severity:"warning"},invalid_response:{transient:false,retryable:false,userAction:"contact_support",severity:"error"}};function j(s){return F[s]}function g(s){return s.replace(/\/+$/,"")}var b=class b{constructor(e){this.cache=new Map;this.http=e.http,this.cacheTtlMs=e.cacheTtlMs??b.DEFAULT_CACHE_TTL_MS;}async discover(e){let t=g(e),n=this.cache.get(t);if(n&&!this.isExpired(n))return n.doc;let r=`${t}/.well-known/openid-configuration`,i;try{let c=await this.http.fetch(r);if(!c.ok)throw new a("discovery_error",`Discovery request failed: ${c.status}`,{details:{status:c.status,statusText:c.statusText}});i=c.data;}catch(c){throw c instanceof a?c:new a("discovery_error","Failed to fetch discovery document",{cause:c instanceof Error?c:void 0,details:{url:r}})}let o=g(i.issuer);if(o!==t)throw new a("discovery_mismatch",`Issuer mismatch in discovery document: expected "${t}", got "${o}"`,{details:{expected:t,actual:o}});return this.cache.set(t,{doc:i,fetchedAt:Date.now()}),i}isExpired(e){return Date.now()-e.fetchedAt>this.cacheTtlMs}clearCache(){this.cache.clear();}clearIssuer(e){this.cache.delete(g(e));}};b.DEFAULT_CACHE_TTL_MS=3600*1e3;var v=b;var _=class{constructor(){this.listeners=new Map;}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{this.off(e,t);}}once(e,t){let n=(r=>{this.off(e,n),t(r);});return this.on(e,n)}off(e,t){let n=this.listeners.get(e);n&&(n.delete(t),n.size===0&&this.listeners.delete(e));}emit(e,t){let n=this.listeners.get(e);if(n)for(let r of n)try{r(t);}catch(i){console.error(`Error in event handler for '${e}':`,i);}}removeAllListeners(e){e?this.listeners.delete(e):this.listeners.clear();}listenerCount(e){return this.listeners.get(e)?.size??0}};var T=class{constructor(e){this.crypto=e;}async generatePKCE(){let e=await this.crypto.generateCodeVerifier(),t=await this.crypto.generateCodeChallenge(e);return {codeVerifier:e,codeChallenge:t,codeChallengeMethod:"S256"}}async generateCodeVerifier(){return this.crypto.generateCodeVerifier()}async generateCodeChallenge(e){return this.crypto.generateCodeChallenge(e)}};function y(s){let e="",t=s.length;for(let n=0;n<t;n+=3){let r=s[n],i=n+1<t?s[n+1]:0,o=n+2<t?s[n+2]:0,c=r<<16|i<<8|o;e+=k[c>>18&63],e+=k[c>>12&63],e+=n+1<t?k[c>>6&63]:"",e+=n+2<t?k[c&63]:"";}return e.replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function N(s){let e=s.replace(/-/g,"+").replace(/_/g,"/");for(;e.length%4;)e+="=";let t=e.length,n=e.endsWith("==")?2:e.endsWith("=")?1:0,r=t*3/4-n,i=new Uint8Array(r),o=0;for(let c=0;c<t;c+=4){let h=A[e.charCodeAt(c)],d=A[e.charCodeAt(c+1)],p=A[e.charCodeAt(c+2)],u=A[e.charCodeAt(c+3)],l=h<<18|d<<12|p<<6|u;o<r&&(i[o++]=l>>16&255),o<r&&(i[o++]=l>>8&255),o<r&&(i[o++]=l&255);}return i}function J(s){let e=new TextEncoder;return y(e.encode(s))}function O(s){return new TextDecoder().decode(N(s))}var k="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",A=new Uint8Array(256);for(let s=0;s<k.length;s++)A[k.charCodeAt(s)]=s;var f={authState:(s,e,t)=>`authrim:${s}:${e}:auth:${t}`,tokens:(s,e)=>`authrim:${s}:${e}:tokens`,idToken:(s,e)=>`authrim:${s}:${e}:id_token`,authStatePrefix:(s,e)=>`authrim:${s}:${e}:auth:`},D=class D{constructor(e,t,n,r){this.crypto=e;this.storage=t;this.issuerHash=n;this.clientIdHash=r;}async generateAuthState(e){let t=e.ttlSeconds??D.DEFAULT_TTL_SECONDS,n=await this.crypto.randomBytes(32),r=await this.crypto.randomBytes(32),i=y(n),o=y(r),c=Date.now(),h={state:i,nonce:o,codeVerifier:e.codeVerifier,redirectUri:e.redirectUri,createdAt:c,expiresAt:c+t*1e3},d=f.authState(this.issuerHash,this.clientIdHash,i);return await this.storage.set(d,JSON.stringify(h)),h}async validateAndConsumeState(e){let t=f.authState(this.issuerHash,this.clientIdHash,e);try{let n=await this.storage.get(t);if(!n)throw new a("invalid_state","State not found or already used");let r;try{r=JSON.parse(n);}catch{throw new a("invalid_state","Malformed state data")}if(Date.now()>r.expiresAt)throw new a("expired_state","State has expired");return r}finally{await this.storage.remove(t);}}async cleanupExpiredStates(){if(!this.storage.getAll)return;let e=f.authStatePrefix(this.issuerHash,this.clientIdHash),t=await this.storage.getAll(),n=Date.now();for(let[r,i]of Object.entries(t))if(r.startsWith(e))try{let o=JSON.parse(i);n>o.expiresAt&&await this.storage.remove(r);}catch{await this.storage.remove(r);}}};D.DEFAULT_TTL_SECONDS=600;var w=D;function q(s){let e=s.split(".");if(e.length!==3)throw new Error("Invalid JWT format: expected 3 parts");let[t,n,r]=e;try{let i=JSON.parse(O(t)),o=JSON.parse(O(n));return {header:i,payload:o,signature:r}}catch{throw new Error("Invalid JWT format: failed to decode")}}function K(s){return q(s).payload}function V(s,e=0){if(s.exp===void 0)return false;let t=Math.floor(Date.now()/1e3);return s.exp+e<t}function M(s){try{return K(s).nonce}catch{return}}var E=class{constructor(e,t){this.http=e;this.clientId=t;}buildAuthorizationUrl(e,t,n,r){let i=e.authorization_endpoint,o=new URLSearchParams;o.set("client_id",this.clientId),o.set("response_type","code"),o.set("redirect_uri",r.redirectUri),o.set("state",t.state),o.set("nonce",t.nonce),o.set("code_challenge",n.codeChallenge),o.set("code_challenge_method",n.codeChallengeMethod);let c=r.scope??"openid profile";if(o.set("scope",c),r.prompt&&o.set("prompt",r.prompt),r.loginHint&&o.set("login_hint",r.loginHint),r.acrValues&&o.set("acr_values",r.acrValues),r.extraParams){let p=new Set(["client_id","response_type","redirect_uri","state","nonce","code_challenge","code_challenge_method","scope"]);for(let[u,l]of Object.entries(r.extraParams))p.has(u.toLowerCase())||o.set(u,l);}let d={url:`${i}?${o.toString()}`};return r.exposeState&&(d.state=t.state,d.nonce=t.nonce),d}parseCallback(e){let t;e.includes("?")?t=(e.startsWith("http")?new URL(e):new URL(e,"https://dummy.local")).searchParams:t=new URLSearchParams(e);let n=t.get("error");if(n){let o=t.get("error_description")??"Authorization failed";throw new a("oauth_error",o,{details:{error:n,error_description:o,error_uri:t.get("error_uri")}})}let r=t.get("code"),i=t.get("state");if(!r)throw new a("missing_code","Authorization code not found in callback");if(!i)throw new a("missing_state","State parameter not found in callback");return {code:r,state:i}}async exchangeCode(e,t){let n=e.token_endpoint,r=new URLSearchParams({grant_type:"authorization_code",client_id:this.clientId,code:t.code,redirect_uri:t.redirectUri,code_verifier:t.codeVerifier}),i;try{i=await this.http.fetch(n,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r.toString()});}catch(p){throw new a("network_error","Token request failed",{cause:p instanceof Error?p:void 0})}if(!i.ok){let p=i.data;throw new a("token_error","Token exchange failed",{details:{status:i.status,error:p?.error,error_description:p?.error_description}})}let o=i.data;if(o.id_token&&M(o.id_token)!==t.nonce)throw new a("nonce_mismatch","ID token nonce does not match expected value");let c=Math.floor(Date.now()/1e3),h=o.expires_in?c+o.expires_in:c+3600;return {accessToken:o.access_token,tokenType:o.token_type??"Bearer",expiresAt:h,refreshToken:o.refresh_token,idToken:o.id_token,scope:o.scope}}};var L={access_token:"urn:ietf:params:oauth:token-type:access_token",refresh_token:"urn:ietf:params:oauth:token-type:refresh_token",id_token:"urn:ietf:params:oauth:token-type:id_token"};var H=class H{constructor(e){this.refreshPromise=null;this.discovery=null;this.http=e.http,this.storage=e.storage,this.clientId=e.clientId,this.issuerHash=e.issuerHash,this.clientIdHash=e.clientIdHash,this.refreshSkewSeconds=e.refreshSkewSeconds??H.DEFAULT_REFRESH_SKEW_SECONDS,this.eventEmitter=e.eventEmitter;}setDiscovery(e){this.discovery=e;}get tokenKey(){return f.tokens(this.issuerHash,this.clientIdHash)}get idTokenKey(){return f.idToken(this.issuerHash,this.clientIdHash)}async getTokens(){let e=await this.storage.get(this.tokenKey);if(!e)return null;try{return JSON.parse(e)}catch{return await this.clearTokens(),null}}async saveTokens(e){await this.storage.set(this.tokenKey,JSON.stringify(e)),e.idToken&&await this.storage.set(this.idTokenKey,e.idToken);}async clearTokens(){await this.storage.remove(this.tokenKey),await this.storage.remove(this.idTokenKey);}async getAccessToken(){let e=await this.getTokens();if(!e)throw new a("no_tokens","No tokens available. Please authenticate first.");if(this.shouldRefresh(e)){if(!e.refreshToken)throw new a("token_expired","Access token expired and no refresh token available");return this.refreshWithLock(e.refreshToken)}return e.accessToken}async getIdToken(){return (await this.getTokens())?.idToken??null}shouldRefresh(e){let t=Math.floor(Date.now()/1e3);return e.expiresAt-this.refreshSkewSeconds<=t}async refreshWithLock(e){if(this.refreshPromise)return (await this.refreshPromise).accessToken;this.refreshPromise=this.doRefreshWithRetry(e);try{return (await this.refreshPromise).accessToken}finally{this.refreshPromise=null;}}async doRefreshWithRetry(e,t=false){try{return await this.doRefresh(e)}catch(n){if(this.isRetryableError(n)&&!t)return this.doRefreshWithRetry(e,true);throw n}}async doRefresh(e){if(!this.discovery)throw new a("no_discovery","Discovery document not set");let t=this.discovery.token_endpoint,n=new URLSearchParams({grant_type:"refresh_token",client_id:this.clientId,refresh_token:e}),r;try{r=await this.http.fetch(t,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:n.toString()});}catch(d){let p=new a("network_error","Token refresh request failed",{cause:d instanceof Error?d:void 0});throw this.eventEmitter?.emit("token:error",{error:p}),p}if(!r.ok){let d=r.data,p=new a("refresh_error","Token refresh failed",{details:{status:r.status,error:d?.error,error_description:d?.error_description}});throw this.eventEmitter?.emit("token:error",{error:p}),p}let i=r.data,o=Math.floor(Date.now()/1e3),c=i.expires_in?o+i.expires_in:o+3600,h={accessToken:i.access_token,tokenType:i.token_type??"Bearer",expiresAt:c,refreshToken:i.refresh_token??e,idToken:i.id_token,scope:i.scope};return await this.saveTokens(h),this.eventEmitter?.emit("token:refreshed",{tokens:h}),h}isRetryableError(e){return e instanceof a?e.code==="network_error":false}async isAuthenticated(){let e=await this.getTokens();if(!e)return false;let t=Math.floor(Date.now()/1e3);return e.expiresAt<=t?!!e.refreshToken:true}async exchangeToken(e){if(!this.discovery)throw new a("no_discovery","Discovery document not set");let t=this.discovery.token_endpoint,n=this.mapTokenTypeToUri(e.subjectTokenType??"access_token"),r=new URLSearchParams({grant_type:"urn:ietf:params:oauth:grant-type:token-exchange",client_id:this.clientId,subject_token:e.subjectToken,subject_token_type:n});e.audience&&r.set("audience",e.audience),e.scope&&r.set("scope",e.scope),e.requestedTokenType&&r.set("requested_token_type",this.mapTokenTypeToUri(e.requestedTokenType)),e.actorToken&&(r.set("actor_token",e.actorToken),e.actorTokenType&&r.set("actor_token_type",this.mapTokenTypeToUri(e.actorTokenType)));let i;try{i=await this.http.fetch(t,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r.toString()});}catch(u){let l=new a("network_error","Token exchange request failed",{cause:u instanceof Error?u:void 0});throw this.eventEmitter?.emit("token:error",{error:l}),l}if(!i.ok){let u=i.data,l=new a("token_exchange_error","Token exchange failed",{details:{status:i.status,error:u?.error,error_description:u?.error_description}});throw this.eventEmitter?.emit("token:error",{error:l}),l}let o=i.data,c=Math.floor(Date.now()/1e3),h=o.expires_in?c+o.expires_in:c+3600,d={accessToken:o.access_token,tokenType:o.token_type??"Bearer",expiresAt:h,refreshToken:o.refresh_token,idToken:o.id_token,scope:o.scope},p={tokens:d,issuedTokenType:o.issued_token_type};return this.eventEmitter?.emit("token:exchanged",{tokens:d,issuedTokenType:o.issued_token_type}),p}mapTokenTypeToUri(e){return L[e]}};H.DEFAULT_REFRESH_SKEW_SECONDS=30;var x=H;var S=class{constructor(e){this.http=e.http,this.clientId=e.clientId;}async introspect(e,t){let n=e.introspection_endpoint;if(!n)throw new a("no_introspection_endpoint","Authorization server does not support token introspection");return this.introspectWithEndpoint(n,t)}async introspectWithEndpoint(e,t){let n=new URLSearchParams({client_id:this.clientId,token:t.token});t.tokenTypeHint&&n.set("token_type_hint",t.tokenTypeHint);let r;try{r=await this.http.fetch(e,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:n.toString()});}catch(i){throw new a("network_error","Token introspection request failed",{cause:i instanceof Error?i:void 0})}if(!r.ok){let i=r.data;throw new a("introspection_error","Token introspection failed",{details:{status:r.status,error:i?.error,error_description:i?.error_description}})}return r.data}async isActive(e,t){return (await this.introspect(e,{token:t})).active}};var m=class{constructor(e){this.http=e.http,this.clientId=e.clientId;}async revoke(e,t){let n=e.revocation_endpoint;if(!n)throw new a("no_revocation_endpoint","Authorization server does not support token revocation");let r=new URLSearchParams({client_id:this.clientId,token:t.token});t.tokenTypeHint&&r.set("token_type_hint",t.tokenTypeHint);let i;try{i=await this.http.fetch(n,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r.toString()});}catch(c){throw new a("network_error","Token revocation request failed",{cause:c instanceof Error?c:void 0})}if(i.ok)return;let o=i.data;throw new a("revocation_error","Token revocation failed",{details:{status:i.status,error:o?.error,error_description:o?.error_description}})}async revokeWithEndpoint(e,t){let n=new URLSearchParams({client_id:this.clientId,token:t.token});t.tokenTypeHint&&n.set("token_type_hint",t.tokenTypeHint);let r;try{r=await this.http.fetch(e,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:n.toString()});}catch(o){throw new a("network_error","Token revocation request failed",{cause:o instanceof Error?o:void 0})}if(r.ok)return;let i=r.data;throw new a("revocation_error","Token revocation failed",{details:{status:r.status,error:i?.error,error_description:i?.error_description}})}};var C=class{constructor(e){this.storage=e.storage,this.clientId=e.clientId,this.issuerHash=e.issuerHash,this.clientIdHash=e.clientIdHash,this.eventEmitter=e.eventEmitter,this.endpoints=e.endpoints,this.tokenRevoker=new m({http:e.http,clientId:e.clientId});}async logout(e,t){let n=await this.getStoredIdToken(),r=await this.getStoredTokens(),i;t?.revokeTokens&&e?.revocation_endpoint&&r&&(i=await this.revokeTokens(e,r)),await this.clearTokens(),this.eventEmitter?.emit("session:ended",{reason:"logout"});let o;if(this.endpoints?.endSession!==void 0?o=this.endpoints.endSession:e&&(o=e.end_session_endpoint),!o)return {localOnly:true,revocation:i};let c=t?.idTokenHint??n,h=new URLSearchParams({client_id:this.clientId});return c&&h.set("id_token_hint",c),t?.postLogoutRedirectUri&&h.set("post_logout_redirect_uri",t.postLogoutRedirectUri),t?.state&&h.set("state",t.state),{logoutUrl:`${o}?${h.toString()}`,localOnly:false,revocation:i}}async revokeTokens(e,t){let n={attempted:true};try{t.refreshToken&&(await this.tokenRevoker.revoke(e,{token:t.refreshToken,tokenTypeHint:"refresh_token"}),n.refreshTokenRevoked=!0),await this.tokenRevoker.revoke(e,{token:t.accessToken,tokenTypeHint:"access_token"}),n.accessTokenRevoked=!0;}catch(r){n.error=r instanceof Error?r:new Error(String(r));}return n}async clearTokens(){let e=f.tokens(this.issuerHash,this.clientIdHash),t=f.idToken(this.issuerHash,this.clientIdHash);await this.storage.remove(e),await this.storage.remove(t);}async getStoredIdToken(){let e=f.idToken(this.issuerHash,this.clientIdHash);return this.storage.get(e)}async getStoredTokens(){let e=f.tokens(this.issuerHash,this.clientIdHash),t=await this.storage.get(e);if(!t)return null;try{return JSON.parse(t)}catch{return null}}};var R=class{constructor(e){this.http=e.http;}async checkSession(e,t){let n=e.userinfo_endpoint;if(!n)return {valid:false,error:new a("no_userinfo_endpoint","UserInfo endpoint not available")};try{let r=await this.http.fetch(n,{method:"GET",headers:{Authorization:`Bearer ${t}`}});return r.ok?{valid:!0,user:r.data}:r.status===401?{valid:!1,error:new a("session_expired","Session has expired")}:{valid:!1,error:new a("session_check_failed","Session check failed",{details:{status:r.status}})}}catch(r){return {valid:false,error:new a("network_error","Failed to check session",{cause:r instanceof Error?r:void 0})}}}async getUserInfo(e,t){let n=e.userinfo_endpoint;if(!n)throw new a("no_userinfo_endpoint","UserInfo endpoint not available");let r;try{r=await this.http.fetch(n,{method:"GET",headers:{Authorization:`Bearer ${t}`}});}catch(i){throw new a("network_error","Failed to fetch user info",{cause:i instanceof Error?i:void 0})}if(!r.ok)throw new a("userinfo_error","Failed to get user info",{details:{status:r.status}});return r.data}};var I=class{constructor(e){this.discovery=null;this.tokenManager=e.tokenManager,this.tokenApiClient=e.tokenApiClient;}setDiscovery(e){this.discovery=e;}async isAuthenticated(){return this.tokenManager.isAuthenticated()}async checkSession(){if(!this.discovery)return {valid:false,error:new a("no_discovery","Discovery document not available")};try{let e=await this.tokenManager.getAccessToken();return this.tokenApiClient.checkSession(this.discovery,e)}catch(e){return e instanceof a?{valid:false,error:e}:{valid:false,error:new a("session_check_failed","Failed to check session",{cause:e instanceof Error?e:void 0})}}}async getUser(){if(!this.discovery)throw new a("no_discovery","Discovery document not available");let e=await this.tokenManager.getAccessToken();return this.tokenApiClient.getUserInfo(this.discovery,e)}};async function B(s,e,t=16){let n=await s.sha256(e);return y(n).slice(0,t)}var P=class{constructor(e){this.initialized=false;this.config=U(e),this.normalizedIssuer=g(e.issuer),this.events=new _,this.discoveryClient=new v({http:this.config.http,cacheTtlMs:this.config.discoveryCacheTtlMs}),this.pkce=new T(this.config.crypto),this.authCodeFlow=new E(this.config.http,this.config.clientId);}async initialize(){if(this.initialized)return;let e=this.config.hashOptions.hashLength;this.issuerHash=await B(this.config.crypto,this.normalizedIssuer,e),this.clientIdHash=await B(this.config.crypto,this.config.clientId,e),this.stateManager=new w(this.config.crypto,this.config.storage,this.issuerHash,this.clientIdHash),this.tokenManager=new x({http:this.config.http,storage:this.config.storage,clientId:this.config.clientId,issuerHash:this.issuerHash,clientIdHash:this.clientIdHash,refreshSkewSeconds:this.config.refreshSkewSeconds,eventEmitter:this.events}),this.logoutHandler=new C({storage:this.config.storage,http:this.config.http,clientId:this.config.clientId,issuerHash:this.issuerHash,clientIdHash:this.clientIdHash,eventEmitter:this.events,endpoints:this.config.endpoints}),this.tokenIntrospector=new S({http:this.config.http,clientId:this.config.clientId}),this.tokenRevoker=new m({http:this.config.http,clientId:this.config.clientId});let t=new R({http:this.config.http});this.sessionManager=new I({tokenManager:this.tokenManager,tokenApiClient:t}),await this.stateManager.cleanupExpiredStates(),this.initialized=true;}ensureInitialized(){if(!this.initialized)throw new a("not_initialized","Client not initialized. Use createAuthrimClient().")}async discover(){let e=await this.discoveryClient.discover(this.normalizedIssuer);return this.tokenManager.setDiscovery(e),this.sessionManager.setDiscovery(e),e}async buildAuthorizationUrl(e){this.ensureInitialized();let t=await this.discover(),n=await this.pkce.generatePKCE(),r=await this.stateManager.generateAuthState({redirectUri:e.redirectUri,codeVerifier:n.codeVerifier,ttlSeconds:this.config.stateTtlSeconds}),i=this.authCodeFlow.buildAuthorizationUrl(t,r,n,e);return this.events.emit("auth:redirecting",{url:i.url}),i}async handleCallback(e){this.ensureInitialized();let{code:t,state:n}=this.authCodeFlow.parseCallback(e);this.events.emit("auth:callback",{code:t,state:n});let r=await this.stateManager.validateAndConsumeState(n),i=await this.discover(),o=await this.authCodeFlow.exchangeCode(i,{code:t,state:n,redirectUri:r.redirectUri,codeVerifier:r.codeVerifier,nonce:r.nonce});return await this.tokenManager.saveTokens(o),o}get token(){return this.ensureInitialized(),{getAccessToken:()=>this.tokenManager.getAccessToken(),getTokens:()=>this.tokenManager.getTokens(),getIdToken:()=>this.tokenManager.getIdToken(),isAuthenticated:()=>this.tokenManager.isAuthenticated(),exchange:async e=>(await this.discover(),this.tokenManager.exchangeToken(e)),introspect:async e=>{let t=await this.discover();return this.tokenIntrospector.introspect(t,e)},revoke:async e=>{let t=await this.discover();return this.tokenRevoker.revoke(t,e)}}}get session(){return this.ensureInitialized(),{isAuthenticated:()=>this.sessionManager.isAuthenticated(),check:()=>this.sessionManager.checkSession()}}async isAuthenticated(){return this.ensureInitialized(),this.tokenManager.isAuthenticated()}async getUser(){return this.ensureInitialized(),this.sessionManager.getUser()}async logout(e){this.ensureInitialized();let t=null;try{t=await this.discover();}catch{}return this.logoutHandler.logout(t,e)}on(e,t){return this.events.on(e,t)}once(e,t){return this.events.once(e,t)}off(e,t){this.events.off(e,t);}};async function W(s){let e=new P(s);return await e.initialize(),e}var $=new Set(["login_required","interaction_required","consent_required","account_selection_required"]),z=class{constructor(e){this.clientId=e;}buildSilentAuthUrl(e,t,n,r){let i=e.authorization_endpoint,o=new URLSearchParams;o.set("client_id",this.clientId),o.set("response_type","code"),o.set("redirect_uri",r.redirectUri),o.set("state",t.state),o.set("nonce",t.nonce),o.set("prompt","none"),o.set("code_challenge",n.codeChallenge),o.set("code_challenge_method",n.codeChallengeMethod);let c=r.scope??"openid";if(o.set("scope",c),r.loginHint&&o.set("login_hint",r.loginHint),r.idTokenHint&&o.set("id_token_hint",r.idTokenHint),r.extraParams){let p=new Set(["client_id","response_type","redirect_uri","state","nonce","code_challenge","code_challenge_method","scope","prompt"]);for(let[u,l]of Object.entries(r.extraParams))p.has(u.toLowerCase())||o.set(u,l);}let d={url:`${i}?${o.toString()}`};return r.exposeState&&(d.state=t.state,d.nonce=t.nonce),d}parseSilentAuthResponse(e){let t;e.includes("?")?t=(e.startsWith("http")?new URL(e):new URL(e,"https://dummy.local")).searchParams:t=new URLSearchParams(e);let n=t.get("error");if(n){let o=t.get("error_description"),c=this.mapSilentAuthError(n);return {success:false,error:new a(c,o??this.getDefaultErrorMessage(n),{details:{error:n,error_description:o,error_uri:t.get("error_uri")}})}}let r=t.get("code"),i=t.get("state");return r?i?{success:true,code:r,state:i}:{success:false,error:new a("missing_state","State parameter not found in silent auth response")}:{success:false,error:new a("missing_code","Authorization code not found in silent auth response")}}isInteractiveLoginRequired(e){return $.has(e.code)}mapSilentAuthError(e){return $.has(e)?e:"oauth_error"}getDefaultErrorMessage(e){switch(e){case "login_required":return "User must log in - no active session found";case "interaction_required":return "User interaction required";case "consent_required":return "User consent required";case "account_selection_required":return "User must select an account";default:return "Silent authentication failed"}}};async function G(s,e){let t=await e.sha256(s),n=t.slice(0,t.length/2);return y(n)}
2
+ exports.AuthorizationCodeFlow=E;exports.AuthrimClient=P;exports.AuthrimError=a;exports.DiscoveryClient=v;exports.EventEmitter=_;exports.LogoutHandler=C;exports.PKCEHelper=T;exports.STORAGE_KEYS=f;exports.SessionManager=I;exports.SilentAuthHandler=z;exports.StateManager=w;exports.TOKEN_TYPE_URIS=L;exports.TokenApiClient=R;exports.TokenIntrospector=S;exports.TokenManager=x;exports.TokenRevoker=m;exports.base64urlDecode=N;exports.base64urlEncode=y;exports.base64urlToString=O;exports.calculateDsHash=G;exports.createAuthrimClient=W;exports.decodeIdToken=K;exports.decodeJwt=q;exports.getErrorMeta=j;exports.getIdTokenNonce=M;exports.isJwtExpired=V;exports.normalizeIssuer=g;exports.resolveConfig=U;exports.stringToBase64url=J;//# sourceMappingURL=index.cjs.map
3
3
  //# sourceMappingURL=index.cjs.map