@douvery/auth 0.3.0 → 0.3.2

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.
@@ -1,925 +1,9 @@
1
1
  import { createContextId, component$, useSignal, useContextProvider, useVisibleTask$, Slot, useContext, useTask$ } from '@builder.io/qwik';
2
+ import { createDouveryAuth } from '@douvery/auth';
3
+ export { DouveryAuthClient, createDouveryAuth } from '@douvery/auth';
2
4
  import { jsx } from '@builder.io/qwik/jsx-runtime';
3
5
 
4
6
  // src/qwik/index.tsx
5
-
6
- // src/types.ts
7
- var AuthError = class extends Error {
8
- constructor(code, message, cause) {
9
- super(message);
10
- this.code = code;
11
- this.cause = cause;
12
- this.name = "AuthError";
13
- }
14
- };
15
-
16
- // src/pkce.ts
17
- function generateCodeVerifier(length = 64) {
18
- const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
19
- const randomValues = crypto.getRandomValues(new Uint8Array(length));
20
- return Array.from(randomValues).map((v) => charset[v % charset.length]).join("");
21
- }
22
- function generateState() {
23
- return generateCodeVerifier(32);
24
- }
25
- function generateNonce() {
26
- return generateCodeVerifier(32);
27
- }
28
- async function generateCodeChallenge(verifier) {
29
- const encoder = new TextEncoder();
30
- const data = encoder.encode(verifier);
31
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
32
- return base64UrlEncode(hashBuffer);
33
- }
34
- function base64UrlEncode(buffer) {
35
- const bytes = new Uint8Array(buffer);
36
- let binary = "";
37
- for (let i = 0; i < bytes.length; i++) {
38
- binary += String.fromCharCode(bytes[i]);
39
- }
40
- const base64 = btoa(binary);
41
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
42
- }
43
- function base64UrlDecode(input) {
44
- let base64 = input.replace(/-/g, "+").replace(/_/g, "/");
45
- const padding = base64.length % 4;
46
- if (padding) {
47
- base64 += "=".repeat(4 - padding);
48
- }
49
- const binary = atob(base64);
50
- const bytes = new Uint8Array(binary.length);
51
- for (let i = 0; i < binary.length; i++) {
52
- bytes[i] = binary.charCodeAt(i);
53
- }
54
- return bytes.buffer;
55
- }
56
- async function generatePKCEPair() {
57
- const codeVerifier = generateCodeVerifier();
58
- const codeChallenge = await generateCodeChallenge(codeVerifier);
59
- return {
60
- codeVerifier,
61
- codeChallenge,
62
- codeChallengeMethod: "S256"
63
- };
64
- }
65
- function decodeJWT(token) {
66
- const parts = token.split(".");
67
- if (parts.length !== 3) {
68
- throw new Error("Invalid JWT format");
69
- }
70
- const payload = parts[1];
71
- const decoded = base64UrlDecode(payload);
72
- const text = new TextDecoder().decode(decoded);
73
- return JSON.parse(text);
74
- }
75
- function isTokenExpired(token, clockSkew = 60) {
76
- try {
77
- const payload = decodeJWT(token);
78
- if (!payload.exp) {
79
- return false;
80
- }
81
- const now = Math.floor(Date.now() / 1e3);
82
- return payload.exp < now - clockSkew;
83
- } catch {
84
- return true;
85
- }
86
- }
87
-
88
- // src/storage.ts
89
- var DEFAULT_PREFIX = "douvery_auth";
90
- var STORAGE_KEYS = {
91
- accessToken: `${DEFAULT_PREFIX}_access_token`,
92
- refreshToken: `${DEFAULT_PREFIX}_refresh_token`,
93
- idToken: `${DEFAULT_PREFIX}_id_token`,
94
- expiresAt: `${DEFAULT_PREFIX}_expires_at`,
95
- state: `${DEFAULT_PREFIX}_state`,
96
- nonce: `${DEFAULT_PREFIX}_nonce`,
97
- codeVerifier: `${DEFAULT_PREFIX}_code_verifier`,
98
- returnTo: `${DEFAULT_PREFIX}_return_to`
99
- };
100
- var MemoryStorage = class {
101
- store = /* @__PURE__ */ new Map();
102
- get(key) {
103
- return this.store.get(key) ?? null;
104
- }
105
- set(key, value) {
106
- this.store.set(key, value);
107
- }
108
- remove(key) {
109
- this.store.delete(key);
110
- }
111
- clear() {
112
- this.store.clear();
113
- }
114
- };
115
- var LocalStorage = class {
116
- get(key) {
117
- if (typeof window === "undefined") return null;
118
- return localStorage.getItem(key);
119
- }
120
- set(key, value) {
121
- if (typeof window === "undefined") return;
122
- localStorage.setItem(key, value);
123
- }
124
- remove(key) {
125
- if (typeof window === "undefined") return;
126
- localStorage.removeItem(key);
127
- }
128
- clear() {
129
- if (typeof window === "undefined") return;
130
- Object.values(STORAGE_KEYS).forEach((key) => {
131
- localStorage.removeItem(key);
132
- });
133
- }
134
- };
135
- var SessionStorage = class {
136
- get(key) {
137
- if (typeof window === "undefined") return null;
138
- return sessionStorage.getItem(key);
139
- }
140
- set(key, value) {
141
- if (typeof window === "undefined") return;
142
- sessionStorage.setItem(key, value);
143
- }
144
- remove(key) {
145
- if (typeof window === "undefined") return;
146
- sessionStorage.removeItem(key);
147
- }
148
- clear() {
149
- if (typeof window === "undefined") return;
150
- Object.values(STORAGE_KEYS).forEach((key) => {
151
- sessionStorage.removeItem(key);
152
- });
153
- }
154
- };
155
- var CookieStorage = class {
156
- constructor(options = {}) {
157
- this.options = options;
158
- this.options = { path: "/", secure: true, sameSite: "Lax", ...options };
159
- }
160
- get(key) {
161
- if (typeof document === "undefined") return null;
162
- const cookies = document.cookie.split(";");
163
- for (const cookie of cookies) {
164
- const [name, value] = cookie.trim().split("=");
165
- if (name === key) {
166
- return decodeURIComponent(value);
167
- }
168
- }
169
- return null;
170
- }
171
- set(key, value) {
172
- if (typeof document === "undefined") return;
173
- const parts = [
174
- `${key}=${encodeURIComponent(value)}`,
175
- `path=${this.options.path}`
176
- ];
177
- if (this.options.domain) parts.push(`domain=${this.options.domain}`);
178
- if (this.options.secure) parts.push("secure");
179
- if (this.options.sameSite) parts.push(`samesite=${this.options.sameSite}`);
180
- if (this.options.maxAge) parts.push(`max-age=${this.options.maxAge}`);
181
- document.cookie = parts.join("; ");
182
- }
183
- remove(key) {
184
- if (typeof document === "undefined") return;
185
- document.cookie = `${key}=; path=${this.options.path}; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
186
- }
187
- clear() {
188
- Object.values(STORAGE_KEYS).forEach((key) => this.remove(key));
189
- }
190
- };
191
- function createStorage(type) {
192
- switch (type) {
193
- case "localStorage":
194
- return new LocalStorage();
195
- case "sessionStorage":
196
- return new SessionStorage();
197
- case "cookie":
198
- return new CookieStorage();
199
- case "memory":
200
- default:
201
- return new MemoryStorage();
202
- }
203
- }
204
- var TokenManager = class {
205
- constructor(storage) {
206
- this.storage = storage;
207
- }
208
- async getTokens() {
209
- const accessToken = await this.storage.get(STORAGE_KEYS.accessToken);
210
- if (!accessToken) return null;
211
- const refreshToken = await this.storage.get(STORAGE_KEYS.refreshToken);
212
- const idToken = await this.storage.get(STORAGE_KEYS.idToken);
213
- const expiresAt = await this.storage.get(STORAGE_KEYS.expiresAt);
214
- return {
215
- accessToken,
216
- refreshToken: refreshToken ?? void 0,
217
- idToken: idToken ?? void 0,
218
- expiresAt: expiresAt ? parseInt(expiresAt, 10) : 0,
219
- tokenType: "Bearer",
220
- scope: []
221
- };
222
- }
223
- async setTokens(tokens) {
224
- await this.storage.set(STORAGE_KEYS.accessToken, tokens.accessToken);
225
- await this.storage.set(STORAGE_KEYS.expiresAt, tokens.expiresAt.toString());
226
- if (tokens.refreshToken) {
227
- await this.storage.set(STORAGE_KEYS.refreshToken, tokens.refreshToken);
228
- }
229
- if (tokens.idToken) {
230
- await this.storage.set(STORAGE_KEYS.idToken, tokens.idToken);
231
- }
232
- }
233
- async clearTokens() {
234
- await this.storage.remove(STORAGE_KEYS.accessToken);
235
- await this.storage.remove(STORAGE_KEYS.refreshToken);
236
- await this.storage.remove(STORAGE_KEYS.idToken);
237
- await this.storage.remove(STORAGE_KEYS.expiresAt);
238
- }
239
- async saveState(state) {
240
- await this.storage.set(STORAGE_KEYS.state, state);
241
- }
242
- async getState() {
243
- return this.storage.get(STORAGE_KEYS.state);
244
- }
245
- async clearState() {
246
- await this.storage.remove(STORAGE_KEYS.state);
247
- }
248
- async saveNonce(nonce) {
249
- await this.storage.set(STORAGE_KEYS.nonce, nonce);
250
- }
251
- async getNonce() {
252
- return this.storage.get(STORAGE_KEYS.nonce);
253
- }
254
- async clearNonce() {
255
- await this.storage.remove(STORAGE_KEYS.nonce);
256
- }
257
- async saveCodeVerifier(verifier) {
258
- await this.storage.set(STORAGE_KEYS.codeVerifier, verifier);
259
- }
260
- async getCodeVerifier() {
261
- return this.storage.get(STORAGE_KEYS.codeVerifier);
262
- }
263
- async clearCodeVerifier() {
264
- await this.storage.remove(STORAGE_KEYS.codeVerifier);
265
- }
266
- async saveReturnTo(url) {
267
- await this.storage.set(STORAGE_KEYS.returnTo, url);
268
- }
269
- async getReturnTo() {
270
- return this.storage.get(STORAGE_KEYS.returnTo);
271
- }
272
- async clearReturnTo() {
273
- await this.storage.remove(STORAGE_KEYS.returnTo);
274
- }
275
- async clearAll() {
276
- await this.storage.clear();
277
- }
278
- };
279
-
280
- // src/client.ts
281
- var DEFAULT_ISSUER = "https://auth.douvery.com";
282
- var DEFAULT_SCOPES = ["openid", "profile", "email"];
283
- var DouveryAuthClient = class {
284
- config;
285
- tokenManager;
286
- discovery = null;
287
- eventHandlers = /* @__PURE__ */ new Set();
288
- refreshTimer = null;
289
- state = {
290
- status: "loading",
291
- user: null,
292
- tokens: null,
293
- error: null
294
- };
295
- constructor(config) {
296
- this.config = {
297
- issuer: DEFAULT_ISSUER,
298
- scopes: DEFAULT_SCOPES,
299
- storage: "localStorage",
300
- autoRefresh: true,
301
- refreshThreshold: 60,
302
- debug: false,
303
- ...config
304
- };
305
- const storage = config.customStorage ?? createStorage(this.config.storage ?? "localStorage");
306
- this.tokenManager = new TokenManager(storage);
307
- }
308
- /** Initialize the auth client */
309
- async initialize() {
310
- this.log("Initializing auth client...");
311
- try {
312
- if (this.isCallback()) {
313
- this.log("Handling OAuth callback...");
314
- const result = await this.handleCallback();
315
- if (result.success && result.user && result.tokens) {
316
- this.updateState({
317
- status: "authenticated",
318
- user: result.user,
319
- tokens: result.tokens,
320
- error: null
321
- });
322
- this.setupAutoRefresh();
323
- } else {
324
- this.updateState({
325
- status: "unauthenticated",
326
- user: null,
327
- tokens: null,
328
- error: result.error ?? null
329
- });
330
- }
331
- } else {
332
- const tokens = await this.tokenManager.getTokens();
333
- if (tokens && tokens.accessToken) {
334
- if (!isTokenExpired(tokens.accessToken)) {
335
- this.log("Found valid existing session");
336
- const user = await this.fetchUser(tokens.accessToken);
337
- this.updateState({
338
- status: "authenticated",
339
- user,
340
- tokens,
341
- error: null
342
- });
343
- this.setupAutoRefresh();
344
- } else if (tokens.refreshToken) {
345
- this.log("Access token expired, attempting refresh...");
346
- await this.refreshTokens();
347
- } else {
348
- this.log("Session expired, no refresh token");
349
- await this.tokenManager.clearTokens();
350
- this.updateState({
351
- status: "unauthenticated",
352
- user: null,
353
- tokens: null,
354
- error: null
355
- });
356
- }
357
- } else {
358
- this.log("No existing session found");
359
- this.updateState({
360
- status: "unauthenticated",
361
- user: null,
362
- tokens: null,
363
- error: null
364
- });
365
- }
366
- }
367
- this.emit({ type: "INITIALIZED" });
368
- } catch (error) {
369
- this.log("Initialization error:", error);
370
- this.updateState({
371
- status: "unauthenticated",
372
- user: null,
373
- tokens: null,
374
- error: error instanceof AuthError ? error : new AuthError(
375
- "unknown_error",
376
- "Initialization failed",
377
- error
378
- )
379
- });
380
- }
381
- return this.state;
382
- }
383
- /** Start the login flow */
384
- async login(options = {}) {
385
- this.log("Starting login flow...");
386
- this.emit({ type: "LOGIN_STARTED" });
387
- try {
388
- const discovery = await this.getDiscovery();
389
- const pkce = await generatePKCEPair();
390
- const state = generateState();
391
- const nonce = generateNonce();
392
- await this.tokenManager.saveState(state);
393
- await this.tokenManager.saveNonce(nonce);
394
- await this.tokenManager.saveCodeVerifier(pkce.codeVerifier);
395
- if (options.returnTo) {
396
- await this.tokenManager.saveReturnTo(options.returnTo);
397
- }
398
- const params = new URLSearchParams({
399
- response_type: "code",
400
- client_id: this.config.clientId,
401
- redirect_uri: this.config.redirectUri,
402
- scope: this.config.scopes.join(" "),
403
- state,
404
- nonce,
405
- code_challenge: pkce.codeChallenge,
406
- code_challenge_method: pkce.codeChallengeMethod,
407
- ...options.authorizationParams
408
- });
409
- if (options.prompt) params.set("prompt", options.prompt);
410
- if (options.loginHint) params.set("login_hint", options.loginHint);
411
- if (options.uiLocales) params.set("ui_locales", options.uiLocales);
412
- if (options.maxAge !== void 0)
413
- params.set("max_age", options.maxAge.toString());
414
- if (options.acrValues) params.set("acr_values", options.acrValues);
415
- const authUrl = `${discovery.authorization_endpoint}?${params}`;
416
- this.log("Redirecting to:", authUrl);
417
- window.location.href = authUrl;
418
- } catch (error) {
419
- const authError = error instanceof AuthError ? error : new AuthError(
420
- "configuration_error",
421
- "Login failed",
422
- error
423
- );
424
- this.emit({ type: "LOGIN_ERROR", error: authError });
425
- throw authError;
426
- }
427
- }
428
- /** Logout the user */
429
- async logout(options = {}) {
430
- this.log("Starting logout...");
431
- this.emit({ type: "LOGOUT_STARTED" });
432
- try {
433
- await this.tokenManager.clearAll();
434
- this.clearAutoRefresh();
435
- this.updateState({
436
- status: "unauthenticated",
437
- user: null,
438
- tokens: null,
439
- error: null
440
- });
441
- if (options.localOnly) {
442
- this.emit({ type: "LOGOUT_SUCCESS" });
443
- return;
444
- }
445
- if (options.federated !== false) {
446
- const discovery = await this.getDiscovery();
447
- if (discovery.end_session_endpoint) {
448
- const params = new URLSearchParams();
449
- if (this.state.tokens?.idToken) {
450
- params.set("id_token_hint", this.state.tokens.idToken);
451
- }
452
- if (options.returnTo || this.config.postLogoutRedirectUri) {
453
- params.set(
454
- "post_logout_redirect_uri",
455
- options.returnTo || this.config.postLogoutRedirectUri
456
- );
457
- }
458
- params.set("client_id", this.config.clientId);
459
- const logoutUrl = `${discovery.end_session_endpoint}?${params}`;
460
- this.log("Redirecting to logout:", logoutUrl);
461
- window.location.href = logoutUrl;
462
- return;
463
- }
464
- }
465
- this.emit({ type: "LOGOUT_SUCCESS" });
466
- if (options.returnTo) {
467
- window.location.href = options.returnTo;
468
- }
469
- } catch (error) {
470
- const authError = error instanceof AuthError ? error : new AuthError("unknown_error", "Logout failed", error);
471
- this.emit({ type: "LOGOUT_ERROR", error: authError });
472
- throw authError;
473
- }
474
- }
475
- /** Check if current URL is an OAuth callback */
476
- isCallback() {
477
- if (typeof window === "undefined") return false;
478
- const params = new URLSearchParams(window.location.search);
479
- return params.has("code") || params.has("error");
480
- }
481
- /** Handle the OAuth callback */
482
- async handleCallback() {
483
- this.log("Processing callback...");
484
- if (typeof window === "undefined") {
485
- return {
486
- success: false,
487
- error: new AuthError(
488
- "configuration_error",
489
- "Cannot handle callback on server"
490
- )
491
- };
492
- }
493
- const params = new URLSearchParams(window.location.search);
494
- const code = params.get("code");
495
- const stateParam = params.get("state");
496
- const errorParam = params.get("error");
497
- const errorDescription = params.get("error_description");
498
- if (errorParam) {
499
- const error = new AuthError(
500
- errorParam,
501
- errorDescription ?? "Authorization failed"
502
- );
503
- return { success: false, error };
504
- }
505
- const savedState = await this.tokenManager.getState();
506
- if (!stateParam || stateParam !== savedState) {
507
- return {
508
- success: false,
509
- error: new AuthError("state_mismatch", "State parameter mismatch")
510
- };
511
- }
512
- if (!code) {
513
- return {
514
- success: false,
515
- error: new AuthError(
516
- "invalid_request",
517
- "No authorization code received"
518
- )
519
- };
520
- }
521
- const codeVerifier = await this.tokenManager.getCodeVerifier();
522
- if (!codeVerifier) {
523
- return {
524
- success: false,
525
- error: new AuthError("pkce_error", "No code verifier found")
526
- };
527
- }
528
- try {
529
- const tokens = await this.exchangeCode(code, codeVerifier);
530
- await this.tokenManager.setTokens(tokens);
531
- const user = await this.fetchUser(tokens.accessToken);
532
- const returnTo = await this.tokenManager.getReturnTo();
533
- await this.tokenManager.clearState();
534
- await this.tokenManager.clearNonce();
535
- await this.tokenManager.clearCodeVerifier();
536
- await this.tokenManager.clearReturnTo();
537
- window.history.replaceState({}, "", window.location.pathname);
538
- this.emit({ type: "LOGIN_SUCCESS", user, tokens });
539
- return { success: true, user, tokens, returnTo: returnTo ?? void 0 };
540
- } catch (error) {
541
- const authError = error instanceof AuthError ? error : new AuthError(
542
- "invalid_grant",
543
- "Token exchange failed",
544
- error
545
- );
546
- this.emit({ type: "LOGIN_ERROR", error: authError });
547
- return { success: false, error: authError };
548
- }
549
- }
550
- async exchangeCode(code, codeVerifier) {
551
- const discovery = await this.getDiscovery();
552
- const response = await fetch(discovery.token_endpoint, {
553
- method: "POST",
554
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
555
- body: new URLSearchParams({
556
- grant_type: "authorization_code",
557
- code,
558
- redirect_uri: this.config.redirectUri,
559
- client_id: this.config.clientId,
560
- code_verifier: codeVerifier
561
- })
562
- });
563
- if (!response.ok) {
564
- const error = await response.json().catch(() => ({}));
565
- throw new AuthError(
566
- error.error ?? "invalid_grant",
567
- error.error_description ?? "Token exchange failed"
568
- );
569
- }
570
- const tokenSet = await response.json();
571
- return this.tokenSetToInfo(tokenSet);
572
- }
573
- /** Refresh the access token */
574
- async refreshTokens() {
575
- this.log("Refreshing tokens...");
576
- const tokens = await this.tokenManager.getTokens();
577
- if (!tokens?.refreshToken) {
578
- throw new AuthError("token_refresh_failed", "No refresh token available");
579
- }
580
- const discovery = await this.getDiscovery();
581
- const response = await fetch(discovery.token_endpoint, {
582
- method: "POST",
583
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
584
- body: new URLSearchParams({
585
- grant_type: "refresh_token",
586
- refresh_token: tokens.refreshToken,
587
- client_id: this.config.clientId
588
- })
589
- });
590
- if (!response.ok) {
591
- const error = await response.json().catch(() => ({}));
592
- const authError = new AuthError(
593
- error.error ?? "token_refresh_failed",
594
- error.error_description ?? "Token refresh failed"
595
- );
596
- this.emit({ type: "TOKEN_REFRESH_ERROR", error: authError });
597
- await this.tokenManager.clearTokens();
598
- this.updateState({
599
- status: "unauthenticated",
600
- user: null,
601
- tokens: null,
602
- error: authError
603
- });
604
- this.emit({ type: "SESSION_EXPIRED" });
605
- throw authError;
606
- }
607
- const tokenSet = await response.json();
608
- const newTokens = this.tokenSetToInfo(tokenSet);
609
- await this.tokenManager.setTokens(newTokens);
610
- const user = newTokens.idToken ? this.extractUserFromIdToken(newTokens.idToken) : this.state.user;
611
- this.updateState({ ...this.state, tokens: newTokens, user });
612
- this.emit({ type: "TOKEN_REFRESHED", tokens: newTokens });
613
- this.setupAutoRefresh();
614
- return newTokens;
615
- }
616
- /** Get current access token (auto-refreshes if needed) */
617
- async getAccessToken() {
618
- const tokens = await this.tokenManager.getTokens();
619
- if (!tokens) return null;
620
- if (isTokenExpired(tokens.accessToken)) {
621
- if (tokens.refreshToken) {
622
- const newTokens = await this.refreshTokens();
623
- return newTokens.accessToken;
624
- }
625
- return null;
626
- }
627
- return tokens.accessToken;
628
- }
629
- // ============================================
630
- // Navigation Methods
631
- // ============================================
632
- /** Redirect to select/switch account */
633
- selectAccount(options = {}) {
634
- const url = this.buildSelectAccountUrl(options);
635
- this.navigate(url, options);
636
- }
637
- /** Build select-account URL without redirecting */
638
- buildSelectAccountUrl(options = {}) {
639
- const params = new URLSearchParams();
640
- if (options.returnTo) params.set("continue", options.returnTo);
641
- if (options.clientId) params.set("client_id", options.clientId);
642
- if (options.loginHint) params.set("login_hint", options.loginHint);
643
- return this.createAuthUrl("/select-account", params);
644
- }
645
- /** Redirect to add another account (multi-session) */
646
- addAccount(options = {}) {
647
- const url = this.buildAddAccountUrl(options);
648
- this.navigate(url, options);
649
- }
650
- /** Build add-account URL without redirecting */
651
- buildAddAccountUrl(options = {}) {
652
- const params = new URLSearchParams({ add_account: "true" });
653
- if (options.returnTo) params.set("continue", options.returnTo);
654
- if (options.clientId) params.set("client_id", options.clientId);
655
- if (options.loginHint) params.set("login_hint", options.loginHint);
656
- return this.createAuthUrl("/login", params);
657
- }
658
- /** Redirect to register a new account */
659
- register(options = {}) {
660
- const url = this.buildRegisterUrl(options);
661
- this.navigate(url, options);
662
- }
663
- /** Build register URL without redirecting */
664
- buildRegisterUrl(options = {}) {
665
- const params = new URLSearchParams();
666
- if (options.returnTo) params.set("continue", options.returnTo);
667
- if (options.clientId) params.set("client_id", options.clientId);
668
- if (options.email) params.set("email", options.email);
669
- if (options.firstName) params.set("first_name", options.firstName);
670
- if (options.lastName) params.set("last_name", options.lastName);
671
- if (options.uiLocales) params.set("ui_locales", options.uiLocales);
672
- return this.createAuthUrl("/register", params);
673
- }
674
- /** Redirect to recover account (forgot password) */
675
- recoverAccount(options = {}) {
676
- const url = this.buildRecoverAccountUrl(options);
677
- this.navigate(url, options);
678
- }
679
- /** Build recover-account URL without redirecting */
680
- buildRecoverAccountUrl(options = {}) {
681
- const params = new URLSearchParams();
682
- if (options.returnTo) params.set("continue", options.returnTo);
683
- if (options.clientId) params.set("client_id", options.clientId);
684
- if (options.email) params.set("email", options.email);
685
- return this.createAuthUrl("/recover-account", params);
686
- }
687
- /** Redirect to verify account (email verification) */
688
- verifyAccount(options = {}) {
689
- const url = this.buildVerifyAccountUrl(options);
690
- this.navigate(url, options);
691
- }
692
- /** Build verify-account URL without redirecting */
693
- buildVerifyAccountUrl(options = {}) {
694
- const params = new URLSearchParams();
695
- if (options.returnTo) params.set("continue", options.returnTo);
696
- if (options.clientId) params.set("client_id", options.clientId);
697
- if (options.email) params.set("email", options.email);
698
- return this.createAuthUrl("/verify-account", params);
699
- }
700
- /** Redirect to upgrade account (guest → full account) */
701
- upgradeAccount(options = {}) {
702
- const url = this.buildUpgradeAccountUrl(options);
703
- this.navigate(url, options);
704
- }
705
- /** Build upgrade-account URL without redirecting */
706
- buildUpgradeAccountUrl(options = {}) {
707
- const params = new URLSearchParams();
708
- if (options.returnTo) params.set("continue", options.returnTo);
709
- if (options.clientId) params.set("client_id", options.clientId);
710
- if (options.scopes?.length) params.set("scope", options.scopes.join(" "));
711
- return this.createAuthUrl("/upgrade-account", params);
712
- }
713
- /** Redirect to passkey setup */
714
- setupPasskey(options = {}) {
715
- const url = this.buildSetupPasskeyUrl(options);
716
- this.navigate(url, options);
717
- }
718
- /** Build setup-passkey URL without redirecting */
719
- buildSetupPasskeyUrl(options = {}) {
720
- const params = new URLSearchParams();
721
- if (options.returnTo) params.set("continue", options.returnTo);
722
- if (options.clientId) params.set("client_id", options.clientId);
723
- return this.createAuthUrl("/setup-passkey", params);
724
- }
725
- /** Redirect to address setup */
726
- setupAddress(options = {}) {
727
- const url = this.buildSetupAddressUrl(options);
728
- this.navigate(url, options);
729
- }
730
- /** Build setup-address URL without redirecting */
731
- buildSetupAddressUrl(options = {}) {
732
- const params = new URLSearchParams();
733
- if (options.returnTo) params.set("continue", options.returnTo);
734
- if (options.clientId) params.set("client_id", options.clientId);
735
- return this.createAuthUrl("/setup-address", params);
736
- }
737
- /** Build a login URL without redirecting (useful for links/buttons) */
738
- buildLoginUrl(options = {}) {
739
- const params = new URLSearchParams();
740
- if (options.returnTo) params.set("continue", options.returnTo);
741
- if (options.loginHint) params.set("email", options.loginHint);
742
- if (options.prompt) params.set("prompt", options.prompt);
743
- if (options.uiLocales) params.set("ui_locales", options.uiLocales);
744
- return this.createAuthUrl("/login", params);
745
- }
746
- /** Build a logout URL without redirecting */
747
- buildLogoutUrl(options = {}) {
748
- const params = new URLSearchParams();
749
- if (options.returnTo) params.set("continue", options.returnTo);
750
- return this.createAuthUrl("/logout", params);
751
- }
752
- /** Revoke a token (access or refresh) */
753
- async revokeToken(options = {}) {
754
- this.log("Revoking token...");
755
- const token = options.token ?? (await this.tokenManager.getTokens())?.accessToken;
756
- if (!token) {
757
- throw new AuthError("invalid_token", "No token to revoke");
758
- }
759
- const revokeUrl = `${this.config.issuer}/oauth/revoke`;
760
- const body = new URLSearchParams({
761
- token,
762
- client_id: this.config.clientId
763
- });
764
- if (options.tokenTypeHint) {
765
- body.set("token_type_hint", options.tokenTypeHint);
766
- }
767
- const response = await fetch(revokeUrl, {
768
- method: "POST",
769
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
770
- body
771
- });
772
- if (!response.ok) {
773
- const error = await response.json().catch(() => ({}));
774
- throw new AuthError(
775
- error.error ?? "server_error",
776
- error.error_description ?? "Token revocation failed"
777
- );
778
- }
779
- this.log("Token revoked successfully");
780
- }
781
- /** Check if the user's session is expired */
782
- isSessionExpired() {
783
- if (!this.state.tokens) return true;
784
- return isTokenExpired(this.state.tokens.accessToken);
785
- }
786
- /** Check if user needs email verification */
787
- needsEmailVerification() {
788
- return this.state.user?.emailVerified === false;
789
- }
790
- /** Check if user is a guest account */
791
- isGuestAccount() {
792
- return this.state.user?.accountType === "guest";
793
- }
794
- tokenSetToInfo(tokenSet) {
795
- return {
796
- accessToken: tokenSet.access_token,
797
- refreshToken: tokenSet.refresh_token,
798
- idToken: tokenSet.id_token,
799
- expiresAt: Date.now() + tokenSet.expires_in * 1e3,
800
- tokenType: tokenSet.token_type,
801
- scope: tokenSet.scope?.split(" ") ?? []
802
- };
803
- }
804
- /** Build an AuthUrl object for a given path and params */
805
- createAuthUrl(path, params) {
806
- const query = params.toString();
807
- const url = `${this.config.issuer}${path}${query ? `?${query}` : ""}`;
808
- return {
809
- url,
810
- redirect: () => {
811
- window.location.href = url;
812
- },
813
- open: () => {
814
- return window.open(url, "_blank");
815
- }
816
- };
817
- }
818
- /** Navigate to an auth URL, respecting openInNewTab option */
819
- navigate(authUrl, options) {
820
- if (options.openInNewTab) {
821
- authUrl.open();
822
- } else {
823
- authUrl.redirect();
824
- }
825
- }
826
- async fetchUser(accessToken) {
827
- const discovery = await this.getDiscovery();
828
- const response = await fetch(discovery.userinfo_endpoint, {
829
- headers: { Authorization: `Bearer ${accessToken}` }
830
- });
831
- if (!response.ok) {
832
- throw new AuthError("invalid_token", "Failed to fetch user info");
833
- }
834
- const userInfo = await response.json();
835
- return this.normalizeUser(userInfo);
836
- }
837
- extractUserFromIdToken(idToken) {
838
- const claims = decodeJWT(idToken);
839
- return this.normalizeUser(claims);
840
- }
841
- normalizeUser(claims) {
842
- return {
843
- id: claims.sub,
844
- email: claims.email,
845
- emailVerified: claims.email_verified,
846
- name: claims.name,
847
- firstName: claims.given_name,
848
- lastName: claims.family_name,
849
- picture: claims.picture,
850
- phoneNumber: claims.phone_number,
851
- phoneNumberVerified: claims.phone_number_verified,
852
- locale: claims.locale,
853
- ...claims
854
- };
855
- }
856
- async getDiscovery() {
857
- if (this.discovery) return this.discovery;
858
- const discoveryUrl = `${this.config.issuer}/.well-known/openid-configuration`;
859
- const response = await fetch(discoveryUrl);
860
- if (!response.ok) {
861
- throw new AuthError(
862
- "configuration_error",
863
- "Failed to fetch discovery document"
864
- );
865
- }
866
- this.discovery = await response.json();
867
- return this.discovery;
868
- }
869
- setupAutoRefresh() {
870
- if (!this.config.autoRefresh || !this.state.tokens) return;
871
- this.clearAutoRefresh();
872
- const expiresIn = this.state.tokens.expiresAt - Date.now();
873
- const refreshIn = expiresIn - this.config.refreshThreshold * 1e3;
874
- if (refreshIn > 0) {
875
- this.log(`Scheduling token refresh in ${Math.round(refreshIn / 1e3)}s`);
876
- this.refreshTimer = setTimeout(() => {
877
- this.refreshTokens().catch(
878
- (error) => this.log("Auto-refresh failed:", error)
879
- );
880
- }, refreshIn);
881
- }
882
- }
883
- clearAutoRefresh() {
884
- if (this.refreshTimer) {
885
- clearTimeout(this.refreshTimer);
886
- this.refreshTimer = null;
887
- }
888
- }
889
- getState() {
890
- return { ...this.state };
891
- }
892
- isAuthenticated() {
893
- return this.state.status === "authenticated";
894
- }
895
- getUser() {
896
- return this.state.user;
897
- }
898
- subscribe(handler) {
899
- this.eventHandlers.add(handler);
900
- return () => this.eventHandlers.delete(handler);
901
- }
902
- updateState(newState) {
903
- this.state = newState;
904
- }
905
- emit(event) {
906
- this.eventHandlers.forEach((handler) => {
907
- try {
908
- handler(event);
909
- } catch (error) {
910
- console.error("Event handler error:", error);
911
- }
912
- });
913
- }
914
- log(...args) {
915
- if (this.config.debug) {
916
- console.log("[DouveryAuth]", ...args);
917
- }
918
- }
919
- };
920
- function createDouveryAuth(config) {
921
- return new DouveryAuthClient(config);
922
- }
923
7
  var DouveryAuthContext = createContextId("douvery-auth");
924
8
  var DouveryAuthProvider = component$(
925
9
  ({ config }) => {
@@ -1080,6 +164,6 @@ function useSessionStatus() {
1080
164
  return { isExpired, needsVerification, isGuest };
1081
165
  }
1082
166
 
1083
- export { DouveryAuthClient, DouveryAuthContext, DouveryAuthProvider, createDouveryAuth, useAuthActions, useAuthUrls, useDouveryAuth, useIsAuthenticated, useSessionStatus, useUser };
167
+ export { DouveryAuthContext, DouveryAuthProvider, useAuthActions, useAuthUrls, useDouveryAuth, useIsAuthenticated, useSessionStatus, useUser };
1084
168
  //# sourceMappingURL=index.js.map
1085
169
  //# sourceMappingURL=index.js.map