@tktchurch/auth 0.9.1 → 0.9.5

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/SECURITY.md CHANGED
@@ -11,45 +11,49 @@ correctly and make unsafe choices hard.
11
11
 
12
12
  ## Public vs full distribution
13
13
 
14
- | Artifact | Registry | Admin modules | `request` / `fetchWithAuth` | Source maps |
15
- | --- | --- | --- | --- | --- |
16
- | Consumer build | npmjs (public) | omitted | omitted | omitted |
17
- | Full build | GitHub Packages (private) | included | included | included |
14
+ | Artifact | Registry | Admin modules | `request` / `fetchWithAuth` | Source maps |
15
+ | -------------- | ------------------------- | ------------- | --------------------------- | ----------- |
16
+ | Consumer build | npmjs (public) | omitted | omitted | omitted |
17
+ | Full build | GitHub Packages (private) | included | included | included |
18
18
 
19
- Hiding admin endpoint paths in the public consumer bundle **does not** secure the OAuth server. Standard OIDC discovery documents and network traffic still reveal public endpoints. Backend `PermissionMiddleware` remains the authoritative access control. Do not treat SDK packaging as an obfuscation or secrecy control.
19
+ Hiding admin endpoint paths in the public consumer bundle **does not** secure
20
+ the OAuth server. Standard OIDC discovery documents and network traffic still
21
+ reveal public endpoints. Backend `PermissionMiddleware` remains the
22
+ authoritative access control. Do not treat SDK packaging as an obfuscation or
23
+ secrecy control.
20
24
 
21
25
  ## Threat model
22
26
 
23
- | Threat | SDK mitigation | Integrator responsibility |
24
- | --- | --- | --- |
25
- | CSRF on authorization callback | Constant-time `state` verification (`timingSafeEqual`) | Persist `state` server-side; never skip callback validation |
26
- | PKCE downgrade / param clobbering | Reserved authorize params cannot be overridden via `extraParams` | Store `codeVerifier` server-side (HttpOnly cookie / session) |
27
- | Open redirect after login | `sanitizePostAuthRedirect()` rejects scheme-bearing, protocol-relative, and control-character targets | Always sanitize `next`/`returnTo` before redirect |
28
- | Confidential client secret in browser | `clientSecret` throws in browser unless `allowClientSecretInBrowser` is explicitly set | Keep secrets in server routes only |
29
- | Token theft via XSS | No browser persistence adapter; memory default | HttpOnly cookies for refresh tokens; strict CSP |
30
- | Malicious / corrupt stored tokens | `assertValidAuthTokens` / `parseStoredAuthTokens` for custom adapters | Choose a secure storage backend for your platform |
31
- | Prototype pollution via stored JSON | Reviver blocks `__proto__` / `constructor` / `prototype`; allowlisted fields only | Avoid custom storage that merges untrusted objects |
32
- | Storage DoS (huge payloads) | 32 KiB cap on serialized token blobs via `serializeAuthTokens` | Enforce limits in custom adapters |
33
- | Cleartext token transport | HTTPS enforced for `baseUrl` (loopback HTTP exempt) | Terminate TLS at the edge; do not disable in production |
34
- | Weak randomness | CSPRNG only (`crypto.getRandomValues`); PKCE S256 default | Never substitute `Math.random` |
35
- | Credential / token logging | SDK does not log secrets; errors avoid stack/internal config leaks | Do not log request bodies or token responses |
36
- | MITM / slowloris | Conservative fetch timeouts (default 15s); TLS via HTTPS base URL | Use platform TLS verification; inject corporate `fetch` only when needed |
37
- | Consent endpoint credential leak | `authorize.consent` validates callback URL origin/path; no logging of passwords/MFA codes | Call consent only from server routes; never expose credentials to the browser |
38
- | Admin token overreach | SDK does not enforce permissions; all admin modules require bearer token | Scope admin tokens minimally; backend `PermissionMiddleware` is authoritative |
39
- | MFA setup secret exposure | `mfa.setup` returns TOTP secret/QR — treat response as highly sensitive | Display QR server-side or in trusted UI only; never log setup payloads |
40
- | Pagination abuse | Client caps `perPage` at 100 via `normalizePageQuery` | Prefer reasonable page sizes in admin UIs |
27
+ | Threat | SDK mitigation | Integrator responsibility |
28
+ | ------------------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
29
+ | CSRF on authorization callback | Constant-time `state` verification (`timingSafeEqual`) | Persist `state` server-side; never skip callback validation |
30
+ | PKCE downgrade / param clobbering | Reserved authorize params cannot be overridden via `extraParams` | Store `codeVerifier` server-side (HttpOnly cookie / session) |
31
+ | Open redirect after login | `sanitizePostAuthRedirect()` rejects scheme-bearing, protocol-relative, and control-character targets | Always sanitize `next`/`returnTo` before redirect |
32
+ | Confidential client secret in browser | `clientSecret` throws in browser unless `allowClientSecretInBrowser` is explicitly set | Keep secrets in server routes only |
33
+ | Token theft via XSS | No browser persistence adapter; memory default | HttpOnly cookies for refresh tokens; strict CSP |
34
+ | Malicious / corrupt stored tokens | `assertValidAuthTokens` / `parseStoredAuthTokens` for custom adapters | Choose a secure storage backend for your platform |
35
+ | Prototype pollution via stored JSON | Reviver blocks `__proto__` / `constructor` / `prototype`; allowlisted fields only | Avoid custom storage that merges untrusted objects |
36
+ | Storage DoS (huge payloads) | 32 KiB cap on serialized token blobs via `serializeAuthTokens` | Enforce limits in custom adapters |
37
+ | Cleartext token transport | HTTPS enforced for `baseUrl` (loopback HTTP exempt) | Terminate TLS at the edge; do not disable in production |
38
+ | Weak randomness | CSPRNG only (`crypto.getRandomValues`); PKCE S256 default | Never substitute `Math.random` |
39
+ | Credential / token logging | SDK does not log secrets; errors avoid stack/internal config leaks | Do not log request bodies or token responses |
40
+ | MITM / slowloris | Conservative fetch timeouts (default 15s); TLS via HTTPS base URL | Use platform TLS verification; inject corporate `fetch` only when needed |
41
+ | Consent endpoint credential leak | `authorize.consent` validates callback URL origin/path; no logging of passwords/MFA codes | Call consent only from server routes; never expose credentials to the browser |
42
+ | Admin token overreach | SDK does not enforce permissions; all admin modules require bearer token | Scope admin tokens minimally; backend `PermissionMiddleware` is authoritative |
43
+ | MFA setup secret exposure | `mfa.setup` returns TOTP secret/QR — treat response as highly sensitive | Display QR server-side or in trusted UI only; never log setup payloads |
44
+ | Pagination abuse | Client caps `perPage` at 100 via `normalizePageQuery` | Prefer reasonable page sizes in admin UIs |
41
45
 
42
46
  ## Sensitive endpoints (0.5.0)
43
47
 
44
- | Endpoint | Risk | Guidance |
45
- | --- | --- | --- |
46
- | `authorize.consent` / `consentWithCredentials` | Username, password, MFA codes in request body | Server-side only; validate consent callback URL |
47
- | `passwordReset.*` | Account takeover if token mishandled | Use HTTPS; short-lived reset tokens |
48
- | `mfa.setup` / `mfa.regenerateBackupCodes` | TOTP secrets and backup codes | Never log responses; show once to user |
49
- | `user.admin.resetMfa` | Forcibly disables target user MFA | Requires `user:mfa_reset` permission |
50
- | Admin `clients.regenerateSecret` / `webhooks.rotateSecret` | Secret rotation | Treat new secrets like passwords |
51
- | `privacy.submitDataRequest` / `privacy.requestExport` | DSAR / portability payloads | Never log request bodies or export payloads |
52
- | `user.deleteMe` | Irreversible account erasure | Require password re-auth; clear SDK tokens after success |
48
+ | Endpoint | Risk | Guidance |
49
+ | ---------------------------------------------------------- | --------------------------------------------- | -------------------------------------------------------- |
50
+ | `authorize.consent` / `consentWithCredentials` | Username, password, MFA codes in request body | Server-side only; validate consent callback URL |
51
+ | `passwordReset.*` | Account takeover if token mishandled | Use HTTPS; short-lived reset tokens |
52
+ | `mfa.setup` / `mfa.regenerateBackupCodes` | TOTP secrets and backup codes | Never log responses; show once to user |
53
+ | `user.admin.resetMfa` | Forcibly disables target user MFA | Requires `user:mfa_reset` permission |
54
+ | Admin `clients.regenerateSecret` / `webhooks.rotateSecret` | Secret rotation | Treat new secrets like passwords |
55
+ | `privacy.submitDataRequest` / `privacy.requestExport` | DSAR / portability payloads | Never log request bodies or export payloads |
56
+ | `user.deleteMe` | Irreversible account erasure | Require password re-auth; clear SDK tokens after success |
53
57
 
54
58
  ## Privacy endpoints (0.7.x)
55
59
 
@@ -63,9 +67,9 @@ Hiding admin endpoint paths in the public consumer bundle **does not** secure th
63
67
  ### Do not use `localStorage` or `sessionStorage`
64
68
 
65
69
  The SDK **does not ship** a `localStorage` adapter (removed in 0.4.0). Browser
66
- storage is readable by any script on the page — one XSS vulnerability exfiltrates
67
- `accessToken` and `refreshToken`. Use HttpOnly cookies (server routes) or
68
- platform secure storage instead.
70
+ storage is readable by any script on the page — one XSS vulnerability
71
+ exfiltrates `accessToken` and `refreshToken`. Use HttpOnly cookies (server
72
+ routes) or platform secure storage instead.
69
73
 
70
74
  ### Custom `TokenStorage`
71
75
 
