@tktchurch/auth 0.9.1 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +112 -41
- package/PUBLISHING.md +34 -20
- package/README.md +66 -36
- package/SECURITY.md +47 -42
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +80 -25
- package/dist/index.d.ts +80 -25
- package/dist/index.js +1 -1
- package/dist/memory-DNWYyNSB.d.cts +1379 -0
- package/dist/memory-DNWYyNSB.d.ts +1379 -0
- package/dist/storage/memory.d.cts +1 -1
- package/dist/storage/memory.d.ts +1 -1
- package/package.json +1 -1
- package/dist/memory-CVWZvsJ0.d.cts +0 -730
- package/dist/memory-CVWZvsJ0.d.ts +0 -730
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
|
|
15
|
-
|
|
|
16
|
-
| Consumer build | npmjs (public)
|
|
17
|
-
| Full build
|
|
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
|
|
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
|
|
24
|
-
|
|
|
25
|
-
| CSRF on authorization callback
|
|
26
|
-
| PKCE downgrade / param clobbering
|
|
27
|
-
| Open redirect after login
|
|
28
|
-
| Confidential client secret in browser | `clientSecret` throws in browser unless `allowClientSecretInBrowser` is explicitly set
|
|
29
|
-
| Token theft via XSS
|
|
30
|
-
| Malicious / corrupt stored tokens
|
|
31
|
-
| Prototype pollution via stored JSON
|
|
32
|
-
| Storage DoS (huge payloads)
|
|
33
|
-
| Cleartext token transport
|
|
34
|
-
| Weak randomness
|
|
35
|
-
| Credential / token logging
|
|
36
|
-
| MITM / slowloris
|
|
37
|
-
| Consent endpoint credential leak
|
|
38
|
-
| Admin token overreach
|
|
39
|
-
| MFA setup secret exposure
|
|
40
|
-
| Pagination abuse
|
|
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
|
|
45
|
-
|
|
|
46
|
-
| `authorize.consent` / `consentWithCredentials`
|
|
47
|
-
| `passwordReset.*`
|
|
48
|
-
| `mfa.setup` / `mfa.regenerateBackupCodes`
|
|
49
|
-
| `user.admin.resetMfa`
|
|
50
|
-
| Admin `clients.regenerateSecret` / `webhooks.rotateSecret` | Secret rotation
|
|
51
|
-
| `privacy.submitDataRequest` / `privacy.requestExport`
|
|
52
|
-
| `user.deleteMe`
|
|
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
|
|
67
|
-
`accessToken` and `refreshToken`. Use HttpOnly cookies (server
|
|
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
|
|
102
|
-
|
|
|
103
|
-
| iOS / macOS
|
|
104
|
-
| Android
|
|
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
|
|
107
|
-
| Electron
|
|
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
|
|
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 Q=Object.defineProperty;var Fe=Object.getOwnPropertyDescriptor;var Le=Object.getOwnPropertyNames;var Be=Object.prototype.hasOwnProperty;var Je=(e,t)=>{for(var n in t)Q(e,n,{get:t[n],enumerable:!0})},He=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of Le(t))!Be.call(e,r)&&r!==n&&Q(e,r,{get:()=>t[r],enumerable:!(o=Fe(t,r))||o.enumerable});return e};var Ve=e=>He(Q({},"__esModule",{value:!0}),e);var bt={};Je(bt,{AuthError:()=>i,DEFAULT_AUTHORIZE_SCOPE:()=>L,DEFAULT_TOKEN_STORAGE_KEY:()=>Ee,MAX_STORED_TOKEN_BYTES:()=>H,MFARequiredAuthError:()=>S,PhoneOtpError:()=>k,assertValidAuthTokens:()=>O,createAuthClient:()=>$,createMemoryTokenStorage:()=>P,createPhoneOtpClient:()=>$e,deriveCodeChallenge:()=>X,generateCodeVerifier:()=>Y,generateNonce:()=>j,generatePkce:()=>M,generateState:()=>D,isAuthError:()=>E,isUnrecoverableAuthError:()=>ue,parseStoredAuthTokens:()=>Ie,randomUrlSafeString:()=>x,safeParseStoredJson:()=>Ue,sanitizePostAuthRedirect:()=>Ne,serializeAuthTokens:()=>xe,timingSafeEqual:()=>N,validateStorageKey:()=>ze});module.exports=Ve(bt);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 Qe(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 We(e,t){if(!t)return new i("http_error",`Request failed with status ${e}`,{status:e});let n=Qe(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 ae(e){let t,n=await e.text();if(n)try{t=JSON.parse(n)}catch{t={reason:n}}return We(e.status,t)}function E(e){return e instanceof i}function ue(e){return E(e)?e.code==="invalid_client"||e.code==="invalid_grant"||e.code==="unauthorized_client"||e.code==="invalid_token":!1}function W(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 h(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=W(e.authorization_details);return u&&(s.authorizationDetails=u),s}function Ge(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 G(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 K(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 Ke(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(Ge),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 de(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 Ke(s)},async current(){let t=await f(e),n=await e.http.request("/api/v1/sessions/current",{token:t});return G(n)},async get(t){let n=await f(e),o=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{token:n});return G(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:h({device_name:n.deviceName,is_trusted:n.isTrusted})});return G(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:h({reason:n?.reason})});return K(r)},async revokeAll(){let t=await f(e),n=await e.http.request("/api/v1/sessions/revoke-all",{method:"POST",token:t});return K(n)},async revokeAllDevices(){let t=await f(e),n=await e.http.request("/api/v1/sessions/revoke-all-devices",{method:"POST",token:t});return K(n)}}}function ce(){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 pe(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 x(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 ce().getRandomValues(t),pe(t)}function D(){return x(32)}function j(){return x(32)}function Y(){return x(32)}async function X(e){let t=ce();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 pe(new Uint8Array(o))}async function M(e="S256"){let t=Y();if(e==="plain")return{codeVerifier:t,codeChallenge:t,codeChallengeMethod:"plain"};let n=await X(t);return{codeVerifier:t,codeChallenge:n,codeChallengeMethod:"S256"}}function N(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 Ye(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:W(e.authorization_details)}}function F(e){return{async password(t){let n=h({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=h({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=h({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=h({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:h({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:h({client_id:e.config.clientId,client_secret:e.config.clientSecret,token:t.token})});return Ye(n)},async tokenExchange(t){let n=h({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 fe(e,t){let n=e.trim();if(!n)throw new i("invalid_request",`${t} is required.`);return n}async function y(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 y(e,t,n)}function A(e,t){return encodeURIComponent(fe(e,t))}function Xe(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 Ze(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 et(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 le(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 me(e){async function t(o,r){let s=le(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=Xe(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=le(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=et(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 he(e){return{async publicClient(t){let n=await e.http.request(`/oauth/clients/${encodeURIComponent(t)}/public`);return Ze(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 L="openid profile email offline_access",ge=new Set(["response_type","client_id","redirect_uri","scope","state","nonce","code_challenge","code_challenge_method","prompt","login_hint","authorization_details","request_uri"]);function tt(e){if(typeof e=="string"){let t=e.indexOf("?"),n=t>=0?e.slice(t+1):e;return new URLSearchParams(n)}return e instanceof URLSearchParams?e:"raw"in e&&typeof e.raw=="object"?new URLSearchParams(e.raw):new URLSearchParams(e)}function ye(e){let t=tt(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 _e(e){let t=F(e);return{...me(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??L,s=o.state??D(),a=o.nonce??j(),u=await M(o.codeChallengeMethod??"S256"),_=o.authorizationEndpoint??`${e.config.baseUrl}/oauth/authorize`,R=new URL(_),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(ge.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??L,s=o.state??D(),a=o.nonce??j(),u=await M(o.codeChallengeMethod??"S256"),_={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&&(_.client_secret=e.config.clientSecret),o.authorizationDetails&&o.authorizationDetails.length>0&&(_.authorization_details=JSON.stringify(o.authorizationDetails));for(let[m,c]of Object.entries(o.extraParams??{})){if(ge.has(m))throw new i("invalid_request",`extraParams may not override the reserved authorization parameter "${m}".`);_[m]=c}let R=await e.http.request("/oauth/par",{method:"POST",body:_}),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:ye,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 _=typeof u.authorization_url=="string"?u.authorization_url:void 0;return{kind:"json",status:r.status,authorizationUrl:_,payload:u}},async handleCallback(o){let s=typeof o.callback=="object"&&!(o.callback instanceof URLSearchParams)&&"raw"in o.callback?o.callback:ye(o.callback);if(s.error)throw new i("access_denied",s.errorDescription??`Authorization failed: ${s.error}`,{details:s.raw});if(!s.state||!N(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 q(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 Z(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:q(t),remainingSteps:Array.isArray(n)?n.filter(r=>typeof r=="object"&&r!==null).map(q):[],expiresAt:String(e.expires_at??""),continuationToken:o}}function nt(e){return{sessionId:String(e.session_id??""),status:String(e.status??""),purpose:String(e.purpose??""),currentStep:e.current_step&&typeof e.current_step=="object"?q(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(q):[],failedAttempts:typeof e.failed_attempts=="number"?e.failed_attempts:0,expiresAt:String(e.expires_at??""),lastActivityAt:String(e.last_activity_at??"")}}async function ee(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:q(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(q):[],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 ke(e){return{async initAuthentication(t){let n=await e.http.request("/oauth/authenticate/init",{method:"POST",body:h({client_id:t?.clientId??e.config.clientId,username:t?.username,flow_type:t?.flowType})});return Z(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 ee(e,n)},async initRegistration(t){let n=await e.http.request("/oauth/register/init",{method:"POST",body:h({client_id:t?.clientId??e.config.clientId,invite_code:t?.inviteCode,flow_type:t?.flowType})});return Z(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 ee(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 nt(r)},async initRecovery(t){let n=await e.http.request("/oauth/recovery/init",{method:"POST",body:h({client_id:t?.clientId??e.config.clientId,recovery_type:t?.recoveryType,redirect_uri:t?.redirectUri})});return Z(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 ee(e,n)}}}function B(e){return Array.isArray(e)?e.filter(t=>typeof t=="string"):[]}function ot(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:B(e.response_types_supported),grantTypesSupported:B(e.grant_types_supported),scopesSupported:B(e.scopes_supported),codeChallengeMethodsSupported:B(e.code_challenge_methods_supported),raw:e}}function rt(e){return{sub:String(e.sub??""),name:typeof e.name=="string"?e.name:void 0,givenName:typeof e.given_name=="string"?e.given_name:void 0,familyName:typeof e.family_name=="string"?e.family_name:void 0,middleName:typeof e.middle_name=="string"?e.middle_name:void 0,nickname:typeof e.nickname=="string"?e.nickname:void 0,preferredUsername:typeof e.preferred_username=="string"?e.preferred_username:void 0,profile:typeof e.profile=="string"?e.profile:void 0,picture:typeof e.picture=="string"?e.picture:void 0,website:typeof e.website=="string"?e.website:void 0,gender:typeof e.gender=="string"?e.gender:void 0,birthdate:typeof e.birthdate=="string"?e.birthdate:void 0,zoneinfo:typeof e.zoneinfo=="string"?e.zoneinfo:void 0,locale:typeof e.locale=="string"?e.locale:void 0,updatedAt:typeof e.updated_at=="number"?e.updated_at:void 0,email:typeof e.email=="string"?e.email:void 0,emailVerified:typeof e.email_verified=="boolean"?e.email_verified:void 0,phoneNumber:typeof e.phone_number=="string"?e.phone_number:void 0,phoneNumberVerified:typeof e.phone_number_verified=="boolean"?e.phone_number_verified:void 0}}function be(e){return{async userInfo(){let t=await f(e),n=await e.http.request("/oauth/userinfo",{token:t});return rt(n)},async discover(){let t=await e.http.request("/.well-known/openid-configuration");return ot(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 J(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 te(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 st(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 Re(e){return it(e)}function it(e){return{async me(){let t=await f(e),n=await e.http.request("/api/v1/users/me",{token:t});return J(n)},async updateMe(t){let n=await y(e,"/api/v1/users/me",{method:"PATCH",body:t});return J(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 y(e,"/api/v1/users/me/avatar",{method:"DELETE"});return J(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 J(a)},async verifyPhone(t){return y(e,"/api/v1/users/me/phone/verify",{method:"POST",body:t})},async verifyRecoveryEmail(t){return y(e,"/api/v1/users/me/recovery-email/verify",{method:"POST",body:t})},async securityOverview(){return y(e,"/api/v1/users/me/security-overview")},recoveryContacts:{async list(){let t=await y(e,"/api/v1/users/me/recovery-contacts");return Array.isArray(t)?t.filter(n=>typeof n=="object"&&n!==null).map(te):[]},async create(t){let n=await y(e,"/api/v1/users/me/recovery-contacts",{method:"POST",body:t});return te(n)},async update(t,n){let o=await y(e,`/api/v1/users/me/recovery-contacts/${A(t,"contactId")}`,{method:"PATCH",body:n});return te(o)},async delete(t){await v(e,`/api/v1/users/me/recovery-contacts/${A(t,"contactId")}`,{method:"DELETE"})}},identities:{async list(){let t=await y(e,"/api/v1/users/me/identities");return Array.isArray(t)?t.filter(n=>typeof n=="object"&&n!==null).map(st):[]},async unlink(t){await v(e,`/api/v1/users/me/identities/${A(t,"identityId")}`,{method:"DELETE"})},async setPrimary(t){return y(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 we(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 at(e){return{isActive:!!(e.is_active??e.isActive),message:typeof e.message=="string"?e.message:void 0,scheduledStart:typeof e.scheduled_start=="string"?e.scheduled_start:void 0,scheduledEnd:typeof e.scheduled_end=="string"?e.scheduled_end:void 0,...e}}function Se(e){return{async status(){let t=await e.http.request("/api/v1/maintenance/status");return at(t)}}}var ut=128,ne=254;function T(e,t){if(typeof e!="string")return;let n=e.trim();if(!(!n||n.length>t))return n}function ve(e){return{async request(t){let n=h({email:T(t.email,ne),identifier:T(t.identifier,ne),recovery_email:T(t.recoveryEmail,ne),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>ut)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 dt(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 Te(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 y(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 y(e,"/api/v1/users/me/mfa/verify",{method:"POST",body:{device_id:t.deviceId,totp_code:t.totpCode}})},async devices(){let t=await y(e,"/api/v1/users/me/mfa/devices");return Array.isArray(t)?t.filter(n=>typeof n=="object"&&n!==null).map(dt):[]},async removeDevice(t){await v(e,`/api/v1/users/me/mfa/devices/${A(t,"deviceId")}`,{method:"DELETE"})},async regenerateBackupCodes(){let t=await y(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:h({password:t?.password})})}}}var Ae="https://prod-auth.tktchurch.com",ct=new Set(["localhost","127.0.0.1","[::1]"]);function pt(e){return ct.has(e.toLowerCase())}function oe(e,t={}){let n=(e??Ae).trim();if(!n)return oe(Ae,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&&!pt(s.hostname))throw new i("invalid_request","baseUrl must use HTTPS. Set allowInsecureTransport=true only for trusted local development.");return r}function ft(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 lt(e,t){return/^https?:\/\//i.test(t)?t:`${e}${t.startsWith("/")?t:`/${t}`}`}function mt(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 Ce(e){return{async request(t,n){let o=await this.requestRaw(t,n);if(!o.ok)throw await ae(o);if(o.status===204)return;let r=await o.text();if(r)return JSON.parse(r)},async requestRaw(t,n){let o=lt(e.baseUrl,t),r=mt(e.defaultHeaders,n?.headers,n?.token),{signal:s,cleanup:a}=ft(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(_){throw s?.aborted??!1?new i("timeout",`Request timed out: ${o}`,{details:_}):new i("network_error",`Network request failed: ${o}`,{details:_})}finally{a()}}}}var Ee="@tktchurch/auth/tokens",H=32768,qe=new Set(["accessToken","tokenType","expiresIn","refreshToken","scope","idToken","authorizationDetails"]),Oe=new Set(["__proto__","constructor","prototype"]),ht=/^[\w@./:-]{1,128}$/;function se(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function Pe(e){return typeof e=="string"&&e.length>0}function re(e){return typeof e=="string"&&e.length>0?e:void 0}function O(e){if(!se(e))throw new i("invalid_request","Token storage rejected a non-object token payload.");for(let s of Object.keys(e))if(!qe.has(s))throw new i("invalid_request",`Token storage rejected unknown field "${s}".`);if(!Pe(e.accessToken))throw new i("invalid_request","Token storage requires a non-empty accessToken.");if(!Pe(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=re(e.refreshToken);n&&(t.refreshToken=n);let o=re(e.scope);o&&(t.scope=o);let r=re(e.idToken);if(r&&(t.idToken=r),typeof e.authorizationDetails<"u"){if(!Array.isArray(e.authorizationDetails)||!e.authorizationDetails.every(s=>se(s)&&typeof s.type=="string"))throw new i("invalid_request","Token storage rejected malformed authorizationDetails.");t.authorizationDetails=e.authorizationDetails}return t}function Ie(e){if(!se(e))return null;for(let t of Object.keys(e))if(Oe.has(t)||!qe.has(t))return null;try{return O(e)}catch{return null}}function Ue(e){return JSON.parse(e,(t,n)=>{if(Oe.has(t))throw new SyntaxError("Forbidden JSON key in stored token payload.");return n})}function ze(e){let t=e.trim();if(!t)throw new i("invalid_request","Token storage key must be a non-empty string.");if(!ht.test(t))throw new i("invalid_request","Token storage key contains unsupported characters or exceeds 128 characters.");return t}function xe(e){let t=O(e),n=JSON.stringify(t);if(n.length>H)throw new i("invalid_request",`Serialized token payload exceeds ${H} bytes.`);return n}var ie=class{state=null;get(){return this.state}set(t){if(!t){this.clear();return}this.state=O(t)}clear(){this.state=null}};function P(){return new ie}function gt(){return typeof window<"u"&&typeof window.document<"u"}function yt(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 De(e){if(!e.clientId?.trim())throw new i("invalid_request","clientId is required.");if(gt()&&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:oe(e.baseUrl,{allowInsecureTransport:e.allowInsecureTransport??!1}),fetch:yt(e.fetch),storage:e.storage??P(),timeoutMs:e.timeoutMs??15e3,defaultHeaders:e.defaultHeaders??{},autoRefresh:e.autoRefresh??!0,allowClientSecretInBrowser:e.allowClientSecretInBrowser??!1,allowInsecureTransport:e.allowInsecureTransport??!1}}function $(e){let t=De(e),n=Ce({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:F(a),flow:ke(a),oidc:be(a),authorize:_e(a),user:Re(a),sessions:de(a),webauthn:we(a),passwordReset:ve(a),mfa:Te(a),maintenance:Se(a),oauth:he(a),tokens:{get:o,set:r,clear:s}}}var k=class extends Error{name="PhoneOtpError"};function I(e,t){return e instanceof k?e:E(e)?new k(e.message||t):e instanceof Error&&e.message?new k(e.message):new k(t)}function je(e){if(!E(e)||typeof e.status!="number")return!1;let t=e.status;return t>=400&&t<500&&t!==429}function Me(e){if(e.status!=="in_progress"||!e.debug)return;let t=e.debug.verification_code;return typeof t=="string"?t:void 0}function _t(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 $e(e){let t=e.auth,n=e.guestProfile??{firstName:"Guest",lastName:"User"},o=n.emailDomain??"registrations.tktchurch.local",r=n.emailLocalPrefix??"reg";function s(){return t||$({baseUrl:e.baseUrl,clientId:e.clientId,allowInsecureTransport:e.allowInsecureTransport,timeoutMs:e.timeoutMs??2e4,storage:P(),autoRefresh:!1})}async function a(p,m){let c=s(),d=await c.flow.initAuthentication({flowType:"phone_only",username:m}),g=await c.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:Me(g)}}async function u(p){let m=s(),c=await m.flow.initRegistration({flowType:"phone_registration"}),d=await m.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:Me(d)}}async function _(p,m,c){let d={phone_country_code:p,phone_number:m};try{return await u(d)}catch(g){if(!je(g))throw I(g,"Could not send the code. Please try again.");try{return await a(d,c)}catch{throw I(g,"Could not send the code. Please try again.")}}}async function R(p,m,c){let d={phone_country_code:p,phone_number:m};try{return await a(d,c)}catch(g){if(!je(g))throw I(g,"Could not start sign-in.");try{return await u(d)}catch{throw I(g,"Could not start sign-in.")}}}async function l(p,m,c,d,g,z){return m==="registration"?await p.flow.executeRegistrationStep({sessionId:c,stepId:d,continuationToken:g,credential:z}):await p.flow.executeStep({sessionId:c,stepId:d,continuationToken:g,credential:z})}async function w(p,m){let c=s();try{let d=await l(c,p.mode,p.oauthSessionId,p.verifyStepId,p.continuationToken,{code:m}),g=0;for(;d.status==="in_progress"&&g<6;){g++;let z=d.nextStep.stepType,V={};z==="profile_info"?V={first_name:n.firstName,last_name:n.lastName,email:`${r}+${p.oauthSessionId.slice(0,8).toLowerCase()}@${o}`}:z==="terms_acceptance"&&(V={accepted:"true"}),d=await l(c,p.mode,p.oauthSessionId,d.nextStep.stepId,p.continuationToken,V)}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){let m;if(t)m=t;else{let c=P();await c.set({accessToken:p,tokenType:"Bearer",expiresIn:3600}),m=$({baseUrl:e.baseUrl,clientId:e.clientId,allowInsecureTransport:e.allowInsecureTransport,timeoutMs:e.timeoutMs??2e4,storage:c,autoRefresh:!1})}try{let c=await m.user.me();return _t(c)}catch(c){throw I(c,c instanceof i?`Profile fetch failed (${c.status??"error"})`:"Profile fetch failed")}}return{startPhoneOTP:_,startPhoneOTPForSignIn:R,verifyPhoneOTP:w,fetchProfile:U}}var kt=/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;function Ne(e,t="/"){if(!e)return t;let n=e;try{n=decodeURIComponent(e)}catch{return t}return n.startsWith("/")&&!n.startsWith("//")&&!n.startsWith("/\\")&&!kt.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 {
|
|
2
|
-
export {
|
|
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,83 @@ 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
|
+
* Q-052: server surfaces (Deno microsites, Nitro handlers, edge functions)
|
|
78
|
+
* routinely invoke this client from many concurrent requests. A single shared
|
|
79
|
+
* {@link createMemoryTokenStorage} across those requests races — one flow's
|
|
80
|
+
* `verifyPhoneOTP` completion (or `fetchProfile` token set) can overwrite
|
|
81
|
+
* another mid-request. Each public operation therefore builds its own
|
|
82
|
+
* {@link AuthClient} + storage pair; tests can still inject `config.auth` to
|
|
83
|
+
* stub the transport and keep call assertions.
|
|
84
|
+
*/
|
|
85
|
+
declare function createPhoneOtpClient(config: PhoneOtpClientConfig): PhoneOtpClient;
|
|
86
|
+
|
|
32
87
|
/** Default persistence key for custom TokenStorage adapters. */
|
|
33
88
|
declare const DEFAULT_TOKEN_STORAGE_KEY = "@tktchurch/auth/tokens";
|
|
34
89
|
/** Upper bound on serialized token payload size (defense against storage DoS). */
|
|
@@ -125,4 +180,4 @@ declare function sanitizePostAuthRedirect(value?: string | null, fallback?: stri
|
|
|
125
180
|
/** Default OIDC scope set covering identity claims plus refresh capability. */
|
|
126
181
|
declare const DEFAULT_AUTHORIZE_SCOPE = "openid profile email offline_access";
|
|
127
182
|
|
|
128
|
-
export {
|
|
183
|
+
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 {
|
|
2
|
-
export {
|
|
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,83 @@ 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
|
+
* Q-052: server surfaces (Deno microsites, Nitro handlers, edge functions)
|
|
78
|
+
* routinely invoke this client from many concurrent requests. A single shared
|
|
79
|
+
* {@link createMemoryTokenStorage} across those requests races — one flow's
|
|
80
|
+
* `verifyPhoneOTP` completion (or `fetchProfile` token set) can overwrite
|
|
81
|
+
* another mid-request. Each public operation therefore builds its own
|
|
82
|
+
* {@link AuthClient} + storage pair; tests can still inject `config.auth` to
|
|
83
|
+
* stub the transport and keep call assertions.
|
|
84
|
+
*/
|
|
85
|
+
declare function createPhoneOtpClient(config: PhoneOtpClientConfig): PhoneOtpClient;
|
|
86
|
+
|
|
32
87
|
/** Default persistence key for custom TokenStorage adapters. */
|
|
33
88
|
declare const DEFAULT_TOKEN_STORAGE_KEY = "@tktchurch/auth/tokens";
|
|
34
89
|
/** Upper bound on serialized token payload size (defense against storage DoS). */
|
|
@@ -125,4 +180,4 @@ declare function sanitizePostAuthRedirect(value?: string | null, fallback?: stri
|
|
|
125
180
|
/** Default OIDC scope set covering identity claims plus refresh capability. */
|
|
126
181
|
declare const DEFAULT_AUTHORIZE_SCOPE = "openid profile email offline_access";
|
|
127
182
|
|
|
128
|
-
export {
|
|
183
|
+
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 };
|