@tktchurch/auth 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +48 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/server.cjs +1 -0
- package/dist/server.d.cts +289 -0
- package/dist/server.d.ts +289 -0
- package/dist/server.js +1 -0
- package/dist/storage/memory.d.cts +12 -1
- package/dist/storage/memory.d.ts +12 -1
- package/dist/{memory-Dqbnhmkh.d.cts → types-1sQaP6TE.d.cts} +25 -10
- package/dist/{memory-Dqbnhmkh.d.ts → types-1sQaP6TE.d.ts} +25 -10
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var s=class extends Error{code;status;details;constructor(t,n,r){super(n),this.name="AuthError",this.code=t,this.status=r?.status,this.details=r?.details}},T=class extends s{mfaMethods;constructor(t,n,r,o){super("mfa_required",t,{status:r,details:o}),this.name="MFARequiredAuthError",this.mfaMethods=n}},P=class extends s{constructor(t,n){super("contract_error",t,{details:n}),this.name="AuthContractError"}};function xe(e){switch(e){case"mfa_required":case"invalid_request":case"invalid_client":case"invalid_grant":case"unauthorized_client":case"unsupported_grant_type":case"invalid_scope":case"access_denied":case"unsupported_response_type":case"server_error":case"temporarily_unavailable":case"invalid_token":case"insufficient_scope":case"authentication_required":case"insufficient_user_authentication":return e;default:return"unknown_error"}}function De(e,t){if(!t)return new s("http_error",`Request failed with status ${e}`,{status:e});let n=xe(t.error),r=t.error_description??t.reason??(t.error?`Auth error: ${t.error}`:`Request failed with status ${e}`);if(n==="mfa_required"){let o=Array.isArray(t.mfa_methods)?t.mfa_methods.filter(i=>typeof i=="string"):[];return new T(r,o,e,t)}return new s(n,r,{status:e,details:t})}async function ie(e){let t,n=await e.text();if(n)try{t=JSON.parse(n)}catch{t={reason:n}}return De(e.status,t)}function z(e){return e instanceof s}function je(e){return z(e)?e.code==="invalid_client"||e.code==="invalid_grant"||e.code==="unauthorized_client"||e.code==="invalid_token":!1}function H(e){if(!Array.isArray(e))return;let t=e.filter(n=>typeof n=="object"&&n!==null&&typeof n.type=="string");return t.length>0?t:void 0}async function m(e){let t=await e.getTokens();if(!t?.accessToken)throw new s("invalid_token","No access token is available. Authenticate first.");return t.accessToken}function y(e){return Object.fromEntries(Object.entries(e).filter(([,t])=>typeof t<"u"))}function b(e,t){let n=e.access_token,r=e.token_type,o=e.expires_in;if(typeof n!="string"||typeof r!="string"||typeof o!="number")throw new s("unknown_error","Unexpected token response payload.",{details:e});let i={accessToken:n,tokenType:r,expiresIn:o},a=typeof e.refresh_token=="string"?e.refresh_token:t;a&&(i.refreshToken=a),typeof e.scope=="string"&&(i.scope=e.scope),typeof e.id_token=="string"&&(i.idToken=e.id_token);let u=H(e.authorization_details);return u&&(i.authorizationDetails=u),i}function Me(e){return{id:String(e.id??""),deviceName:typeof e.device_name=="string"?e.device_name:void 0,deviceType:String(e.device_type??""),browser:typeof e.browser=="string"?e.browser:void 0,os:typeof e.os=="string"?e.os:void 0,ipAddress:String(e.ip_address??""),country:typeof e.country=="string"?e.country:void 0,city:typeof e.city=="string"?e.city:void 0,isActive:!!e.is_active,isCurrent:!!e.is_current,isTrusted:!!e.is_trusted,lastUsedAt:typeof e.last_used_at=="string"?e.last_used_at:void 0,createdAt:typeof e.created_at=="string"?e.created_at:void 0}}function V(e){return{id:String(e.id??""),deviceId:typeof e.device_id=="string"?e.device_id:void 0,deviceName:typeof e.device_name=="string"?e.device_name:void 0,deviceType:String(e.device_type??""),userAgent:typeof e.user_agent=="string"?e.user_agent:void 0,browser:typeof e.browser=="string"?e.browser:void 0,browserVersion:typeof e.browser_version=="string"?e.browser_version:void 0,os:typeof e.os=="string"?e.os:void 0,osVersion:typeof e.os_version=="string"?e.os_version:void 0,ipAddress:String(e.ip_address??""),country:typeof e.country=="string"?e.country:void 0,city:typeof e.city=="string"?e.city:void 0,isActive:!!e.is_active,isRevoked:!!e.is_revoked,isCurrent:!!e.is_current,isTrusted:!!e.is_trusted,requiresMfa:!!e.requires_mfa,accessTokenCount:typeof e.access_token_count=="number"?e.access_token_count:0,lastUsedAt:typeof e.last_used_at=="string"?e.last_used_at:void 0,expiresAt:typeof e.expires_at=="string"?e.expires_at:void 0,createdAt:typeof e.created_at=="string"?e.created_at:void 0,revokedAt:typeof e.revoked_at=="string"?e.revoked_at:void 0,revocationReason:typeof e.revocation_reason=="string"?e.revocation_reason:void 0}}function Q(e){return{success:!!e.success,sessionsRevoked:typeof e.sessions_revoked=="number"?e.sessions_revoked:0,tokensRevoked:typeof e.tokens_revoked=="number"?e.tokens_revoked:0}}function $e(e){let t=Array.isArray(e.items)?e.items:[],r=e.metadata&&typeof e.metadata=="object"?e.metadata:{};return{items:t.filter(o=>typeof o=="object"&&o!==null).map(Me),metadata:{page:typeof r.page=="number"?r.page:1,per:typeof r.per=="number"?r.per:t.length,total:typeof r.total=="number"?r.total:t.length}}}function se(e){return{async list(t){let n=await m(e),r=new URLSearchParams;typeof t?.page=="number"&&r.set("page",String(t.page)),typeof t?.perPage=="number"&&r.set("per_page",String(t.perPage)),t?.userId&&r.set("user_id",t.userId);let o=`/api/v1/sessions${r.size?`?${r.toString()}`:""}`,i=await e.http.request(o,{token:n});return $e(i)},async current(){let t=await m(e),n=await e.http.request("/api/v1/sessions/current",{token:t});return V(n)},async get(t){let n=await m(e),r=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{token:n});return V(r)},async update(t,n){let r=await m(e),o=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{method:"PATCH",token:r,body:y({device_name:n.deviceName,is_trusted:n.isTrusted})});return V(o)},async revoke(t,n){let r=await m(e),o=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{method:"DELETE",token:r,body:y({reason:n?.reason})});return Q(o)},async revokeAll(){let t=await m(e),n=await e.http.request("/api/v1/sessions/revoke-all",{method:"POST",token:t});return Q(n)},async revokeAllDevices(){let t=await m(e),n=await e.http.request("/api/v1/sessions/revoke-all-devices",{method:"POST",token:t});return Q(n)}}}function ae(){let e=globalThis.crypto;if(!e?.getRandomValues)throw new s("invalid_request","Web Crypto API is unavailable in this runtime. PKCE requires globalThis.crypto.");return e}function ue(e){let t="";for(let r of e)t+=String.fromCharCode(r);if(typeof btoa!="function")throw new s("server_error","base64UrlEncode requires globalThis.btoa (Web/Deno runtime).");return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function D(e=32){if(!Number.isInteger(e)||e<=0)throw new s("invalid_request","randomUrlSafeString length must be a positive integer.");let t=new Uint8Array(e);return ae().getRandomValues(t),ue(t)}function j(){return D(32)}function M(){return D(32)}function de(){return D(32)}async function ce(e){let t=ae();if(!t.subtle?.digest)throw new s("invalid_request","SubtleCrypto.digest is unavailable; cannot derive an S256 code challenge.");let n=new TextEncoder().encode(e),r=await t.subtle.digest("SHA-256",n);return ue(new Uint8Array(r))}async function $(e="S256"){let t=de();if(e==="plain")return{codeVerifier:t,codeChallenge:t,codeChallengeMethod:"plain"};let n=await ce(t);return{codeVerifier:t,codeChallenge:n,codeChallengeMethod:"S256"}}function W(e,t){let n=new TextEncoder().encode(e),r=new TextEncoder().encode(t),o=Math.max(n.length,r.length),i=n.length^r.length;for(let a=0;a<o;a+=1)i|=(n[a]??0)^(r[a]??0);return i===0}function Ne(e){return{active:!!e.active,scope:typeof e.scope=="string"?e.scope:void 0,clientId:typeof e.client_id=="string"?e.client_id:void 0,username:typeof e.username=="string"?e.username:void 0,tokenType:typeof e.token_type=="string"?e.token_type:void 0,exp:typeof e.exp=="number"?e.exp:void 0,iat:typeof e.iat=="number"?e.iat:void 0,sub:typeof e.sub=="string"?e.sub:void 0,authorizationDetails:H(e.authorization_details)}}function N(e){return{async password(t){let n=y({grant_type:"password",client_id:e.config.clientId,client_secret:e.config.clientSecret,username:t.username,email:t.email,password:t.password,scope:t.scope,mfa_code:t.mfaCode,mfa_backup_code:t.mfaBackupCode}),r=await e.http.request("/oauth/token",{method:"POST",body:n}),o=b(r);return await e.setTokens(o),o},async refresh(t){let n=await e.getTokens(),r=t?.refreshToken??n?.refreshToken;if(!r)throw new s("invalid_request","No refresh token is available.");let o=y({grant_type:"refresh_token",client_id:e.config.clientId,client_secret:e.config.clientSecret,refresh_token:r,scope:t?.scope}),i=await e.http.request("/oauth/token",{method:"POST",body:o}),a=b(i,r);return await e.setTokens(a),a},async clientCredentials(t){let n=y({grant_type:"client_credentials",client_id:e.config.clientId,client_secret:e.config.clientSecret,scope:t?.scope}),r=await e.http.request("/oauth/token",{method:"POST",body:n}),o=b(r);return await e.setTokens(o),o},async authorizationCode(t){let n=y({grant_type:"authorization_code",client_id:e.config.clientId,client_secret:e.config.clientSecret,code:t.code,redirect_uri:t.redirectUri,code_verifier:t.codeVerifier,scope:t.scope}),r=await e.http.request("/oauth/token",{method:"POST",body:n}),o=b(r);return await e.setTokens(o),o},async revoke(t){let n=await e.getTokens(),r=t?.token??n?.accessToken;if(!r)throw new s("invalid_request","No token provided for revocation.");await e.http.request("/oauth/revoke",{method:"POST",body:y({client_id:e.config.clientId,client_secret:e.config.clientSecret,token:r,token_type_hint:t?.tokenTypeHint})}),n&&(r===n.accessToken||r===n.refreshToken)&&await e.clearTokens()},async introspect(t){let n=await e.http.request("/oauth/introspect",{method:"POST",body:y({client_id:e.config.clientId,client_secret:e.config.clientSecret,token:t.token})});return Ne(n)},async tokenExchange(t){let n=y({grant_type:"urn:ietf:params:oauth:grant-type:token-exchange",client_id:e.config.clientId,client_secret:e.config.clientSecret,subject_token:t.subjectToken,subject_token_type:t.subjectTokenType??"urn:ietf:params:oauth:token-type:access_token",requested_subject:t.requestedSubject,scope:t.scope,audience:t.audience}),r=await e.http.request("/oauth/token",{method:"POST",body:n}),o=b(r);return await e.setTokens(o),o}}}function pe(e,t){let n=e.trim();if(!n)throw new s("invalid_request",`${t} is required.`);return n}async function g(e,t,n){let r=await m(e),o=n?.query,i=o&&Object.keys(o).length>0?`?${new URLSearchParams(Object.entries(o).filter(([,a])=>typeof a<"u").map(([a,u])=>[a,String(u)])).toString()}`:"";return e.http.request(`${t}${i}`,{method:n?.method,body:n?.body,token:r})}async function v(e,t,n){await g(e,t,n)}function A(e,t){return encodeURIComponent(pe(e,t))}function Fe(e){let t=Array.isArray(e.mfa_methods)?e.mfa_methods.filter(n=>typeof n=="string"):void 0;return{redirectUri:String(e.redirect_uri??""),code:typeof e.code=="string"?e.code:void 0,state:typeof e.state=="string"?e.state:void 0,error:typeof e.error=="string"?e.error:void 0,errorDescription:typeof e.error_description=="string"?e.error_description:void 0,mfaMethods:t,accessToken:typeof e.access_token=="string"?e.access_token:void 0,tokenType:typeof e.token_type=="string"?e.token_type:void 0,expiresIn:typeof e.expires_in=="number"?e.expires_in:void 0,scope:typeof e.scope=="string"?e.scope:void 0,idToken:typeof e.id_token=="string"?e.id_token:void 0}}function Le(e){let t=Array.isArray(e.redirect_uris)?e.redirect_uris.filter(n=>typeof n=="string"&&n.length>0):[];return{clientId:String(e.client_id??""),name:String(e.name??""),description:typeof e.description=="string"?e.description:void 0,isFirstParty:!!e.is_first_party,redirectUris:t}}function Be(e){let t=Array.isArray(e.mfa_methods)?e.mfa_methods.filter(r=>typeof r=="string"):void 0,n=Array.isArray(e.uncovered_scopes)?e.uncovered_scopes.filter(r=>typeof r=="string"):void 0;return{consentSatisfied:!!e.consent_satisfied,requiresMfa:!!e.requires_mfa,mfaMethods:t,uncoveredScopes:n,error:typeof e.error=="string"?e.error:void 0,errorDescription:typeof e.error_description=="string"?e.error_description:void 0,code:typeof e.code=="string"?e.code:void 0,redirectUri:typeof e.redirect_uri=="string"?e.redirect_uri:void 0,state:typeof e.state=="string"?e.state:void 0}}function fe(e){if(!e.password&&!e.accessToken)throw new s("invalid_request","Either password or accessToken is required for consent.")}function le(e,t){let n=new URL(e),r=new URL(t);if(n.origin!==r.origin||n.pathname!=="/oauth/authorize/consent")throw new s("invalid_request","callbackUrl must target /oauth/authorize/consent on the configured auth server.");return n}function me(e){async function t(r,o){fe(o);let i=le(r,e.config.baseUrl),a={username:o.username,approved:o.approved??!0,requested_scopes:o.requestedScopes,mfa_code:o.mfaCode,mfa_backup_code:o.mfaBackupCode};o.password!==void 0&&(a.password=o.password);let u=await e.http.request(`${i.pathname}${i.search}`,{method:"POST",token:o.accessToken,body:a}),p=Fe(u);if(p.error==="mfa_required")throw new T(p.errorDescription??"Multi-factor authentication is required",p.mfaMethods??[],void 0,u);return p}async function n(r,o){fe(o);let i=le(r,e.config.baseUrl),a={username:o.username,requested_scopes:o.requestedScopes,mfa_code:o.mfaCode,mfa_backup_code:o.mfaBackupCode};o.password!==void 0&&(a.password=o.password);let u=await e.http.request(`${i.pathname}/evaluate${i.search}`,{method:"POST",token:o.accessToken,body:a}),p=Be(u);if(p.error==="mfa_required"||p.requiresMfa)throw new T(p.errorDescription??"Multi-factor authentication is required",p.mfaMethods??[],void 0,u);return p}return{consent:t,evaluateConsent:n,consentWithCredentials(r){return t(r.callbackUrl,{username:r.username,password:r.password,accessToken:r.accessToken,approved:r.approved,requestedScopes:r.requestedScopes,mfaCode:r.mfaCode,mfaBackupCode:r.mfaBackupCode})},evaluateConsentWithCredentials(r){return n(r.callbackUrl,{username:r.username,password:r.password,accessToken:r.accessToken,requestedScopes:r.requestedScopes,mfaCode:r.mfaCode,mfaBackupCode:r.mfaBackupCode})}}}function he(e){return{async publicClient(t){let n=await e.http.request(`/oauth/clients/${encodeURIComponent(t)}/public`);return Le(n)},async discoverAuthorizationServer(){let t=await e.http.request("/.well-known/oauth-authorization-server");return{issuer:String(t.issuer??""),authorizationEndpoint:String(t.authorization_endpoint??""),tokenEndpoint:String(t.token_endpoint??""),jwksUri:typeof t.jwks_uri=="string"?t.jwks_uri:void 0,revocationEndpoint:typeof t.revocation_endpoint=="string"?t.revocation_endpoint:void 0,introspectionEndpoint:typeof t.introspection_endpoint=="string"?t.introspection_endpoint:void 0,registrationEndpoint:typeof t.registration_endpoint=="string"?t.registration_endpoint:void 0,scopesSupported:Array.isArray(t.scopes_supported)?t.scopes_supported.filter(n=>typeof n=="string"):void 0,responseTypesSupported:Array.isArray(t.response_types_supported)?t.response_types_supported.filter(n=>typeof n=="string"):[],grantTypesSupported:Array.isArray(t.grant_types_supported)?t.grant_types_supported.filter(n=>typeof n=="string"):void 0,codeChallengeMethodsSupported:Array.isArray(t.code_challenge_methods_supported)?t.code_challenge_methods_supported.filter(n=>typeof n=="string"):void 0,pushedAuthorizationRequestEndpoint:typeof t.pushed_authorization_request_endpoint=="string"?t.pushed_authorization_request_endpoint:void 0,requirePushedAuthorizationRequests:typeof t.require_pushed_authorization_requests=="boolean"?t.require_pushed_authorization_requests:void 0,authorizationDetailsTypesSupported:Array.isArray(t.authorization_details_types_supported)?t.authorization_details_types_supported.filter(n=>typeof n=="string"):void 0,raw:t}}}}var G="openid profile email offline_access",ge=new Set(["response_type","client_id","redirect_uri","scope","state","nonce","code_challenge","code_challenge_method","prompt","max_age","acr_values","login_hint","authorization_details","request_uri"]);function Je(e){if(typeof e=="string"){let t=e.indexOf("?"),n=t>=0?e.slice(t+1):e;return new URLSearchParams(n)}return e instanceof URLSearchParams?e:"raw"in e&&typeof e.raw=="object"?new URLSearchParams(e.raw):new URLSearchParams(e)}function ye(e){let t=Je(e),n={};return t.forEach((r,o)=>{n[o]=r}),{code:t.get("code")??void 0,state:t.get("state")??void 0,error:t.get("error")??void 0,errorDescription:t.get("error_description")??void 0,errorUri:t.get("error_uri")??void 0,raw:n}}function _e(e){let t=N(e);return{...me(e),async createRequest(r){if(!r.redirectUri?.trim())throw new s("invalid_request","redirectUri is required to build an authorization request.");let o=r.scope??G,i=r.state??j(),a=r.nonce??M(),u=await $(r.codeChallengeMethod??"S256"),p=r.authorizationEndpoint??`${e.config.baseUrl}/oauth/authorize`,R=new URL(p),l=R.searchParams;if(l.set("response_type",r.responseType??"code"),l.set("client_id",e.config.clientId),l.set("redirect_uri",r.redirectUri),l.set("scope",o),l.set("state",i),l.set("nonce",a),l.set("code_challenge",u.codeChallenge),l.set("code_challenge_method",u.codeChallengeMethod),r.authorizationDetails&&r.authorizationDetails.length>0&&l.set("authorization_details",JSON.stringify(r.authorizationDetails)),r.prompt&&l.set("prompt",r.prompt),r.maxAge!=null){if(!Number.isSafeInteger(r.maxAge)||r.maxAge<0)throw new s("invalid_request","maxAge must be a non-negative integer.");l.set("max_age",String(r.maxAge))}r.acrValues?.length&&l.set("acr_values",r.acrValues.join(" ")),r.loginHint&&l.set("login_hint",r.loginHint);for(let[w,I]of Object.entries(r.extraParams??{})){if(ge.has(w))throw new s("invalid_request",`extraParams may not override the reserved authorization parameter "${w}".`);l.set(w,I)}return{url:R.toString(),state:i,nonce:a,codeVerifier:u.codeVerifier,codeChallenge:u.codeChallenge,redirectUri:r.redirectUri,scope:o}},async pushAuthorizationRequest(r){if(!r.redirectUri?.trim())throw new s("invalid_request","redirectUri is required to push an authorization request.");let o=r.scope??G,i=r.state??j(),a=r.nonce??M(),u=await $(r.codeChallengeMethod??"S256"),p={response_type:r.responseType??"code",client_id:e.config.clientId,redirect_uri:r.redirectUri,scope:o,state:i,nonce:a,code_challenge:u.codeChallenge,code_challenge_method:u.codeChallengeMethod};if(e.config.clientSecret&&(p.client_secret=e.config.clientSecret),r.authorizationDetails&&r.authorizationDetails.length>0&&(p.authorization_details=JSON.stringify(r.authorizationDetails)),r.prompt&&(p.prompt=r.prompt),r.maxAge!=null){if(!Number.isSafeInteger(r.maxAge)||r.maxAge<0)throw new s("invalid_request","maxAge must be a non-negative integer.");p.max_age=String(r.maxAge)}r.acrValues?.length&&(p.acr_values=r.acrValues.join(" "));for(let[h,c]of Object.entries(r.extraParams??{})){if(ge.has(h))throw new s("invalid_request",`extraParams may not override the reserved authorization parameter "${h}".`);p[h]=c}let R=await e.http.request("/oauth/par",{method:"POST",body:p}),l=R.request_uri,w=R.expires_in;if(typeof l!="string"||typeof w!="number")throw new s("unknown_error","Unexpected pushed authorization response payload.",{details:R});let I=r.authorizationEndpoint??`${e.config.baseUrl}/oauth/authorize`,f=new URL(I);return f.searchParams.set("client_id",e.config.clientId),f.searchParams.set("request_uri",l),{requestUri:l,expiresIn:w,request:{url:f.toString(),state:i,nonce:a,codeVerifier:u.codeVerifier,codeChallenge:u.codeChallenge,redirectUri:r.redirectUri,scope:o,requestUri:l}}},parseCallback:ye,async probeAuthorize(r){let o=await e.http.requestRaw(r,{headers:{Accept:"application/json"},redirect:"manual"}),i=o.headers.get("location");if(i&&o.status>=300&&o.status<400)return{kind:"redirect",location:i,status:o.status};let a=await o.text(),u=a?JSON.parse(a):{};if(!o.ok)throw new s("server_error",typeof u.error_description=="string"?u.error_description:"Authorization probe failed",{status:o.status,details:u});let p=typeof u.authorization_url=="string"?u.authorization_url:void 0;return{kind:"json",status:o.status,authorizationUrl:p,payload:u}},async handleCallback(r){let i=typeof r.callback=="object"&&!(r.callback instanceof URLSearchParams)&&"raw"in r.callback?r.callback:ye(r.callback);if(i.error)throw new s("access_denied",i.errorDescription??`Authorization failed: ${i.error}`,{details:i.raw});if(!i.state||!W(i.state,r.expectedState))throw new s("invalid_request","State mismatch on authorization callback (possible CSRF).");if(!i.code)throw new s("invalid_request","Authorization callback is missing the `code` parameter.");return t.authorizationCode({code:i.code,redirectUri:r.redirectUri,codeVerifier:r.codeVerifier,scope:r.scope})}}}function E(e){let t=e.step_id,n=e.step_type,r=e.name,o=e.order,i=e.is_required,a=e.timeout;if(typeof t!="string"||typeof n!="string"||typeof r!="string"||typeof o!="number"||typeof i!="boolean"||typeof a!="number")throw new s("unknown_error","Unexpected flow step payload.",{details:e});return{stepId:t,stepType:n,name:r,description:typeof e.description=="string"?e.description:void 0,order:o,isRequired:i,timeout:a,inputSchema:e.input_schema&&typeof e.input_schema=="object"?e.input_schema:void 0}}function K(e){let t=e.current_step,n=e.remaining_steps;if(!t||typeof t!="object")throw new s("unknown_error","Missing current_step in flow response.",{details:e});let r=e.continuation_token;if(typeof r!="string"||!r)throw new s("unknown_error","Missing continuation_token in flow response.",{details:e});return{sessionId:String(e.session_id??""),flowId:String(e.flow_id??""),flowName:String(e.flow_name??""),currentStep:E(t),remainingSteps:Array.isArray(n)?n.filter(o=>typeof o=="object"&&o!==null).map(E):[],expiresAt:String(e.expires_at??""),continuationToken:r}}function He(e){return{sessionId:String(e.session_id??""),status:String(e.status??""),purpose:String(e.purpose??""),currentStep:e.current_step&&typeof e.current_step=="object"?E(e.current_step):void 0,completedSteps:Array.isArray(e.completed_steps)?e.completed_steps.filter(t=>typeof t=="string"):[],remainingSteps:Array.isArray(e.remaining_steps)?e.remaining_steps.filter(t=>typeof t=="object"&&t!==null).map(E):[],failedAttempts:typeof e.failed_attempts=="number"?e.failed_attempts:0,expiresAt:String(e.expires_at??""),lastActivityAt:String(e.last_activity_at??"")}}async function Y(e,t){let n=t.status;if(n==="error"||typeof t.error=="string"&&n!=="in_progress"&&n!=="complete")throw new s("invalid_request",typeof t.error=="string"?t.error:"Flow step failed.",{details:t});if(n==="in_progress"){let r=t.next_step,o=t.remaining_steps;if(!r||typeof r!="object")throw new s("unknown_error","Missing next_step in flow step response.",{details:t});let i=t.debug&&typeof t.debug=="object"&&!Array.isArray(t.debug)?t.debug:void 0;return{status:"in_progress",sessionId:String(t.session_id??""),nextStep:E(r),completedSteps:Array.isArray(t.completed_steps)?t.completed_steps.filter(u=>typeof u=="string"):[],remainingSteps:Array.isArray(o)?o.filter(u=>typeof u=="object"&&u!==null).map(E):[],debug:i}}if(n==="complete"){let r=b(t);return await e.setTokens(r),{status:"complete",accessToken:r.accessToken,refreshToken:r.refreshToken,tokenType:r.tokenType,expiresIn:r.expiresIn,scope:r.scope,amr:Array.isArray(t.amr)?t.amr.filter(i=>typeof i=="string"):[],acr:typeof t.acr=="string"?t.acr:""}}throw new s("unknown_error","Unknown flow step status.",{details:t})}function ke(e){return{async initAuthentication(t){let n=await e.http.request("/oauth/authenticate/init",{method:"POST",body:y({client_id:t?.clientId??e.config.clientId,username:t?.username,flow_type:t?.flowType})});return K(n)},async executeStep(t){let n=await e.http.request("/oauth/authenticate/step",{method:"POST",body:{session_id:t.sessionId,step_id:t.stepId,continuation_token:t.continuationToken,credential:t.credential}});return Y(e,n)},async initRegistration(t){let n=await e.http.request("/oauth/register/init",{method:"POST",body:y({client_id:t?.clientId??e.config.clientId,invite_code:t?.inviteCode,flow_type:t?.flowType})});return K(n)},async executeRegistrationStep(t){let n=await e.http.request("/oauth/register/step",{method:"POST",body:{session_id:t.sessionId,step_id:t.stepId,continuation_token:t.continuationToken,credential:t.credential}});return Y(e,n)},async getStatus(t,n){let r=new URLSearchParams({continuation_token:n}),o=await e.http.request(`/oauth/flow/status/${encodeURIComponent(t)}?${r}`);return He(o)},async initRecovery(t){let n=await e.http.request("/oauth/recovery/init",{method:"POST",body:y({client_id:t?.clientId??e.config.clientId,recovery_type:t?.recoveryType,redirect_uri:t?.redirectUri})});return K(n)},async executeRecoveryStep(t){let n=await e.http.request("/oauth/recovery/step",{method:"POST",body:{session_id:t.sessionId,step_id:t.stepId,continuation_token:t.continuationToken,credential:t.credential}});return Y(e,n)}}}function F(e){return Array.isArray(e)?e.filter(t=>typeof t=="string"):[]}function Ve(e){return{issuer:String(e.issuer??""),authorizationEndpoint:String(e.authorization_endpoint??""),tokenEndpoint:String(e.token_endpoint??""),userinfoEndpoint:typeof e.userinfo_endpoint=="string"?e.userinfo_endpoint:void 0,jwksUri:String(e.jwks_uri??""),revocationEndpoint:typeof e.revocation_endpoint=="string"?e.revocation_endpoint:void 0,introspectionEndpoint:typeof e.introspection_endpoint=="string"?e.introspection_endpoint:void 0,responseTypesSupported:F(e.response_types_supported),grantTypesSupported:F(e.grant_types_supported),scopesSupported:F(e.scopes_supported),codeChallengeMethodsSupported:F(e.code_challenge_methods_supported),raw:e}}function Qe(e){return{sub:String(e.sub??""),name:typeof e.name=="string"?e.name:void 0,givenName:typeof e.given_name=="string"?e.given_name:void 0,familyName:typeof e.family_name=="string"?e.family_name:void 0,middleName:typeof e.middle_name=="string"?e.middle_name:void 0,nickname:typeof e.nickname=="string"?e.nickname:void 0,preferredUsername:typeof e.preferred_username=="string"?e.preferred_username:void 0,profile:typeof e.profile=="string"?e.profile:void 0,picture:typeof e.picture=="string"?e.picture:void 0,website:typeof e.website=="string"?e.website:void 0,gender:typeof e.gender=="string"?e.gender:void 0,birthdate:typeof e.birthdate=="string"?e.birthdate:void 0,zoneinfo:typeof e.zoneinfo=="string"?e.zoneinfo:void 0,locale:typeof e.locale=="string"?e.locale:void 0,updatedAt:typeof e.updated_at=="number"?e.updated_at:void 0,email:typeof e.email=="string"?e.email:void 0,emailVerified:typeof e.email_verified=="boolean"?e.email_verified:void 0,phoneNumber:typeof e.phone_number=="string"?e.phone_number:void 0,phoneNumberVerified:typeof e.phone_number_verified=="boolean"?e.phone_number_verified:void 0}}function be(e){return{async userInfo(){let t=await m(e),n=await e.http.request("/oauth/userinfo",{token:t});return Qe(n)},async discover(){let t=await e.http.request("/.well-known/openid-configuration");return Ve(t)},async jwks(){let t=await e.http.request("/oauth/jwks");return{keys:Array.isArray(t.keys)?t.keys.filter(r=>typeof r=="object"&&r!==null):[]}}}}function L(e){return{...e,id:typeof e.id=="string"?e.id:"",organizationId:typeof e.organization_id=="string"?e.organization_id:void 0,email:typeof e.email=="string"?e.email:void 0,emailVerified:typeof e.email_verified=="boolean"?e.email_verified:void 0,username:typeof e.username=="string"?e.username:void 0,firstName:typeof e.first_name=="string"?e.first_name:void 0,lastName:typeof e.last_name=="string"?e.last_name:void 0,isActive:typeof e.is_active=="boolean"?e.is_active:void 0,isVerified:typeof e.is_verified=="boolean"?e.is_verified:void 0,mfaEnabled:typeof e.mfa_enabled=="boolean"?e.mfa_enabled:void 0}}function Re(e){if(typeof e!="object"||e===null||Array.isArray(e))throw new P("Malformed primary-email verification response.",e);let t=e,n=t.status,r=t.email,o=t.email_verified;if(!((n==="sent"||n==="verified")&&typeof r=="string"&&r.length>0&&typeof o=="boolean"&&(n==="sent"&&o===!1||n==="verified"&&o===!0)))throw new P("Malformed primary-email verification response.",t);return{status:n,email:r,emailVerified:o}}function X(e){return{id:String(e.id??""),name:typeof e.name=="string"?e.name:void 0,email:typeof e.email=="string"?e.email:void 0,phoneNumber:typeof e.phone_number=="string"?e.phone_number:void 0,relationship:typeof e.relationship=="string"?e.relationship:void 0,isVerified:typeof e.is_verified=="boolean"?e.is_verified:void 0}}function We(e){return{id:String(e.id??""),provider:String(e.provider??""),providerUserId:typeof e.provider_user_id=="string"?e.provider_user_id:void 0,email:typeof e.email=="string"?e.email:void 0,isPrimary:typeof e.is_primary=="boolean"?e.is_primary:void 0}}function we(e){return Ge(e)}function Ge(e){return{async me(){let t=await m(e),n=await e.http.request("/api/v1/users/me",{token:t});return L(n)},async updateMe(t){let n=await g(e,"/api/v1/users/me",{method:"PATCH",body:t});return L(n)},emailVerification:{async start(t){let n=await g(e,"/api/v1/users/me/email/verify",{method:"POST",body:{email:t.email}});return Re(n)},async verify(t){let n=await g(e,"/api/v1/users/me/email/verify",{method:"POST",body:{code:t.code}});return Re(n)}},async changePassword(t){await v(e,"/api/v1/users/me/password",{method:"POST",body:{current_password:t.currentPassword,new_password:t.newPassword}})},async deleteAvatar(){let t=await g(e,"/api/v1/users/me/avatar",{method:"DELETE"});return L(t)},async uploadAvatar(t,n="avatar.jpg"){let r=await m(e),o=new FormData,i=t instanceof Blob?t:new Blob([new Uint8Array(t)]);o.append("avatar",i,n);let a=await e.http.request("/api/v1/users/me/avatar",{method:"POST",token:r,body:o});return L(a)},async verifyPhone(t){return g(e,"/api/v1/users/me/phone/verify",{method:"POST",body:t})},async verifyRecoveryEmail(t){return g(e,"/api/v1/users/me/recovery-email/verify",{method:"POST",body:t})},async securityOverview(){return g(e,"/api/v1/users/me/security-overview")},recoveryContacts:{async list(){let t=await g(e,"/api/v1/users/me/recovery-contacts");return Array.isArray(t)?t.filter(n=>typeof n=="object"&&n!==null).map(X):[]},async create(t){let n=await g(e,"/api/v1/users/me/recovery-contacts",{method:"POST",body:t});return X(n)},async update(t,n){let r=await g(e,`/api/v1/users/me/recovery-contacts/${A(t,"contactId")}`,{method:"PATCH",body:n});return X(r)},async delete(t){await v(e,`/api/v1/users/me/recovery-contacts/${A(t,"contactId")}`,{method:"DELETE"})}},identities:{async list(){let t=await g(e,"/api/v1/users/me/identities");return Array.isArray(t)?t.filter(n=>typeof n=="object"&&n!==null).map(We):[]},async unlink(t){await v(e,`/api/v1/users/me/identities/${A(t,"identityId")}`,{method:"DELETE"})},async setPrimary(t){return g(e,`/api/v1/users/me/identities/${A(t,"identityId")}/primary`,{method:"PATCH"})}},async deleteMe(t){await v(e,"/api/v1/users/me",{method:"DELETE",body:{password:t.password}})}}}function C(e){return e&&typeof e=="object"?e:{}}function ve(e){return{async registerBegin(t){let n=await m(e),r=await e.http.request("/api/v1/users/me/webauthn/register/begin",{method:"POST",token:n,body:t});return C(r)},async registerFinish(t){let n=await m(e),r=await e.http.request("/api/v1/users/me/webauthn/register/finish",{method:"POST",token:n,body:t});return C(r)},async authenticateBegin(t){let n=await e.http.request("/api/v1/users/me/webauthn/authenticate/begin",{method:"POST",body:t});return C(n)},async authenticateFinish(t){let n=await e.http.request("/api/v1/users/me/webauthn/authenticate/finish",{method:"POST",body:t}),r=C(n);if(typeof r.access_token=="string"){let o=b(r);await e.setTokens(o)}return r},async credentials(){let t=await m(e),n=await e.http.request("/api/v1/users/me/webauthn/credentials",{token:t});return C(n)},async removeCredential(t,n){let r=await m(e);await e.http.request(`/api/v1/users/me/webauthn/credentials/${encodeURIComponent(t)}`,{method:"DELETE",token:r,body:n?.password?{password:n.password}:void 0})},async renameCredential(t,n){let r=await m(e),o=await e.http.request(`/api/v1/users/me/webauthn/credentials/${encodeURIComponent(t)}/rename`,{method:"PATCH",token:r,body:n});return C(o)},async setPrimaryCredential(t){let n=await m(e),r=await e.http.request(`/api/v1/users/me/webauthn/credentials/${encodeURIComponent(t)}/primary`,{method:"POST",token:n});return C(r)}}}function Ke(e){return{isActive:!!(e.is_active??e.isActive),message:typeof e.message=="string"?e.message:void 0,scheduledStart:typeof e.scheduled_start=="string"?e.scheduled_start:void 0,scheduledEnd:typeof e.scheduled_end=="string"?e.scheduled_end:void 0,...e}}function Se(e){return{async status(){let t=await e.http.request("/api/v1/maintenance/status");return Ke(t)}}}var Ye=128,Z=254;function S(e,t){if(typeof e!="string")return;let n=e.trim();if(!(!n||n.length>t))return n}function Te(e){return{async request(t){let n=y({email:S(t.email,Z),identifier:S(t.identifier,Z),recovery_email:S(t.recoveryEmail,Z),phone_number:S(t.phoneNumber,32),member_id:S(t.memberId,64),security_question:S(t.securityQuestion,256),security_answer:S(t.securityAnswer,256),redirect_uri:S(t.redirectUri,2048)}),r=await e.http.request("/oauth/password/reset/request",{method:"POST",body:n});return{ok:!!r.ok,message:String(r.message??"")}},async validate(t){let n=t.trim();if(!n)throw new s("invalid_request","token is required.");let r=await e.http.request(`/oauth/password/reset/validate?token=${encodeURIComponent(n)}`);return{ok:!!r.ok,valid:!!r.valid,expiresAt:typeof r.expires_at=="string"?r.expires_at:void 0}},async complete(t){if(!t.token.trim())throw new s("invalid_request","token is required.");if(!t.password||t.password.length>Ye)throw new s("invalid_request","password is required and must be <= 128 characters.");let n=await e.http.request("/oauth/password/reset/complete",{method:"POST",body:{token:t.token.trim(),password:t.password,password_confirm:t.passwordConfirm}});return{ok:!!n.ok,message:String(n.message??"")}}}}function Xe(e){return{id:String(e.id??""),deviceName:String(e.device_name??""),isVerified:!!e.is_verified,isPrimary:!!e.is_primary,createdAt:typeof e.created_at=="string"?e.created_at:void 0,lastUsedAt:typeof e.last_used_at=="string"?e.last_used_at:void 0}}function Ae(e){return{async setup(t){let n=t.deviceName?.trim();if(!n||n.length>128)throw new s("invalid_request","deviceName is required.");let r=await g(e,"/api/v1/users/me/mfa/setup",{method:"POST",body:{device_name:n}});return{deviceId:String(r.device_id??""),secret:String(r.secret??""),qrCodeUri:String(r.qr_code_uri??""),backupCodes:Array.isArray(r.backup_codes)?r.backup_codes.filter(o=>typeof o=="string"):[],algorithm:String(r.algorithm??""),digits:typeof r.digits=="number"?r.digits:6,period:typeof r.period=="number"?r.period:30}},async verify(t){return g(e,"/api/v1/users/me/mfa/verify",{method:"POST",body:{device_id:t.deviceId,totp_code:t.totpCode}})},async devices(){let t=await g(e,"/api/v1/users/me/mfa/devices");return Array.isArray(t)?t.filter(n=>typeof n=="object"&&n!==null).map(Xe):[]},async removeDevice(t){await v(e,`/api/v1/users/me/mfa/devices/${A(t,"deviceId")}`,{method:"DELETE"})},async regenerateBackupCodes(){let t=await g(e,"/api/v1/users/me/mfa/backup-codes/regenerate",{method:"POST"});return{backupCodes:Array.isArray(t.backup_codes)?t.backup_codes.filter(n=>typeof n=="string"):[]}},async disable(t){await v(e,"/api/v1/users/me/mfa/disable",{method:"POST",body:y({password:t?.password})})}}}var Ce="https://prod-auth.tktchurch.com",Ze=new Set(["localhost","127.0.0.1","[::1]"]);function et(e){return Ze.has(e.toLowerCase())}function ee(e,t={}){let n=(e??Ce).trim();if(!n)return ee(Ce,t);let o=(/^https?:\/\//i.test(n)?n:`https://${n}`).replace(/\/+$/,""),i;try{i=new URL(o)}catch{throw new s("invalid_request","baseUrl must be a valid absolute URL.")}if(i.protocol==="http:"&&!t.allowInsecureTransport&&!et(i.hostname))throw new s("invalid_request","baseUrl must use HTTPS. Set allowInsecureTransport=true only for trusted local development.");return o}function tt(e,t){if(!e&&t<=0)return{cleanup:()=>{}};let n=new AbortController,r,o=()=>{n.abort(e?.reason)};return e&&(e.aborted?n.abort(e.reason):e.addEventListener("abort",o,{once:!0})),t>0&&(r=setTimeout(()=>{n.abort(new Error(`Request timed out after ${t}ms`))},t)),{signal:n.signal,cleanup:()=>{r&&clearTimeout(r),e&&e.removeEventListener("abort",o)}}}function nt(e,t){return/^https?:\/\//i.test(t)?t:`${e}${t.startsWith("/")?t:`/${t}`}`}function rt(e,t,n){let r=new Headers(e);return t&&new Headers(t).forEach((i,a)=>{r.set(a,i)}),n&&!r.has("Authorization")&&r.set("Authorization",`Bearer ${n}`),r}function Pe(e){return{async request(t,n){let r=await this.requestRaw(t,n);if(!r.ok)throw await ie(r);if(r.status===204)return;let o=await r.text();if(o)return JSON.parse(o)},async requestRaw(t,n){let r=nt(e.baseUrl,t),o=rt(e.defaultHeaders,n?.headers,n?.token),{signal:i,cleanup:a}=tt(n?.signal,e.timeoutMs),u={method:n?.method??"GET",headers:o,signal:i,redirect:n?.redirect};typeof n?.body<"u"&&(n.body instanceof FormData?u.body=n.body:(o.has("Content-Type")||o.set("Content-Type","application/json"),u.body=JSON.stringify(n.body)));try{return await e.fetch(r,u)}catch(p){throw i?.aborted??!1?new s("timeout",`Request timed out: ${r}`,{details:p}):new s("network_error",`Network request failed: ${r}`,{details:p})}finally{a()}}}}var ot="@tktchurch/auth/tokens",ne=32768,qe=new Set(["accessToken","tokenType","expiresIn","refreshToken","scope","idToken","authorizationDetails"]),Oe=new Set(["__proto__","constructor","prototype"]),it=/^[\w@./:-]{1,128}$/;function re(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function Ee(e){return typeof e=="string"&&e.length>0}function te(e){return typeof e=="string"&&e.length>0?e:void 0}function x(e){if(!re(e))throw new s("invalid_request","Token storage rejected a non-object token payload.");for(let i of Object.keys(e))if(!qe.has(i))throw new s("invalid_request",`Token storage rejected unknown field "${i}".`);if(!Ee(e.accessToken))throw new s("invalid_request","Token storage requires a non-empty accessToken.");if(!Ee(e.tokenType))throw new s("invalid_request","Token storage requires a non-empty tokenType.");if(typeof e.expiresIn!="number"||!Number.isFinite(e.expiresIn)||e.expiresIn<0)throw new s("invalid_request","Token storage requires a non-negative numeric expiresIn.");let t={accessToken:e.accessToken,tokenType:e.tokenType,expiresIn:e.expiresIn},n=te(e.refreshToken);n&&(t.refreshToken=n);let r=te(e.scope);r&&(t.scope=r);let o=te(e.idToken);if(o&&(t.idToken=o),typeof e.authorizationDetails<"u"){if(!Array.isArray(e.authorizationDetails)||!e.authorizationDetails.every(i=>re(i)&&typeof i.type=="string"))throw new s("invalid_request","Token storage rejected malformed authorizationDetails.");t.authorizationDetails=e.authorizationDetails}return t}function st(e){if(!re(e))return null;for(let t of Object.keys(e))if(Oe.has(t)||!qe.has(t))return null;try{return x(e)}catch{return null}}function at(e){return JSON.parse(e,(t,n)=>{if(Oe.has(t))throw new SyntaxError("Forbidden JSON key in stored token payload.");return n})}function ut(e){let t=e.trim();if(!t)throw new s("invalid_request","Token storage key must be a non-empty string.");if(!it.test(t))throw new s("invalid_request","Token storage key contains unsupported characters or exceeds 128 characters.");return t}function dt(e){let t=x(e),n=JSON.stringify(t);if(n.length>ne)throw new s("invalid_request",`Serialized token payload exceeds ${ne} bytes.`);return n}var oe=class{state=null;get(){return this.state}set(t){if(!t){this.clear();return}this.state=x(t)}clear(){this.state=null}};function q(){return new oe}function ct(){return typeof window<"u"&&typeof window.document<"u"}function pt(e){if(e)return e;if(typeof globalThis.fetch=="function")return globalThis.fetch.bind(globalThis);throw new s("invalid_request","No fetch implementation available. Provide config.fetch.")}function Ie(e){if(!e.clientId?.trim())throw new s("invalid_request","clientId is required.");if(ct()&&e.clientSecret&&!e.allowClientSecretInBrowser)throw new s("invalid_request","clientSecret in browser runtime is blocked by default. Set allowClientSecretInBrowser=true to override.");return{clientId:e.clientId,clientSecret:e.clientSecret,baseUrl:ee(e.baseUrl,{allowInsecureTransport:e.allowInsecureTransport??!1}),fetch:pt(e.fetch),storage:e.storage??q(),timeoutMs:e.timeoutMs??15e3,defaultHeaders:e.defaultHeaders??{},autoRefresh:e.autoRefresh??!0,allowClientSecretInBrowser:e.allowClientSecretInBrowser??!1,allowInsecureTransport:e.allowInsecureTransport??!1}}function B(e){let t=Ie(e),n=Pe({baseUrl:t.baseUrl,fetch:t.fetch,timeoutMs:t.timeoutMs,defaultHeaders:t.defaultHeaders}),r=async()=>await t.storage.get(),o=async u=>{await t.storage.set(u)},i=async()=>{await t.storage.clear()},a={config:t,http:n,getTokens:r,setTokens:o,clearTokens:i};return{token:N(a),flow:ke(a),oidc:be(a),authorize:_e(a),user:we(a),sessions:se(a),webauthn:ve(a),passwordReset:Te(a),mfa:Ae(a),maintenance:Se(a),oauth:he(a),tokens:{get:r,set:o,clear:i}}}var k=class extends Error{name="PhoneOtpError"};function O(e,t){return e instanceof k?e:z(e)?new k(e.message||t):e instanceof Error&&e.message?new k(e.message):new k(t)}function Ue(e){if(!z(e)||typeof e.status!="number")return!1;let t=e.status;return t>=400&&t<500&&t!==429}function ze(e){if(e.status!=="in_progress"||!e.debug)return;let t=e.debug.verification_code;return typeof t=="string"?t:void 0}function ft(e){return{...e,id:typeof e.id=="string"?e.id:"",first_name:typeof e.first_name=="string"?e.first_name:typeof e.firstName=="string"?e.firstName:void 0,last_name:typeof e.last_name=="string"?e.last_name:typeof e.lastName=="string"?e.lastName:void 0,email:typeof e.email=="string"?e.email:void 0,phone_number:typeof e.phone_number=="string"?e.phone_number:typeof e.phoneNumber=="string"?e.phoneNumber:void 0,phone_country_code:typeof e.phone_country_code=="string"?e.phone_country_code:typeof e.phoneCountryCode=="string"?e.phoneCountryCode:void 0,phone_verified:typeof e.phone_verified=="boolean"?e.phone_verified:typeof e.phoneVerified=="boolean"?e.phoneVerified:void 0,profile_picture_url:typeof e.profile_picture_url=="string"?e.profile_picture_url:typeof e.profilePictureUrl=="string"?e.profilePictureUrl:void 0,picture:typeof e.picture=="string"?e.picture:void 0,birthdate:typeof e.birthdate=="string"?e.birthdate:void 0}}function lt(e){let t=e.auth,n=e.guestProfile??{firstName:"Guest",lastName:"User"},r=n.emailDomain??"registrations.tktchurch.local",o=n.emailLocalPrefix??"reg";function i(){return t||B({baseUrl:e.baseUrl,clientId:e.clientId,allowInsecureTransport:e.allowInsecureTransport,timeoutMs:e.timeoutMs??2e4,storage:q(),autoRefresh:!1})}async function a(f,h){let c=i(),d=await c.flow.initAuthentication({flowType:"phone_only",username:h}),_=await c.flow.executeStep({sessionId:d.sessionId,stepId:d.currentStep.stepId,continuationToken:d.continuationToken,credential:f});if(_.status!=="in_progress")throw new k("Unexpected sign-in response.");return{oauthSessionId:d.sessionId,verifyStepId:_.nextStep.stepId,mode:"authentication",continuationToken:d.continuationToken,debugCode:ze(_)}}async function u(f){let h=i(),c=await h.flow.initRegistration({flowType:"phone_registration"}),d=await h.flow.executeRegistrationStep({sessionId:c.sessionId,stepId:c.currentStep.stepId,continuationToken:c.continuationToken,credential:f});if(d.status!=="in_progress")throw new k("Unexpected verification response.");return{oauthSessionId:c.sessionId,verifyStepId:d.nextStep.stepId,mode:"registration",continuationToken:c.continuationToken,debugCode:ze(d)}}async function p(f,h,c){let d={phone_country_code:f,phone_number:h};try{return await u(d)}catch(_){if(!Ue(_))throw O(_,"Could not send the code. Please try again.");try{return await a(d,c)}catch{throw O(_,"Could not send the code. Please try again.")}}}async function R(f,h,c){let d={phone_country_code:f,phone_number:h};try{return await a(d,c)}catch(_){if(!Ue(_))throw O(_,"Could not start sign-in.");try{return await u(d)}catch{throw O(_,"Could not start sign-in.")}}}async function l(f,h,c,d,_,U){return h==="registration"?await f.flow.executeRegistrationStep({sessionId:c,stepId:d,continuationToken:_,credential:U}):await f.flow.executeStep({sessionId:c,stepId:d,continuationToken:_,credential:U})}async function w(f,h){let c=i();try{let d=await l(c,f.mode,f.oauthSessionId,f.verifyStepId,f.continuationToken,{code:h}),_=0;for(;d.status==="in_progress"&&_<6;){_++;let U=d.nextStep.stepType,J={};U==="profile_info"?J={first_name:n.firstName,last_name:n.lastName,email:`${o}+${f.oauthSessionId.slice(0,8).toLowerCase()}@${r}`}:U==="terms_acceptance"&&(J={accepted:"true"}),d=await l(c,f.mode,f.oauthSessionId,d.nextStep.stepId,f.continuationToken,J)}if(d.status!=="complete")throw new k("Sign-in did not complete. Please try again.");return{accessToken:d.accessToken,refreshToken:d.refreshToken,expiresIn:d.expiresIn}}catch(d){throw d instanceof k?d:O(d,"That code didn't match. Try again.")}}async function I(f){let h;if(t)h=t;else{let c=q();await c.set({accessToken:f,tokenType:"Bearer",expiresIn:3600}),h=B({baseUrl:e.baseUrl,clientId:e.clientId,allowInsecureTransport:e.allowInsecureTransport,timeoutMs:e.timeoutMs??2e4,storage:c,autoRefresh:!1})}try{let c=await h.user.me();return ft(c)}catch(c){throw O(c,c instanceof s?`Profile fetch failed (${c.status??"error"})`:"Profile fetch failed")}}return{startPhoneOTP:p,startPhoneOTPForSignIn:R,verifyPhoneOTP:w,fetchProfile:I}}var mt=/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;function ht(e,t="/"){if(!e)return t;let n=e;try{n=decodeURIComponent(e)}catch{return t}return n.startsWith("/")&&!n.startsWith("//")&&!n.startsWith("/\\")&&!mt.test(n)&&!/^\/+[\\/]/.test(n)?n:t}export{P as AuthContractError,s as AuthError,G as DEFAULT_AUTHORIZE_SCOPE,ot as DEFAULT_TOKEN_STORAGE_KEY,ne as MAX_STORED_TOKEN_BYTES,T as MFARequiredAuthError,k as PhoneOtpError,x as assertValidAuthTokens,B as createAuthClient,q as createMemoryTokenStorage,lt as createPhoneOtpClient,ce as deriveCodeChallenge,de as generateCodeVerifier,M as generateNonce,$ as generatePkce,j as generateState,z as isAuthError,je as isUnrecoverableAuthError,st as parseStoredAuthTokens,D as randomUrlSafeString,at as safeParseStoredJson,ht as sanitizePostAuthRedirect,dt as serializeAuthTokens,W as timingSafeEqual,ut as validateStorageKey};
|
|
1
|
+
var i=class extends Error{code;status;details;constructor(t,n,r){super(n),this.name="AuthError",this.code=t,this.status=r?.status,this.details=r?.details}},C=class extends i{mfaMethods;constructor(t,n,r,o){super("mfa_required",t,{status:r,details:o}),this.name="MFARequiredAuthError",this.mfaMethods=n}},w=class extends i{constructor(t,n){super("contract_error",t,{details:n}),this.name="AuthContractError"}};function De(e){switch(e){case"mfa_required":case"invalid_request":case"invalid_client":case"invalid_grant":case"unauthorized_client":case"unsupported_grant_type":case"invalid_scope":case"access_denied":case"unsupported_response_type":case"server_error":case"temporarily_unavailable":case"invalid_token":case"insufficient_scope":case"authentication_required":case"insufficient_user_authentication":return e;default:return"unknown_error"}}function je(e,t){if(!t)return new i("http_error",`Request failed with status ${e}`,{status:e});let n=De(t.error),r=t.error_description??t.reason??(t.error?`Auth error: ${t.error}`:`Request failed with status ${e}`);if(n==="mfa_required"){let o=Array.isArray(t.mfa_methods)?t.mfa_methods.filter(s=>typeof s=="string"):[];return new C(r,o,e,t)}return new i(n,r,{status:e,details:t})}async function se(e){let t,n=await e.text();if(n)try{t=JSON.parse(n)}catch{t={reason:n}}return je(e.status,t)}function z(e){return e instanceof i}function Me(e){return z(e)?e.code==="invalid_client"||e.code==="invalid_grant"||e.code==="unauthorized_client"||e.code==="invalid_token":!1}function H(e){if(!Array.isArray(e))return;let t=e.filter(n=>typeof n=="object"&&n!==null&&typeof n.type=="string");return t.length>0?t:void 0}async function m(e){let t=await e.getTokens();if(!t?.accessToken)throw new i("invalid_token","No access token is available. Authenticate first.");return t.accessToken}function y(e){return Object.fromEntries(Object.entries(e).filter(([,t])=>typeof t<"u"))}function b(e,t){let n=e.access_token,r=e.token_type,o=e.expires_in;if(typeof n!="string"||typeof r!="string"||typeof o!="number")throw new i("unknown_error","Unexpected token response payload.",{details:e});let s={accessToken:n,tokenType:r,expiresIn:o},a=typeof e.refresh_token=="string"?e.refresh_token:t;a&&(s.refreshToken=a),typeof e.scope=="string"&&(s.scope=e.scope),typeof e.id_token=="string"&&(s.idToken=e.id_token);let u=H(e.authorization_details);return u&&(s.authorizationDetails=u),s}function $e(e){return{id:String(e.id??""),deviceName:typeof e.device_name=="string"?e.device_name:void 0,deviceType:String(e.device_type??""),browser:typeof e.browser=="string"?e.browser:void 0,os:typeof e.os=="string"?e.os:void 0,ipAddress:String(e.ip_address??""),country:typeof e.country=="string"?e.country:void 0,city:typeof e.city=="string"?e.city:void 0,isActive:!!e.is_active,isCurrent:!!e.is_current,isTrusted:!!e.is_trusted,lastUsedAt:typeof e.last_used_at=="string"?e.last_used_at:void 0,createdAt:typeof e.created_at=="string"?e.created_at:void 0}}function V(e){return{id:String(e.id??""),deviceId:typeof e.device_id=="string"?e.device_id:void 0,deviceName:typeof e.device_name=="string"?e.device_name:void 0,deviceType:String(e.device_type??""),userAgent:typeof e.user_agent=="string"?e.user_agent:void 0,browser:typeof e.browser=="string"?e.browser:void 0,browserVersion:typeof e.browser_version=="string"?e.browser_version:void 0,os:typeof e.os=="string"?e.os:void 0,osVersion:typeof e.os_version=="string"?e.os_version:void 0,ipAddress:String(e.ip_address??""),country:typeof e.country=="string"?e.country:void 0,city:typeof e.city=="string"?e.city:void 0,isActive:!!e.is_active,isRevoked:!!e.is_revoked,isCurrent:!!e.is_current,isTrusted:!!e.is_trusted,requiresMfa:!!e.requires_mfa,accessTokenCount:typeof e.access_token_count=="number"?e.access_token_count:0,lastUsedAt:typeof e.last_used_at=="string"?e.last_used_at:void 0,expiresAt:typeof e.expires_at=="string"?e.expires_at:void 0,createdAt:typeof e.created_at=="string"?e.created_at:void 0,revokedAt:typeof e.revoked_at=="string"?e.revoked_at:void 0,revocationReason:typeof e.revocation_reason=="string"?e.revocation_reason:void 0}}function Q(e){return{success:!!e.success,sessionsRevoked:typeof e.sessions_revoked=="number"?e.sessions_revoked:0,tokensRevoked:typeof e.tokens_revoked=="number"?e.tokens_revoked:0}}function Ne(e){let t=Array.isArray(e.items)?e.items:[],r=e.metadata&&typeof e.metadata=="object"?e.metadata:{};return{items:t.filter(o=>typeof o=="object"&&o!==null).map($e),metadata:{page:typeof r.page=="number"?r.page:1,per:typeof r.per=="number"?r.per:t.length,total:typeof r.total=="number"?r.total:t.length}}}function ie(e){return{async list(t){let n=await m(e),r=new URLSearchParams;typeof t?.page=="number"&&r.set("page",String(t.page)),typeof t?.perPage=="number"&&r.set("per_page",String(t.perPage)),t?.userId&&r.set("user_id",t.userId);let o=`/api/v1/sessions${r.size?`?${r.toString()}`:""}`,s=await e.http.request(o,{token:n});return Ne(s)},async current(){let t=await m(e),n=await e.http.request("/api/v1/sessions/current",{token:t});return V(n)},async get(t){let n=await m(e),r=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{token:n});return V(r)},async update(t,n){let r=await m(e),o=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{method:"PATCH",token:r,body:y({device_name:n.deviceName,is_trusted:n.isTrusted})});return V(o)},async revoke(t,n){let r=await m(e),o=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{method:"DELETE",token:r,body:y({reason:n?.reason})});return Q(o)},async revokeAll(){let t=await m(e),n=await e.http.request("/api/v1/sessions/revoke-all",{method:"POST",token:t});return Q(n)},async revokeAllDevices(){let t=await m(e),n=await e.http.request("/api/v1/sessions/revoke-all-devices",{method:"POST",token:t});return Q(n)}}}function ae(){let e=globalThis.crypto;if(!e?.getRandomValues)throw new i("invalid_request","Web Crypto API is unavailable in this runtime. PKCE requires globalThis.crypto.");return e}function ue(e){let t="";for(let r of e)t+=String.fromCharCode(r);if(typeof btoa!="function")throw new i("server_error","base64UrlEncode requires globalThis.btoa (Web/Deno runtime).");return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function D(e=32){if(!Number.isInteger(e)||e<=0)throw new i("invalid_request","randomUrlSafeString length must be a positive integer.");let t=new Uint8Array(e);return ae().getRandomValues(t),ue(t)}function j(){return D(32)}function M(){return D(32)}function de(){return D(32)}async function ce(e){let t=ae();if(!t.subtle?.digest)throw new i("invalid_request","SubtleCrypto.digest is unavailable; cannot derive an S256 code challenge.");let n=new TextEncoder().encode(e),r=await t.subtle.digest("SHA-256",n);return ue(new Uint8Array(r))}async function $(e="S256"){let t=de();if(e==="plain")return{codeVerifier:t,codeChallenge:t,codeChallengeMethod:"plain"};let n=await ce(t);return{codeVerifier:t,codeChallenge:n,codeChallengeMethod:"S256"}}function W(e,t){let n=new TextEncoder().encode(e),r=new TextEncoder().encode(t),o=Math.max(n.length,r.length),s=n.length^r.length;for(let a=0;a<o;a+=1)s|=(n[a]??0)^(r[a]??0);return s===0}function Fe(e){return{active:!!e.active,scope:typeof e.scope=="string"?e.scope:void 0,clientId:typeof e.client_id=="string"?e.client_id:void 0,username:typeof e.username=="string"?e.username:void 0,tokenType:typeof e.token_type=="string"?e.token_type:void 0,exp:typeof e.exp=="number"?e.exp:void 0,iat:typeof e.iat=="number"?e.iat:void 0,sub:typeof e.sub=="string"?e.sub:void 0,authorizationDetails:H(e.authorization_details)}}function N(e){return{async password(t){let n=y({grant_type:"password",client_id:e.config.clientId,client_secret:e.config.clientSecret,username:t.username,email:t.email,password:t.password,scope:t.scope,mfa_code:t.mfaCode,mfa_backup_code:t.mfaBackupCode}),r=await e.http.request("/oauth/token",{method:"POST",body:n}),o=b(r);return await e.setTokens(o),o},async refresh(t){let n=await e.getTokens(),r=t?.refreshToken??n?.refreshToken;if(!r)throw new i("invalid_request","No refresh token is available.");let o=y({grant_type:"refresh_token",client_id:e.config.clientId,client_secret:e.config.clientSecret,refresh_token:r,scope:t?.scope}),s=await e.http.request("/oauth/token",{method:"POST",body:o}),a=b(s,r);return await e.setTokens(a),a},async clientCredentials(t){let n=y({grant_type:"client_credentials",client_id:e.config.clientId,client_secret:e.config.clientSecret,scope:t?.scope}),r=await e.http.request("/oauth/token",{method:"POST",body:n}),o=b(r);return await e.setTokens(o),o},async authorizationCode(t){let n=y({grant_type:"authorization_code",client_id:e.config.clientId,client_secret:e.config.clientSecret,code:t.code,redirect_uri:t.redirectUri,code_verifier:t.codeVerifier,scope:t.scope}),r=await e.http.request("/oauth/token",{method:"POST",body:n}),o=b(r);return await e.setTokens(o),o},async revoke(t){let n=await e.getTokens(),r=t?.token??n?.accessToken;if(!r)throw new i("invalid_request","No token provided for revocation.");await e.http.request("/oauth/revoke",{method:"POST",body:y({client_id:e.config.clientId,client_secret:e.config.clientSecret,token:r,token_type_hint:t?.tokenTypeHint})}),n&&(r===n.accessToken||r===n.refreshToken)&&await e.clearTokens()},async introspect(t){let n=await e.http.request("/oauth/introspect",{method:"POST",body:y({client_id:e.config.clientId,client_secret:e.config.clientSecret,token:t.token})});return Fe(n)},async tokenExchange(t){let n=y({grant_type:"urn:ietf:params:oauth:grant-type:token-exchange",client_id:e.config.clientId,client_secret:e.config.clientSecret,subject_token:t.subjectToken,subject_token_type:t.subjectTokenType??"urn:ietf:params:oauth:token-type:access_token",requested_subject:t.requestedSubject,scope:t.scope,audience:t.audience}),r=await e.http.request("/oauth/token",{method:"POST",body:n}),o=b(r);return await e.setTokens(o),o}}}function pe(e,t){let n=e.trim();if(!n)throw new i("invalid_request",`${t} is required.`);return n}async function h(e,t,n){let r=await m(e),o=n?.query,s=o&&Object.keys(o).length>0?`?${new URLSearchParams(Object.entries(o).filter(([,a])=>typeof a<"u").map(([a,u])=>[a,String(u)])).toString()}`:"";return e.http.request(`${t}${s}`,{method:n?.method,body:n?.body,token:r})}async function v(e,t,n){await h(e,t,n)}function S(e,t){return encodeURIComponent(pe(e,t))}function Le(e){let t=Array.isArray(e.mfa_methods)?e.mfa_methods.filter(n=>typeof n=="string"):void 0;return{redirectUri:String(e.redirect_uri??""),code:typeof e.code=="string"?e.code:void 0,state:typeof e.state=="string"?e.state:void 0,error:typeof e.error=="string"?e.error:void 0,errorDescription:typeof e.error_description=="string"?e.error_description:void 0,mfaMethods:t,accessToken:typeof e.access_token=="string"?e.access_token:void 0,tokenType:typeof e.token_type=="string"?e.token_type:void 0,expiresIn:typeof e.expires_in=="number"?e.expires_in:void 0,scope:typeof e.scope=="string"?e.scope:void 0,idToken:typeof e.id_token=="string"?e.id_token:void 0}}function Be(e){let t=Array.isArray(e.redirect_uris)?e.redirect_uris.filter(n=>typeof n=="string"&&n.length>0):[];return{clientId:String(e.client_id??""),name:String(e.name??""),description:typeof e.description=="string"?e.description:void 0,isFirstParty:!!e.is_first_party,redirectUris:t}}function Je(e){let t=Array.isArray(e.mfa_methods)?e.mfa_methods.filter(r=>typeof r=="string"):void 0,n=Array.isArray(e.uncovered_scopes)?e.uncovered_scopes.filter(r=>typeof r=="string"):void 0;return{consentSatisfied:!!e.consent_satisfied,requiresMfa:!!e.requires_mfa,mfaMethods:t,uncoveredScopes:n,error:typeof e.error=="string"?e.error:void 0,errorDescription:typeof e.error_description=="string"?e.error_description:void 0,code:typeof e.code=="string"?e.code:void 0,redirectUri:typeof e.redirect_uri=="string"?e.redirect_uri:void 0,state:typeof e.state=="string"?e.state:void 0}}function fe(e){if(!e.password&&!e.accessToken)throw new i("invalid_request","Either password or accessToken is required for consent.")}function le(e,t){let n=new URL(e),r=new URL(t);if(n.origin!==r.origin||n.pathname!=="/oauth/authorize/consent")throw new i("invalid_request","callbackUrl must target /oauth/authorize/consent on the configured auth server.");return n}function me(e){async function t(r,o){fe(o);let s=le(r,e.config.baseUrl),a={username:o.username,approved:o.approved??!0,requested_scopes:o.requestedScopes,mfa_code:o.mfaCode,mfa_backup_code:o.mfaBackupCode};o.password!==void 0&&(a.password=o.password);let u=await e.http.request(`${s.pathname}${s.search}`,{method:"POST",token:o.accessToken,body:a}),p=Le(u);if(p.error==="mfa_required")throw new C(p.errorDescription??"Multi-factor authentication is required",p.mfaMethods??[],void 0,u);return p}async function n(r,o){fe(o);let s=le(r,e.config.baseUrl),a={username:o.username,requested_scopes:o.requestedScopes,mfa_code:o.mfaCode,mfa_backup_code:o.mfaBackupCode};o.password!==void 0&&(a.password=o.password);let u=await e.http.request(`${s.pathname}/evaluate${s.search}`,{method:"POST",token:o.accessToken,body:a}),p=Je(u);if(p.error==="mfa_required"||p.requiresMfa)throw new C(p.errorDescription??"Multi-factor authentication is required",p.mfaMethods??[],void 0,u);return p}return{consent:t,evaluateConsent:n,consentWithCredentials(r){return t(r.callbackUrl,{username:r.username,password:r.password,accessToken:r.accessToken,approved:r.approved,requestedScopes:r.requestedScopes,mfaCode:r.mfaCode,mfaBackupCode:r.mfaBackupCode})},evaluateConsentWithCredentials(r){return n(r.callbackUrl,{username:r.username,password:r.password,accessToken:r.accessToken,requestedScopes:r.requestedScopes,mfaCode:r.mfaCode,mfaBackupCode:r.mfaBackupCode})}}}function he(e){return{async publicClient(t){let n=await e.http.request(`/oauth/clients/${encodeURIComponent(t)}/public`);return Be(n)},async discoverAuthorizationServer(){let t=await e.http.request("/.well-known/oauth-authorization-server");return{issuer:String(t.issuer??""),authorizationEndpoint:String(t.authorization_endpoint??""),tokenEndpoint:String(t.token_endpoint??""),jwksUri:typeof t.jwks_uri=="string"?t.jwks_uri:void 0,revocationEndpoint:typeof t.revocation_endpoint=="string"?t.revocation_endpoint:void 0,introspectionEndpoint:typeof t.introspection_endpoint=="string"?t.introspection_endpoint:void 0,registrationEndpoint:typeof t.registration_endpoint=="string"?t.registration_endpoint:void 0,scopesSupported:Array.isArray(t.scopes_supported)?t.scopes_supported.filter(n=>typeof n=="string"):void 0,responseTypesSupported:Array.isArray(t.response_types_supported)?t.response_types_supported.filter(n=>typeof n=="string"):[],grantTypesSupported:Array.isArray(t.grant_types_supported)?t.grant_types_supported.filter(n=>typeof n=="string"):void 0,codeChallengeMethodsSupported:Array.isArray(t.code_challenge_methods_supported)?t.code_challenge_methods_supported.filter(n=>typeof n=="string"):void 0,pushedAuthorizationRequestEndpoint:typeof t.pushed_authorization_request_endpoint=="string"?t.pushed_authorization_request_endpoint:void 0,requirePushedAuthorizationRequests:typeof t.require_pushed_authorization_requests=="boolean"?t.require_pushed_authorization_requests:void 0,authorizationDetailsTypesSupported:Array.isArray(t.authorization_details_types_supported)?t.authorization_details_types_supported.filter(n=>typeof n=="string"):void 0,raw:t}}}}var G="openid profile email offline_access",ge=new Set(["response_type","client_id","redirect_uri","scope","state","nonce","code_challenge","code_challenge_method","prompt","max_age","acr_values","login_hint","authorization_details","request_uri"]);function He(e){if(typeof e=="string"){let t=e.indexOf("?"),n=t>=0?e.slice(t+1):e;return new URLSearchParams(n)}return e instanceof URLSearchParams?e:"raw"in e&&typeof e.raw=="object"?new URLSearchParams(e.raw):new URLSearchParams(e)}function ye(e){let t=He(e),n={};return t.forEach((r,o)=>{n[o]=r}),{code:t.get("code")??void 0,state:t.get("state")??void 0,error:t.get("error")??void 0,errorDescription:t.get("error_description")??void 0,errorUri:t.get("error_uri")??void 0,raw:n}}function _e(e){let t=N(e);return{...me(e),async createRequest(r){if(!r.redirectUri?.trim())throw new i("invalid_request","redirectUri is required to build an authorization request.");let o=r.scope??G,s=r.state??j(),a=r.nonce??M(),u=await $(r.codeChallengeMethod??"S256"),p=r.authorizationEndpoint??`${e.config.baseUrl}/oauth/authorize`,R=new URL(p),l=R.searchParams;if(l.set("response_type",r.responseType??"code"),l.set("client_id",e.config.clientId),l.set("redirect_uri",r.redirectUri),l.set("scope",o),l.set("state",s),l.set("nonce",a),l.set("code_challenge",u.codeChallenge),l.set("code_challenge_method",u.codeChallengeMethod),r.authorizationDetails&&r.authorizationDetails.length>0&&l.set("authorization_details",JSON.stringify(r.authorizationDetails)),r.prompt&&l.set("prompt",r.prompt),r.maxAge!=null){if(!Number.isSafeInteger(r.maxAge)||r.maxAge<0)throw new i("invalid_request","maxAge must be a non-negative integer.");l.set("max_age",String(r.maxAge))}r.acrValues?.length&&l.set("acr_values",r.acrValues.join(" ")),r.loginHint&&l.set("login_hint",r.loginHint);for(let[A,I]of Object.entries(r.extraParams??{})){if(ge.has(A))throw new i("invalid_request",`extraParams may not override the reserved authorization parameter "${A}".`);l.set(A,I)}return{url:R.toString(),state:s,nonce:a,codeVerifier:u.codeVerifier,codeChallenge:u.codeChallenge,redirectUri:r.redirectUri,scope:o}},async pushAuthorizationRequest(r){if(!r.redirectUri?.trim())throw new i("invalid_request","redirectUri is required to push an authorization request.");let o=r.scope??G,s=r.state??j(),a=r.nonce??M(),u=await $(r.codeChallengeMethod??"S256"),p={response_type:r.responseType??"code",client_id:e.config.clientId,redirect_uri:r.redirectUri,scope:o,state:s,nonce:a,code_challenge:u.codeChallenge,code_challenge_method:u.codeChallengeMethod};if(e.config.clientSecret&&(p.client_secret=e.config.clientSecret),r.authorizationDetails&&r.authorizationDetails.length>0&&(p.authorization_details=JSON.stringify(r.authorizationDetails)),r.prompt&&(p.prompt=r.prompt),r.maxAge!=null){if(!Number.isSafeInteger(r.maxAge)||r.maxAge<0)throw new i("invalid_request","maxAge must be a non-negative integer.");p.max_age=String(r.maxAge)}r.acrValues?.length&&(p.acr_values=r.acrValues.join(" "));for(let[g,c]of Object.entries(r.extraParams??{})){if(ge.has(g))throw new i("invalid_request",`extraParams may not override the reserved authorization parameter "${g}".`);p[g]=c}let R=await e.http.request("/oauth/par",{method:"POST",body:p}),l=R.request_uri,A=R.expires_in;if(typeof l!="string"||typeof A!="number")throw new i("unknown_error","Unexpected pushed authorization response payload.",{details:R});let I=r.authorizationEndpoint??`${e.config.baseUrl}/oauth/authorize`,f=new URL(I);return f.searchParams.set("client_id",e.config.clientId),f.searchParams.set("request_uri",l),{requestUri:l,expiresIn:A,request:{url:f.toString(),state:s,nonce:a,codeVerifier:u.codeVerifier,codeChallenge:u.codeChallenge,redirectUri:r.redirectUri,scope:o,requestUri:l}}},parseCallback:ye,async probeAuthorize(r){let o=await e.http.requestRaw(r,{headers:{Accept:"application/json"},redirect:"manual"}),s=o.headers.get("location");if(s&&o.status>=300&&o.status<400)return{kind:"redirect",location:s,status:o.status};let a=await o.text(),u=a?JSON.parse(a):{};if(!o.ok)throw new i("server_error",typeof u.error_description=="string"?u.error_description:"Authorization probe failed",{status:o.status,details:u});let p=typeof u.authorization_url=="string"?u.authorization_url:void 0;return{kind:"json",status:o.status,authorizationUrl:p,payload:u}},async handleCallback(r){let s=typeof r.callback=="object"&&!(r.callback instanceof URLSearchParams)&&"raw"in r.callback?r.callback:ye(r.callback);if(s.error)throw new i("access_denied",s.errorDescription??`Authorization failed: ${s.error}`,{details:s.raw});if(!s.state||!W(s.state,r.expectedState))throw new i("invalid_request","State mismatch on authorization callback (possible CSRF).");if(!s.code)throw new i("invalid_request","Authorization callback is missing the `code` parameter.");return t.authorizationCode({code:s.code,redirectUri:r.redirectUri,codeVerifier:r.codeVerifier,scope:r.scope})}}}function E(e){let t=e.step_id,n=e.step_type,r=e.name,o=e.order,s=e.is_required,a=e.timeout;if(typeof t!="string"||typeof n!="string"||typeof r!="string"||typeof o!="number"||typeof s!="boolean"||typeof a!="number")throw new i("unknown_error","Unexpected flow step payload.",{details:e});return{stepId:t,stepType:n,name:r,description:typeof e.description=="string"?e.description:void 0,order:o,isRequired:s,timeout:a,inputSchema:e.input_schema&&typeof e.input_schema=="object"?e.input_schema:void 0}}function K(e){let t=e.current_step,n=e.remaining_steps;if(!t||typeof t!="object")throw new i("unknown_error","Missing current_step in flow response.",{details:e});let r=e.continuation_token;if(typeof r!="string"||!r)throw new i("unknown_error","Missing continuation_token in flow response.",{details:e});return{sessionId:String(e.session_id??""),flowId:String(e.flow_id??""),flowName:String(e.flow_name??""),currentStep:E(t),remainingSteps:Array.isArray(n)?n.filter(o=>typeof o=="object"&&o!==null).map(E):[],expiresAt:String(e.expires_at??""),continuationToken:r}}function Ve(e){return{sessionId:String(e.session_id??""),status:String(e.status??""),purpose:String(e.purpose??""),currentStep:e.current_step&&typeof e.current_step=="object"?E(e.current_step):void 0,completedSteps:Array.isArray(e.completed_steps)?e.completed_steps.filter(t=>typeof t=="string"):[],remainingSteps:Array.isArray(e.remaining_steps)?e.remaining_steps.filter(t=>typeof t=="object"&&t!==null).map(E):[],failedAttempts:typeof e.failed_attempts=="number"?e.failed_attempts:0,expiresAt:String(e.expires_at??""),lastActivityAt:String(e.last_activity_at??"")}}async function Y(e,t){let n=t.status;if(n==="error"||typeof t.error=="string"&&n!=="in_progress"&&n!=="complete")throw new i("invalid_request",typeof t.error=="string"?t.error:"Flow step failed.",{details:t});if(n==="in_progress"){let r=t.next_step,o=t.remaining_steps;if(!r||typeof r!="object")throw new i("unknown_error","Missing next_step in flow step response.",{details:t});let s=t.debug&&typeof t.debug=="object"&&!Array.isArray(t.debug)?t.debug:void 0;return{status:"in_progress",sessionId:String(t.session_id??""),nextStep:E(r),completedSteps:Array.isArray(t.completed_steps)?t.completed_steps.filter(u=>typeof u=="string"):[],remainingSteps:Array.isArray(o)?o.filter(u=>typeof u=="object"&&u!==null).map(E):[],debug:s}}if(n==="complete"){let r=b(t);return await e.setTokens(r),{status:"complete",accessToken:r.accessToken,refreshToken:r.refreshToken,tokenType:r.tokenType,expiresIn:r.expiresIn,scope:r.scope,amr:Array.isArray(t.amr)?t.amr.filter(s=>typeof s=="string"):[],acr:typeof t.acr=="string"?t.acr:""}}throw new i("unknown_error","Unknown flow step status.",{details:t})}function ke(e){return{async initAuthentication(t){let n=await e.http.request("/oauth/authenticate/init",{method:"POST",body:y({client_id:t?.clientId??e.config.clientId,username:t?.username,flow_type:t?.flowType})});return K(n)},async executeStep(t){let n=await e.http.request("/oauth/authenticate/step",{method:"POST",body:{session_id:t.sessionId,step_id:t.stepId,continuation_token:t.continuationToken,credential:t.credential}});return Y(e,n)},async initRegistration(t){let n=await e.http.request("/oauth/register/init",{method:"POST",body:y({client_id:t?.clientId??e.config.clientId,invite_code:t?.inviteCode,flow_type:t?.flowType})});return K(n)},async executeRegistrationStep(t){let n=await e.http.request("/oauth/register/step",{method:"POST",body:{session_id:t.sessionId,step_id:t.stepId,continuation_token:t.continuationToken,credential:t.credential}});return Y(e,n)},async getStatus(t,n){let r=new URLSearchParams({continuation_token:n}),o=await e.http.request(`/oauth/flow/status/${encodeURIComponent(t)}?${r}`);return Ve(o)},async initRecovery(t){let n=await e.http.request("/oauth/recovery/init",{method:"POST",body:y({client_id:t?.clientId??e.config.clientId,recovery_type:t?.recoveryType,redirect_uri:t?.redirectUri})});return K(n)},async executeRecoveryStep(t){let n=await e.http.request("/oauth/recovery/step",{method:"POST",body:{session_id:t.sessionId,step_id:t.stepId,continuation_token:t.continuationToken,credential:t.credential}});return Y(e,n)}}}function F(e){return Array.isArray(e)?e.filter(t=>typeof t=="string"):[]}function Qe(e){return{issuer:String(e.issuer??""),authorizationEndpoint:String(e.authorization_endpoint??""),tokenEndpoint:String(e.token_endpoint??""),userinfoEndpoint:typeof e.userinfo_endpoint=="string"?e.userinfo_endpoint:void 0,jwksUri:String(e.jwks_uri??""),revocationEndpoint:typeof e.revocation_endpoint=="string"?e.revocation_endpoint:void 0,introspectionEndpoint:typeof e.introspection_endpoint=="string"?e.introspection_endpoint:void 0,responseTypesSupported:F(e.response_types_supported),grantTypesSupported:F(e.grant_types_supported),scopesSupported:F(e.scopes_supported),codeChallengeMethodsSupported:F(e.code_challenge_methods_supported),raw:e}}function We(e){return{sub:String(e.sub??""),name:typeof e.name=="string"?e.name:void 0,givenName:typeof e.given_name=="string"?e.given_name:void 0,familyName:typeof e.family_name=="string"?e.family_name:void 0,middleName:typeof e.middle_name=="string"?e.middle_name:void 0,nickname:typeof e.nickname=="string"?e.nickname:void 0,preferredUsername:typeof e.preferred_username=="string"?e.preferred_username:void 0,profile:typeof e.profile=="string"?e.profile:void 0,picture:typeof e.picture=="string"?e.picture:void 0,website:typeof e.website=="string"?e.website:void 0,gender:typeof e.gender=="string"?e.gender:void 0,birthdate:typeof e.birthdate=="string"?e.birthdate:void 0,zoneinfo:typeof e.zoneinfo=="string"?e.zoneinfo:void 0,locale:typeof e.locale=="string"?e.locale:void 0,updatedAt:typeof e.updated_at=="number"?e.updated_at:void 0,email:typeof e.email=="string"?e.email:void 0,emailVerified:typeof e.email_verified=="boolean"?e.email_verified:void 0,phoneNumber:typeof e.phone_number=="string"?e.phone_number:void 0,phoneNumberVerified:typeof e.phone_number_verified=="boolean"?e.phone_number_verified:void 0}}function be(e){return{async userInfo(){let t=await m(e),n=await e.http.request("/oauth/userinfo",{token:t});return We(n)},async discover(){let t=await e.http.request("/.well-known/openid-configuration");return Qe(t)},async jwks(){let t=await e.http.request("/oauth/jwks");return{keys:Array.isArray(t.keys)?t.keys.filter(r=>typeof r=="object"&&r!==null):[]}}}}function L(e){return{...e,id:typeof e.id=="string"?e.id:"",organizationId:typeof e.organization_id=="string"?e.organization_id:void 0,email:typeof e.email=="string"?e.email:void 0,emailVerified:typeof e.email_verified=="boolean"?e.email_verified:void 0,username:typeof e.username=="string"?e.username:void 0,firstName:typeof e.first_name=="string"?e.first_name:void 0,lastName:typeof e.last_name=="string"?e.last_name:void 0,isActive:typeof e.is_active=="boolean"?e.is_active:void 0,isVerified:typeof e.is_verified=="boolean"?e.is_verified:void 0,mfaEnabled:typeof e.mfa_enabled=="boolean"?e.mfa_enabled:void 0}}var Ge=new Set(["created","processing","active","deleted"]);function Re(e){if(typeof e!="object"||e===null||Array.isArray(e))throw new w("Malformed avatar asset response.",e);let t=e,n=t.state,r=s=>s===null||typeof s>"u"||typeof s=="string",o=s=>s===null||typeof s>"u"||typeof s=="number"&&Number.isSafeInteger(s)&&s>=0;if(typeof t.id!="string"||t.id.length===0||typeof n!="string"||!Ge.has(n)||typeof t.is_active!="boolean"||t.is_active!==(n==="active")||typeof t.is_managed!="boolean"||!r(t.content_type)||!o(t.byte_count)||!r(t.created_at)||!r(t.deleted_at))throw new w("Malformed avatar asset response.",t);return{id:t.id,state:n,isActive:t.is_active,isManaged:t.is_managed,contentType:typeof t.content_type=="string"?t.content_type:void 0,byteCount:typeof t.byte_count=="number"?t.byte_count:void 0,createdAt:typeof t.created_at=="string"?t.created_at:void 0,deletedAt:typeof t.deleted_at=="string"?t.deleted_at:void 0}}function we(e){if(typeof e!="object"||e===null||Array.isArray(e))throw new w("Malformed primary-email verification response.",e);let t=e,n=t.status,r=t.email,o=t.email_verified;if(!((n==="sent"||n==="verified")&&typeof r=="string"&&r.length>0&&typeof o=="boolean"&&(n==="sent"&&o===!1||n==="verified"&&o===!0)))throw new w("Malformed primary-email verification response.",t);return{status:n,email:r,emailVerified:o}}function X(e){return{id:String(e.id??""),name:typeof e.name=="string"?e.name:void 0,email:typeof e.email=="string"?e.email:void 0,phoneNumber:typeof e.phone_number=="string"?e.phone_number:void 0,relationship:typeof e.relationship=="string"?e.relationship:void 0,isVerified:typeof e.is_verified=="boolean"?e.is_verified:void 0}}function Ke(e){return{id:String(e.id??""),provider:String(e.provider??""),providerUserId:typeof e.provider_user_id=="string"?e.provider_user_id:void 0,email:typeof e.email=="string"?e.email:void 0,isPrimary:typeof e.is_primary=="boolean"?e.is_primary:void 0}}function ve(e){return Ye(e)}function Ye(e){return{async me(){let t=await m(e),n=await e.http.request("/api/v1/users/me",{token:t});return L(n)},async updateMe(t){let n=await h(e,"/api/v1/users/me",{method:"PATCH",body:t});return L(n)},emailVerification:{async start(t){let n=await h(e,"/api/v1/users/me/email/verify",{method:"POST",body:{email:t.email}});return we(n)},async verify(t){let n=await h(e,"/api/v1/users/me/email/verify",{method:"POST",body:{code:t.code}});return we(n)}},async changePassword(t){await v(e,"/api/v1/users/me/password",{method:"POST",body:{current_password:t.currentPassword,new_password:t.newPassword}})},async deleteAvatar(){let t=await h(e,"/api/v1/users/me/avatar",{method:"DELETE"});return L(t)},async uploadAvatar(t,n="avatar.jpg"){let r=await m(e),o=new FormData,s=t instanceof Blob?t:new Blob([new Uint8Array(t)]);o.append("avatar",s,n);let a=await e.http.request("/api/v1/users/me/avatar",{method:"POST",token:r,body:o});return L(a)},avatarAssets:{async list(){let t=await h(e,"/api/v1/users/me/avatar/assets");if(!Array.isArray(t))throw new w("Malformed avatar asset list response.",t);return t.map(Re)},async select(t){let n=await h(e,`/api/v1/users/me/avatar/assets/${S(t,"assetId")}/select`,{method:"POST"});return Re(n)},async delete(t){await v(e,`/api/v1/users/me/avatar/assets/${S(t,"assetId")}`,{method:"DELETE"})}},async verifyPhone(t){return h(e,"/api/v1/users/me/phone/verify",{method:"POST",body:t})},async verifyRecoveryEmail(t){return h(e,"/api/v1/users/me/recovery-email/verify",{method:"POST",body:t})},async securityOverview(){return h(e,"/api/v1/users/me/security-overview")},recoveryContacts:{async list(){let t=await h(e,"/api/v1/users/me/recovery-contacts");return Array.isArray(t)?t.filter(n=>typeof n=="object"&&n!==null).map(X):[]},async create(t){let n=await h(e,"/api/v1/users/me/recovery-contacts",{method:"POST",body:t});return X(n)},async update(t,n){let r=await h(e,`/api/v1/users/me/recovery-contacts/${S(t,"contactId")}`,{method:"PATCH",body:n});return X(r)},async delete(t){await v(e,`/api/v1/users/me/recovery-contacts/${S(t,"contactId")}`,{method:"DELETE"})}},identities:{async list(){let t=await h(e,"/api/v1/users/me/identities");return Array.isArray(t)?t.filter(n=>typeof n=="object"&&n!==null).map(Ke):[]},async unlink(t){await v(e,`/api/v1/users/me/identities/${S(t,"identityId")}`,{method:"DELETE"})},async setPrimary(t){return h(e,`/api/v1/users/me/identities/${S(t,"identityId")}/primary`,{method:"PATCH"})}},async deleteMe(t){await v(e,"/api/v1/users/me",{method:"DELETE",body:{password:t.password}})}}}function P(e){return e&&typeof e=="object"?e:{}}function Se(e){return{async registerBegin(t){let n=await m(e),r=await e.http.request("/api/v1/users/me/webauthn/register/begin",{method:"POST",token:n,body:t});return P(r)},async registerFinish(t){let n=await m(e),r=await e.http.request("/api/v1/users/me/webauthn/register/finish",{method:"POST",token:n,body:t});return P(r)},async authenticateBegin(t){let n=await e.http.request("/api/v1/users/me/webauthn/authenticate/begin",{method:"POST",body:t});return P(n)},async authenticateFinish(t){let n=await e.http.request("/api/v1/users/me/webauthn/authenticate/finish",{method:"POST",body:t}),r=P(n);if(typeof r.access_token=="string"){let o=b(r);await e.setTokens(o)}return r},async credentials(){let t=await m(e),n=await e.http.request("/api/v1/users/me/webauthn/credentials",{token:t});return P(n)},async removeCredential(t,n){let r=await m(e);await e.http.request(`/api/v1/users/me/webauthn/credentials/${encodeURIComponent(t)}`,{method:"DELETE",token:r,body:n?.password?{password:n.password}:void 0})},async renameCredential(t,n){let r=await m(e),o=await e.http.request(`/api/v1/users/me/webauthn/credentials/${encodeURIComponent(t)}/rename`,{method:"PATCH",token:r,body:n});return P(o)},async setPrimaryCredential(t){let n=await m(e),r=await e.http.request(`/api/v1/users/me/webauthn/credentials/${encodeURIComponent(t)}/primary`,{method:"POST",token:n});return P(r)}}}function Xe(e){return{isActive:!!(e.is_active??e.isActive),message:typeof e.message=="string"?e.message:void 0,scheduledStart:typeof e.scheduled_start=="string"?e.scheduled_start:void 0,scheduledEnd:typeof e.scheduled_end=="string"?e.scheduled_end:void 0,...e}}function Ae(e){return{async status(){let t=await e.http.request("/api/v1/maintenance/status");return Xe(t)}}}var Ze=128,Z=254;function T(e,t){if(typeof e!="string")return;let n=e.trim();if(!(!n||n.length>t))return n}function Te(e){return{async request(t){let n=y({email:T(t.email,Z),identifier:T(t.identifier,Z),recovery_email:T(t.recoveryEmail,Z),phone_number:T(t.phoneNumber,32),member_id:T(t.memberId,64),security_question:T(t.securityQuestion,256),security_answer:T(t.securityAnswer,256),redirect_uri:T(t.redirectUri,2048)}),r=await e.http.request("/oauth/password/reset/request",{method:"POST",body:n});return{ok:!!r.ok,message:String(r.message??"")}},async validate(t){let n=t.trim();if(!n)throw new i("invalid_request","token is required.");let r=await e.http.request(`/oauth/password/reset/validate?token=${encodeURIComponent(n)}`);return{ok:!!r.ok,valid:!!r.valid,expiresAt:typeof r.expires_at=="string"?r.expires_at:void 0}},async complete(t){if(!t.token.trim())throw new i("invalid_request","token is required.");if(!t.password||t.password.length>Ze)throw new i("invalid_request","password is required and must be <= 128 characters.");let n=await e.http.request("/oauth/password/reset/complete",{method:"POST",body:{token:t.token.trim(),password:t.password,password_confirm:t.passwordConfirm}});return{ok:!!n.ok,message:String(n.message??"")}}}}function et(e){return{id:String(e.id??""),deviceName:String(e.device_name??""),isVerified:!!e.is_verified,isPrimary:!!e.is_primary,createdAt:typeof e.created_at=="string"?e.created_at:void 0,lastUsedAt:typeof e.last_used_at=="string"?e.last_used_at:void 0}}function Ce(e){return{async setup(t){let n=t.deviceName?.trim();if(!n||n.length>128)throw new i("invalid_request","deviceName is required.");let r=await h(e,"/api/v1/users/me/mfa/setup",{method:"POST",body:{device_name:n}});return{deviceId:String(r.device_id??""),secret:String(r.secret??""),qrCodeUri:String(r.qr_code_uri??""),backupCodes:Array.isArray(r.backup_codes)?r.backup_codes.filter(o=>typeof o=="string"):[],algorithm:String(r.algorithm??""),digits:typeof r.digits=="number"?r.digits:6,period:typeof r.period=="number"?r.period:30}},async verify(t){return h(e,"/api/v1/users/me/mfa/verify",{method:"POST",body:{device_id:t.deviceId,totp_code:t.totpCode}})},async devices(){let t=await h(e,"/api/v1/users/me/mfa/devices");return Array.isArray(t)?t.filter(n=>typeof n=="object"&&n!==null).map(et):[]},async removeDevice(t){await v(e,`/api/v1/users/me/mfa/devices/${S(t,"deviceId")}`,{method:"DELETE"})},async regenerateBackupCodes(){let t=await h(e,"/api/v1/users/me/mfa/backup-codes/regenerate",{method:"POST"});return{backupCodes:Array.isArray(t.backup_codes)?t.backup_codes.filter(n=>typeof n=="string"):[]}},async disable(t){await v(e,"/api/v1/users/me/mfa/disable",{method:"POST",body:y({password:t?.password})})}}}var Pe="https://prod-auth.tktchurch.com",tt=new Set(["localhost","127.0.0.1","[::1]"]);function nt(e){return tt.has(e.toLowerCase())}function ee(e,t={}){let n=(e??Pe).trim();if(!n)return ee(Pe,t);let o=(/^https?:\/\//i.test(n)?n:`https://${n}`).replace(/\/+$/,""),s;try{s=new URL(o)}catch{throw new i("invalid_request","baseUrl must be a valid absolute URL.")}if(s.protocol==="http:"&&!t.allowInsecureTransport&&!nt(s.hostname))throw new i("invalid_request","baseUrl must use HTTPS. Set allowInsecureTransport=true only for trusted local development.");return o}function rt(e,t){if(!e&&t<=0)return{cleanup:()=>{}};let n=new AbortController,r,o=()=>{n.abort(e?.reason)};return e&&(e.aborted?n.abort(e.reason):e.addEventListener("abort",o,{once:!0})),t>0&&(r=setTimeout(()=>{n.abort(new Error(`Request timed out after ${t}ms`))},t)),{signal:n.signal,cleanup:()=>{r&&clearTimeout(r),e&&e.removeEventListener("abort",o)}}}function ot(e,t){return/^https?:\/\//i.test(t)?t:`${e}${t.startsWith("/")?t:`/${t}`}`}function st(e,t,n){let r=new Headers(e);return t&&new Headers(t).forEach((s,a)=>{r.set(a,s)}),n&&!r.has("Authorization")&&r.set("Authorization",`Bearer ${n}`),r}function Ee(e){return{async request(t,n){let r=await this.requestRaw(t,n);if(!r.ok)throw await se(r);if(r.status===204)return;let o=await r.text();if(o)return JSON.parse(o)},async requestRaw(t,n){let r=ot(e.baseUrl,t),o=st(e.defaultHeaders,n?.headers,n?.token),{signal:s,cleanup:a}=rt(n?.signal,e.timeoutMs),u={method:n?.method??"GET",headers:o,signal:s,redirect:n?.redirect};typeof n?.body<"u"&&(n.body instanceof FormData?u.body=n.body:(o.has("Content-Type")||o.set("Content-Type","application/json"),u.body=JSON.stringify(n.body)));try{return await e.fetch(r,u)}catch(p){throw s?.aborted??!1?new i("timeout",`Request timed out: ${r}`,{details:p}):new i("network_error",`Network request failed: ${r}`,{details:p})}finally{a()}}}}var it="@tktchurch/auth/tokens",ne=32768,Oe=new Set(["accessToken","tokenType","expiresIn","refreshToken","scope","idToken","authorizationDetails"]),Ie=new Set(["__proto__","constructor","prototype"]),at=/^[\w@./:-]{1,128}$/;function re(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function qe(e){return typeof e=="string"&&e.length>0}function te(e){return typeof e=="string"&&e.length>0?e:void 0}function x(e){if(!re(e))throw new i("invalid_request","Token storage rejected a non-object token payload.");for(let s of Object.keys(e))if(!Oe.has(s))throw new i("invalid_request",`Token storage rejected unknown field "${s}".`);if(!qe(e.accessToken))throw new i("invalid_request","Token storage requires a non-empty accessToken.");if(!qe(e.tokenType))throw new i("invalid_request","Token storage requires a non-empty tokenType.");if(typeof e.expiresIn!="number"||!Number.isFinite(e.expiresIn)||e.expiresIn<0)throw new i("invalid_request","Token storage requires a non-negative numeric expiresIn.");let t={accessToken:e.accessToken,tokenType:e.tokenType,expiresIn:e.expiresIn},n=te(e.refreshToken);n&&(t.refreshToken=n);let r=te(e.scope);r&&(t.scope=r);let o=te(e.idToken);if(o&&(t.idToken=o),typeof e.authorizationDetails<"u"){if(!Array.isArray(e.authorizationDetails)||!e.authorizationDetails.every(s=>re(s)&&typeof s.type=="string"))throw new i("invalid_request","Token storage rejected malformed authorizationDetails.");t.authorizationDetails=e.authorizationDetails}return t}function ut(e){if(!re(e))return null;for(let t of Object.keys(e))if(Ie.has(t)||!Oe.has(t))return null;try{return x(e)}catch{return null}}function dt(e){return JSON.parse(e,(t,n)=>{if(Ie.has(t))throw new SyntaxError("Forbidden JSON key in stored token payload.");return n})}function ct(e){let t=e.trim();if(!t)throw new i("invalid_request","Token storage key must be a non-empty string.");if(!at.test(t))throw new i("invalid_request","Token storage key contains unsupported characters or exceeds 128 characters.");return t}function pt(e){let t=x(e),n=JSON.stringify(t);if(n.length>ne)throw new i("invalid_request",`Serialized token payload exceeds ${ne} bytes.`);return n}var oe=class{state=null;get(){return this.state}set(t){if(!t){this.clear();return}this.state=x(t)}clear(){this.state=null}};function q(){return new oe}function ft(){return typeof window<"u"&&typeof window.document<"u"}function lt(e){if(e)return e;if(typeof globalThis.fetch=="function")return globalThis.fetch.bind(globalThis);throw new i("invalid_request","No fetch implementation available. Provide config.fetch.")}function Ue(e){if(!e.clientId?.trim())throw new i("invalid_request","clientId is required.");if(ft()&&e.clientSecret&&!e.allowClientSecretInBrowser)throw new i("invalid_request","clientSecret in browser runtime is blocked by default. Set allowClientSecretInBrowser=true to override.");return{clientId:e.clientId,clientSecret:e.clientSecret,baseUrl:ee(e.baseUrl,{allowInsecureTransport:e.allowInsecureTransport??!1}),fetch:lt(e.fetch),storage:e.storage??q(),timeoutMs:e.timeoutMs??15e3,defaultHeaders:e.defaultHeaders??{},autoRefresh:e.autoRefresh??!0,allowClientSecretInBrowser:e.allowClientSecretInBrowser??!1,allowInsecureTransport:e.allowInsecureTransport??!1}}function B(e){let t=Ue(e),n=Ee({baseUrl:t.baseUrl,fetch:t.fetch,timeoutMs:t.timeoutMs,defaultHeaders:t.defaultHeaders}),r=async()=>await t.storage.get(),o=async u=>{await t.storage.set(u)},s=async()=>{await t.storage.clear()},a={config:t,http:n,getTokens:r,setTokens:o,clearTokens:s};return{token:N(a),flow:ke(a),oidc:be(a),authorize:_e(a),user:ve(a),sessions:ie(a),webauthn:Se(a),passwordReset:Te(a),mfa:Ce(a),maintenance:Ae(a),oauth:he(a),tokens:{get:r,set:o,clear:s}}}var k=class extends Error{name="PhoneOtpError"};function O(e,t){return e instanceof k?e:z(e)?new k(e.message||t):e instanceof Error&&e.message?new k(e.message):new k(t)}function ze(e){if(!z(e)||typeof e.status!="number")return!1;let t=e.status;return t>=400&&t<500&&t!==429}function xe(e){if(e.status!=="in_progress"||!e.debug)return;let t=e.debug.verification_code;return typeof t=="string"?t:void 0}function mt(e){return{...e,id:typeof e.id=="string"?e.id:"",first_name:typeof e.first_name=="string"?e.first_name:typeof e.firstName=="string"?e.firstName:void 0,last_name:typeof e.last_name=="string"?e.last_name:typeof e.lastName=="string"?e.lastName:void 0,email:typeof e.email=="string"?e.email:void 0,phone_number:typeof e.phone_number=="string"?e.phone_number:typeof e.phoneNumber=="string"?e.phoneNumber:void 0,phone_country_code:typeof e.phone_country_code=="string"?e.phone_country_code:typeof e.phoneCountryCode=="string"?e.phoneCountryCode:void 0,phone_verified:typeof e.phone_verified=="boolean"?e.phone_verified:typeof e.phoneVerified=="boolean"?e.phoneVerified:void 0,profile_picture_url:typeof e.profile_picture_url=="string"?e.profile_picture_url:typeof e.profilePictureUrl=="string"?e.profilePictureUrl:void 0,picture:typeof e.picture=="string"?e.picture:void 0,birthdate:typeof e.birthdate=="string"?e.birthdate:void 0}}function ht(e){let t=e.auth,n=e.guestProfile??{firstName:"Guest",lastName:"User"},r=n.emailDomain??"registrations.tktchurch.local",o=n.emailLocalPrefix??"reg";function s(){return t||B({baseUrl:e.baseUrl,clientId:e.clientId,allowInsecureTransport:e.allowInsecureTransport,timeoutMs:e.timeoutMs??2e4,storage:q(),autoRefresh:!1})}async function a(f,g){let c=s(),d=await c.flow.initAuthentication({flowType:"phone_only",username:g}),_=await c.flow.executeStep({sessionId:d.sessionId,stepId:d.currentStep.stepId,continuationToken:d.continuationToken,credential:f});if(_.status!=="in_progress")throw new k("Unexpected sign-in response.");return{oauthSessionId:d.sessionId,verifyStepId:_.nextStep.stepId,mode:"authentication",continuationToken:d.continuationToken,debugCode:xe(_)}}async function u(f){let g=s(),c=await g.flow.initRegistration({flowType:"phone_registration"}),d=await g.flow.executeRegistrationStep({sessionId:c.sessionId,stepId:c.currentStep.stepId,continuationToken:c.continuationToken,credential:f});if(d.status!=="in_progress")throw new k("Unexpected verification response.");return{oauthSessionId:c.sessionId,verifyStepId:d.nextStep.stepId,mode:"registration",continuationToken:c.continuationToken,debugCode:xe(d)}}async function p(f,g,c){let d={phone_country_code:f,phone_number:g};try{return await u(d)}catch(_){if(!ze(_))throw O(_,"Could not send the code. Please try again.");try{return await a(d,c)}catch{throw O(_,"Could not send the code. Please try again.")}}}async function R(f,g,c){let d={phone_country_code:f,phone_number:g};try{return await a(d,c)}catch(_){if(!ze(_))throw O(_,"Could not start sign-in.");try{return await u(d)}catch{throw O(_,"Could not start sign-in.")}}}async function l(f,g,c,d,_,U){return g==="registration"?await f.flow.executeRegistrationStep({sessionId:c,stepId:d,continuationToken:_,credential:U}):await f.flow.executeStep({sessionId:c,stepId:d,continuationToken:_,credential:U})}async function A(f,g){let c=s();try{let d=await l(c,f.mode,f.oauthSessionId,f.verifyStepId,f.continuationToken,{code:g}),_=0;for(;d.status==="in_progress"&&_<6;){_++;let U=d.nextStep.stepType,J={};U==="profile_info"?J={first_name:n.firstName,last_name:n.lastName,email:`${o}+${f.oauthSessionId.slice(0,8).toLowerCase()}@${r}`}:U==="terms_acceptance"&&(J={accepted:"true"}),d=await l(c,f.mode,f.oauthSessionId,d.nextStep.stepId,f.continuationToken,J)}if(d.status!=="complete")throw new k("Sign-in did not complete. Please try again.");return{accessToken:d.accessToken,refreshToken:d.refreshToken,expiresIn:d.expiresIn}}catch(d){throw d instanceof k?d:O(d,"That code didn't match. Try again.")}}async function I(f){let g;if(t)g=t;else{let c=q();await c.set({accessToken:f,tokenType:"Bearer",expiresIn:3600}),g=B({baseUrl:e.baseUrl,clientId:e.clientId,allowInsecureTransport:e.allowInsecureTransport,timeoutMs:e.timeoutMs??2e4,storage:c,autoRefresh:!1})}try{let c=await g.user.me();return mt(c)}catch(c){throw O(c,c instanceof i?`Profile fetch failed (${c.status??"error"})`:"Profile fetch failed")}}return{startPhoneOTP:p,startPhoneOTPForSignIn:R,verifyPhoneOTP:A,fetchProfile:I}}var gt=/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;function yt(e,t="/"){if(!e)return t;let n=e;try{n=decodeURIComponent(e)}catch{return t}return n.startsWith("/")&&!n.startsWith("//")&&!n.startsWith("/\\")&&!gt.test(n)&&!/^\/+[\\/]/.test(n)?n:t}export{w as AuthContractError,i as AuthError,G as DEFAULT_AUTHORIZE_SCOPE,it as DEFAULT_TOKEN_STORAGE_KEY,ne as MAX_STORED_TOKEN_BYTES,C as MFARequiredAuthError,k as PhoneOtpError,x as assertValidAuthTokens,B as createAuthClient,q as createMemoryTokenStorage,ht as createPhoneOtpClient,ce as deriveCodeChallenge,de as generateCodeVerifier,M as generateNonce,$ as generatePkce,j as generateState,z as isAuthError,Me as isUnrecoverableAuthError,ut as parseStoredAuthTokens,D as randomUrlSafeString,dt as safeParseStoredJson,yt as sanitizePostAuthRedirect,pt as serializeAuthTokens,W as timingSafeEqual,ct as validateStorageKey};
|
package/dist/server.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var D=Object.defineProperty;var ce=Object.getOwnPropertyDescriptor;var le=Object.getOwnPropertyNames;var ue=Object.prototype.hasOwnProperty;var z=(e,t)=>()=>(e&&(t=e(e=0)),t);var K=(e,t)=>{for(var n in t)D(e,n,{get:t[n],enumerable:!0})},de=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of le(t))!ue.call(e,o)&&o!==n&&D(e,o,{get:()=>t[o],enumerable:!(r=ce(t,o))||r.enumerable});return e};var me=e=>de(D({},"__esModule",{value:!0}),e);var l,w=z(()=>{"use strict";l=class extends Error{code;status;details;constructor(t,n,r){super(n),this.name="AuthError",this.code=t,this.status=r?.status,this.details=r?.details}}});var B={};K(B,{sealJson:()=>p,sealString:()=>T,unsealJson:()=>S,unsealString:()=>O});function q(){let e=globalThis.crypto;if(!e?.subtle||!e.getRandomValues)throw new l("server_error","Web Crypto API is unavailable; session seal requires globalThis.crypto.subtle.");return e}function R(e){let t="";for(let n of e)t+=String.fromCharCode(n);if(typeof btoa!="function")throw new l("server_error","base64UrlEncode requires globalThis.btoa.");return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function L(e){let t=e.replace(/-/g,"+").replace(/_/g,"/"),n=t.length%4===0?"":"=".repeat(4-t.length%4);if(typeof atob!="function")throw new l("server_error","base64UrlDecode requires globalThis.atob.");let r=atob(t+n),o=new Uint8Array(r.length);for(let s=0;s<r.length;s++)o[s]=r.charCodeAt(s);return o}async function X(e){let t=H.get(e);return t||(t=(async()=>{let n=q(),r=await n.subtle.digest("SHA-256",new TextEncoder().encode(e));return n.subtle.importKey("raw",r,{name:"AES-GCM"},!1,["encrypt","decrypt"])})(),H.set(e,t),t)}async function T(e,t){if(!e||e.length<16)throw new l("invalid_request","Session secret must be at least 16 characters.");let n=q(),r=await X(e),o=n.getRandomValues(new Uint8Array(J)),s=new Uint8Array(await n.subtle.encrypt({name:"AES-GCM",iv:o},r,new TextEncoder().encode(t)));if(s.length<E)throw new l("server_error","AES-GCM encrypt produced empty output.");let c=s.slice(0,s.length-E),u=s.slice(s.length-E);return[R(o),R(u),R(c)].join(".")}async function O(e,t){let n=t.split(".");if(n.length!==3||!n[0]||!n[1]||!n[2])throw new l("invalid_token","Invalid sealed session format.");let r=L(n[0]),o=L(n[1]),s=L(n[2]);if(r.length!==J||o.length!==E)throw new l("invalid_token","Invalid sealed session parts.");let c=new Uint8Array(s.length+o.length);c.set(s),c.set(o,s.length);let u=q(),x=await X(e);try{let a=await u.subtle.decrypt({name:"AES-GCM",iv:r},x,c);return new TextDecoder().decode(a)}catch{throw new l("invalid_token","Failed to decrypt session.")}}async function p(e,t){return T(e,JSON.stringify(t))}async function S(e,t){return JSON.parse(await O(e,t))}var J,E,H,A=z(()=>{"use strict";w();J=12,E=16;H=new Map});var ye={};K(ye,{DEFAULT_APP_MAX_AGE_SECONDS:()=>G,DEFAULT_MAX_SESSION_COOKIE_VALUE_LENGTH:()=>P,DEFAULT_SSO_COOKIE_NAME:()=>k,DEFAULT_SSO_MAX_AGE_SECONDS:()=>h,DEFAULT_TRANSIENT_MAX_AGE_SECONDS:()=>$,MemorySidStore:()=>v,RedisSidStore:()=>N,clearCookieOptions:()=>C,compactSession:()=>F,compactSessionForSeal:()=>I,cookieOptions:()=>d,createOpaqueSid:()=>V,createSessionCookies:()=>oe,expireLegacyParentDomainCookies:()=>b,formatCookieAttributes:()=>M,hasSsoCookieHint:()=>ie,mintOpaqueSsoSession:()=>Q,mintSealedSsoCookieValue:()=>j,readCookieHeader:()=>re,readSealedSsoCookieValue:()=>Y,resolveSsoParentDomain:()=>_,revokeSsoSession:()=>Z,sealJson:()=>p,sealString:()=>T,serializeSetCookie:()=>m,slimUserForCookie:()=>f,ssoClearCookieHeader:()=>te,ssoCookieOptions:()=>ne,ssoSetCookieHeader:()=>ee,startSilentAuthorize:()=>se,unsealJson:()=>S,unsealString:()=>O});module.exports=me(ye);var k="tkt_sid",h=2592e3,$=600,G=2592e3;function _(e,t){if(t)return t;let n=((e||"").split(":")[0]||"").toLowerCase();if(n){if(n==="tktchurch.com"||n.endsWith(".tktchurch.com"))return"tktchurch.com";if(n==="tktchurch.net"||n.endsWith(".tktchurch.net"))return"tktchurch.net"}}function d(e){let t=e.allowInsecure===!0,n=e.secure??!t,r=e.sameSite??"lax",o=e.path??"/",s={httpOnly:!0,secure:n,sameSite:r,path:o};if(typeof e.maxAge=="number"?s.maxAge=e.maxAge:e.kind==="transient"?s.maxAge=600:e.kind==="sso"&&(s.maxAge=2592e3),e.kind==="sso"){let c=_(e.hostname,e.ssoParentDomain);c&&(s.domain=c)}return s}function M(e){let t=[`Path=${e.path}`,"HttpOnly"];return e.secure&&t.push("Secure"),t.push(`SameSite=${e.sameSite==="none"?"None":e.sameSite==="strict"?"Strict":"Lax"}`),typeof e.maxAge=="number"&&t.push(`Max-Age=${e.maxAge}`),e.domain&&t.push(`Domain=${e.domain}`),t.join("; ")}function m(e,t,n){return`${e}=${t}; ${M(n)}`}function b(e,t={}){let n=t.parentDomains??[],r=_(t.hostname);r&&!n.includes(r)&&n.push(r),n.length===0&&n.push("tktchurch.com","tktchurch.net");let o=t.allowInsecure===!0,s=[];for(let c of e)for(let u of n)s.push(m(c,"",{httpOnly:!0,secure:!o,sameSite:"lax",path:"/",maxAge:0,domain:u}));return s}function C(e,t={}){return d({...t,kind:e,maxAge:0})}A();var P=3800;function pe(e){let t=e||[];return t.includes("*:*")?["*:*"]:t}function ge(e){if(typeof e!="string")return null;let t=e.trim();return!t||t.startsWith("data:")||t.length>512?null:t}function Se(e,t){let n=(e||[]).map(r=>({...r,description:null,permissions:[]}));return t==="none"?[]:(t==="identity"&&(n=n.filter(r=>r.slug==="admin"||r.slug==="superadmin"||r.slug==="super_admin").slice(0,4)),n)}function f(e,t={}){let n=t.includePicture!==!1,r=t.roles||"all";return{...e,phoneNumber:e.phoneNumber??null,roles:Se(e.roles,r),permissions:pe(e.permissions),picture:n?ge(e.picture):null}}function F(e,t={}){let n=t.maxLength??3800,r=t.measure??(a=>JSON.stringify(a).length<=n),o=e.user;if(!o){let a={...e,idToken:void 0},y=r(a);return y instanceof Promise?y.then(U=>a):a}let s={...e,idToken:void 0},c=[e,s,{...s,user:f(o,{roles:"all"})},{...s,user:f(o,{roles:"identity"})},{...s,user:f(o,{roles:"none",includePicture:!0})},{...s,user:f(o,{roles:"none",includePicture:!1})}],u={...e,idToken:void 0,accessToken:e.accessToken,refreshToken:e.refreshToken,user:f(o,{roles:"none",includePicture:!1})};if(c.every(a=>typeof r(a)=="boolean")){for(let a of c)if(r(a))return a;return u}return(async()=>{for(let a of c)if(await r(a))return a;return u})()}async function I(e,t,n=3800){let{sealJson:r}=await Promise.resolve().then(()=>(A(),B)),o=F(t,{maxLength:n,measure:async s=>(await r(e,s)).length<=n});return o instanceof Promise,o}w();w();function fe(){let e=globalThis.crypto;if(!e?.getRandomValues)throw new l("invalid_request","Web Crypto API is unavailable in this runtime. PKCE requires globalThis.crypto.");return e}function he(e){let t="";for(let r of e)t+=String.fromCharCode(r);if(typeof btoa!="function")throw new l("server_error","base64UrlEncode requires globalThis.btoa (Web/Deno runtime).");return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function W(e=32){if(!Number.isInteger(e)||e<=0)throw new l("invalid_request","randomUrlSafeString length must be a positive integer.");let t=new Uint8Array(e);return fe().getRandomValues(t),he(t)}A();var v=class{map=new Map;async get(t){let n=this.map.get(t);return n?n.expiresAtMs<=Date.now()?(this.map.delete(t),null):n.record:null}async set(t,n,r){this.map.set(t,{record:n,expiresAtMs:Date.now()+Math.max(1,r)*1e3})}async delete(t){this.map.delete(t)}},N=class{constructor(t,n="tkt:sso:"){this.redis=t;this.keyPrefix=n}key(t){return`${this.keyPrefix}${t}`}async get(t){let n=await this.redis.get(this.key(t));if(!n)return null;try{let r=JSON.parse(n);return r.expiresAt<=Date.now()?(await this.delete(t),null):r}catch{return null}}async set(t,n,r){await this.redis.set(this.key(t),JSON.stringify(n),{ex:Math.max(1,r)})}async delete(t){await this.redis.del(this.key(t))}};async function j(e,t){let n={...t,createdAt:t.createdAt??Date.now()};return{cookieValue:await p(e,n),record:n}}async function Y(e,t){try{let n=await S(e,t);return!n?.userId||n.expiresAt<=Date.now()?null:n}catch{return null}}function V(e=32){return W(e)}async function Q(e,t){let n=t.ttlSeconds??2592e3,r=V(),o={userId:t.userId,refreshHandle:t.refreshHandle,rememberMe:t.rememberMe,createdAt:Date.now(),expiresAt:t.expiresAt??Date.now()+n*1e3};return await e.set(r,o,n),{sid:r,record:o}}async function Z(e,t){await e.delete(t)}function ee(e,t={}){let n=t.cookieName??k,r=d({kind:"sso",hostname:t.hostname,maxAge:t.maxAge??2592e3,allowInsecure:t.allowInsecure});if(!r.domain)throw new l("invalid_request","SSO cookie requires a tktchurch.com / tktchurch.net hostname.");return m(n,e,r)}function te(e={}){let t=e.cookieName??k,n=d({kind:"sso",hostname:e.hostname,maxAge:0,allowInsecure:e.allowInsecure});return m(t,"",n)}function ne(e={}){return d({kind:"sso",hostname:e.hostname,maxAge:e.maxAge??2592e3,allowInsecure:e.allowInsecure})}function re(e,t){if(!e)return null;let n=e.match(new RegExp(`(?:^|;\\s*)${t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}=([^;]*)`));return n?.[1]?decodeURIComponent(n[1]):null}A();function oe(e){let t=e.kind??"app",n=e.maxValueLength??3800,r=e.allowInsecure===!0;async function o(i){let g=await I(e.secret,i,n);return p(e.secret,g)}async function s(i){if(!i)return null;try{return await S(e.secret,i)}catch{return null}}function c(i={}){return d({kind:i.kind??t,hostname:i.hostname,maxAge:i.maxAge,allowInsecure:r})}async function u(i,g={}){let ae=await o(i);return m(e.cookieName,ae,c(g))}function x(i={}){return m(e.cookieName,"",C(i.kind??t,{hostname:i.hostname,allowInsecure:r}))}function a(i){let g=e.legacyParentDomainNames??[e.cookieName];return b(g,{hostname:i,allowInsecure:r})}async function y(i){return p(e.secret,i)}async function U(i){return s(i)}return{cookieName:e.cookieName,sealSessionValue:o,readSessionValue:s,optionsFor:c,clearOptionsFor:(i={})=>C(i.kind??t,{hostname:i.hostname,allowInsecure:r}),serializeSessionSetCookie:u,serializeSessionClearCookie:x,serializeLegacyExpiry:a,sealTransient:y,unsealTransient:U,transientOptions:(i,g)=>d({kind:"transient",hostname:i,maxAge:g,allowInsecure:r})}}async function se(e,t){let{silent:n=!0,...r}=t;return e.authorize.createRequest({...r,prompt:n?"none":void 0})}function ie(e,t="tkt_sid"){if(!e)return!1;let n=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return new RegExp(`(?:^|;\\s*)${n}=([^;]+)`).test(e)}0&&(module.exports={DEFAULT_APP_MAX_AGE_SECONDS,DEFAULT_MAX_SESSION_COOKIE_VALUE_LENGTH,DEFAULT_SSO_COOKIE_NAME,DEFAULT_SSO_MAX_AGE_SECONDS,DEFAULT_TRANSIENT_MAX_AGE_SECONDS,MemorySidStore,RedisSidStore,clearCookieOptions,compactSession,compactSessionForSeal,cookieOptions,createOpaqueSid,createSessionCookies,expireLegacyParentDomainCookies,formatCookieAttributes,hasSsoCookieHint,mintOpaqueSsoSession,mintSealedSsoCookieValue,readCookieHeader,readSealedSsoCookieValue,resolveSsoParentDomain,revokeSsoSession,sealJson,sealString,serializeSetCookie,slimUserForCookie,ssoClearCookieHeader,ssoCookieOptions,ssoSetCookieHeader,startSilentAuthorize,unsealJson,unsealString});
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { i as AuthorizationUrlOptions, a as AuthClient, h as AuthorizationRequest } from './types-1sQaP6TE.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cookie domain / attribute policy for TKTChurch browser sessions.
|
|
5
|
+
*
|
|
6
|
+
* - `sso` — tiny opaque identity cookie on the parent domain (`.tktchurch.com`)
|
|
7
|
+
* - `app` — host-only product session (never parent Domain — avoids 431 bleed)
|
|
8
|
+
* - `transient` — host-only short-lived PKCE / OTP state
|
|
9
|
+
*/
|
|
10
|
+
type CookieKind = "sso" | "app" | "transient";
|
|
11
|
+
interface CookiePolicyInput {
|
|
12
|
+
kind: CookieKind;
|
|
13
|
+
/** Request hostname without port (e.g. `developers.tktchurch.com`). */
|
|
14
|
+
hostname?: string;
|
|
15
|
+
maxAge?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Force Secure. Defaults to true unless `allowInsecure` is set.
|
|
18
|
+
* Never derive Secure from client-controlled `X-Forwarded-Proto`.
|
|
19
|
+
*/
|
|
20
|
+
secure?: boolean;
|
|
21
|
+
/** Local HTTP only — mirrors ALLOW_INSECURE_COOKIES. */
|
|
22
|
+
allowInsecure?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Parent domain for `sso` cookies. Defaults to `tktchurch.com` when hostname
|
|
25
|
+
* ends with `.tktchurch.com`, else `tktchurch.net` for `.tktchurch.net`.
|
|
26
|
+
*/
|
|
27
|
+
ssoParentDomain?: string;
|
|
28
|
+
path?: string;
|
|
29
|
+
sameSite?: "lax" | "strict" | "none";
|
|
30
|
+
}
|
|
31
|
+
/** Options compatible with h3 `setCookie` / the `cookie` package. */
|
|
32
|
+
interface SessionCookieOptions {
|
|
33
|
+
httpOnly: true;
|
|
34
|
+
secure: boolean;
|
|
35
|
+
sameSite: "lax" | "strict" | "none";
|
|
36
|
+
path: string;
|
|
37
|
+
maxAge?: number;
|
|
38
|
+
/** Only set for `kind: "sso"`. */
|
|
39
|
+
domain?: string;
|
|
40
|
+
}
|
|
41
|
+
declare const DEFAULT_SSO_COOKIE_NAME = "tkt_sid";
|
|
42
|
+
declare const DEFAULT_SSO_MAX_AGE_SECONDS: number;
|
|
43
|
+
declare const DEFAULT_TRANSIENT_MAX_AGE_SECONDS: number;
|
|
44
|
+
declare const DEFAULT_APP_MAX_AGE_SECONDS: number;
|
|
45
|
+
/** Parent registrable domain for SSO, or undefined when not on a TKT host. */
|
|
46
|
+
declare function resolveSsoParentDomain(hostname: string | undefined, override?: string): string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Build Set-Cookie attribute options for a session cookie kind.
|
|
49
|
+
* `app` and `transient` are always host-only (no `domain`).
|
|
50
|
+
*/
|
|
51
|
+
declare function cookieOptions(input: CookiePolicyInput): SessionCookieOptions;
|
|
52
|
+
/** Serialize options into a `Set-Cookie` attribute suffix (no name=value). */
|
|
53
|
+
declare function formatCookieAttributes(options: SessionCookieOptions): string;
|
|
54
|
+
/** Full `Set-Cookie` header value. */
|
|
55
|
+
declare function serializeSetCookie(name: string, value: string, options: SessionCookieOptions): string;
|
|
56
|
+
/**
|
|
57
|
+
* Emit Max-Age=0 Set-Cookie headers that clear legacy parent-domain app cookies
|
|
58
|
+
* (the bleed that causes Deno 431 on microsites).
|
|
59
|
+
*/
|
|
60
|
+
declare function expireLegacyParentDomainCookies(names: string[], input?: {
|
|
61
|
+
hostname?: string;
|
|
62
|
+
allowInsecure?: boolean;
|
|
63
|
+
parentDomains?: string[];
|
|
64
|
+
}): string[];
|
|
65
|
+
/** h3-compatible options for clearing a cookie (same domain/path as set). */
|
|
66
|
+
declare function clearCookieOptions(kind: CookieKind, input?: Omit<CookiePolicyInput, "kind" | "maxAge">): SessionCookieOptions;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* AES-256-GCM seal/unseal for session cookies (Web Crypto — Node 18+, Deno, edge).
|
|
70
|
+
*
|
|
71
|
+
* Wire format matches existing Nuxt apps: `iv.tag.ciphertext` (each base64url).
|
|
72
|
+
*/
|
|
73
|
+
/** Encrypt a UTF-8 string into `iv.tag.ciphertext` base64url parts. */
|
|
74
|
+
declare function sealString(secret: string, plaintext: string): Promise<string>;
|
|
75
|
+
/** Decrypt a `iv.tag.ciphertext` sealed string. */
|
|
76
|
+
declare function unsealString(secret: string, sealed: string): Promise<string>;
|
|
77
|
+
/** JSON seal helper. */
|
|
78
|
+
declare function sealJson<T>(secret: string, value: T): Promise<string>;
|
|
79
|
+
/** JSON unseal helper. */
|
|
80
|
+
declare function unsealJson<T>(secret: string, sealed: string): Promise<T>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Progressive compaction so sealed session cookies stay under browser limits.
|
|
84
|
+
*/
|
|
85
|
+
declare const DEFAULT_MAX_SESSION_COOKIE_VALUE_LENGTH = 3800;
|
|
86
|
+
type CompactRoleMode = "all" | "identity" | "none";
|
|
87
|
+
interface CompactableUser {
|
|
88
|
+
id?: string;
|
|
89
|
+
sub?: string;
|
|
90
|
+
organizationId?: string | null;
|
|
91
|
+
email?: string | null;
|
|
92
|
+
username?: string | null;
|
|
93
|
+
firstName?: string | null;
|
|
94
|
+
lastName?: string | null;
|
|
95
|
+
phoneNumber?: string | null;
|
|
96
|
+
emailVerified?: boolean;
|
|
97
|
+
phoneNumberVerified?: boolean;
|
|
98
|
+
isActive?: boolean;
|
|
99
|
+
isVerified?: boolean;
|
|
100
|
+
mfaEnabled?: boolean;
|
|
101
|
+
picture?: string | null;
|
|
102
|
+
permissions?: string[];
|
|
103
|
+
roles?: Array<{
|
|
104
|
+
id?: string;
|
|
105
|
+
organizationId?: string | null;
|
|
106
|
+
name?: string | null;
|
|
107
|
+
slug?: string | null;
|
|
108
|
+
description?: string | null;
|
|
109
|
+
isSystem?: boolean;
|
|
110
|
+
permissions?: unknown[];
|
|
111
|
+
[key: string]: unknown;
|
|
112
|
+
}>;
|
|
113
|
+
[key: string]: unknown;
|
|
114
|
+
}
|
|
115
|
+
interface CompactableSession {
|
|
116
|
+
accessToken?: string;
|
|
117
|
+
refreshToken?: string;
|
|
118
|
+
idToken?: string;
|
|
119
|
+
expiresAt?: number;
|
|
120
|
+
scope?: string;
|
|
121
|
+
rememberMe?: boolean;
|
|
122
|
+
user?: CompactableUser;
|
|
123
|
+
[key: string]: unknown;
|
|
124
|
+
}
|
|
125
|
+
declare function slimUserForCookie(user: CompactableUser, options?: {
|
|
126
|
+
includePicture?: boolean;
|
|
127
|
+
roles?: CompactRoleMode;
|
|
128
|
+
}): CompactableUser;
|
|
129
|
+
/**
|
|
130
|
+
* Pick the smallest session variant that fits `maxLength` when JSON-stringified
|
|
131
|
+
* (caller should measure sealed length; this uses JSON length as a stand-in when
|
|
132
|
+
* `measure` is omitted).
|
|
133
|
+
*/
|
|
134
|
+
declare function compactSession<T extends CompactableSession>(session: T, options?: {
|
|
135
|
+
maxLength?: number;
|
|
136
|
+
/** Return true when the candidate fits (e.g. sealed length). */
|
|
137
|
+
measure?: (candidate: T) => boolean | Promise<boolean>;
|
|
138
|
+
}): T | Promise<T>;
|
|
139
|
+
/** Sync compact using sealed-length measurement. */
|
|
140
|
+
declare function compactSessionForSeal<T extends CompactableSession>(secret: string, session: T, maxLength?: number): Promise<T>;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Opaque SSO session id (`tkt_sid`) + pluggable store.
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
interface SsoSessionRecord {
|
|
147
|
+
/** Subject / user id. */
|
|
148
|
+
userId: string;
|
|
149
|
+
/** Optional refresh-token handle or hash (never required for presence checks). */
|
|
150
|
+
refreshHandle?: string;
|
|
151
|
+
createdAt: number;
|
|
152
|
+
expiresAt: number;
|
|
153
|
+
rememberMe?: boolean;
|
|
154
|
+
}
|
|
155
|
+
interface SidStore {
|
|
156
|
+
get(sid: string): Promise<SsoSessionRecord | null>;
|
|
157
|
+
set(sid: string, record: SsoSessionRecord, ttlSeconds: number): Promise<void>;
|
|
158
|
+
delete(sid: string): Promise<void>;
|
|
159
|
+
}
|
|
160
|
+
/** In-memory store (tests / single-process only). */
|
|
161
|
+
declare class MemorySidStore implements SidStore {
|
|
162
|
+
private readonly map;
|
|
163
|
+
get(sid: string): Promise<SsoSessionRecord | null>;
|
|
164
|
+
set(sid: string, record: SsoSessionRecord, ttlSeconds: number): Promise<void>;
|
|
165
|
+
delete(sid: string): Promise<void>;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Redis-compatible store. Pass any client with get/set/del
|
|
169
|
+
* (node-redis, ioredis, Upstash REST wrapper, etc.).
|
|
170
|
+
*/
|
|
171
|
+
interface RedisLike {
|
|
172
|
+
get(key: string): Promise<string | null>;
|
|
173
|
+
set(key: string, value: string, options?: {
|
|
174
|
+
ex?: number;
|
|
175
|
+
}): Promise<unknown>;
|
|
176
|
+
del(key: string): Promise<unknown>;
|
|
177
|
+
}
|
|
178
|
+
declare class RedisSidStore implements SidStore {
|
|
179
|
+
private readonly redis;
|
|
180
|
+
private readonly keyPrefix;
|
|
181
|
+
constructor(redis: RedisLike, keyPrefix?: string);
|
|
182
|
+
private key;
|
|
183
|
+
get(sid: string): Promise<SsoSessionRecord | null>;
|
|
184
|
+
set(sid: string, record: SsoSessionRecord, ttlSeconds: number): Promise<void>;
|
|
185
|
+
delete(sid: string): Promise<void>;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Cookie-embedded SSO record when no Redis is available (Accounts on Vercel).
|
|
189
|
+
* The cookie value is the sealed record itself (still tiny — no JWTs).
|
|
190
|
+
* `sid` returned to callers is a random id used only as a local handle; the
|
|
191
|
+
* sealed payload is what browsers store as `tkt_sid`.
|
|
192
|
+
*/
|
|
193
|
+
interface SealedSidCookie {
|
|
194
|
+
/** Value to put in the `tkt_sid` cookie (sealed JSON). */
|
|
195
|
+
cookieValue: string;
|
|
196
|
+
record: SsoSessionRecord;
|
|
197
|
+
}
|
|
198
|
+
declare function mintSealedSsoCookieValue(secret: string, record: Omit<SsoSessionRecord, "createdAt"> & {
|
|
199
|
+
createdAt?: number;
|
|
200
|
+
}): Promise<SealedSidCookie>;
|
|
201
|
+
declare function readSealedSsoCookieValue(secret: string, cookieValue: string): Promise<SsoSessionRecord | null>;
|
|
202
|
+
declare function createOpaqueSid(bytes?: number): string;
|
|
203
|
+
declare function mintOpaqueSsoSession(store: SidStore, record: Omit<SsoSessionRecord, "createdAt" | "expiresAt"> & {
|
|
204
|
+
ttlSeconds?: number;
|
|
205
|
+
expiresAt?: number;
|
|
206
|
+
}): Promise<{
|
|
207
|
+
sid: string;
|
|
208
|
+
record: SsoSessionRecord;
|
|
209
|
+
}>;
|
|
210
|
+
declare function revokeSsoSession(store: SidStore, sid: string): Promise<void>;
|
|
211
|
+
declare function ssoSetCookieHeader(sidOrSealedValue: string, input?: {
|
|
212
|
+
hostname?: string;
|
|
213
|
+
maxAge?: number;
|
|
214
|
+
allowInsecure?: boolean;
|
|
215
|
+
cookieName?: string;
|
|
216
|
+
}): string;
|
|
217
|
+
declare function ssoClearCookieHeader(input?: {
|
|
218
|
+
hostname?: string;
|
|
219
|
+
allowInsecure?: boolean;
|
|
220
|
+
cookieName?: string;
|
|
221
|
+
}): string;
|
|
222
|
+
declare function ssoCookieOptions(input?: {
|
|
223
|
+
hostname?: string;
|
|
224
|
+
maxAge?: number;
|
|
225
|
+
allowInsecure?: boolean;
|
|
226
|
+
}): SessionCookieOptions;
|
|
227
|
+
/** Read a named cookie from a Cookie header. */
|
|
228
|
+
declare function readCookieHeader(cookieHeader: string | null | undefined, name: string): string | null;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* High-level session cookie helpers shared by Nuxt (h3) and Deno apps.
|
|
232
|
+
*/
|
|
233
|
+
|
|
234
|
+
interface SessionCookieConfig {
|
|
235
|
+
secret: string;
|
|
236
|
+
/** Product session cookie name (e.g. `dev_auth_session`). */
|
|
237
|
+
cookieName: string;
|
|
238
|
+
/** Default kind for the main session cookie. */
|
|
239
|
+
kind?: Exclude<CookieKind, "sso">;
|
|
240
|
+
maxValueLength?: number;
|
|
241
|
+
allowInsecure?: boolean;
|
|
242
|
+
/** Cookie names previously set with Domain=.tktchurch.com to expire. */
|
|
243
|
+
legacyParentDomainNames?: string[];
|
|
244
|
+
}
|
|
245
|
+
interface SetSessionInput {
|
|
246
|
+
hostname?: string;
|
|
247
|
+
maxAge?: number;
|
|
248
|
+
kind?: Exclude<CookieKind, "sso">;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Framework-agnostic session cookie controller.
|
|
252
|
+
* Use `serialize*` for Deno `Headers.append("set-cookie", …)` and
|
|
253
|
+
* `options*` with h3 `setCookie` / `deleteCookie`.
|
|
254
|
+
*/
|
|
255
|
+
declare function createSessionCookies(config: SessionCookieConfig): {
|
|
256
|
+
cookieName: string;
|
|
257
|
+
sealSessionValue: <T extends CompactableSession>(session: T) => Promise<string>;
|
|
258
|
+
readSessionValue: <T>(raw: string | null | undefined) => Promise<T | null>;
|
|
259
|
+
optionsFor: (input?: SetSessionInput) => SessionCookieOptions;
|
|
260
|
+
clearOptionsFor: (input?: SetSessionInput) => SessionCookieOptions;
|
|
261
|
+
serializeSessionSetCookie: <T extends CompactableSession>(session: T, input?: SetSessionInput) => Promise<string>;
|
|
262
|
+
serializeSessionClearCookie: (input?: SetSessionInput) => string;
|
|
263
|
+
serializeLegacyExpiry: (hostname?: string) => string[];
|
|
264
|
+
sealTransient: (value: unknown) => Promise<string>;
|
|
265
|
+
unsealTransient: <T>(raw: string | null | undefined) => Promise<T | null>;
|
|
266
|
+
transientOptions: (hostname?: string, maxAge?: number) => SessionCookieOptions;
|
|
267
|
+
};
|
|
268
|
+
type SessionCookies = ReturnType<typeof createSessionCookies>;
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Silent / prompt=none authorization helpers for cross-app SSO.
|
|
272
|
+
*/
|
|
273
|
+
|
|
274
|
+
interface SilentAuthorizeOptions extends Omit<AuthorizationUrlOptions, "prompt"> {
|
|
275
|
+
/** When true (default), sets `prompt=none`. */
|
|
276
|
+
silent?: boolean;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Start an authorization-code + PKCE request intended for SSO resume.
|
|
280
|
+
* Persist `state` / `nonce` / `codeVerifier` in a **host-only transient** cookie.
|
|
281
|
+
*/
|
|
282
|
+
declare function startSilentAuthorize(client: AuthClient, options: SilentAuthorizeOptions): Promise<AuthorizationRequest>;
|
|
283
|
+
/**
|
|
284
|
+
* True when an SSO identity cookie is present (opaque sid or sealed record).
|
|
285
|
+
* Does not validate the store — callers should resolve via SidStore / unseal.
|
|
286
|
+
*/
|
|
287
|
+
declare function hasSsoCookieHint(cookieHeader: string | null | undefined, cookieName?: string): boolean;
|
|
288
|
+
|
|
289
|
+
export { type CompactRoleMode, type CompactableSession, type CompactableUser, type CookieKind, type CookiePolicyInput, DEFAULT_APP_MAX_AGE_SECONDS, DEFAULT_MAX_SESSION_COOKIE_VALUE_LENGTH, DEFAULT_SSO_COOKIE_NAME, DEFAULT_SSO_MAX_AGE_SECONDS, DEFAULT_TRANSIENT_MAX_AGE_SECONDS, MemorySidStore, type RedisLike, RedisSidStore, type SealedSidCookie, type SessionCookieConfig, type SessionCookieOptions, type SessionCookies, type SetSessionInput, type SidStore, type SilentAuthorizeOptions, type SsoSessionRecord, clearCookieOptions, compactSession, compactSessionForSeal, cookieOptions, createOpaqueSid, createSessionCookies, expireLegacyParentDomainCookies, formatCookieAttributes, hasSsoCookieHint, mintOpaqueSsoSession, mintSealedSsoCookieValue, readCookieHeader, readSealedSsoCookieValue, resolveSsoParentDomain, revokeSsoSession, sealJson, sealString, serializeSetCookie, slimUserForCookie, ssoClearCookieHeader, ssoCookieOptions, ssoSetCookieHeader, startSilentAuthorize, unsealJson, unsealString };
|