@tktchurch/auth 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +210 -0
- package/PUBLISHING.md +121 -0
- package/README.md +315 -0
- package/SECURITY.md +157 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +128 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.js +1 -0
- package/dist/memory-CVWZvsJ0.d.cts +730 -0
- package/dist/memory-CVWZvsJ0.d.ts +730 -0
- package/dist/nuxt/module.cjs +52 -0
- package/dist/nuxt/module.d.cts +12 -0
- package/dist/nuxt/module.d.ts +12 -0
- package/dist/nuxt/module.js +52 -0
- package/dist/storage/memory.cjs +1 -0
- package/dist/storage/memory.d.cts +1 -0
- package/dist/storage/memory.d.ts +1 -0
- package/dist/storage/memory.js +1 -0
- package/package.json +90 -0
package/SECURITY.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
This document describes the threat model for `@tktchurch/auth`, how the SDK
|
|
4
|
+
mitigates common OAuth/OIDC client risks, and what integrators remain
|
|
5
|
+
responsible for.
|
|
6
|
+
|
|
7
|
+
The SDK is a **client library**, not a trust boundary. Token issuance,
|
|
8
|
+
permission checks, and session revocation are always enforced by the TKTChurch
|
|
9
|
+
OAuth authorization server (`oauth/`). The SDK's job is to implement OAuth
|
|
10
|
+
correctly and make unsafe choices hard.
|
|
11
|
+
|
|
12
|
+
## Public vs full distribution
|
|
13
|
+
|
|
14
|
+
| Artifact | Registry | Admin modules | `request` / `fetchWithAuth` | Source maps |
|
|
15
|
+
| --- | --- | --- | --- | --- |
|
|
16
|
+
| Consumer build | npmjs (public) | omitted | omitted | omitted |
|
|
17
|
+
| Full build | GitHub Packages (private) | included | included | included |
|
|
18
|
+
|
|
19
|
+
Hiding admin endpoint paths in the public consumer bundle **does not** secure the OAuth server. Standard OIDC discovery documents and network traffic still reveal public endpoints. Backend `PermissionMiddleware` remains the authoritative access control. Do not treat SDK packaging as an obfuscation or secrecy control.
|
|
20
|
+
|
|
21
|
+
## Threat model
|
|
22
|
+
|
|
23
|
+
| Threat | SDK mitigation | Integrator responsibility |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| CSRF on authorization callback | Constant-time `state` verification (`timingSafeEqual`) | Persist `state` server-side; never skip callback validation |
|
|
26
|
+
| PKCE downgrade / param clobbering | Reserved authorize params cannot be overridden via `extraParams` | Store `codeVerifier` server-side (HttpOnly cookie / session) |
|
|
27
|
+
| Open redirect after login | `sanitizePostAuthRedirect()` rejects scheme-bearing, protocol-relative, and control-character targets | Always sanitize `next`/`returnTo` before redirect |
|
|
28
|
+
| Confidential client secret in browser | `clientSecret` throws in browser unless `allowClientSecretInBrowser` is explicitly set | Keep secrets in server routes only |
|
|
29
|
+
| Token theft via XSS | No browser persistence adapter; memory default | HttpOnly cookies for refresh tokens; strict CSP |
|
|
30
|
+
| Malicious / corrupt stored tokens | `assertValidAuthTokens` / `parseStoredAuthTokens` for custom adapters | Choose a secure storage backend for your platform |
|
|
31
|
+
| Prototype pollution via stored JSON | Reviver blocks `__proto__` / `constructor` / `prototype`; allowlisted fields only | Avoid custom storage that merges untrusted objects |
|
|
32
|
+
| Storage DoS (huge payloads) | 32 KiB cap on serialized token blobs via `serializeAuthTokens` | Enforce limits in custom adapters |
|
|
33
|
+
| Cleartext token transport | HTTPS enforced for `baseUrl` (loopback HTTP exempt) | Terminate TLS at the edge; do not disable in production |
|
|
34
|
+
| Weak randomness | CSPRNG only (`crypto.getRandomValues`); PKCE S256 default | Never substitute `Math.random` |
|
|
35
|
+
| Credential / token logging | SDK does not log secrets; errors avoid stack/internal config leaks | Do not log request bodies or token responses |
|
|
36
|
+
| MITM / slowloris | Conservative fetch timeouts (default 15s); TLS via HTTPS base URL | Use platform TLS verification; inject corporate `fetch` only when needed |
|
|
37
|
+
| Consent endpoint credential leak | `authorize.consent` validates callback URL origin/path; no logging of passwords/MFA codes | Call consent only from server routes; never expose credentials to the browser |
|
|
38
|
+
| Admin token overreach | SDK does not enforce permissions; all admin modules require bearer token | Scope admin tokens minimally; backend `PermissionMiddleware` is authoritative |
|
|
39
|
+
| MFA setup secret exposure | `mfa.setup` returns TOTP secret/QR — treat response as highly sensitive | Display QR server-side or in trusted UI only; never log setup payloads |
|
|
40
|
+
| Pagination abuse | Client caps `perPage` at 100 via `normalizePageQuery` | Prefer reasonable page sizes in admin UIs |
|
|
41
|
+
|
|
42
|
+
## Sensitive endpoints (0.5.0)
|
|
43
|
+
|
|
44
|
+
| Endpoint | Risk | Guidance |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `authorize.consent` / `consentWithCredentials` | Username, password, MFA codes in request body | Server-side only; validate consent callback URL |
|
|
47
|
+
| `passwordReset.*` | Account takeover if token mishandled | Use HTTPS; short-lived reset tokens |
|
|
48
|
+
| `mfa.setup` / `mfa.regenerateBackupCodes` | TOTP secrets and backup codes | Never log responses; show once to user |
|
|
49
|
+
| `user.admin.resetMfa` | Forcibly disables target user MFA | Requires `user:mfa_reset` permission |
|
|
50
|
+
| Admin `clients.regenerateSecret` / `webhooks.rotateSecret` | Secret rotation | Treat new secrets like passwords |
|
|
51
|
+
| `privacy.submitDataRequest` / `privacy.requestExport` | DSAR / portability payloads | Never log request bodies or export payloads |
|
|
52
|
+
| `user.deleteMe` | Irreversible account erasure | Require password re-auth; clear SDK tokens after success |
|
|
53
|
+
|
|
54
|
+
## Privacy endpoints (0.7.x)
|
|
55
|
+
|
|
56
|
+
### Default: in-memory (`createMemoryTokenStorage`)
|
|
57
|
+
|
|
58
|
+
- Tokens live only for the process lifetime.
|
|
59
|
+
- Recommended for **server-side** clients (SSR API routes, workers, CLI).
|
|
60
|
+
- Pair with HttpOnly, `Secure`, `SameSite` cookies when the browser needs a
|
|
61
|
+
session across requests.
|
|
62
|
+
|
|
63
|
+
### Do not use `localStorage` or `sessionStorage`
|
|
64
|
+
|
|
65
|
+
The SDK **does not ship** a `localStorage` adapter (removed in 0.4.0). Browser
|
|
66
|
+
storage is readable by any script on the page — one XSS vulnerability exfiltrates
|
|
67
|
+
`accessToken` and `refreshToken`. Use HttpOnly cookies (server routes) or
|
|
68
|
+
platform secure storage instead.
|
|
69
|
+
|
|
70
|
+
### Custom `TokenStorage`
|
|
71
|
+
|
|
72
|
+
Export helpers for custom backends (Keychain, Keystore, encrypted server
|
|
73
|
+
session, Redis with encryption at rest):
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import {
|
|
77
|
+
assertValidAuthTokens,
|
|
78
|
+
parseStoredAuthTokens,
|
|
79
|
+
serializeAuthTokens,
|
|
80
|
+
} from "@tktchurch/auth";
|
|
81
|
+
|
|
82
|
+
export function createSecureTokenStorage(/* platform deps */): TokenStorage {
|
|
83
|
+
return {
|
|
84
|
+
get() {
|
|
85
|
+
const raw = readFromKeychain();
|
|
86
|
+
return raw ? parseStoredAuthTokens(JSON.parse(raw)) : null;
|
|
87
|
+
},
|
|
88
|
+
set(tokens) {
|
|
89
|
+
if (!tokens) return clearKeychain();
|
|
90
|
+
writeToKeychain(serializeAuthTokens(tokens));
|
|
91
|
+
},
|
|
92
|
+
clear() {
|
|
93
|
+
clearKeychain();
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Platform guidance:
|
|
100
|
+
|
|
101
|
+
| Runtime | Recommended persistence |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| iOS / macOS | Keychain via native bridge |
|
|
104
|
+
| Android | Android Keystore / EncryptedSharedPreferences |
|
|
105
|
+
| Web (production) | HttpOnly server session cookie for refresh; short-lived access in memory |
|
|
106
|
+
| Node / edge | Encrypted server session or memory per request |
|
|
107
|
+
| Electron | `safeStorage` + OS keychain |
|
|
108
|
+
|
|
109
|
+
### Secrets that must never use `localStorage`
|
|
110
|
+
|
|
111
|
+
- PKCE `codeVerifier`
|
|
112
|
+
- OAuth `clientSecret`
|
|
113
|
+
- Long-lived `refreshToken` (unless threat model explicitly accepts XSS risk)
|
|
114
|
+
|
|
115
|
+
## Network security
|
|
116
|
+
|
|
117
|
+
- `baseUrl` is normalized to HTTPS when no scheme is provided.
|
|
118
|
+
- Plain `http://` is rejected unless the host is loopback (`localhost`,
|
|
119
|
+
`127.0.0.1`, `[::1]`) or `allowInsecureTransport: true` is explicitly set.
|
|
120
|
+
- Default request timeout: **15 seconds** (configurable via `timeoutMs`).
|
|
121
|
+
- The SDK does not implement automatic exponential backoff on the HTTP layer;
|
|
122
|
+
callers should wrap retries for idempotent reads if needed.
|
|
123
|
+
|
|
124
|
+
TLS certificate verification is delegated to the runtime `fetch` implementation.
|
|
125
|
+
Do not disable TLS verification in custom `fetch` wrappers.
|
|
126
|
+
|
|
127
|
+
## Cryptography
|
|
128
|
+
|
|
129
|
+
- PKCE: SHA-256 (`S256`) by default; `plain` exists only for interoperability.
|
|
130
|
+
- Random values: Web Crypto CSPRNG (`generateState`, `generateNonce`, PKCE).
|
|
131
|
+
- No MD5, SHA-1, or custom crypto primitives in the SDK.
|
|
132
|
+
|
|
133
|
+
## Error handling
|
|
134
|
+
|
|
135
|
+
- `AuthError` exposes OAuth-normalized `code` and safe `message` strings.
|
|
136
|
+
- Raw HTTP bodies on errors are attached as `details` for app logging — treat
|
|
137
|
+
`details` as internal diagnostics, not end-user copy.
|
|
138
|
+
- Unrecoverable token errors (`invalid_grant`, `invalid_token`, …) trigger
|
|
139
|
+
`tokens.clear()` in `fetchWithAuth` so stale credentials are not retried
|
|
140
|
+
indefinitely.
|
|
141
|
+
|
|
142
|
+
## Reporting vulnerabilities
|
|
143
|
+
|
|
144
|
+
Report security issues privately to the TKTChurch engineering team via your
|
|
145
|
+
usual internal channel. Do not open public GitHub issues for undisclosed
|
|
146
|
+
vulnerabilities.
|
|
147
|
+
|
|
148
|
+
## Security checklist for integrators
|
|
149
|
+
|
|
150
|
+
1. Use authorization code + PKCE for interactive login.
|
|
151
|
+
2. Store `codeVerifier`, `state`, and `nonce` server-side across the redirect.
|
|
152
|
+
3. Call `authorize.handleCallback` — do not hand-roll token exchange.
|
|
153
|
+
4. Keep `clientSecret` on the server.
|
|
154
|
+
5. Sanitize post-login redirects with `sanitizePostAuthRedirect`.
|
|
155
|
+
6. Use memory or HttpOnly cookie storage for refresh tokens in production web apps.
|
|
156
|
+
7. Set a strict Content-Security-Policy on pages that load third-party scripts.
|
|
157
|
+
8. Rotate refresh tokens on every refresh (server pattern — see `examples/`).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +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});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { T as TokenEndpoints, F as FlowEndpoints, O as OidcEndpoints, A as AuthorizeEndpoints, U as UserEndpoints, S as SessionEndpoints, W as WebAuthnEndpoints, P as PasswordResetEndpoints, M as MfaEndpoints, a as MaintenanceStatus, b as OAuthMetaEndpoints, c as TokenStateEndpoints, d as AuthClientConfig, e as AuthTokens } from './memory-CVWZvsJ0.cjs';
|
|
2
|
+
export { f as AuthorizationCodeTokenRequest, g as AuthorizationConsentBody, h as AuthorizationConsentResponse, i as AuthorizationDetail, j as AuthorizationProbeResult, k as AuthorizationRequest, l as AuthorizationUrlOptions, C as CallbackParams, m as ClientCredentialsRequest, E as ExecuteStepRequest, n as FlowInitResponse, o as FlowSessionStatus, p as FlowStep, q as FlowStepCompleteResult, r as FlowStepInProgressResult, s as FlowStepResult, H as HandleCallbackOptions, I as InitAuthenticationRequest, t as InitRegistrationRequest, u as IntrospectTokenRequest, v as MfaDevice, w as OAuthAuthorizationServerMetadata, x as OidcDiscoveryDocument, y as PageQuery, z as PageResponse, B as PasswordTokenRequest, D as PublicClientInfo, G as PushAuthorizationRequestOptions, J as PushedAuthorizationResult, R as RefreshTokenRequest, K as RevocationResponse, L as RevokeSessionRequest, N as RevokeTokenRequest, Q as SessionDetail, V as SessionListQuery, X as SessionSummary, Y as TokenExchangeRequest, Z as TokenIntrospectionResponse, _ as TokenStorage, $ as UpdateSessionRequest, a0 as UserInfoResponse, a1 as UserProfile, a2 as createMemoryTokenStorage } from './memory-CVWZvsJ0.cjs';
|
|
3
|
+
|
|
4
|
+
/** Public maintenance probe — status only (no admin schedule/enable APIs). */
|
|
5
|
+
interface ConsumerMaintenanceEndpoints {
|
|
6
|
+
status(): Promise<MaintenanceStatus>;
|
|
7
|
+
}
|
|
8
|
+
/** Self-service user profile APIs without admin user-management helpers. */
|
|
9
|
+
type ConsumerUserEndpoints = Omit<UserEndpoints, "admin">;
|
|
10
|
+
/** OAuth/OIDC consumer client surface shipped on public npmjs. */
|
|
11
|
+
interface ConsumerAuthClient {
|
|
12
|
+
token: TokenEndpoints;
|
|
13
|
+
flow: FlowEndpoints;
|
|
14
|
+
oidc: OidcEndpoints;
|
|
15
|
+
authorize: AuthorizeEndpoints;
|
|
16
|
+
user: ConsumerUserEndpoints;
|
|
17
|
+
sessions: SessionEndpoints;
|
|
18
|
+
webauthn: WebAuthnEndpoints;
|
|
19
|
+
passwordReset: PasswordResetEndpoints;
|
|
20
|
+
mfa: MfaEndpoints;
|
|
21
|
+
maintenance: ConsumerMaintenanceEndpoints;
|
|
22
|
+
oauth: OAuthMetaEndpoints;
|
|
23
|
+
tokens: TokenStateEndpoints;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a consumer-only auth client for public npm distribution.
|
|
28
|
+
* Admin console APIs, generic passthrough helpers, and internal modules are omitted.
|
|
29
|
+
*/
|
|
30
|
+
declare function createAuthClient(config: AuthClientConfig): ConsumerAuthClient;
|
|
31
|
+
|
|
32
|
+
/** Default persistence key for custom TokenStorage adapters. */
|
|
33
|
+
declare const DEFAULT_TOKEN_STORAGE_KEY = "@tktchurch/auth/tokens";
|
|
34
|
+
/** Upper bound on serialized token payload size (defense against storage DoS). */
|
|
35
|
+
declare const MAX_STORED_TOKEN_BYTES = 32768;
|
|
36
|
+
/**
|
|
37
|
+
* Validates and normalizes token objects before persistence.
|
|
38
|
+
* Throws `AuthError` so callers can surface misconfiguration without persisting
|
|
39
|
+
* malformed or attacker-controlled shapes.
|
|
40
|
+
*/
|
|
41
|
+
declare function assertValidAuthTokens(tokens: AuthTokens): AuthTokens;
|
|
42
|
+
/**
|
|
43
|
+
* Parses tokens previously read from storage. Returns `null` for missing,
|
|
44
|
+
* corrupt, oversized, or structurally invalid payloads (fail-safe).
|
|
45
|
+
*/
|
|
46
|
+
declare function parseStoredAuthTokens(raw: unknown): AuthTokens | null;
|
|
47
|
+
/** Parses JSON from storage with a reviver that blocks prototype-pollution keys. */
|
|
48
|
+
declare function safeParseStoredJson(text: string): unknown;
|
|
49
|
+
/** Validates a storage key before use (length, charset, no control characters). */
|
|
50
|
+
declare function validateStorageKey(key: string): string;
|
|
51
|
+
/** Serializes validated tokens and enforces the persisted size ceiling. */
|
|
52
|
+
declare function serializeAuthTokens(tokens: AuthTokens): string;
|
|
53
|
+
|
|
54
|
+
/** Supported normalized auth error codes returned by the SDK. */
|
|
55
|
+
type AuthErrorCode = "mfa_required" | "invalid_request" | "invalid_client" | "invalid_grant" | "unauthorized_client" | "unsupported_grant_type" | "invalid_scope" | "access_denied" | "unsupported_response_type" | "server_error" | "temporarily_unavailable" | "invalid_token" | "insufficient_scope" | "authentication_required" | "insufficient_user_authentication" | "http_error" | "network_error" | "timeout" | "unknown_error";
|
|
56
|
+
/** Base SDK error type for HTTP, OAuth, and transport failures. */
|
|
57
|
+
declare class AuthError extends Error {
|
|
58
|
+
readonly code: AuthErrorCode;
|
|
59
|
+
readonly status?: number;
|
|
60
|
+
readonly details?: unknown;
|
|
61
|
+
constructor(code: AuthErrorCode, message: string, options?: {
|
|
62
|
+
status?: number;
|
|
63
|
+
details?: unknown;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** Specialized error for `mfa_required` responses. */
|
|
67
|
+
declare class MFARequiredAuthError extends AuthError {
|
|
68
|
+
readonly mfaMethods: string[];
|
|
69
|
+
constructor(message: string, mfaMethods: string[], status?: number, details?: unknown);
|
|
70
|
+
}
|
|
71
|
+
/** Type guard for SDK errors. */
|
|
72
|
+
declare function isAuthError(error: unknown): error is AuthError;
|
|
73
|
+
/** Returns true for errors that should clear local token state. */
|
|
74
|
+
declare function isUnrecoverableAuthError(error: unknown): boolean;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* PKCE (RFC 7636) and CSRF anti-forgery primitives.
|
|
78
|
+
*
|
|
79
|
+
* SECURITY: all randomness is sourced from the platform CSPRNG
|
|
80
|
+
* (`globalThis.crypto.getRandomValues`) — never `Math.random`. The same code
|
|
81
|
+
* path runs in browsers and Node 18+/Bun/Deno/edge runtimes, all of which
|
|
82
|
+
* expose the Web Crypto API on `globalThis.crypto`.
|
|
83
|
+
*/
|
|
84
|
+
/** PKCE code challenge methods supported by the authorization server. */
|
|
85
|
+
type CodeChallengeMethod = "S256" | "plain";
|
|
86
|
+
/** A generated PKCE pair plus the challenge method used to derive it. */
|
|
87
|
+
interface PkcePair {
|
|
88
|
+
/** High-entropy secret kept on the client and sent only at token exchange. */
|
|
89
|
+
codeVerifier: string;
|
|
90
|
+
/** Value placed in the authorization request (`code_challenge`). */
|
|
91
|
+
codeChallenge: string;
|
|
92
|
+
/** Derivation method placed in the authorization request. */
|
|
93
|
+
codeChallengeMethod: CodeChallengeMethod;
|
|
94
|
+
}
|
|
95
|
+
/** Returns `length` CSPRNG bytes encoded as unpadded base64url. */
|
|
96
|
+
declare function randomUrlSafeString(length?: number): string;
|
|
97
|
+
/**
|
|
98
|
+
* Generates an opaque `state` value for CSRF protection on the redirect.
|
|
99
|
+
* Persist it across the redirect and verify it on the callback.
|
|
100
|
+
*/
|
|
101
|
+
declare function generateState(): string;
|
|
102
|
+
/**
|
|
103
|
+
* Generates an OIDC `nonce` to bind the eventual ID token to this request.
|
|
104
|
+
* Persist it and compare against the `nonce` claim after token exchange.
|
|
105
|
+
*/
|
|
106
|
+
declare function generateNonce(): string;
|
|
107
|
+
/** Generates a PKCE code verifier: 43–128 chars of the unreserved alphabet (RFC 7636 §4.1). */
|
|
108
|
+
declare function generateCodeVerifier(): string;
|
|
109
|
+
/** Computes the S256 code challenge for a verifier (SHA-256 → base64url). */
|
|
110
|
+
declare function deriveCodeChallenge(codeVerifier: string): Promise<string>;
|
|
111
|
+
/**
|
|
112
|
+
* Generates a complete PKCE pair. Defaults to `S256`, the only method this SDK
|
|
113
|
+
* recommends — `plain` exists solely for servers that cannot do SHA-256 and
|
|
114
|
+
* should be avoided.
|
|
115
|
+
*/
|
|
116
|
+
declare function generatePkce(method?: CodeChallengeMethod): Promise<PkcePair>;
|
|
117
|
+
/**
|
|
118
|
+
* Constant-time string comparison for verifying opaque values such as `state`.
|
|
119
|
+
* Avoids leaking match length/position through early-exit timing.
|
|
120
|
+
*/
|
|
121
|
+
declare function timingSafeEqual(a: string, b: string): boolean;
|
|
122
|
+
|
|
123
|
+
declare function sanitizePostAuthRedirect(value?: string | null, fallback?: string): string;
|
|
124
|
+
|
|
125
|
+
/** Default OIDC scope set covering identity claims plus refresh capability. */
|
|
126
|
+
declare const DEFAULT_AUTHORIZE_SCOPE = "openid profile email offline_access";
|
|
127
|
+
|
|
128
|
+
export { type ConsumerAuthClient as AuthClient, AuthClientConfig, AuthError, type AuthErrorCode, AuthTokens, AuthorizeEndpoints, type CodeChallengeMethod, type ConsumerAuthClient, type ConsumerMaintenanceEndpoints, type ConsumerUserEndpoints, DEFAULT_AUTHORIZE_SCOPE, DEFAULT_TOKEN_STORAGE_KEY, FlowEndpoints, MAX_STORED_TOKEN_BYTES, MFARequiredAuthError, MaintenanceStatus, MfaEndpoints, OAuthMetaEndpoints, OidcEndpoints, PasswordResetEndpoints, type PkcePair, SessionEndpoints, TokenEndpoints, UserEndpoints, WebAuthnEndpoints, assertValidAuthTokens, createAuthClient, deriveCodeChallenge, generateCodeVerifier, generateNonce, generatePkce, generateState, isAuthError, isUnrecoverableAuthError, parseStoredAuthTokens, randomUrlSafeString, safeParseStoredJson, sanitizePostAuthRedirect, serializeAuthTokens, timingSafeEqual, validateStorageKey };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { T as TokenEndpoints, F as FlowEndpoints, O as OidcEndpoints, A as AuthorizeEndpoints, U as UserEndpoints, S as SessionEndpoints, W as WebAuthnEndpoints, P as PasswordResetEndpoints, M as MfaEndpoints, a as MaintenanceStatus, b as OAuthMetaEndpoints, c as TokenStateEndpoints, d as AuthClientConfig, e as AuthTokens } from './memory-CVWZvsJ0.js';
|
|
2
|
+
export { f as AuthorizationCodeTokenRequest, g as AuthorizationConsentBody, h as AuthorizationConsentResponse, i as AuthorizationDetail, j as AuthorizationProbeResult, k as AuthorizationRequest, l as AuthorizationUrlOptions, C as CallbackParams, m as ClientCredentialsRequest, E as ExecuteStepRequest, n as FlowInitResponse, o as FlowSessionStatus, p as FlowStep, q as FlowStepCompleteResult, r as FlowStepInProgressResult, s as FlowStepResult, H as HandleCallbackOptions, I as InitAuthenticationRequest, t as InitRegistrationRequest, u as IntrospectTokenRequest, v as MfaDevice, w as OAuthAuthorizationServerMetadata, x as OidcDiscoveryDocument, y as PageQuery, z as PageResponse, B as PasswordTokenRequest, D as PublicClientInfo, G as PushAuthorizationRequestOptions, J as PushedAuthorizationResult, R as RefreshTokenRequest, K as RevocationResponse, L as RevokeSessionRequest, N as RevokeTokenRequest, Q as SessionDetail, V as SessionListQuery, X as SessionSummary, Y as TokenExchangeRequest, Z as TokenIntrospectionResponse, _ as TokenStorage, $ as UpdateSessionRequest, a0 as UserInfoResponse, a1 as UserProfile, a2 as createMemoryTokenStorage } from './memory-CVWZvsJ0.js';
|
|
3
|
+
|
|
4
|
+
/** Public maintenance probe — status only (no admin schedule/enable APIs). */
|
|
5
|
+
interface ConsumerMaintenanceEndpoints {
|
|
6
|
+
status(): Promise<MaintenanceStatus>;
|
|
7
|
+
}
|
|
8
|
+
/** Self-service user profile APIs without admin user-management helpers. */
|
|
9
|
+
type ConsumerUserEndpoints = Omit<UserEndpoints, "admin">;
|
|
10
|
+
/** OAuth/OIDC consumer client surface shipped on public npmjs. */
|
|
11
|
+
interface ConsumerAuthClient {
|
|
12
|
+
token: TokenEndpoints;
|
|
13
|
+
flow: FlowEndpoints;
|
|
14
|
+
oidc: OidcEndpoints;
|
|
15
|
+
authorize: AuthorizeEndpoints;
|
|
16
|
+
user: ConsumerUserEndpoints;
|
|
17
|
+
sessions: SessionEndpoints;
|
|
18
|
+
webauthn: WebAuthnEndpoints;
|
|
19
|
+
passwordReset: PasswordResetEndpoints;
|
|
20
|
+
mfa: MfaEndpoints;
|
|
21
|
+
maintenance: ConsumerMaintenanceEndpoints;
|
|
22
|
+
oauth: OAuthMetaEndpoints;
|
|
23
|
+
tokens: TokenStateEndpoints;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a consumer-only auth client for public npm distribution.
|
|
28
|
+
* Admin console APIs, generic passthrough helpers, and internal modules are omitted.
|
|
29
|
+
*/
|
|
30
|
+
declare function createAuthClient(config: AuthClientConfig): ConsumerAuthClient;
|
|
31
|
+
|
|
32
|
+
/** Default persistence key for custom TokenStorage adapters. */
|
|
33
|
+
declare const DEFAULT_TOKEN_STORAGE_KEY = "@tktchurch/auth/tokens";
|
|
34
|
+
/** Upper bound on serialized token payload size (defense against storage DoS). */
|
|
35
|
+
declare const MAX_STORED_TOKEN_BYTES = 32768;
|
|
36
|
+
/**
|
|
37
|
+
* Validates and normalizes token objects before persistence.
|
|
38
|
+
* Throws `AuthError` so callers can surface misconfiguration without persisting
|
|
39
|
+
* malformed or attacker-controlled shapes.
|
|
40
|
+
*/
|
|
41
|
+
declare function assertValidAuthTokens(tokens: AuthTokens): AuthTokens;
|
|
42
|
+
/**
|
|
43
|
+
* Parses tokens previously read from storage. Returns `null` for missing,
|
|
44
|
+
* corrupt, oversized, or structurally invalid payloads (fail-safe).
|
|
45
|
+
*/
|
|
46
|
+
declare function parseStoredAuthTokens(raw: unknown): AuthTokens | null;
|
|
47
|
+
/** Parses JSON from storage with a reviver that blocks prototype-pollution keys. */
|
|
48
|
+
declare function safeParseStoredJson(text: string): unknown;
|
|
49
|
+
/** Validates a storage key before use (length, charset, no control characters). */
|
|
50
|
+
declare function validateStorageKey(key: string): string;
|
|
51
|
+
/** Serializes validated tokens and enforces the persisted size ceiling. */
|
|
52
|
+
declare function serializeAuthTokens(tokens: AuthTokens): string;
|
|
53
|
+
|
|
54
|
+
/** Supported normalized auth error codes returned by the SDK. */
|
|
55
|
+
type AuthErrorCode = "mfa_required" | "invalid_request" | "invalid_client" | "invalid_grant" | "unauthorized_client" | "unsupported_grant_type" | "invalid_scope" | "access_denied" | "unsupported_response_type" | "server_error" | "temporarily_unavailable" | "invalid_token" | "insufficient_scope" | "authentication_required" | "insufficient_user_authentication" | "http_error" | "network_error" | "timeout" | "unknown_error";
|
|
56
|
+
/** Base SDK error type for HTTP, OAuth, and transport failures. */
|
|
57
|
+
declare class AuthError extends Error {
|
|
58
|
+
readonly code: AuthErrorCode;
|
|
59
|
+
readonly status?: number;
|
|
60
|
+
readonly details?: unknown;
|
|
61
|
+
constructor(code: AuthErrorCode, message: string, options?: {
|
|
62
|
+
status?: number;
|
|
63
|
+
details?: unknown;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** Specialized error for `mfa_required` responses. */
|
|
67
|
+
declare class MFARequiredAuthError extends AuthError {
|
|
68
|
+
readonly mfaMethods: string[];
|
|
69
|
+
constructor(message: string, mfaMethods: string[], status?: number, details?: unknown);
|
|
70
|
+
}
|
|
71
|
+
/** Type guard for SDK errors. */
|
|
72
|
+
declare function isAuthError(error: unknown): error is AuthError;
|
|
73
|
+
/** Returns true for errors that should clear local token state. */
|
|
74
|
+
declare function isUnrecoverableAuthError(error: unknown): boolean;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* PKCE (RFC 7636) and CSRF anti-forgery primitives.
|
|
78
|
+
*
|
|
79
|
+
* SECURITY: all randomness is sourced from the platform CSPRNG
|
|
80
|
+
* (`globalThis.crypto.getRandomValues`) — never `Math.random`. The same code
|
|
81
|
+
* path runs in browsers and Node 18+/Bun/Deno/edge runtimes, all of which
|
|
82
|
+
* expose the Web Crypto API on `globalThis.crypto`.
|
|
83
|
+
*/
|
|
84
|
+
/** PKCE code challenge methods supported by the authorization server. */
|
|
85
|
+
type CodeChallengeMethod = "S256" | "plain";
|
|
86
|
+
/** A generated PKCE pair plus the challenge method used to derive it. */
|
|
87
|
+
interface PkcePair {
|
|
88
|
+
/** High-entropy secret kept on the client and sent only at token exchange. */
|
|
89
|
+
codeVerifier: string;
|
|
90
|
+
/** Value placed in the authorization request (`code_challenge`). */
|
|
91
|
+
codeChallenge: string;
|
|
92
|
+
/** Derivation method placed in the authorization request. */
|
|
93
|
+
codeChallengeMethod: CodeChallengeMethod;
|
|
94
|
+
}
|
|
95
|
+
/** Returns `length` CSPRNG bytes encoded as unpadded base64url. */
|
|
96
|
+
declare function randomUrlSafeString(length?: number): string;
|
|
97
|
+
/**
|
|
98
|
+
* Generates an opaque `state` value for CSRF protection on the redirect.
|
|
99
|
+
* Persist it across the redirect and verify it on the callback.
|
|
100
|
+
*/
|
|
101
|
+
declare function generateState(): string;
|
|
102
|
+
/**
|
|
103
|
+
* Generates an OIDC `nonce` to bind the eventual ID token to this request.
|
|
104
|
+
* Persist it and compare against the `nonce` claim after token exchange.
|
|
105
|
+
*/
|
|
106
|
+
declare function generateNonce(): string;
|
|
107
|
+
/** Generates a PKCE code verifier: 43–128 chars of the unreserved alphabet (RFC 7636 §4.1). */
|
|
108
|
+
declare function generateCodeVerifier(): string;
|
|
109
|
+
/** Computes the S256 code challenge for a verifier (SHA-256 → base64url). */
|
|
110
|
+
declare function deriveCodeChallenge(codeVerifier: string): Promise<string>;
|
|
111
|
+
/**
|
|
112
|
+
* Generates a complete PKCE pair. Defaults to `S256`, the only method this SDK
|
|
113
|
+
* recommends — `plain` exists solely for servers that cannot do SHA-256 and
|
|
114
|
+
* should be avoided.
|
|
115
|
+
*/
|
|
116
|
+
declare function generatePkce(method?: CodeChallengeMethod): Promise<PkcePair>;
|
|
117
|
+
/**
|
|
118
|
+
* Constant-time string comparison for verifying opaque values such as `state`.
|
|
119
|
+
* Avoids leaking match length/position through early-exit timing.
|
|
120
|
+
*/
|
|
121
|
+
declare function timingSafeEqual(a: string, b: string): boolean;
|
|
122
|
+
|
|
123
|
+
declare function sanitizePostAuthRedirect(value?: string | null, fallback?: string): string;
|
|
124
|
+
|
|
125
|
+
/** Default OIDC scope set covering identity claims plus refresh capability. */
|
|
126
|
+
declare const DEFAULT_AUTHORIZE_SCOPE = "openid profile email offline_access";
|
|
127
|
+
|
|
128
|
+
export { type ConsumerAuthClient as AuthClient, AuthClientConfig, AuthError, type AuthErrorCode, AuthTokens, AuthorizeEndpoints, type CodeChallengeMethod, type ConsumerAuthClient, type ConsumerMaintenanceEndpoints, type ConsumerUserEndpoints, DEFAULT_AUTHORIZE_SCOPE, DEFAULT_TOKEN_STORAGE_KEY, FlowEndpoints, MAX_STORED_TOKEN_BYTES, MFARequiredAuthError, MaintenanceStatus, MfaEndpoints, OAuthMetaEndpoints, OidcEndpoints, PasswordResetEndpoints, type PkcePair, SessionEndpoints, TokenEndpoints, UserEndpoints, WebAuthnEndpoints, assertValidAuthTokens, createAuthClient, deriveCodeChallenge, generateCodeVerifier, generateNonce, generatePkce, generateState, isAuthError, isUnrecoverableAuthError, parseStoredAuthTokens, randomUrlSafeString, safeParseStoredJson, sanitizePostAuthRedirect, serializeAuthTokens, timingSafeEqual, validateStorageKey };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var i=class extends Error{code;status;details;constructor(t,n,r){super(n),this.name="AuthError",this.code=t,this.status=r?.status,this.details=r?.details}},R=class extends i{mfaMethods;constructor(t,n,r,o){super("mfa_required",t,{status:r,details:o}),this.name="MFARequiredAuthError",this.mfaMethods=n}};function we(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 ve(e,t){if(!t)return new i("http_error",`Request failed with status ${e}`,{status:e});let n=we(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(r,o,e,t)}return new i(n,r,{status:e,details:t})}async function K(e){let t,n=await e.text();if(n)try{t=JSON.parse(n)}catch{t={reason:n}}return ve(e.status,t)}function G(e){return e instanceof i}function Se(e){return G(e)?e.code==="invalid_client"||e.code==="invalid_grant"||e.code==="unauthorized_client"||e.code==="invalid_token":!1}function z(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=z(e.authorization_details);return d&&(s.authorizationDetails=d),s}function Ae(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 D(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 j(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 Te(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(Ae),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 Y(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 Te(s)},async current(){let t=await u(e),n=await e.http.request("/api/v1/sessions/current",{token:t});return D(n)},async get(t){let n=await u(e),r=await e.http.request(`/api/v1/sessions/${encodeURIComponent(t)}`,{token:n});return D(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 D(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 j(o)},async revokeAll(){let t=await u(e),n=await e.http.request("/api/v1/sessions/revoke-all",{method:"POST",token:t});return j(n)},async revokeAllDevices(){let t=await u(e),n=await e.http.request("/api/v1/sessions/revoke-all-devices",{method:"POST",token:t});return j(n)}}}function X(){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 Z(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 X().getRandomValues(t),Z(t)}function A(){return S(32)}function T(){return S(32)}function ee(){return S(32)}async function te(e){let t=X();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 Z(new Uint8Array(r))}async function C(e="S256"){let t=ee();if(e==="plain")return{codeVerifier:t,codeChallenge:t,codeChallengeMethod:"plain"};let n=await te(t);return{codeVerifier:t,codeChallenge:n,codeChallengeMethod:"S256"}}function x(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 Ce(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:z(e.authorization_details)}}function E(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 Ce(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 ne(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 _(e,t){return encodeURIComponent(ne(e,t))}function Ee(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 qe(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 Pe(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 re(e){async function t(n,r){let o=Pe(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=Ee(s);if(a.error==="mfa_required")throw new R(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 oe(e){return{async publicClient(t){let n=await e.http.request(`/oauth/clients/${encodeURIComponent(t)}/public`);return qe(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 $="openid profile email offline_access",se=new Set(["response_type","client_id","redirect_uri","scope","state","nonce","code_challenge","code_challenge_method","prompt","login_hint","authorization_details","request_uri"]);function Oe(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 ie(e){let t=Oe(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 ae(e){let t=E(e);return{...re(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??$,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[b,O]of Object.entries(r.extraParams??{})){if(se.has(b))throw new i("invalid_request",`extraParams may not override the reserved authorization parameter "${b}".`);p.set(b,O)}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??$,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[I,Re]of Object.entries(r.extraParams??{})){if(se.has(I))throw new i("invalid_request",`extraParams may not override the reserved authorization parameter "${I}".`);l[I]=Re}let g=await e.http.request("/oauth/par",{method:"POST",body:l}),p=g.request_uri,b=g.expires_in;if(typeof p!="string"||typeof b!="number")throw new i("unknown_error","Unexpected pushed authorization response payload.",{details:g});let O=r.authorizationEndpoint??`${e.config.baseUrl}/oauth/authorize`,U=new URL(O);return U.searchParams.set("client_id",e.config.clientId),U.searchParams.set("request_uri",p),{requestUri:p,expiresIn:b,request:{url:U.toString(),state:s,nonce:a,codeVerifier:d.codeVerifier,codeChallenge:d.codeChallenge,redirectUri:r.redirectUri,scope:o,requestUri:p}}},parseCallback:ie,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:ie(r.callback);if(s.error)throw new i("access_denied",s.errorDescription??`Authorization failed: ${s.error}`,{details:s.raw});if(!s.state||!x(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 M(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 Ue(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 L(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 de(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 M(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 L(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 M(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 L(e,n)},async getStatus(t){let n=await e.http.request(`/oauth/flow/status/${encodeURIComponent(t)}`);return Ue(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 M(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 L(e,n)}}}function q(e){return Array.isArray(e)?e.filter(t=>typeof t=="string"):[]}function Ie(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:q(e.response_types_supported),grantTypesSupported:q(e.grant_types_supported),scopesSupported:q(e.scopes_supported),codeChallengeMethodsSupported:q(e.code_challenge_methods_supported),raw:e}}function ze(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 ue(e){return{async userInfo(){let t=await u(e),n=await e.http.request("/oauth/userinfo",{token:t});return ze(n)},async discover(){let t=await e.http.request("/.well-known/openid-configuration");return Ie(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 P(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 N(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 De(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 ce(e){return je(e)}function je(e){return{async me(){let t=await u(e),n=await e.http.request("/api/v1/users/me",{token:t});return P(n)},async updateMe(t){let n=await f(e,"/api/v1/users/me",{method:"PATCH",body:t});return P(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 P(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 P(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(N):[]},async create(t){let n=await f(e,"/api/v1/users/me/recovery-contacts",{method:"POST",body:t});return N(n)},async update(t,n){let r=await f(e,`/api/v1/users/me/recovery-contacts/${_(t,"contactId")}`,{method:"PATCH",body:n});return N(r)},async delete(t){await h(e,`/api/v1/users/me/recovery-contacts/${_(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(De):[]},async unlink(t){await h(e,`/api/v1/users/me/identities/${_(t,"identityId")}`,{method:"DELETE"})},async setPrimary(t){return f(e,`/api/v1/users/me/identities/${_(t,"identityId")}/primary`,{method:"PATCH"})}},async deleteMe(t){await h(e,"/api/v1/users/me",{method:"DELETE",body:{password:t.password}})}}}function k(e){return e&&typeof e=="object"?e:{}}function pe(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 k(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 k(r)},async authenticateBegin(t){let n=await e.http.request("/api/v1/users/me/webauthn/authenticate/begin",{method:"POST",body:t});return k(n)},async authenticateFinish(t){let n=await e.http.request("/api/v1/users/me/webauthn/authenticate/finish",{method:"POST",body:t}),r=k(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 k(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 k(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 k(r)}}}function xe(e){return{isActive:!!(e.is_active??e.isActive),message:typeof e.message=="string"?e.message:void 0,scheduledStart:typeof e.scheduled_start=="string"?e.scheduled_start:void 0,scheduledEnd:typeof e.scheduled_end=="string"?e.scheduled_end:void 0,...e}}function fe(e){return{async status(){let t=await e.http.request("/api/v1/maintenance/status");return xe(t)}}}var $e=128,H=254;function y(e,t){if(typeof e!="string")return;let n=e.trim();if(!(!n||n.length>t))return n}function le(e){return{async request(t){let n=c({email:y(t.email,H),identifier:y(t.identifier,H),recovery_email:y(t.recoveryEmail,H),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>$e)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 Me(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 me(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(Me):[]},async removeDevice(t){await h(e,`/api/v1/users/me/mfa/devices/${_(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 he="https://prod-auth.tktchurch.com",Le=new Set(["localhost","127.0.0.1","[::1]"]);function Ne(e){return Le.has(e.toLowerCase())}function J(e,t={}){let n=(e??he).trim();if(!n)return J(he,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&&!Ne(s.hostname))throw new i("invalid_request","baseUrl must use HTTPS. Set allowInsecureTransport=true only for trusted local development.");return o}function He(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 Je(e,t){return/^https?:\/\//i.test(t)?t:`${e}${t.startsWith("/")?t:`/${t}`}`}function Be(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 ye(e){return{async request(t,n){let r=await this.requestRaw(t,n);if(!r.ok)throw await K(r);if(r.status===204)return;let o=await r.text();if(o)return JSON.parse(o)},async requestRaw(t,n){let r=Je(e.baseUrl,t),o=Be(e.defaultHeaders,n?.headers,n?.token),{signal:s,cleanup:a}=He(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 Fe="@tktchurch/auth/tokens",F=32768,_e=new Set(["accessToken","tokenType","expiresIn","refreshToken","scope","idToken","authorizationDetails"]),ke=new Set(["__proto__","constructor","prototype"]),Ve=/^[\w@./:-]{1,128}$/;function V(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function ge(e){return typeof e=="string"&&e.length>0}function B(e){return typeof e=="string"&&e.length>0?e:void 0}function v(e){if(!V(e))throw new i("invalid_request","Token storage rejected a non-object token payload.");for(let s of Object.keys(e))if(!_e.has(s))throw new i("invalid_request",`Token storage rejected unknown field "${s}".`);if(!ge(e.accessToken))throw new i("invalid_request","Token storage requires a non-empty accessToken.");if(!ge(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=B(e.refreshToken);n&&(t.refreshToken=n);let r=B(e.scope);r&&(t.scope=r);let o=B(e.idToken);if(o&&(t.idToken=o),typeof e.authorizationDetails<"u"){if(!Array.isArray(e.authorizationDetails)||!e.authorizationDetails.every(s=>V(s)&&typeof s.type=="string"))throw new i("invalid_request","Token storage rejected malformed authorizationDetails.");t.authorizationDetails=e.authorizationDetails}return t}function Qe(e){if(!V(e))return null;for(let t of Object.keys(e))if(ke.has(t)||!_e.has(t))return null;try{return v(e)}catch{return null}}function We(e){return JSON.parse(e,(t,n)=>{if(ke.has(t))throw new SyntaxError("Forbidden JSON key in stored token payload.");return n})}function Ke(e){let t=e.trim();if(!t)throw new i("invalid_request","Token storage key must be a non-empty string.");if(!Ve.test(t))throw new i("invalid_request","Token storage key contains unsupported characters or exceeds 128 characters.");return t}function Ge(e){let t=v(e),n=JSON.stringify(t);if(n.length>F)throw new i("invalid_request",`Serialized token payload exceeds ${F} bytes.`);return n}var Q=class{state=null;get(){return this.state}set(t){if(!t){this.clear();return}this.state=v(t)}clear(){this.state=null}};function W(){return new Q}function Ye(){return typeof window<"u"&&typeof window.document<"u"}function Xe(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 be(e){if(!e.clientId?.trim())throw new i("invalid_request","clientId is required.");if(Ye()&&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:J(e.baseUrl,{allowInsecureTransport:e.allowInsecureTransport??!1}),fetch:Xe(e.fetch),storage:e.storage??W(),timeoutMs:e.timeoutMs??15e3,defaultHeaders:e.defaultHeaders??{},autoRefresh:e.autoRefresh??!0,allowClientSecretInBrowser:e.allowClientSecretInBrowser??!1,allowInsecureTransport:e.allowInsecureTransport??!1}}function Ze(e){let t=be(e),n=ye({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:E(a),flow:de(a),oidc:ue(a),authorize:ae(a),user:ce(a),sessions:Y(a),webauthn:pe(a),passwordReset:le(a),mfa:me(a),maintenance:fe(a),oauth:oe(a),tokens:{get:r,set:o,clear:s}}}var et=/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/;function tt(e,t="/"){if(!e)return t;let n=e;try{n=decodeURIComponent(e)}catch{return t}return n.startsWith("/")&&!n.startsWith("//")&&!n.startsWith("/\\")&&!et.test(n)&&!/^\/+[\\/]/.test(n)?n:t}export{i as AuthError,$ as DEFAULT_AUTHORIZE_SCOPE,Fe as DEFAULT_TOKEN_STORAGE_KEY,F as MAX_STORED_TOKEN_BYTES,R as MFARequiredAuthError,v as assertValidAuthTokens,Ze as createAuthClient,W as createMemoryTokenStorage,te as deriveCodeChallenge,ee as generateCodeVerifier,T as generateNonce,C as generatePkce,A as generateState,G as isAuthError,Se as isUnrecoverableAuthError,Qe as parseStoredAuthTokens,S as randomUrlSafeString,We as safeParseStoredJson,tt as sanitizePostAuthRedirect,Ge as serializeAuthTokens,x as timingSafeEqual,Ke as validateStorageKey};
|