@@ -98,13 +102,13 @@ export function createSecureTokenStorage(/* platform deps */): TokenStorage {
98
102
 
99
103
  Platform guidance:
100
104
 
101
- | Runtime | Recommended persistence |
102
- | --- | --- |
103
- | iOS / macOS | Keychain via native bridge |
104
- | Android | Android Keystore / EncryptedSharedPreferences |
105
+ | Runtime | Recommended persistence |
106
+ | ---------------- | ------------------------------------------------------------------------ |
107
+ | iOS / macOS | Keychain via native bridge |
108
+ | Android | Android Keystore / EncryptedSharedPreferences |
105
109
  | Web (production) | HttpOnly server session cookie for refresh; short-lived access in memory |
106
- | Node / edge | Encrypted server session or memory per request |
107
- | Electron | `safeStorage` + OS keychain |
110
+ | Node / edge | Encrypted server session or memory per request |
111
+ | Electron | `safeStorage` + OS keychain |
108
112
 
109
113
  ### Secrets that must never use `localStorage`
110
114
 
@@ -152,6 +156,7 @@ vulnerabilities.
152
156
  3. Call `authorize.handleCallback` — do not hand-roll token exchange.
153
157
  4. Keep `clientSecret` on the server.
154
158
  5. Sanitize post-login redirects with `sanitizePostAuthRedirect`.
155
- 6. Use memory or HttpOnly cookie storage for refresh tokens in production web apps.
159
+ 6. Use memory or HttpOnly cookie storage for refresh tokens in production web
160
+ apps.
156
161
  7. Set a strict Content-Security-Policy on pages that load third-party scripts.
157
162
  8. Rotate refresh tokens on every refresh (server pattern — see `examples/`).
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var $=Object.defineProperty;var Oe=Object.getOwnPropertyDescriptor;var Ue=Object.getOwnPropertyNames;var Ie=Object.prototype.hasOwnProperty;var ze=(e,t)=>{for(var n in t)$(e,n,{get:t[n],enumerable:!0})},De=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Ue(t))!Ie.call(e,o)&&o!==n&&$(e,o,{get:()=>t[o],enumerable:!(r=Oe(t,o))||r.enumerable});return e};var je=e=>De($({},"__esModule",{value:!0}),e);var ut={};ze(ut,{AuthError:()=>i,DEFAULT_AUTHORIZE_SCOPE:()=>P,DEFAULT_TOKEN_STORAGE_KEY:()=>be,MAX_STORED_TOKEN_BYTES:()=>I,MFARequiredAuthError:()=>_,assertValidAuthTokens:()=>v,createAuthClient:()=>Ee,createMemoryTokenStorage:()=>z,deriveCodeChallenge:()=>B,generateCodeVerifier:()=>J,generateNonce:()=>T,generatePkce:()=>C,generateState:()=>A,isAuthError:()=>M,isUnrecoverableAuthError:()=>ee,parseStoredAuthTokens:()=>ve,randomUrlSafeString:()=>S,safeParseStoredJson:()=>Se,sanitizePostAuthRedirect:()=>qe,serializeAuthTokens:()=>Te,timingSafeEqual:()=>E,validateStorageKey:()=>Ae});module.exports=je(ut);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}},_=class extends i{mfaMethods;constructor(t,n,r,o){super("mfa_required",t,{status:r,details:o}),this.name="MFARequiredAuthError",this.mfaMethods=n}};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 $e(e,t){if(!t)return new i("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(s=>typeof s=="string"):[];return new _(r,o,e,t)}return new i(n,r,{status:e,details:t})}async function Z(e){let t,n=await e.text();if(n)try{t=JSON.parse(n)}catch{t={reason:n}}return $e(e.status,t)}function M(e){return e instanceof i}function ee(e){return M(e)?e.code==="invalid_client"||e.code==="invalid_grant"||e.code==="unauthorized_client"||e.code==="invalid_token":!1}function L(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 u(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 c(e){return Object.fromEntries(Object.entries(e).filter(([,t])=>typeof t<"u"))}function m(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 d=L(e.authorization_details);return d&&(s.authorizationDetails=d),s}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 N(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 H(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 Le(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 te(e){return{async list(t){let n=await u(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 Le(s)},async current(){let t=await u(e),n=await e.http.request("/api/v1/sessions/current",{token:t});return N(n)},async get(t){let n=await u(e),r=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{token:n});return N(r)},async update(t,n){let r=await u(e),o=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{method:"PATCH",token:r,body:c({device_name:n.deviceName,is_trusted:n.isTrusted})});return N(o)},async revoke(t,n){let r=await u(e),o=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{method:"DELETE",token:r,body:c({reason:n?.reason})});return H(o)},async revokeAll(){let t=await u(e),n=await e.http.request("/api/v1/sessions/revoke-all",{method:"POST",token:t});return H(n)},async revokeAllDevices(){let t=await u(e),n=await e.http.request("/api/v1/sessions/revoke-all-devices",{method:"POST",token:t});return H(n)}}}function ne(){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 re(e){let t="";for(let r of e)t+=String.fromCharCode(r);return(typeof btoa=="function"?btoa(t):Buffer.from(e).toString("base64")).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function S(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 ne().getRandomValues(t),re(t)}function A(){return S(32)}function T(){return S(32)}function J(){return S(32)}async function B(e){let t=ne();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 re(new Uint8Array(r))}async function C(e="S256"){let t=J();if(e==="plain")return{codeVerifier:t,codeChallenge:t,codeChallengeMethod:"plain"};let n=await B(t);return{codeVerifier:t,codeChallenge:n,codeChallengeMethod:"S256"}}function E(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 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:L(e.authorization_details)}}function q(e){return{async password(t){let n=c({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=m(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=c({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=m(s,r);return await e.setTokens(a),a},async clientCredentials(t){let n=c({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=m(r);return await e.setTokens(o),o},async authorizationCode(t){let n=c({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=m(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:c({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:c({client_id:e.config.clientId,client_secret:e.config.clientSecret,token:t.token})});return Ne(n)},async tokenExchange(t){let n=c({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=m(r);return await e.setTokens(o),o}}}function oe(e,t){let n=e.trim();if(!n)throw new i("invalid_request",`${t} is required.`);return n}async function f(e,t,n){let r=await u(e),o=n?.query,s=o&&Object.keys(o).length>0?`?${new URLSearchParams(Object.entries(o).filter(([,a])=>typeof a<"u").map(([a,d])=>[a,String(d)])).toString()}`:"";return e.http.request(`${t}${s}`,{method:n?.method,body:n?.body,token:r})}async function h(e,t,n){await f(e,t,n)}function k(e,t){return encodeURIComponent(oe(e,t))}function He(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 Je(e){return{clientId:String(e.client_id??""),name:String(e.name??""),description:typeof e.description=="string"?e.description:void 0,isFirstParty:!!e.is_first_party}}function Be(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 se(e){async function t(n,r){let o=Be(n,e.config.baseUrl),s=await e.http.request(`${o.pathname}${o.search}`,{method:"POST",body:{username:r.username,password:r.password,approved:r.approved??!0,requested_scopes:r.requestedScopes,mfa_code:r.mfaCode,mfa_backup_code:r.mfaBackupCode}}),a=He(s);if(a.error==="mfa_required")throw new _(a.errorDescription??"Multi-factor authentication is required",a.mfaMethods??[],void 0,s);return a}return{consent:t,consentWithCredentials(n){return t(n.callbackUrl,{username:n.username,password:n.password,approved:n.approved,requestedScopes:n.requestedScopes,mfaCode:n.mfaCode,mfaBackupCode:n.mfaBackupCode})}}}function ie(e){return{async publicClient(t){let n=await e.http.request(`/oauth/clients/${encodeURIComponent(t)}/public`);return Je(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 P="openid profile email offline_access",ae=new Set(["response_type","client_id","redirect_uri","scope","state","nonce","code_challenge","code_challenge_method","prompt","login_hint","authorization_details","request_uri"]);function Fe(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 de(e){let t=Fe(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 ue(e){let t=q(e);return{...se(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??P,s=r.state??A(),a=r.nonce??T(),d=await C(r.codeChallengeMethod??"S256"),l=r.authorizationEndpoint??`${e.config.baseUrl}/oauth/authorize`,g=new URL(l),p=g.searchParams;p.set("response_type",r.responseType??"code"),p.set("client_id",e.config.clientId),p.set("redirect_uri",r.redirectUri),p.set("scope",o),p.set("state",s),p.set("nonce",a),p.set("code_challenge",d.codeChallenge),p.set("code_challenge_method",d.codeChallengeMethod),r.authorizationDetails&&r.authorizationDetails.length>0&&p.set("authorization_details",JSON.stringify(r.authorizationDetails)),r.prompt&&p.set("prompt",r.prompt),r.loginHint&&p.set("login_hint",r.loginHint);for(let[R,D]of Object.entries(r.extraParams??{})){if(ae.has(R))throw new i("invalid_request",`extraParams may not override the reserved authorization parameter "${R}".`);p.set(R,D)}return{url:g.toString(),state:s,nonce:a,codeVerifier:d.codeVerifier,codeChallenge:d.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??P,s=r.state??A(),a=r.nonce??T(),d=await C(r.codeChallengeMethod??"S256"),l={response_type:r.responseType??"code",client_id:e.config.clientId,redirect_uri:r.redirectUri,scope:o,state:s,nonce:a,code_challenge:d.codeChallenge,code_challenge_method:d.codeChallengeMethod};e.config.clientSecret&&(l.client_secret=e.config.clientSecret),r.authorizationDetails&&r.authorizationDetails.length>0&&(l.authorization_details=JSON.stringify(r.authorizationDetails));for(let[x,Pe]of Object.entries(r.extraParams??{})){if(ae.has(x))throw new i("invalid_request",`extraParams may not override the reserved authorization parameter "${x}".`);l[x]=Pe}let g=await e.http.request("/oauth/par",{method:"POST",body:l}),p=g.request_uri,R=g.expires_in;if(typeof p!="string"||typeof R!="number")throw new i("unknown_error","Unexpected pushed authorization response payload.",{details:g});let D=r.authorizationEndpoint??`${e.config.baseUrl}/oauth/authorize`,j=new URL(D);return j.searchParams.set("client_id",e.config.clientId),j.searchParams.set("request_uri",p),{requestUri:p,expiresIn:R,request:{url:j.toString(),state:s,nonce:a,codeVerifier:d.codeVerifier,codeChallenge:d.codeChallenge,redirectUri:r.redirectUri,scope:o,requestUri:p}}},parseCallback:de,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(),d=a?JSON.parse(a):{};if(!o.ok)throw new i("server_error",typeof d.error_description=="string"?d.error_description:"Authorization probe failed",{status:o.status,details:d});let l=typeof d.authorization_url=="string"?d.authorization_url:void 0;return{kind:"json",status:o.status,authorizationUrl:l,payload:d}},async handleCallback(r){let s=typeof r.callback=="object"&&!(r.callback instanceof URLSearchParams)&&"raw"in r.callback?r.callback:de(r.callback);if(s.error)throw new i("access_denied",s.errorDescription??`Authorization failed: ${s.error}`,{details:s.raw});if(!s.state||!E(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 w(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 F(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});return{sessionId:String(e.session_id??""),flowId:String(e.flow_id??""),flowName:String(e.flow_name??""),currentStep:w(t),remainingSteps:Array.isArray(n)?n.filter(r=>typeof r=="object"&&r!==null).map(w):[],expiresAt:String(e.expires_at??"")}}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"?w(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(w):[],failedAttempts:typeof e.failed_attempts=="number"?e.failed_attempts:0,expiresAt:String(e.expires_at??""),lastActivityAt:String(e.last_activity_at??"")}}async function V(e,t){let n=t.status;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});return{status:"in_progress",sessionId:String(t.session_id??""),nextStep:w(r),completedSteps:Array.isArray(t.completed_steps)?t.completed_steps.filter(a=>typeof a=="string"):[],remainingSteps:Array.isArray(o)?o.filter(a=>typeof a=="object"&&a!==null).map(w):[]}}if(n==="complete"){let r=m(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 ce(e){return{async initAuthentication(t){let n=await e.http.request("/oauth/authenticate/init",{method:"POST",body:c({client_id:t?.clientId??e.config.clientId,username:t?.username})});return F(n)},async executeStep(t){let n=await e.http.request("/oauth/authenticate/step",{method:"POST",body:{session_id:t.sessionId,step_id:t.stepId,credential:t.credential}});return V(e,n)},async initRegistration(t){let n=await e.http.request("/oauth/register/init",{method:"POST",body:c({client_id:t?.clientId??e.config.clientId,invite_code:t?.inviteCode})});return F(n)},async executeRegistrationStep(t){let n=await e.http.request("/oauth/register/step",{method:"POST",body:{session_id:t.sessionId,step_id:t.stepId,credential:t.credential}});return V(e,n)},async getStatus(t){let n=await e.http.request(`/oauth/flow/status/${encodeURIComponent(t)}`);return Ve(n)},async initRecovery(t){let n=await e.http.request("/oauth/recovery/init",{method:"POST",body:c({client_id:t?.clientId??e.config.clientId,recovery_type:t?.recoveryType,redirect_uri:t?.redirectUri})});return F(n)},async executeRecoveryStep(t){let n=await e.http.request("/oauth/recovery/step",{method:"POST",body:{session_id:t.sessionId,step_id:t.stepId,credential:t.credential}});return V(e,n)}}}function O(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:O(e.response_types_supported),grantTypesSupported:O(e.grant_types_supported),scopesSupported:O(e.scopes_supported),codeChallengeMethodsSupported:O(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 pe(e){return{async userInfo(){let t=await u(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 U(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,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 Q(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 fe(e){return Ge(e)}function Ge(e){return{async me(){let t=await u(e),n=await e.http.request("/api/v1/users/me",{token:t});return U(n)},async updateMe(t){let n=await f(e,"/api/v1/users/me",{method:"PATCH",body:t});return U(n)},async changePassword(t){await h(e,"/api/v1/users/me/password",{method:"POST",body:{current_password:t.currentPassword,new_password:t.newPassword}})},async deleteAvatar(){let t=await f(e,"/api/v1/users/me/avatar",{method:"DELETE"});return U(t)},async uploadAvatar(t,n="avatar.jpg"){let r=await u(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 U(a)},async verifyPhone(t){return f(e,"/api/v1/users/me/phone/verify",{method:"POST",body:t})},async securityOverview(){return f(e,"/api/v1/users/me/security-overview")},recoveryContacts:{async list(){let t=await f(e,"/api/v1/users/me/recovery-contacts");return Array.isArray(t)?t.filter(n=>typeof n=="object"&&n!==null).map(Q):[]},async create(t){let n=await f(e,"/api/v1/users/me/recovery-contacts",{method:"POST",body:t});return Q(n)},async update(t,n){let r=await f(e,`/api/v1/users/me/recovery-contacts/${k(t,"contactId")}`,{method:"PATCH",body:n});return Q(r)},async delete(t){await h(e,`/api/v1/users/me/recovery-contacts/${k(t,"contactId")}`,{method:"DELETE"})}},identities:{async list(){let t=await f(e,"/api/v1/users/me/identities");return Array.isArray(t)?t.filter(n=>typeof n=="object"&&n!==null).map(Ke):[]},async unlink(t){await h(e,`/api/v1/users/me/identities/${k(t,"identityId")}`,{method:"DELETE"})},async setPrimary(t){return f(e,`/api/v1/users/me/identities/${k(t,"identityId")}/primary`,{method:"PATCH"})}},async deleteMe(t){await h(e,"/api/v1/users/me",{method:"DELETE",body:{password:t.password}})}}}function b(e){return e&&typeof e=="object"?e:{}}function le(e){return{async registerBegin(t){let n=await u(e),r=await e.http.request("/api/v1/users/me/webauthn/register/begin",{method:"POST",token:n,body:t});return b(r)},async registerFinish(t){let n=await u(e),r=await e.http.request("/api/v1/users/me/webauthn/register/finish",{method:"POST",token:n,body:t});return b(r)},async authenticateBegin(t){let n=await e.http.request("/api/v1/users/me/webauthn/authenticate/begin",{method:"POST",body:t});return b(n)},async authenticateFinish(t){let n=await e.http.request("/api/v1/users/me/webauthn/authenticate/finish",{method:"POST",body:t}),r=b(n);if(typeof r.access_token=="string"){let o=m(r);await e.setTokens(o)}return r},async credentials(){let t=await u(e),n=await e.http.request("/api/v1/users/me/webauthn/credentials",{token:t});return b(n)},async removeCredential(t,n){let r=await u(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 u(e),o=await e.http.request(`/api/v1/users/me/webauthn/credentials/${encodeURIComponent(t)}/rename`,{method:"PATCH",token:r,body:n});return b(o)},async setPrimaryCredential(t){let n=await u(e),r=await e.http.request(`/api/v1/users/me/webauthn/credentials/${encodeURIComponent(t)}/primary`,{method:"POST",token:n});return b(r)}}}function Ye(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 me(e){return{async status(){let t=await e.http.request("/api/v1/maintenance/status");return Ye(t)}}}var Xe=128,W=254;function y(e,t){if(typeof e!="string")return;let n=e.trim();if(!(!n||n.length>t))return n}function he(e){return{async request(t){let n=c({email:y(t.email,W),identifier:y(t.identifier,W),recovery_email:y(t.recoveryEmail,W),phone_number:y(t.phoneNumber,32),member_id:y(t.memberId,64),security_question:y(t.securityQuestion,256),security_answer:y(t.securityAnswer,256),redirect_uri:y(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>Xe)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 Ze(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 ye(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 f(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 f(e,"/api/v1/users/me/mfa/verify",{method:"POST",body:{device_id:t.deviceId,totp_code:t.totpCode}})},async devices(){let t=await f(e,"/api/v1/users/me/mfa/devices");return Array.isArray(t)?t.filter(n=>typeof n=="object"&&n!==null).map(Ze):[]},async removeDevice(t){await h(e,`/api/v1/users/me/mfa/devices/${k(t,"deviceId")}`,{method:"DELETE"})},async regenerateBackupCodes(){let t=await f(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 h(e,"/api/v1/users/me/mfa/disable",{method:"POST",body:c({password:t?.password})})}}}var ge="https://prod-auth.tktchurch.com",et=new Set(["localhost","127.0.0.1","[::1]"]);function tt(e){return et.has(e.toLowerCase())}function K(e,t={}){let n=(e??ge).trim();if(!n)return K(ge,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&&!tt(s.hostname))throw new i("invalid_request","baseUrl must use HTTPS. Set allowInsecureTransport=true only for trusted local development.");return o}function nt(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 rt(e,t){return/^https?:\/\//i.test(t)?t:`${e}${t.startsWith("/")?t:`/${t}`}`}function ot(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 _e(e){return{async request(t,n){let r=await this.requestRaw(t,n);if(!r.ok)throw await Z(r);if(r.status===204)return;let o=await r.text();if(o)return JSON.parse(o)},async requestRaw(t,n){let r=rt(e.baseUrl,t),o=ot(e.defaultHeaders,n?.headers,n?.token),{signal:s,cleanup:a}=nt(n?.signal,e.timeoutMs),d={method:n?.method??"GET",headers:o,signal:s,redirect:n?.redirect};typeof n?.body<"u"&&(n.body instanceof FormData?d.body=n.body:(o.has("Content-Type")||o.set("Content-Type","application/json"),d.body=JSON.stringify(n.body)));try{return await e.fetch(r,d)}catch(l){throw s?.aborted??!1?new i("timeout",`Request timed out: ${r}`,{details:l}):new i("network_error",`Network request failed: ${r}`,{details:l})}finally{a()}}}}var be="@tktchurch/auth/tokens",I=32768,Re=new Set(["accessToken","tokenType","expiresIn","refreshToken","scope","idToken","authorizationDetails"]),we=new Set(["__proto__","constructor","prototype"]),st=/^[\w@./:-]{1,128}$/;function Y(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function ke(e){return typeof e=="string"&&e.length>0}function G(e){return typeof e=="string"&&e.length>0?e:void 0}function v(e){if(!Y(e))throw new i("invalid_request","Token storage rejected a non-object token payload.");for(let s of Object.keys(e))if(!Re.has(s))throw new i("invalid_request",`Token storage rejected unknown field "${s}".`);if(!ke(e.accessToken))throw new i("invalid_request","Token storage requires a non-empty accessToken.");if(!ke(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=G(e.refreshToken);n&&(t.refreshToken=n);let r=G(e.scope);r&&(t.scope=r);let o=G(e.idToken);if(o&&(t.idToken=o),typeof e.authorizationDetails<"u"){if(!Array.isArray(e.authorizationDetails)||!e.authorizationDetails.every(s=>Y(s)&&typeof s.type=="string"))throw new i("invalid_request","Token storage rejected malformed authorizationDetails.");t.authorizationDetails=e.authorizationDetails}return t}function ve(e){if(!Y(e))return null;for(let t of Object.keys(e))if(we.has(t)||!Re.has(t))return null;try{return v(e)}catch{return null}}function Se(e){return JSON.parse(e,(t,n)=>{if(we.has(t))throw new SyntaxError("Forbidden JSON key in stored token payload.");return n})}function Ae(e){let t=e.trim();if(!t)throw new i("invalid_request","Token storage key must be a non-empty string.");if(!st.test(t))throw new i("invalid_request","Token storage key contains unsupported characters or exceeds 128 characters.");return t}function Te(e){let t=v(e),n=JSON.stringify(t);if(n.length>I)throw new i("invalid_request",`Serialized token payload exceeds ${I} bytes.`);return n}var X=class{state=null;get(){return this.state}set(t){if(!t){this.clear();return}this.state=v(t)}clear(){this.state=null}};function z(){return new X}function it(){return typeof window<"u"&&typeof window.document<"u"}function at(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 Ce(e){if(!e.clientId?.trim())throw new i("invalid_request","clientId is required.");if(it()&&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:K(e.baseUrl,{allowInsecureTransport:e.allowInsecureTransport??!1}),fetch:at(e.fetch),storage:e.storage??z(),timeoutMs:e.timeoutMs??15e3,defaultHeaders:e.defaultHeaders??{},autoRefresh:e.autoRefresh??!0,allowClientSecretInBrowser:e.allowClientSecretInBrowser??!1,allowInsecureTransport:e.allowInsecureTransport??!1}}function Ee(e){let t=Ce(e),n=_e({baseUrl:t.baseUrl,fetch:t.fetch,timeoutMs:t.timeoutMs,defaultHeaders:t.defaultHeaders}),r=async()=>await t.storage.get(),o=async d=>{await t.storage.set(d)},s=async()=>{await t.storage.clear()},a={config:t,http:n,getTokens:r,setTokens:o,clearTokens:s};return{token:q(a),flow:ce(a),oidc:pe(a),authorize:ue(a),user:fe(a),sessions:te(a),webauthn:le(a),passwordReset:he(a),mfa:ye(a),maintenance:me(a),oauth:ie(a),tokens:{get:r,set:o,clear:s}}}var dt=/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;function qe(e,t="/"){if(!e)return t;let n=e;try{n=decodeURIComponent(e)}catch{return t}return n.startsWith("/")&&!n.startsWith("//")&&!n.startsWith("/\\")&&!dt.test(n)&&!/^\/+[\\/]/.test(n)?n:t}0&&(module.exports={AuthError,DEFAULT_AUTHORIZE_SCOPE,DEFAULT_TOKEN_STORAGE_KEY,MAX_STORED_TOKEN_BYTES,MFARequiredAuthError,assertValidAuthTokens,createAuthClient,createMemoryTokenStorage,deriveCodeChallenge,generateCodeVerifier,generateNonce,generatePkce,generateState,isAuthError,isUnrecoverableAuthError,parseStoredAuthTokens,randomUrlSafeString,safeParseStoredJson,sanitizePostAuthRedirect,serializeAuthTokens,timingSafeEqual,validateStorageKey});
1
+ "use strict";var V=Object.defineProperty;var Ne=Object.getOwnPropertyDescriptor;var Fe=Object.getOwnPropertyNames;var Le=Object.prototype.hasOwnProperty;var Be=(e,t)=>{for(var n in t)V(e,n,{get:t[n],enumerable:!0})},Je=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of Fe(t))!Le.call(e,r)&&r!==n&&V(e,r,{get:()=>t[r],enumerable:!(o=Ne(t,r))||o.enumerable});return e};var He=e=>Je(V({},"__esModule",{value:!0}),e);var kt={};Be(kt,{AuthError:()=>i,DEFAULT_AUTHORIZE_SCOPE:()=>N,DEFAULT_TOKEN_STORAGE_KEY:()=>Pe,MAX_STORED_TOKEN_BYTES:()=>B,MFARequiredAuthError:()=>S,PhoneOtpError:()=>k,assertValidAuthTokens:()=>q,createAuthClient:()=>J,createMemoryTokenStorage:()=>O,createPhoneOtpClient:()=>Me,deriveCodeChallenge:()=>Y,generateCodeVerifier:()=>K,generateNonce:()=>D,generatePkce:()=>j,generateState:()=>x,isAuthError:()=>P,isUnrecoverableAuthError:()=>ae,parseStoredAuthTokens:()=>Oe,randomUrlSafeString:()=>z,safeParseStoredJson:()=>Ie,sanitizePostAuthRedirect:()=>$e,serializeAuthTokens:()=>ze,timingSafeEqual:()=>M,validateStorageKey:()=>Ue});module.exports=He(kt);var i=class extends Error{code;status;details;constructor(t,n,o){super(n),this.name="AuthError",this.code=t,this.status=o?.status,this.details=o?.details}},S=class extends i{mfaMethods;constructor(t,n,o,r){super("mfa_required",t,{status:o,details:r}),this.name="MFARequiredAuthError",this.mfaMethods=n}};function Ve(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 Qe(e,t){if(!t)return new i("http_error",`Request failed with status ${e}`,{status:e});let n=Ve(t.error),o=t.error_description??t.reason??(t.error?`Auth error: ${t.error}`:`Request failed with status ${e}`);if(n==="mfa_required"){let r=Array.isArray(t.mfa_methods)?t.mfa_methods.filter(s=>typeof s=="string"):[];return new S(o,r,e,t)}return new i(n,o,{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 Qe(e.status,t)}function P(e){return e instanceof i}function ae(e){return P(e)?e.code==="invalid_client"||e.code==="invalid_grant"||e.code==="unauthorized_client"||e.code==="invalid_token":!1}function Q(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 f(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 m(e){return Object.fromEntries(Object.entries(e).filter(([,t])=>typeof t<"u"))}function b(e,t){let n=e.access_token,o=e.token_type,r=e.expires_in;if(typeof n!="string"||typeof o!="string"||typeof r!="number")throw new i("unknown_error","Unexpected token response payload.",{details:e});let s={accessToken:n,tokenType:o,expiresIn:r},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=Q(e.authorization_details);return u&&(s.authorizationDetails=u),s}function We(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 W(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 G(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 Ge(e){let t=Array.isArray(e.items)?e.items:[],o=e.metadata&&typeof e.metadata=="object"?e.metadata:{};return{items:t.filter(r=>typeof r=="object"&&r!==null).map(We),metadata:{page:typeof o.page=="number"?o.page:1,per:typeof o.per=="number"?o.per:t.length,total:typeof o.total=="number"?o.total:t.length}}}function ue(e){return{async list(t){let n=await f(e),o=new URLSearchParams;typeof t?.page=="number"&&o.set("page",String(t.page)),typeof t?.perPage=="number"&&o.set("per_page",String(t.perPage)),t?.userId&&o.set("user_id",t.userId);let r=`/api/v1/sessions${o.size?`?${o.toString()}`:""}`,s=await e.http.request(r,{token:n});return Ge(s)},async current(){let t=await f(e),n=await e.http.request("/api/v1/sessions/current",{token:t});return W(n)},async get(t){let n=await f(e),o=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{token:n});return W(o)},async update(t,n){let o=await f(e),r=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{method:"PATCH",token:o,body:m({device_name:n.deviceName,is_trusted:n.isTrusted})});return W(r)},async revoke(t,n){let o=await f(e),r=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{method:"DELETE",token:o,body:m({reason:n?.reason})});return G(r)},async revokeAll(){let t=await f(e),n=await e.http.request("/api/v1/sessions/revoke-all",{method:"POST",token:t});return G(n)},async revokeAllDevices(){let t=await f(e),n=await e.http.request("/api/v1/sessions/revoke-all-devices",{method:"POST",token:t});return G(n)}}}function de(){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 ce(e){let t="";for(let o of e)t+=String.fromCharCode(o);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 z(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 de().getRandomValues(t),ce(t)}function x(){return z(32)}function D(){return z(32)}function K(){return z(32)}async function Y(e){let t=de();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),o=await t.subtle.digest("SHA-256",n);return ce(new Uint8Array(o))}async function j(e="S256"){let t=K();if(e==="plain")return{codeVerifier:t,codeChallenge:t,codeChallengeMethod:"plain"};let n=await Y(t);return{codeVerifier:t,codeChallenge:n,codeChallengeMethod:"S256"}}function M(e,t){let n=new TextEncoder().encode(e),o=new TextEncoder().encode(t),r=Math.max(n.length,o.length),s=n.length^o.length;for(let a=0;a<r;a+=1)s|=(n[a]??0)^(o[a]??0);return s===0}function Ke(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:Q(e.authorization_details)}}function $(e){return{async password(t){let n=m({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}),o=await e.http.request("/oauth/token",{method:"POST",body:n}),r=b(o);return await e.setTokens(r),r},async refresh(t){let n=await e.getTokens(),o=t?.refreshToken??n?.refreshToken;if(!o)throw new i("invalid_request","No refresh token is available.");let r=m({grant_type:"refresh_token",client_id:e.config.clientId,client_secret:e.config.clientSecret,refresh_token:o,scope:t?.scope}),s=await e.http.request("/oauth/token",{method:"POST",body:r}),a=b(s,o);return await e.setTokens(a),a},async clientCredentials(t){let n=m({grant_type:"client_credentials",client_id:e.config.clientId,client_secret:e.config.clientSecret,scope:t?.scope}),o=await e.http.request("/oauth/token",{method:"POST",body:n}),r=b(o);return await e.setTokens(r),r},async authorizationCode(t){let n=m({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}),o=await e.http.request("/oauth/token",{method:"POST",body:n}),r=b(o);return await e.setTokens(r),r},async revoke(t){let n=await e.getTokens(),o=t?.token??n?.accessToken;if(!o)throw new i("invalid_request","No token provided for revocation.");await e.http.request("/oauth/revoke",{method:"POST",body:m({client_id:e.config.clientId,client_secret:e.config.clientSecret,token:o,token_type_hint:t?.tokenTypeHint})}),n&&(o===n.accessToken||o===n.refreshToken)&&await e.clearTokens()},async introspect(t){let n=await e.http.request("/oauth/introspect",{method:"POST",body:m({client_id:e.config.clientId,client_secret:e.config.clientSecret,token:t.token})});return Ke(n)},async tokenExchange(t){let n=m({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}),o=await e.http.request("/oauth/token",{method:"POST",body:n}),r=b(o);return await e.setTokens(r),r}}}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 o=await f(e),r=n?.query,s=r&&Object.keys(r).length>0?`?${new URLSearchParams(Object.entries(r).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:o})}async function v(e,t,n){await h(e,t,n)}function A(e,t){return encodeURIComponent(pe(e,t))}function Ye(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 Xe(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 Ze(e){let t=Array.isArray(e.mfa_methods)?e.mfa_methods.filter(o=>typeof o=="string"):void 0,n=Array.isArray(e.uncovered_scopes)?e.uncovered_scopes.filter(o=>typeof o=="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}}function fe(e,t){let n=new URL(e),o=new URL(t);if(n.origin!==o.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 le(e){async function t(o,r){let s=fe(o,e.config.baseUrl),a=await e.http.request(`${s.pathname}${s.search}`,{method:"POST",body:{username:r.username,password:r.password,approved:r.approved??!0,requested_scopes:r.requestedScopes,mfa_code:r.mfaCode,mfa_backup_code:r.mfaBackupCode}}),u=Ye(a);if(u.error==="mfa_required")throw new S(u.errorDescription??"Multi-factor authentication is required",u.mfaMethods??[],void 0,a);return u}async function n(o,r){let s=fe(o,e.config.baseUrl),a=await e.http.request(`${s.pathname}/evaluate${s.search}`,{method:"POST",body:{username:r.username,password:r.password,requested_scopes:r.requestedScopes,mfa_code:r.mfaCode,mfa_backup_code:r.mfaBackupCode}}),u=Ze(a);if(u.error==="mfa_required"||u.requiresMfa)throw new S(u.errorDescription??"Multi-factor authentication is required",u.mfaMethods??[],void 0,a);return u}return{consent:t,evaluateConsent:n,consentWithCredentials(o){return t(o.callbackUrl,{username:o.username,password:o.password,approved:o.approved,requestedScopes:o.requestedScopes,mfaCode:o.mfaCode,mfaBackupCode:o.mfaBackupCode})},evaluateConsentWithCredentials(o){return n(o.callbackUrl,{username:o.username,password:o.password,requestedScopes:o.requestedScopes,mfaCode:o.mfaCode,mfaBackupCode:o.mfaBackupCode})}}}function me(e){return{async publicClient(t){let n=await e.http.request(`/oauth/clients/${encodeURIComponent(t)}/public`);return Xe(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 N="openid profile email offline_access",he=new Set(["response_type","client_id","redirect_uri","scope","state","nonce","code_challenge","code_challenge_method","prompt","login_hint","authorization_details","request_uri"]);function et(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 ge(e){let t=et(e),n={};return t.forEach((o,r)=>{n[r]=o}),{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 ye(e){let t=$(e);return{...le(e),async createRequest(o){if(!o.redirectUri?.trim())throw new i("invalid_request","redirectUri is required to build an authorization request.");let r=o.scope??N,s=o.state??x(),a=o.nonce??D(),u=await j(o.codeChallengeMethod??"S256"),y=o.authorizationEndpoint??`${e.config.baseUrl}/oauth/authorize`,R=new URL(y),l=R.searchParams;l.set("response_type",o.responseType??"code"),l.set("client_id",e.config.clientId),l.set("redirect_uri",o.redirectUri),l.set("scope",r),l.set("state",s),l.set("nonce",a),l.set("code_challenge",u.codeChallenge),l.set("code_challenge_method",u.codeChallengeMethod),o.authorizationDetails&&o.authorizationDetails.length>0&&l.set("authorization_details",JSON.stringify(o.authorizationDetails)),o.prompt&&l.set("prompt",o.prompt),o.loginHint&&l.set("login_hint",o.loginHint);for(let[w,U]of Object.entries(o.extraParams??{})){if(he.has(w))throw new i("invalid_request",`extraParams may not override the reserved authorization parameter "${w}".`);l.set(w,U)}return{url:R.toString(),state:s,nonce:a,codeVerifier:u.codeVerifier,codeChallenge:u.codeChallenge,redirectUri:o.redirectUri,scope:r}},async pushAuthorizationRequest(o){if(!o.redirectUri?.trim())throw new i("invalid_request","redirectUri is required to push an authorization request.");let r=o.scope??N,s=o.state??x(),a=o.nonce??D(),u=await j(o.codeChallengeMethod??"S256"),y={response_type:o.responseType??"code",client_id:e.config.clientId,redirect_uri:o.redirectUri,scope:r,state:s,nonce:a,code_challenge:u.codeChallenge,code_challenge_method:u.codeChallengeMethod};e.config.clientSecret&&(y.client_secret=e.config.clientSecret),o.authorizationDetails&&o.authorizationDetails.length>0&&(y.authorization_details=JSON.stringify(o.authorizationDetails));for(let[c,d]of Object.entries(o.extraParams??{})){if(he.has(c))throw new i("invalid_request",`extraParams may not override the reserved authorization parameter "${c}".`);y[c]=d}let R=await e.http.request("/oauth/par",{method:"POST",body:y}),l=R.request_uri,w=R.expires_in;if(typeof l!="string"||typeof w!="number")throw new i("unknown_error","Unexpected pushed authorization response payload.",{details:R});let U=o.authorizationEndpoint??`${e.config.baseUrl}/oauth/authorize`,p=new URL(U);return p.searchParams.set("client_id",e.config.clientId),p.searchParams.set("request_uri",l),{requestUri:l,expiresIn:w,request:{url:p.toString(),state:s,nonce:a,codeVerifier:u.codeVerifier,codeChallenge:u.codeChallenge,redirectUri:o.redirectUri,scope:r,requestUri:l}}},parseCallback:ge,async probeAuthorize(o){let r=await e.http.requestRaw(o,{headers:{Accept:"application/json"},redirect:"manual"}),s=r.headers.get("location");if(s&&r.status>=300&&r.status<400)return{kind:"redirect",location:s,status:r.status};let a=await r.text(),u=a?JSON.parse(a):{};if(!r.ok)throw new i("server_error",typeof u.error_description=="string"?u.error_description:"Authorization probe failed",{status:r.status,details:u});let y=typeof u.authorization_url=="string"?u.authorization_url:void 0;return{kind:"json",status:r.status,authorizationUrl:y,payload:u}},async handleCallback(o){let s=typeof o.callback=="object"&&!(o.callback instanceof URLSearchParams)&&"raw"in o.callback?o.callback:ge(o.callback);if(s.error)throw new i("access_denied",s.errorDescription??`Authorization failed: ${s.error}`,{details:s.raw});if(!s.state||!M(s.state,o.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:o.redirectUri,codeVerifier:o.codeVerifier,scope:o.scope})}}}function E(e){let t=e.step_id,n=e.step_type,o=e.name,r=e.order,s=e.is_required,a=e.timeout;if(typeof t!="string"||typeof n!="string"||typeof o!="string"||typeof r!="number"||typeof s!="boolean"||typeof a!="number")throw new i("unknown_error","Unexpected flow step payload.",{details:e});return{stepId:t,stepType:n,name:o,description:typeof e.description=="string"?e.description:void 0,order:r,isRequired:s,timeout:a,inputSchema:e.input_schema&&typeof e.input_schema=="object"?e.input_schema:void 0}}function X(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 o=e.continuation_token;if(typeof o!="string"||!o)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(r=>typeof r=="object"&&r!==null).map(E):[],expiresAt:String(e.expires_at??""),continuationToken:o}}function tt(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 Z(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 o=t.next_step,r=t.remaining_steps;if(!o||typeof o!="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(o),completedSteps:Array.isArray(t.completed_steps)?t.completed_steps.filter(u=>typeof u=="string"):[],remainingSteps:Array.isArray(r)?r.filter(u=>typeof u=="object"&&u!==null).map(E):[],debug:s}}if(n==="complete"){let o=b(t);return await e.setTokens(o),{status:"complete",accessToken:o.accessToken,refreshToken:o.refreshToken,tokenType:o.tokenType,expiresIn:o.expiresIn,scope:o.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 _e(e){return{async initAuthentication(t){let n=await e.http.request("/oauth/authenticate/init",{method:"POST",body:m({client_id:t?.clientId??e.config.clientId,username:t?.username,flow_type:t?.flowType})});return X(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 Z(e,n)},async initRegistration(t){let n=await e.http.request("/oauth/register/init",{method:"POST",body:m({client_id:t?.clientId??e.config.clientId,invite_code:t?.inviteCode,flow_type:t?.flowType})});return X(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 Z(e,n)},async getStatus(t,n){let o=new URLSearchParams({continuation_token:n}),r=await e.http.request(`/oauth/flow/status/${encodeURIComponent(t)}?${o}`);return tt(r)},async initRecovery(t){let n=await e.http.request("/oauth/recovery/init",{method:"POST",body:m({client_id:t?.clientId??e.config.clientId,recovery_type:t?.recoveryType,redirect_uri:t?.redirectUri})});return X(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 Z(e,n)}}}function F(e){return Array.isArray(e)?e.filter(t=>typeof t=="string"):[]}function nt(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 ot(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 ke(e){return{async userInfo(){let t=await f(e),n=await e.http.request("/oauth/userinfo",{token:t});return ot(n)},async discover(){let t=await e.http.request("/.well-known/openid-configuration");return nt(t)},async jwks(){let t=await e.http.request("/oauth/jwks");return{keys:Array.isArray(t.keys)?t.keys.filter(o=>typeof o=="object"&&o!==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,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 ee(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 rt(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 be(e){return st(e)}function st(e){return{async me(){let t=await f(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)},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 o=await f(e),r=new FormData,s=t instanceof Blob?t:new Blob([new Uint8Array(t)]);r.append("avatar",s,n);let a=await e.http.request("/api/v1/users/me/avatar",{method:"POST",token:o,body:r});return L(a)},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(ee):[]},async create(t){let n=await h(e,"/api/v1/users/me/recovery-contacts",{method:"POST",body:t});return ee(n)},async update(t,n){let o=await h(e,`/api/v1/users/me/recovery-contacts/${A(t,"contactId")}`,{method:"PATCH",body:n});return ee(o)},async delete(t){await v(e,`/api/v1/users/me/recovery-contacts/${A(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(rt):[]},async unlink(t){await v(e,`/api/v1/users/me/identities/${A(t,"identityId")}`,{method:"DELETE"})},async setPrimary(t){return h(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 Re(e){return{async registerBegin(t){let n=await f(e),o=await e.http.request("/api/v1/users/me/webauthn/register/begin",{method:"POST",token:n,body:t});return C(o)},async registerFinish(t){let n=await f(e),o=await e.http.request("/api/v1/users/me/webauthn/register/finish",{method:"POST",token:n,body:t});return C(o)},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}),o=C(n);if(typeof o.access_token=="string"){let r=b(o);await e.setTokens(r)}return o},async credentials(){let t=await f(e),n=await e.http.request("/api/v1/users/me/webauthn/credentials",{token:t});return C(n)},async removeCredential(t,n){let o=await f(e);await e.http.request(`/api/v1/users/me/webauthn/credentials/${encodeURIComponent(t)}`,{method:"DELETE",token:o,body:n?.password?{password:n.password}:void 0})},async renameCredential(t,n){let o=await f(e),r=await e.http.request(`/api/v1/users/me/webauthn/credentials/${encodeURIComponent(t)}/rename`,{method:"PATCH",token:o,body:n});return C(r)},async setPrimaryCredential(t){let n=await f(e),o=await e.http.request(`/api/v1/users/me/webauthn/credentials/${encodeURIComponent(t)}/primary`,{method:"POST",token:n});return C(o)}}}function it(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 we(e){return{async status(){let t=await e.http.request("/api/v1/maintenance/status");return it(t)}}}var at=128,te=254;function T(e,t){if(typeof e!="string")return;let n=e.trim();if(!(!n||n.length>t))return n}function Se(e){return{async request(t){let n=m({email:T(t.email,te),identifier:T(t.identifier,te),recovery_email:T(t.recoveryEmail,te),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)}),o=await e.http.request("/oauth/password/reset/request",{method:"POST",body:n});return{ok:!!o.ok,message:String(o.message??"")}},async validate(t){let n=t.trim();if(!n)throw new i("invalid_request","token is required.");let o=await e.http.request(`/oauth/password/reset/validate?token=${encodeURIComponent(n)}`);return{ok:!!o.ok,valid:!!o.valid,expiresAt:typeof o.expires_at=="string"?o.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>at)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 ut(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 ve(e){return{async setup(t){let n=t.deviceName?.trim();if(!n||n.length>128)throw new i("invalid_request","deviceName is required.");let o=await h(e,"/api/v1/users/me/mfa/setup",{method:"POST",body:{device_name:n}});return{deviceId:String(o.device_id??""),secret:String(o.secret??""),qrCodeUri:String(o.qr_code_uri??""),backupCodes:Array.isArray(o.backup_codes)?o.backup_codes.filter(r=>typeof r=="string"):[],algorithm:String(o.algorithm??""),digits:typeof o.digits=="number"?o.digits:6,period:typeof o.period=="number"?o.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(ut):[]},async removeDevice(t){await v(e,`/api/v1/users/me/mfa/devices/${A(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:m({password:t?.password})})}}}var Te="https://prod-auth.tktchurch.com",dt=new Set(["localhost","127.0.0.1","[::1]"]);function ct(e){return dt.has(e.toLowerCase())}function ne(e,t={}){let n=(e??Te).trim();if(!n)return ne(Te,t);let r=(/^https?:\/\//i.test(n)?n:`https://${n}`).replace(/\/+$/,""),s;try{s=new URL(r)}catch{throw new i("invalid_request","baseUrl must be a valid absolute URL.")}if(s.protocol==="http:"&&!t.allowInsecureTransport&&!ct(s.hostname))throw new i("invalid_request","baseUrl must use HTTPS. Set allowInsecureTransport=true only for trusted local development.");return r}function pt(e,t){if(!e&&t<=0)return{cleanup:()=>{}};let n=new AbortController,o,r=()=>{n.abort(e?.reason)};return e&&(e.aborted?n.abort(e.reason):e.addEventListener("abort",r,{once:!0})),t>0&&(o=setTimeout(()=>{n.abort(new Error(`Request timed out after ${t}ms`))},t)),{signal:n.signal,cleanup:()=>{o&&clearTimeout(o),e&&e.removeEventListener("abort",r)}}}function ft(e,t){return/^https?:\/\//i.test(t)?t:`${e}${t.startsWith("/")?t:`/${t}`}`}function lt(e,t,n){let o=new Headers(e);return t&&new Headers(t).forEach((s,a)=>{o.set(a,s)}),n&&!o.has("Authorization")&&o.set("Authorization",`Bearer ${n}`),o}function Ae(e){return{async request(t,n){let o=await this.requestRaw(t,n);if(!o.ok)throw await ie(o);if(o.status===204)return;let r=await o.text();if(r)return JSON.parse(r)},async requestRaw(t,n){let o=ft(e.baseUrl,t),r=lt(e.defaultHeaders,n?.headers,n?.token),{signal:s,cleanup:a}=pt(n?.signal,e.timeoutMs),u={method:n?.method??"GET",headers:r,signal:s,redirect:n?.redirect};typeof n?.body<"u"&&(n.body instanceof FormData?u.body=n.body:(r.has("Content-Type")||r.set("Content-Type","application/json"),u.body=JSON.stringify(n.body)));try{return await e.fetch(o,u)}catch(y){throw s?.aborted??!1?new i("timeout",`Request timed out: ${o}`,{details:y}):new i("network_error",`Network request failed: ${o}`,{details:y})}finally{a()}}}}var Pe="@tktchurch/auth/tokens",B=32768,Ee=new Set(["accessToken","tokenType","expiresIn","refreshToken","scope","idToken","authorizationDetails"]),qe=new Set(["__proto__","constructor","prototype"]),mt=/^[\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 Ce(e){return typeof e=="string"&&e.length>0}function oe(e){return typeof e=="string"&&e.length>0?e:void 0}function q(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(!Ee.has(s))throw new i("invalid_request",`Token storage rejected unknown field "${s}".`);if(!Ce(e.accessToken))throw new i("invalid_request","Token storage requires a non-empty accessToken.");if(!Ce(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=oe(e.refreshToken);n&&(t.refreshToken=n);let o=oe(e.scope);o&&(t.scope=o);let r=oe(e.idToken);if(r&&(t.idToken=r),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 Oe(e){if(!re(e))return null;for(let t of Object.keys(e))if(qe.has(t)||!Ee.has(t))return null;try{return q(e)}catch{return null}}function Ie(e){return JSON.parse(e,(t,n)=>{if(qe.has(t))throw new SyntaxError("Forbidden JSON key in stored token payload.");return n})}function Ue(e){let t=e.trim();if(!t)throw new i("invalid_request","Token storage key must be a non-empty string.");if(!mt.test(t))throw new i("invalid_request","Token storage key contains unsupported characters or exceeds 128 characters.");return t}function ze(e){let t=q(e),n=JSON.stringify(t);if(n.length>B)throw new i("invalid_request",`Serialized token payload exceeds ${B} bytes.`);return n}var se=class{state=null;get(){return this.state}set(t){if(!t){this.clear();return}this.state=q(t)}clear(){this.state=null}};function O(){return new se}function ht(){return typeof window<"u"&&typeof window.document<"u"}function gt(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 xe(e){if(!e.clientId?.trim())throw new i("invalid_request","clientId is required.");if(ht()&&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:ne(e.baseUrl,{allowInsecureTransport:e.allowInsecureTransport??!1}),fetch:gt(e.fetch),storage:e.storage??O(),timeoutMs:e.timeoutMs??15e3,defaultHeaders:e.defaultHeaders??{},autoRefresh:e.autoRefresh??!0,allowClientSecretInBrowser:e.allowClientSecretInBrowser??!1,allowInsecureTransport:e.allowInsecureTransport??!1}}function J(e){let t=xe(e),n=Ae({baseUrl:t.baseUrl,fetch:t.fetch,timeoutMs:t.timeoutMs,defaultHeaders:t.defaultHeaders}),o=async()=>await t.storage.get(),r=async u=>{await t.storage.set(u)},s=async()=>{await t.storage.clear()},a={config:t,http:n,getTokens:o,setTokens:r,clearTokens:s};return{token:$(a),flow:_e(a),oidc:ke(a),authorize:ye(a),user:be(a),sessions:ue(a),webauthn:Re(a),passwordReset:Se(a),mfa:ve(a),maintenance:we(a),oauth:me(a),tokens:{get:o,set:r,clear:s}}}var k=class extends Error{name="PhoneOtpError"};function I(e,t){return e instanceof k?e:P(e)?new k(e.message||t):e instanceof Error&&e.message?new k(e.message):new k(t)}function De(e){if(!P(e)||typeof e.status!="number")return!1;let t=e.status;return t>=400&&t<500&&t!==429}function je(e){if(e.status!=="in_progress"||!e.debug)return;let t=e.debug.verification_code;return typeof t=="string"?t:void 0}function yt(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 Me(e){let t=O(),n=e.auth??J({baseUrl:e.baseUrl,clientId:e.clientId,allowInsecureTransport:e.allowInsecureTransport,timeoutMs:e.timeoutMs??2e4,storage:t,autoRefresh:!1}),o=e.guestProfile??{firstName:"Guest",lastName:"User"},r=o.emailDomain??"registrations.tktchurch.local",s=o.emailLocalPrefix??"reg";async function a(p,c){let d=await n.flow.initAuthentication({flowType:"phone_only",username:c}),g=await n.flow.executeStep({sessionId:d.sessionId,stepId:d.currentStep.stepId,continuationToken:d.continuationToken,credential:p});if(g.status!=="in_progress")throw new k("Unexpected sign-in response.");return{oauthSessionId:d.sessionId,verifyStepId:g.nextStep.stepId,mode:"authentication",continuationToken:d.continuationToken,debugCode:je(g)}}async function u(p){let c=await n.flow.initRegistration({flowType:"phone_registration"}),d=await n.flow.executeRegistrationStep({sessionId:c.sessionId,stepId:c.currentStep.stepId,continuationToken:c.continuationToken,credential:p});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:je(d)}}async function y(p,c,d){let g={phone_country_code:p,phone_number:c};try{return await u(g)}catch(_){if(!De(_))throw I(_,"Could not send the code. Please try again.");try{return await a(g,d)}catch{throw I(_,"Could not send the code. Please try again.")}}}async function R(p,c,d){let g={phone_country_code:p,phone_number:c};try{return await a(g,d)}catch(_){if(!De(_))throw I(_,"Could not start sign-in.");try{return await u(g)}catch{throw I(_,"Could not start sign-in.")}}}async function l(p,c,d,g,_){return p==="registration"?await n.flow.executeRegistrationStep({sessionId:c,stepId:d,continuationToken:g,credential:_}):await n.flow.executeStep({sessionId:c,stepId:d,continuationToken:g,credential:_})}async function w(p,c){try{let d=await l(p.mode,p.oauthSessionId,p.verifyStepId,p.continuationToken,{code:c}),g=0;for(;d.status==="in_progress"&&g<6;){g++;let _=d.nextStep.stepType,H={};_==="profile_info"?H={first_name:o.firstName,last_name:o.lastName,email:`${s}+${p.oauthSessionId.slice(0,8).toLowerCase()}@${r}`}:_==="terms_acceptance"&&(H={accepted:"true"}),d=await l(p.mode,p.oauthSessionId,d.nextStep.stepId,p.continuationToken,H)}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:I(d,"That code didn't match. Try again.")}}async function U(p){try{await t.set({accessToken:p,tokenType:"Bearer",expiresIn:3600});let c=await n.user.me();return yt(c)}catch(c){throw I(c,c instanceof i?`Profile fetch failed (${c.status??"error"})`:"Profile fetch failed")}}return{startPhoneOTP:y,startPhoneOTPForSignIn:R,verifyPhoneOTP:w,fetchProfile:U}}var _t=/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;function $e(e,t="/"){if(!e)return t;let n=e;try{n=decodeURIComponent(e)}catch{return t}return n.startsWith("/")&&!n.startsWith("//")&&!n.startsWith("/\\")&&!_t.test(n)&&!/^\/+[\\/]/.test(n)?n:t}0&&(module.exports={AuthError,DEFAULT_AUTHORIZE_SCOPE,DEFAULT_TOKEN_STORAGE_KEY,MAX_STORED_TOKEN_BYTES,MFARequiredAuthError,PhoneOtpError,assertValidAuthTokens,createAuthClient,createMemoryTokenStorage,createPhoneOtpClient,deriveCodeChallenge,generateCodeVerifier,generateNonce,generatePkce,generateState,isAuthError,isUnrecoverableAuthError,parseStoredAuthTokens,randomUrlSafeString,safeParseStoredJson,sanitizePostAuthRedirect,serializeAuthTokens,timingSafeEqual,validateStorageKey});
package/dist/index.d.cts CHANGED
@@ -1,27 +1,5 @@
1
- import { T as TokenEndpoints, F as FlowEndpoints, O as OidcEndpoints, A as AuthorizeEndpoints, U as UserEndpoints, S as SessionEndpoints, W as WebAuthnEndpoints, P as PasswordResetEndpoints, M as MfaEndpoints, a as MaintenanceStatus, b as OAuthMetaEndpoints, c as TokenStateEndpoints, d as AuthClientConfig, e as AuthTokens } from './memory-CVWZvsJ0.cjs';
2
- export { f as AuthorizationCodeTokenRequest, g as AuthorizationConsentBody, h as AuthorizationConsentResponse, i as AuthorizationDetail, j as AuthorizationProbeResult, k as AuthorizationRequest, l as AuthorizationUrlOptions, C as CallbackParams, m as ClientCredentialsRequest, E as ExecuteStepRequest, n as FlowInitResponse, o as FlowSessionStatus, p as FlowStep, q as FlowStepCompleteResult, r as FlowStepInProgressResult, s as FlowStepResult, H as HandleCallbackOptions, I as InitAuthenticationRequest, t as InitRegistrationRequest, u as IntrospectTokenRequest, v as MfaDevice, w as OAuthAuthorizationServerMetadata, x as OidcDiscoveryDocument, y as PageQuery, z as PageResponse, B as PasswordTokenRequest, D as PublicClientInfo, G as PushAuthorizationRequestOptions, J as PushedAuthorizationResult, R as RefreshTokenRequest, K as RevocationResponse, L as RevokeSessionRequest, N as RevokeTokenRequest, Q as SessionDetail, V as SessionListQuery, X as SessionSummary, Y as TokenExchangeRequest, Z as TokenIntrospectionResponse, _ as TokenStorage, $ as UpdateSessionRequest, a0 as UserInfoResponse, a1 as UserProfile, a2 as createMemoryTokenStorage } from './memory-CVWZvsJ0.cjs';
3
-
4
- /** Public maintenance probe — status only (no admin schedule/enable APIs). */
5
- interface ConsumerMaintenanceEndpoints {
6
- status(): Promise<MaintenanceStatus>;
7
- }
8
- /** Self-service user profile APIs without admin user-management helpers. */
9
- type ConsumerUserEndpoints = Omit<UserEndpoints, "admin">;
10
- /** OAuth/OIDC consumer client surface shipped on public npmjs. */
11
- interface ConsumerAuthClient {
12
- token: TokenEndpoints;
13
- flow: FlowEndpoints;
14
- oidc: OidcEndpoints;
15
- authorize: AuthorizeEndpoints;
16
- user: ConsumerUserEndpoints;
17
- sessions: SessionEndpoints;
18
- webauthn: WebAuthnEndpoints;
19
- passwordReset: PasswordResetEndpoints;
20
- mfa: MfaEndpoints;
21
- maintenance: ConsumerMaintenanceEndpoints;
22
- oauth: OAuthMetaEndpoints;
23
- tokens: TokenStateEndpoints;
24
- }
1
+ import { A as AuthClientConfig, C as ConsumerAuthClient, a as AuthClient, b as AuthTokens } from './memory-DNWYyNSB.cjs';
2
+ export { c as AuthorizationCodeTokenRequest, d as AuthorizationConsentBody, e as AuthorizationConsentResponse, f as AuthorizationDetail, g as AuthorizationProbeResult, h as AuthorizationRequest, i as AuthorizationUrlOptions, j as AuthorizeEndpoints, k as CallbackParams, l as ClientCredentialsRequest, m as ConsumerMaintenanceEndpoints, n as ConsumerUserEndpoints, E as ExecuteStepRequest, F as FlowEndpoints, o as FlowInitResponse, p as FlowSessionStatus, q as FlowStep, r as FlowStepCompleteResult, s as FlowStepInProgressResult, t as FlowStepResult, H as HandleCallbackOptions, I as InitAuthenticationRequest, u as InitRegistrationRequest, v as IntrospectTokenRequest, M as MaintenanceStatus, w as MfaDevice, x as MfaEndpoints, O as OAuthAuthorizationServerMetadata, y as OAuthMetaEndpoints, z as OidcDiscoveryDocument, B as OidcEndpoints, P as PageQuery, D as PageResponse, G as PasswordResetEndpoints, J as PasswordTokenRequest, K as PublicClientInfo, L as PushAuthorizationRequestOptions, N as PushedAuthorizationResult, R as RefreshTokenRequest, Q as RevocationResponse, S as RevokeSessionRequest, T as RevokeTokenRequest, U as SessionDetail, V as SessionEndpoints, W as SessionListQuery, X as SessionSummary, Y as TokenEndpoints, Z as TokenExchangeRequest, _ as TokenIntrospectionResponse, $ as TokenStorage, a0 as UpdateSessionRequest, a1 as UserEndpoints, a2 as UserInfoResponse, a3 as UserProfile, a4 as WebAuthnEndpoints, a5 as createMemoryTokenStorage } from './memory-DNWYyNSB.cjs';
25
3
 
26
4
  /**
27
5
  * Creates a consumer-only auth client for public npm distribution.
@@ -29,6 +7,75 @@ interface ConsumerAuthClient {
29
7
  */
30
8
  declare function createAuthClient(config: AuthClientConfig): ConsumerAuthClient;
31
9
 
10
+ /**
11
+ * High-level phone OTP registration / sign-in helpers built on AuthFlow endpoints.
12
+ * Used by Deno microsites and any server surface that should not hand-roll flow steps.
13
+ */
14
+
15
+ type PhoneOtpFlowMode = "registration" | "authentication";
16
+ interface PhoneOtpStart {
17
+ oauthSessionId: string;
18
+ verifyStepId: string;
19
+ mode: PhoneOtpFlowMode;
20
+ /** Q-022: required on every subsequent step. */
21
+ continuationToken: string;
22
+ debugCode?: string;
23
+ }
24
+ interface PhoneOtpTokens {
25
+ accessToken: string;
26
+ refreshToken?: string;
27
+ expiresIn?: number;
28
+ }
29
+ /** Snake_case profile shape expected by existing microsite callers. */
30
+ interface PhoneOtpProfile {
31
+ id: string;
32
+ first_name?: string;
33
+ last_name?: string;
34
+ email?: string;
35
+ phone_number?: string;
36
+ phone_country_code?: string;
37
+ phone_verified?: boolean;
38
+ profile_picture_url?: string;
39
+ picture?: string;
40
+ birthdate?: string;
41
+ [key: string]: unknown;
42
+ }
43
+ interface PhoneOtpGuestProfile {
44
+ firstName: string;
45
+ lastName: string;
46
+ /** Local-part prefix before `+{session}@…`; default `reg`. */
47
+ emailLocalPrefix?: string;
48
+ /** Default `registrations.tktchurch.local`. */
49
+ emailDomain?: string;
50
+ }
51
+ interface PhoneOtpClientConfig {
52
+ baseUrl: string;
53
+ clientId: string;
54
+ allowInsecureTransport?: boolean;
55
+ timeoutMs?: number;
56
+ guestProfile?: PhoneOtpGuestProfile;
57
+ /** Optional pre-built client (tests). */
58
+ auth?: AuthClient | ConsumerAuthClient;
59
+ }
60
+ declare class PhoneOtpError extends Error {
61
+ readonly name = "PhoneOtpError";
62
+ }
63
+ interface PhoneOtpClient {
64
+ startPhoneOTP(phoneCountryCode: string, phoneNational: string, e164: string): Promise<PhoneOtpStart>;
65
+ startPhoneOTPForSignIn(phoneCountryCode: string, phoneNational: string, e164: string): Promise<PhoneOtpStart>;
66
+ verifyPhoneOTP(start: Pick<PhoneOtpStart, "oauthSessionId" | "verifyStepId" | "mode" | "continuationToken">, code: string): Promise<PhoneOtpTokens>;
67
+ fetchProfile(accessToken: string): Promise<PhoneOtpProfile>;
68
+ }
69
+ /**
70
+ * Creates a phone OTP client backed by {@link createAuthClient} flow endpoints.
71
+ *
72
+ * Q-023: client-facing phone capture errors are generic — do not branch on
73
+ * “already registered” / “no account” substrings.
74
+ * Q-040: fall back once only on client/validation HTTP failures (4xx except
75
+ * 429), never on 5xx / rate limits / transport errors (no loops).
76
+ */
77
+ declare function createPhoneOtpClient(config: PhoneOtpClientConfig): PhoneOtpClient;
78
+
32
79
  /** Default persistence key for custom TokenStorage adapters. */
33
80
  declare const DEFAULT_TOKEN_STORAGE_KEY = "@tktchurch/auth/tokens";
34
81
  /** Upper bound on serialized token payload size (defense against storage DoS). */
@@ -125,4 +172,4 @@ declare function sanitizePostAuthRedirect(value?: string | null, fallback?: stri
125
172
  /** Default OIDC scope set covering identity claims plus refresh capability. */
126
173
  declare const DEFAULT_AUTHORIZE_SCOPE = "openid profile email offline_access";
127
174
 
128
- export { type ConsumerAuthClient as AuthClient, AuthClientConfig, AuthError, type AuthErrorCode, AuthTokens, AuthorizeEndpoints, type CodeChallengeMethod, type ConsumerAuthClient, type ConsumerMaintenanceEndpoints, type ConsumerUserEndpoints, DEFAULT_AUTHORIZE_SCOPE, DEFAULT_TOKEN_STORAGE_KEY, FlowEndpoints, MAX_STORED_TOKEN_BYTES, MFARequiredAuthError, MaintenanceStatus, MfaEndpoints, OAuthMetaEndpoints, OidcEndpoints, PasswordResetEndpoints, type PkcePair, SessionEndpoints, TokenEndpoints, UserEndpoints, WebAuthnEndpoints, assertValidAuthTokens, createAuthClient, deriveCodeChallenge, generateCodeVerifier, generateNonce, generatePkce, generateState, isAuthError, isUnrecoverableAuthError, parseStoredAuthTokens, randomUrlSafeString, safeParseStoredJson, sanitizePostAuthRedirect, serializeAuthTokens, timingSafeEqual, validateStorageKey };
175
+ export { ConsumerAuthClient as AuthClient, AuthClientConfig, AuthError, type AuthErrorCode, AuthTokens, type CodeChallengeMethod, ConsumerAuthClient, DEFAULT_AUTHORIZE_SCOPE, DEFAULT_TOKEN_STORAGE_KEY, MAX_STORED_TOKEN_BYTES, MFARequiredAuthError, type PhoneOtpClient, type PhoneOtpClientConfig, PhoneOtpError, type PhoneOtpFlowMode, type PhoneOtpGuestProfile, type PhoneOtpProfile, type PhoneOtpStart, type PhoneOtpTokens, type PkcePair, assertValidAuthTokens, createAuthClient, createPhoneOtpClient, deriveCodeChallenge, generateCodeVerifier, generateNonce, generatePkce, generateState, isAuthError, isUnrecoverableAuthError, parseStoredAuthTokens, randomUrlSafeString, safeParseStoredJson, sanitizePostAuthRedirect, serializeAuthTokens, timingSafeEqual, validateStorageKey };
package/dist/index.d.ts CHANGED
@@ -1,27 +1,5 @@
1
- import { T as TokenEndpoints, F as FlowEndpoints, O as OidcEndpoints, A as AuthorizeEndpoints, U as UserEndpoints, S as SessionEndpoints, W as WebAuthnEndpoints, P as PasswordResetEndpoints, M as MfaEndpoints, a as MaintenanceStatus, b as OAuthMetaEndpoints, c as TokenStateEndpoints, d as AuthClientConfig, e as AuthTokens } from './memory-CVWZvsJ0.js';
2
- export { f as AuthorizationCodeTokenRequest, g as AuthorizationConsentBody, h as AuthorizationConsentResponse, i as AuthorizationDetail, j as AuthorizationProbeResult, k as AuthorizationRequest, l as AuthorizationUrlOptions, C as CallbackParams, m as ClientCredentialsRequest, E as ExecuteStepRequest, n as FlowInitResponse, o as FlowSessionStatus, p as FlowStep, q as FlowStepCompleteResult, r as FlowStepInProgressResult, s as FlowStepResult, H as HandleCallbackOptions, I as InitAuthenticationRequest, t as InitRegistrationRequest, u as IntrospectTokenRequest, v as MfaDevice, w as OAuthAuthorizationServerMetadata, x as OidcDiscoveryDocument, y as PageQuery, z as PageResponse, B as PasswordTokenRequest, D as PublicClientInfo, G as PushAuthorizationRequestOptions, J as PushedAuthorizationResult, R as RefreshTokenRequest, K as RevocationResponse, L as RevokeSessionRequest, N as RevokeTokenRequest, Q as SessionDetail, V as SessionListQuery, X as SessionSummary, Y as TokenExchangeRequest, Z as TokenIntrospectionResponse, _ as TokenStorage, $ as UpdateSessionRequest, a0 as UserInfoResponse, a1 as UserProfile, a2 as createMemoryTokenStorage } from './memory-CVWZvsJ0.js';
3
-
4
- /** Public maintenance probe — status only (no admin schedule/enable APIs). */
5
- interface ConsumerMaintenanceEndpoints {
6
- status(): Promise<MaintenanceStatus>;
7
- }
8
- /** Self-service user profile APIs without admin user-management helpers. */
9
- type ConsumerUserEndpoints = Omit<UserEndpoints, "admin">;
10
- /** OAuth/OIDC consumer client surface shipped on public npmjs. */
11
- interface ConsumerAuthClient {
12
- token: TokenEndpoints;
13
- flow: FlowEndpoints;
14
- oidc: OidcEndpoints;
15
- authorize: AuthorizeEndpoints;
16
- user: ConsumerUserEndpoints;
17
- sessions: SessionEndpoints;
18
- webauthn: WebAuthnEndpoints;
19
- passwordReset: PasswordResetEndpoints;
20
- mfa: MfaEndpoints;
21
- maintenance: ConsumerMaintenanceEndpoints;
22
- oauth: OAuthMetaEndpoints;
23
- tokens: TokenStateEndpoints;
24
- }
1
+ import { A as AuthClientConfig, C as ConsumerAuthClient, a as AuthClient, b as AuthTokens } from './memory-DNWYyNSB.js';
2
+ export { c as AuthorizationCodeTokenRequest, d as AuthorizationConsentBody, e as AuthorizationConsentResponse, f as AuthorizationDetail, g as AuthorizationProbeResult, h as AuthorizationRequest, i as AuthorizationUrlOptions, j as AuthorizeEndpoints, k as CallbackParams, l as ClientCredentialsRequest, m as ConsumerMaintenanceEndpoints, n as ConsumerUserEndpoints, E as ExecuteStepRequest, F as FlowEndpoints, o as FlowInitResponse, p as FlowSessionStatus, q as FlowStep, r as FlowStepCompleteResult, s as FlowStepInProgressResult, t as FlowStepResult, H as HandleCallbackOptions, I as InitAuthenticationRequest, u as InitRegistrationRequest, v as IntrospectTokenRequest, M as MaintenanceStatus, w as MfaDevice, x as MfaEndpoints, O as OAuthAuthorizationServerMetadata, y as OAuthMetaEndpoints, z as OidcDiscoveryDocument, B as OidcEndpoints, P as PageQuery, D as PageResponse, G as PasswordResetEndpoints, J as PasswordTokenRequest, K as PublicClientInfo, L as PushAuthorizationRequestOptions, N as PushedAuthorizationResult, R as RefreshTokenRequest, Q as RevocationResponse, S as RevokeSessionRequest, T as RevokeTokenRequest, U as SessionDetail, V as SessionEndpoints, W as SessionListQuery, X as SessionSummary, Y as TokenEndpoints, Z as TokenExchangeRequest, _ as TokenIntrospectionResponse, $ as TokenStorage, a0 as UpdateSessionRequest, a1 as UserEndpoints, a2 as UserInfoResponse, a3 as UserProfile, a4 as WebAuthnEndpoints, a5 as createMemoryTokenStorage } from './memory-DNWYyNSB.js';
25
3
 
26
4
  /**
27
5
  * Creates a consumer-only auth client for public npm distribution.
@@ -29,6 +7,75 @@ interface ConsumerAuthClient {
29
7
  */
30
8
  declare function createAuthClient(config: AuthClientConfig): ConsumerAuthClient;
31
9
 
10
+ /**
11
+ * High-level phone OTP registration / sign-in helpers built on AuthFlow endpoints.
12
+ * Used by Deno microsites and any server surface that should not hand-roll flow steps.
13
+ */
14
+
15
+ type PhoneOtpFlowMode = "registration" | "authentication";
16
+ interface PhoneOtpStart {
17
+ oauthSessionId: string;
18
+ verifyStepId: string;
19
+ mode: PhoneOtpFlowMode;
20
+ /** Q-022: required on every subsequent step. */
21
+ continuationToken: string;
22
+ debugCode?: string;
23
+ }
24
+ interface PhoneOtpTokens {
25
+ accessToken: string;
26
+ refreshToken?: string;
27
+ expiresIn?: number;
28
+ }
29
+ /** Snake_case profile shape expected by existing microsite callers. */
30
+ interface PhoneOtpProfile {
31
+ id: string;
32
+ first_name?: string;
33
+ last_name?: string;
34
+ email?: string;
35
+ phone_number?: string;
36
+ phone_country_code?: string;
37
+ phone_verified?: boolean;
38
+ profile_picture_url?: string;
39
+ picture?: string;
40
+ birthdate?: string;
41
+ [key: string]: unknown;
42
+ }
43
+ interface PhoneOtpGuestProfile {
44
+ firstName: string;
45
+ lastName: string;
46
+ /** Local-part prefix before `+{session}@…`; default `reg`. */
47
+ emailLocalPrefix?: string;
48
+ /** Default `registrations.tktchurch.local`. */
49
+ emailDomain?: string;
50
+ }
51
+ interface PhoneOtpClientConfig {
52
+ baseUrl: string;
53
+ clientId: string;
54
+ allowInsecureTransport?: boolean;
55
+ timeoutMs?: number;
56
+ guestProfile?: PhoneOtpGuestProfile;
57
+ /** Optional pre-built client (tests). */
58
+ auth?: AuthClient | ConsumerAuthClient;
59
+ }
60
+ declare class PhoneOtpError extends Error {
61
+ readonly name = "PhoneOtpError";
62
+ }
63
+ interface PhoneOtpClient {
64
+ startPhoneOTP(phoneCountryCode: string, phoneNational: string, e164: string): Promise<PhoneOtpStart>;
65
+ startPhoneOTPForSignIn(phoneCountryCode: string, phoneNational: string, e164: string): Promise<PhoneOtpStart>;
66
+ verifyPhoneOTP(start: Pick<PhoneOtpStart, "oauthSessionId" | "verifyStepId" | "mode" | "continuationToken">, code: string): Promise<PhoneOtpTokens>;
67
+ fetchProfile(accessToken: string): Promise<PhoneOtpProfile>;
68
+ }
69
+ /**
70
+ * Creates a phone OTP client backed by {@link createAuthClient} flow endpoints.
71
+ *
72
+ * Q-023: client-facing phone capture errors are generic — do not branch on
73
+ * “already registered” / “no account” substrings.
74
+ * Q-040: fall back once only on client/validation HTTP failures (4xx except
75
+ * 429), never on 5xx / rate limits / transport errors (no loops).
76
+ */
77
+ declare function createPhoneOtpClient(config: PhoneOtpClientConfig): PhoneOtpClient;
78
+
32
79
  /** Default persistence key for custom TokenStorage adapters. */
33
80
  declare const DEFAULT_TOKEN_STORAGE_KEY = "@tktchurch/auth/tokens";
34
81
  /** Upper bound on serialized token payload size (defense against storage DoS). */
@@ -125,4 +172,4 @@ declare function sanitizePostAuthRedirect(value?: string | null, fallback?: stri
125
172
  /** Default OIDC scope set covering identity claims plus refresh capability. */
126
173
  declare const DEFAULT_AUTHORIZE_SCOPE = "openid profile email offline_access";
127
174
 
128
- export { type ConsumerAuthClient as AuthClient, AuthClientConfig, AuthError, type AuthErrorCode, AuthTokens, AuthorizeEndpoints, type CodeChallengeMethod, type ConsumerAuthClient, type ConsumerMaintenanceEndpoints, type ConsumerUserEndpoints, DEFAULT_AUTHORIZE_SCOPE, DEFAULT_TOKEN_STORAGE_KEY, FlowEndpoints, MAX_STORED_TOKEN_BYTES, MFARequiredAuthError, MaintenanceStatus, MfaEndpoints, OAuthMetaEndpoints, OidcEndpoints, PasswordResetEndpoints, type PkcePair, SessionEndpoints, TokenEndpoints, UserEndpoints, WebAuthnEndpoints, assertValidAuthTokens, createAuthClient, deriveCodeChallenge, generateCodeVerifier, generateNonce, generatePkce, generateState, isAuthError, isUnrecoverableAuthError, parseStoredAuthTokens, randomUrlSafeString, safeParseStoredJson, sanitizePostAuthRedirect, serializeAuthTokens, timingSafeEqual, validateStorageKey };
175
+ export { ConsumerAuthClient as AuthClient, AuthClientConfig, AuthError, type AuthErrorCode, AuthTokens, type CodeChallengeMethod, ConsumerAuthClient, DEFAULT_AUTHORIZE_SCOPE, DEFAULT_TOKEN_STORAGE_KEY, MAX_STORED_TOKEN_BYTES, MFARequiredAuthError, type PhoneOtpClient, type PhoneOtpClientConfig, PhoneOtpError, type PhoneOtpFlowMode, type PhoneOtpGuestProfile, type PhoneOtpProfile, type PhoneOtpStart, type PhoneOtpTokens, type PkcePair, assertValidAuthTokens, createAuthClient, createPhoneOtpClient, deriveCodeChallenge, generateCodeVerifier, generateNonce, generatePkce, generateState, isAuthError, isUnrecoverableAuthError, parseStoredAuthTokens, randomUrlSafeString, safeParseStoredJson, sanitizePostAuthRedirect, serializeAuthTokens, timingSafeEqual, validateStorageKey };