@douvery/auth 0.2.1 â 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -2
- package/dist/index.d.ts +107 -1
- package/dist/index.js +187 -0
- package/dist/index.js.map +1 -1
- package/dist/qwik/index.d.ts +135 -1
- package/dist/qwik/index.js +296 -24
- package/dist/qwik/index.js.map +1 -1
- package/dist/react/index.d.ts +144 -1
- package/dist/react/index.js +373 -45
- package/dist/react/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
- ðĄ **Event System** - Subscribe to auth events (login, logout, token refresh)
|
|
30
30
|
- ð **SSR Compatible** - Works with server-side rendering
|
|
31
31
|
- ðŠķ **Lightweight** - ~23KB core, framework adapters add minimal overhead
|
|
32
|
+
- ð§ **Auth Navigation** - Built-in helpers for all auth flows (register, recover, verify, switch account, etc.)
|
|
33
|
+
- ð **URL Builders** - Generate auth URLs for `<a>` tags without programmatic redirect
|
|
34
|
+
- ð **Token Revocation** - Revoke access/refresh tokens via RFC 7009
|
|
32
35
|
|
|
33
36
|
---
|
|
34
37
|
|
|
@@ -197,6 +200,31 @@ fetch('/api/protected', {
|
|
|
197
200
|
|
|
198
201
|
// 7. Logout
|
|
199
202
|
await auth.logout();
|
|
203
|
+
|
|
204
|
+
// 8. Navigation helpers â redirect to any auth flow
|
|
205
|
+
auth.selectAccount({ returnTo: '/dashboard' });
|
|
206
|
+
auth.addAccount({ loginHint: 'other@email.com' });
|
|
207
|
+
auth.register({ email: 'new@email.com' });
|
|
208
|
+
auth.recoverAccount({ email: 'user@email.com' });
|
|
209
|
+
auth.verifyAccount();
|
|
210
|
+
auth.upgradeAccount();
|
|
211
|
+
auth.setupPasskey();
|
|
212
|
+
auth.setupAddress();
|
|
213
|
+
|
|
214
|
+
// 9. URL builders (don't redirect, useful for <a> tags)
|
|
215
|
+
const url = auth.buildSelectAccountUrl({ returnTo: '/home' });
|
|
216
|
+
console.log(url.url); // "https://auth.douvery.com/select-account?continue=/home"
|
|
217
|
+
url.redirect(); // Navigate to the URL
|
|
218
|
+
url.open(); // Open in new tab
|
|
219
|
+
|
|
220
|
+
// 10. Revoke a token
|
|
221
|
+
await auth.revokeToken();
|
|
222
|
+
await auth.revokeToken({ tokenTypeHint: 'refresh_token' });
|
|
223
|
+
|
|
224
|
+
// 11. Session status helpers
|
|
225
|
+
auth.isSessionExpired(); // true/false
|
|
226
|
+
auth.needsEmailVerification(); // true/false
|
|
227
|
+
auth.isGuestAccount(); // true/false
|
|
200
228
|
```
|
|
201
229
|
|
|
202
230
|
---
|
|
@@ -280,6 +308,106 @@ await auth.logout({
|
|
|
280
308
|
});
|
|
281
309
|
```
|
|
282
310
|
|
|
311
|
+
### Navigation Options
|
|
312
|
+
|
|
313
|
+
All navigation methods accept a common base of options:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
interface AuthNavigationOptions {
|
|
317
|
+
returnTo?: string; // URL to return to after the action
|
|
318
|
+
clientId?: string; // OAuth client_id for branded experiences
|
|
319
|
+
openInNewTab?: boolean; // Open in new tab instead of redirect
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
// Select / switch account
|
|
325
|
+
auth.selectAccount({
|
|
326
|
+
returnTo: '/dashboard',
|
|
327
|
+
loginHint: 'user@email.com', // Pre-select a specific account
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Add another account (multi-session)
|
|
331
|
+
auth.addAccount({
|
|
332
|
+
loginHint: 'another@email.com',
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Register a new account
|
|
336
|
+
auth.register({
|
|
337
|
+
email: 'new@email.com',
|
|
338
|
+
firstName: 'John',
|
|
339
|
+
lastName: 'Doe',
|
|
340
|
+
uiLocales: 'es',
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Recover account (forgot password)
|
|
344
|
+
auth.recoverAccount({
|
|
345
|
+
email: 'user@email.com',
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Verify account (email verification)
|
|
349
|
+
auth.verifyAccount({
|
|
350
|
+
email: 'user@email.com',
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// Upgrade guest account to full account
|
|
354
|
+
auth.upgradeAccount({
|
|
355
|
+
scopes: ['profile', 'email'],
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Setup passkey
|
|
359
|
+
auth.setupPasskey({ returnTo: '/settings' });
|
|
360
|
+
|
|
361
|
+
// Setup address
|
|
362
|
+
auth.setupAddress({ returnTo: '/checkout' });
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### URL Builders
|
|
366
|
+
|
|
367
|
+
Every navigation method has a corresponding `build*Url()` method that returns an `AuthUrl` object without redirecting. Useful for `<a>` tags or custom navigation logic:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
const url = auth.buildRegisterUrl({ email: 'hint@test.com' });
|
|
371
|
+
|
|
372
|
+
url.url; // "https://auth.douvery.com/register?email=hint%40test.com"
|
|
373
|
+
url.redirect(); // window.location.href = url
|
|
374
|
+
url.open(); // window.open(url, '_blank')
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
| Builder | Path |
|
|
378
|
+
|---|---|
|
|
379
|
+
| `buildLoginUrl()` | `/login` |
|
|
380
|
+
| `buildLogoutUrl()` | `/logout` |
|
|
381
|
+
| `buildSelectAccountUrl()` | `/select-account` |
|
|
382
|
+
| `buildAddAccountUrl()` | `/login?add_account=true` |
|
|
383
|
+
| `buildRegisterUrl()` | `/register` |
|
|
384
|
+
| `buildRecoverAccountUrl()` | `/recover-account` |
|
|
385
|
+
| `buildVerifyAccountUrl()` | `/verify-account` |
|
|
386
|
+
| `buildUpgradeAccountUrl()` | `/upgrade-account` |
|
|
387
|
+
| `buildSetupPasskeyUrl()` | `/setup-passkey` |
|
|
388
|
+
| `buildSetupAddressUrl()` | `/setup-address` |
|
|
389
|
+
|
|
390
|
+
### Token Revocation
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
// Revoke current access token
|
|
394
|
+
await auth.revokeToken();
|
|
395
|
+
|
|
396
|
+
// Revoke a specific token
|
|
397
|
+
await auth.revokeToken({
|
|
398
|
+
token: 'specific-token-value',
|
|
399
|
+
tokenTypeHint: 'refresh_token',
|
|
400
|
+
});
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Session Status Helpers
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
auth.isSessionExpired(); // true if access token is expired
|
|
407
|
+
auth.needsEmailVerification(); // true if email is not verified
|
|
408
|
+
auth.isGuestAccount(); // true if account type is guest
|
|
409
|
+
```
|
|
410
|
+
|
|
283
411
|
### Auth State
|
|
284
412
|
|
|
285
413
|
```typescript
|
|
@@ -330,7 +458,47 @@ type AuthEvent =
|
|
|
330
458
|
| `useUser()` | Current user or null |
|
|
331
459
|
| `useIsAuthenticated()` | Boolean authentication status |
|
|
332
460
|
| `useAccessToken()` | `{ accessToken, getAccessToken }` |
|
|
333
|
-
| `useAuthActions()` |
|
|
461
|
+
| `useAuthActions()` | All auth actions (see below) |
|
|
462
|
+
| `useAuthUrls()` | URL builders for all auth pages |
|
|
463
|
+
| `useSessionStatus()` | `{ isExpired, needsVerification, isGuest }` |
|
|
464
|
+
|
|
465
|
+
#### `useAuthActions()` â Full list
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
const {
|
|
469
|
+
login, // (options?: LoginOptions) => Promise<void>
|
|
470
|
+
logout, // (options?: LogoutOptions) => Promise<void>
|
|
471
|
+
selectAccount, // (options?: SelectAccountOptions) => void
|
|
472
|
+
addAccount, // (options?: AddAccountOptions) => void
|
|
473
|
+
register, // (options?: RegisterOptions) => void
|
|
474
|
+
recoverAccount, // (options?: RecoverAccountOptions) => void
|
|
475
|
+
verifyAccount, // (options?: VerifyAccountOptions) => void
|
|
476
|
+
upgradeAccount, // (options?: UpgradeAccountOptions) => void
|
|
477
|
+
setupPasskey, // (options?: SetupPasskeyOptions) => void
|
|
478
|
+
setupAddress, // (options?: SetupAddressOptions) => void
|
|
479
|
+
revokeToken, // (options?: RevokeTokenOptions) => Promise<void>
|
|
480
|
+
isLoading, // boolean
|
|
481
|
+
} = useAuthActions();
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
#### `useAuthUrls()` â URL builders for links
|
|
485
|
+
|
|
486
|
+
```tsx
|
|
487
|
+
const urls = useAuthUrls();
|
|
488
|
+
|
|
489
|
+
<a href={urls.registerUrl().url}>Create account</a>
|
|
490
|
+
<a href={urls.recoverAccountUrl({ email: user.email }).url}>Forgot password?</a>
|
|
491
|
+
<a href={urls.selectAccountUrl().url}>Switch account</a>
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
#### `useSessionStatus()` â Reactive status
|
|
495
|
+
|
|
496
|
+
```tsx
|
|
497
|
+
const { isExpired, needsVerification, isGuest } = useSessionStatus();
|
|
498
|
+
|
|
499
|
+
{needsVerification && <Banner>Please verify your email</Banner>}
|
|
500
|
+
{isGuest && <button onClick={() => upgradeAccount()}>Upgrade account</button>}
|
|
501
|
+
```
|
|
334
502
|
|
|
335
503
|
### DouveryAuthProvider Props
|
|
336
504
|
|
|
@@ -354,7 +522,36 @@ interface DouveryAuthProviderProps {
|
|
|
354
522
|
| `useDouveryAuth()` | Context | Full context with signals and client |
|
|
355
523
|
| `useUser()` | `Signal<User \| null>` | Reactive user signal |
|
|
356
524
|
| `useIsAuthenticated()` | `Signal<boolean>` | Reactive auth status |
|
|
357
|
-
| `useAuthActions()` |
|
|
525
|
+
| `useAuthActions()` | All actions + `isLoading` | Auth actions (same as React) |
|
|
526
|
+
| `useAuthUrls()` | URL builders | Build URLs for auth pages |
|
|
527
|
+
| `useSessionStatus()` | `{ isExpired, needsVerification, isGuest }` | Reactive session signals |
|
|
528
|
+
|
|
529
|
+
#### Qwik example with navigation
|
|
530
|
+
|
|
531
|
+
```tsx
|
|
532
|
+
import { component$ } from '@builder.io/qwik';
|
|
533
|
+
import { useAuthActions, useSessionStatus } from '@douvery/auth/qwik';
|
|
534
|
+
|
|
535
|
+
export const AccountMenu = component$(() => {
|
|
536
|
+
const { selectAccount, addAccount, recoverAccount, upgradeAccount } = useAuthActions();
|
|
537
|
+
const { isGuest, needsVerification } = useSessionStatus();
|
|
538
|
+
|
|
539
|
+
return (
|
|
540
|
+
<div>
|
|
541
|
+
<button onClick$={() => selectAccount({ returnTo: window.location.href })}>
|
|
542
|
+
Switch account
|
|
543
|
+
</button>
|
|
544
|
+
<button onClick$={() => addAccount()}>Add account</button>
|
|
545
|
+
{isGuest.value && (
|
|
546
|
+
<button onClick$={() => upgradeAccount()}>Upgrade account</button>
|
|
547
|
+
)}
|
|
548
|
+
{needsVerification.value && (
|
|
549
|
+
<button onClick$={() => verifyAccount()}>Verify email</button>
|
|
550
|
+
)}
|
|
551
|
+
</div>
|
|
552
|
+
);
|
|
553
|
+
});
|
|
554
|
+
```
|
|
358
555
|
|
|
359
556
|
---
|
|
360
557
|
|
package/dist/index.d.ts
CHANGED
|
@@ -207,6 +207,64 @@ interface LogoutOptions {
|
|
|
207
207
|
/** Only clear local session, don't redirect @default false */
|
|
208
208
|
localOnly?: boolean;
|
|
209
209
|
}
|
|
210
|
+
/** Base options for all auth navigation redirects */
|
|
211
|
+
interface AuthNavigationOptions {
|
|
212
|
+
/** URL to return to after the action completes */
|
|
213
|
+
returnTo?: string;
|
|
214
|
+
/** OAuth client_id for branded experiences */
|
|
215
|
+
clientId?: string;
|
|
216
|
+
/** Open in a new tab/window instead of redirecting */
|
|
217
|
+
openInNewTab?: boolean;
|
|
218
|
+
}
|
|
219
|
+
interface SelectAccountOptions extends AuthNavigationOptions {
|
|
220
|
+
/** Pre-select a specific email/account */
|
|
221
|
+
loginHint?: string;
|
|
222
|
+
}
|
|
223
|
+
interface RegisterOptions extends AuthNavigationOptions {
|
|
224
|
+
/** Pre-fill email on register form */
|
|
225
|
+
email?: string;
|
|
226
|
+
/** Pre-fill first name */
|
|
227
|
+
firstName?: string;
|
|
228
|
+
/** Pre-fill last name */
|
|
229
|
+
lastName?: string;
|
|
230
|
+
/** UI locale preference */
|
|
231
|
+
uiLocales?: string;
|
|
232
|
+
}
|
|
233
|
+
interface RecoverAccountOptions extends AuthNavigationOptions {
|
|
234
|
+
/** Pre-fill email for recovery */
|
|
235
|
+
email?: string;
|
|
236
|
+
}
|
|
237
|
+
interface VerifyAccountOptions extends AuthNavigationOptions {
|
|
238
|
+
/** Email to verify */
|
|
239
|
+
email?: string;
|
|
240
|
+
}
|
|
241
|
+
interface UpgradeAccountOptions extends AuthNavigationOptions {
|
|
242
|
+
/** Scopes to request on upgrade */
|
|
243
|
+
scopes?: string[];
|
|
244
|
+
}
|
|
245
|
+
interface SetupPasskeyOptions extends AuthNavigationOptions {
|
|
246
|
+
}
|
|
247
|
+
interface SetupAddressOptions extends AuthNavigationOptions {
|
|
248
|
+
}
|
|
249
|
+
interface AddAccountOptions extends AuthNavigationOptions {
|
|
250
|
+
/** Pre-fill email hint for the new account */
|
|
251
|
+
loginHint?: string;
|
|
252
|
+
}
|
|
253
|
+
interface RevokeTokenOptions {
|
|
254
|
+
/** Specific token to revoke. If not provided, revokes current access token */
|
|
255
|
+
token?: string;
|
|
256
|
+
/** Token type hint: "access_token" or "refresh_token" */
|
|
257
|
+
tokenTypeHint?: "access_token" | "refresh_token";
|
|
258
|
+
}
|
|
259
|
+
/** Result of URL builder methods (non-redirecting) */
|
|
260
|
+
interface AuthUrl {
|
|
261
|
+
/** The full URL */
|
|
262
|
+
url: string;
|
|
263
|
+
/** Open the URL via redirect */
|
|
264
|
+
redirect: () => void;
|
|
265
|
+
/** Open the URL in a new tab */
|
|
266
|
+
open: () => Window | null;
|
|
267
|
+
}
|
|
210
268
|
|
|
211
269
|
/**
|
|
212
270
|
* @douvery/auth - Auth Client
|
|
@@ -236,7 +294,55 @@ declare class DouveryAuthClient {
|
|
|
236
294
|
refreshTokens(): Promise<TokenInfo>;
|
|
237
295
|
/** Get current access token (auto-refreshes if needed) */
|
|
238
296
|
getAccessToken(): Promise<string | null>;
|
|
297
|
+
/** Redirect to select/switch account */
|
|
298
|
+
selectAccount(options?: SelectAccountOptions): void;
|
|
299
|
+
/** Build select-account URL without redirecting */
|
|
300
|
+
buildSelectAccountUrl(options?: SelectAccountOptions): AuthUrl;
|
|
301
|
+
/** Redirect to add another account (multi-session) */
|
|
302
|
+
addAccount(options?: AddAccountOptions): void;
|
|
303
|
+
/** Build add-account URL without redirecting */
|
|
304
|
+
buildAddAccountUrl(options?: AddAccountOptions): AuthUrl;
|
|
305
|
+
/** Redirect to register a new account */
|
|
306
|
+
register(options?: RegisterOptions): void;
|
|
307
|
+
/** Build register URL without redirecting */
|
|
308
|
+
buildRegisterUrl(options?: RegisterOptions): AuthUrl;
|
|
309
|
+
/** Redirect to recover account (forgot password) */
|
|
310
|
+
recoverAccount(options?: RecoverAccountOptions): void;
|
|
311
|
+
/** Build recover-account URL without redirecting */
|
|
312
|
+
buildRecoverAccountUrl(options?: RecoverAccountOptions): AuthUrl;
|
|
313
|
+
/** Redirect to verify account (email verification) */
|
|
314
|
+
verifyAccount(options?: VerifyAccountOptions): void;
|
|
315
|
+
/** Build verify-account URL without redirecting */
|
|
316
|
+
buildVerifyAccountUrl(options?: VerifyAccountOptions): AuthUrl;
|
|
317
|
+
/** Redirect to upgrade account (guest â full account) */
|
|
318
|
+
upgradeAccount(options?: UpgradeAccountOptions): void;
|
|
319
|
+
/** Build upgrade-account URL without redirecting */
|
|
320
|
+
buildUpgradeAccountUrl(options?: UpgradeAccountOptions): AuthUrl;
|
|
321
|
+
/** Redirect to passkey setup */
|
|
322
|
+
setupPasskey(options?: SetupPasskeyOptions): void;
|
|
323
|
+
/** Build setup-passkey URL without redirecting */
|
|
324
|
+
buildSetupPasskeyUrl(options?: SetupPasskeyOptions): AuthUrl;
|
|
325
|
+
/** Redirect to address setup */
|
|
326
|
+
setupAddress(options?: SetupAddressOptions): void;
|
|
327
|
+
/** Build setup-address URL without redirecting */
|
|
328
|
+
buildSetupAddressUrl(options?: SetupAddressOptions): AuthUrl;
|
|
329
|
+
/** Build a login URL without redirecting (useful for links/buttons) */
|
|
330
|
+
buildLoginUrl(options?: LoginOptions): AuthUrl;
|
|
331
|
+
/** Build a logout URL without redirecting */
|
|
332
|
+
buildLogoutUrl(options?: LogoutOptions): AuthUrl;
|
|
333
|
+
/** Revoke a token (access or refresh) */
|
|
334
|
+
revokeToken(options?: RevokeTokenOptions): Promise<void>;
|
|
335
|
+
/** Check if the user's session is expired */
|
|
336
|
+
isSessionExpired(): boolean;
|
|
337
|
+
/** Check if user needs email verification */
|
|
338
|
+
needsEmailVerification(): boolean;
|
|
339
|
+
/** Check if user is a guest account */
|
|
340
|
+
isGuestAccount(): boolean;
|
|
239
341
|
private tokenSetToInfo;
|
|
342
|
+
/** Build an AuthUrl object for a given path and params */
|
|
343
|
+
private createAuthUrl;
|
|
344
|
+
/** Navigate to an auth URL, respecting openInNewTab option */
|
|
345
|
+
private navigate;
|
|
240
346
|
private fetchUser;
|
|
241
347
|
private extractUserFromIdToken;
|
|
242
348
|
private normalizeUser;
|
|
@@ -349,4 +455,4 @@ declare class TokenManager {
|
|
|
349
455
|
clearAll(): Promise<void>;
|
|
350
456
|
}
|
|
351
457
|
|
|
352
|
-
export { AuthError, type AuthErrorCode, type AuthEvent, type AuthEventHandler, type AuthState, type AuthStatus, type CallbackResult, CookieStorage, type DecodedIdToken, DouveryAuthClient, type DouveryAuthConfig, LocalStorage, type LoginOptions, type LogoutOptions, MemoryStorage, type OIDCDiscovery, type PKCEPair, STORAGE_KEYS, SessionStorage, type StorageKeys, type TokenInfo, TokenManager, type TokenSet, type TokenStorage, type User, base64UrlDecode, base64UrlEncode, createDouveryAuth, createStorage, decodeJWT, generateCodeChallenge, generateCodeVerifier, generateNonce, generatePKCEPair, generateState, getTokenExpiration, isTokenExpired, verifyCodeChallenge };
|
|
458
|
+
export { type AddAccountOptions, AuthError, type AuthErrorCode, type AuthEvent, type AuthEventHandler, type AuthNavigationOptions, type AuthState, type AuthStatus, type AuthUrl, type CallbackResult, CookieStorage, type DecodedIdToken, DouveryAuthClient, type DouveryAuthConfig, LocalStorage, type LoginOptions, type LogoutOptions, MemoryStorage, type OIDCDiscovery, type PKCEPair, type RecoverAccountOptions, type RegisterOptions, type RevokeTokenOptions, STORAGE_KEYS, type SelectAccountOptions, SessionStorage, type SetupAddressOptions, type SetupPasskeyOptions, type StorageKeys, type TokenInfo, TokenManager, type TokenSet, type TokenStorage, type UpgradeAccountOptions, type User, type VerifyAccountOptions, base64UrlDecode, base64UrlEncode, createDouveryAuth, createStorage, decodeJWT, generateCodeChallenge, generateCodeVerifier, generateNonce, generatePKCEPair, generateState, getTokenExpiration, isTokenExpired, verifyCodeChallenge };
|
package/dist/index.js
CHANGED
|
@@ -636,6 +636,171 @@ var DouveryAuthClient = class {
|
|
|
636
636
|
}
|
|
637
637
|
return tokens.accessToken;
|
|
638
638
|
}
|
|
639
|
+
// ============================================
|
|
640
|
+
// Navigation Methods
|
|
641
|
+
// ============================================
|
|
642
|
+
/** Redirect to select/switch account */
|
|
643
|
+
selectAccount(options = {}) {
|
|
644
|
+
const url = this.buildSelectAccountUrl(options);
|
|
645
|
+
this.navigate(url, options);
|
|
646
|
+
}
|
|
647
|
+
/** Build select-account URL without redirecting */
|
|
648
|
+
buildSelectAccountUrl(options = {}) {
|
|
649
|
+
const params = new URLSearchParams();
|
|
650
|
+
if (options.returnTo) params.set("continue", options.returnTo);
|
|
651
|
+
if (options.clientId) params.set("client_id", options.clientId);
|
|
652
|
+
if (options.loginHint) params.set("login_hint", options.loginHint);
|
|
653
|
+
return this.createAuthUrl("/select-account", params);
|
|
654
|
+
}
|
|
655
|
+
/** Redirect to add another account (multi-session) */
|
|
656
|
+
addAccount(options = {}) {
|
|
657
|
+
const url = this.buildAddAccountUrl(options);
|
|
658
|
+
this.navigate(url, options);
|
|
659
|
+
}
|
|
660
|
+
/** Build add-account URL without redirecting */
|
|
661
|
+
buildAddAccountUrl(options = {}) {
|
|
662
|
+
const params = new URLSearchParams({ add_account: "true" });
|
|
663
|
+
if (options.returnTo) params.set("continue", options.returnTo);
|
|
664
|
+
if (options.clientId) params.set("client_id", options.clientId);
|
|
665
|
+
if (options.loginHint) params.set("login_hint", options.loginHint);
|
|
666
|
+
return this.createAuthUrl("/login", params);
|
|
667
|
+
}
|
|
668
|
+
/** Redirect to register a new account */
|
|
669
|
+
register(options = {}) {
|
|
670
|
+
const url = this.buildRegisterUrl(options);
|
|
671
|
+
this.navigate(url, options);
|
|
672
|
+
}
|
|
673
|
+
/** Build register URL without redirecting */
|
|
674
|
+
buildRegisterUrl(options = {}) {
|
|
675
|
+
const params = new URLSearchParams();
|
|
676
|
+
if (options.returnTo) params.set("continue", options.returnTo);
|
|
677
|
+
if (options.clientId) params.set("client_id", options.clientId);
|
|
678
|
+
if (options.email) params.set("email", options.email);
|
|
679
|
+
if (options.firstName) params.set("first_name", options.firstName);
|
|
680
|
+
if (options.lastName) params.set("last_name", options.lastName);
|
|
681
|
+
if (options.uiLocales) params.set("ui_locales", options.uiLocales);
|
|
682
|
+
return this.createAuthUrl("/register", params);
|
|
683
|
+
}
|
|
684
|
+
/** Redirect to recover account (forgot password) */
|
|
685
|
+
recoverAccount(options = {}) {
|
|
686
|
+
const url = this.buildRecoverAccountUrl(options);
|
|
687
|
+
this.navigate(url, options);
|
|
688
|
+
}
|
|
689
|
+
/** Build recover-account URL without redirecting */
|
|
690
|
+
buildRecoverAccountUrl(options = {}) {
|
|
691
|
+
const params = new URLSearchParams();
|
|
692
|
+
if (options.returnTo) params.set("continue", options.returnTo);
|
|
693
|
+
if (options.clientId) params.set("client_id", options.clientId);
|
|
694
|
+
if (options.email) params.set("email", options.email);
|
|
695
|
+
return this.createAuthUrl("/recover-account", params);
|
|
696
|
+
}
|
|
697
|
+
/** Redirect to verify account (email verification) */
|
|
698
|
+
verifyAccount(options = {}) {
|
|
699
|
+
const url = this.buildVerifyAccountUrl(options);
|
|
700
|
+
this.navigate(url, options);
|
|
701
|
+
}
|
|
702
|
+
/** Build verify-account URL without redirecting */
|
|
703
|
+
buildVerifyAccountUrl(options = {}) {
|
|
704
|
+
const params = new URLSearchParams();
|
|
705
|
+
if (options.returnTo) params.set("continue", options.returnTo);
|
|
706
|
+
if (options.clientId) params.set("client_id", options.clientId);
|
|
707
|
+
if (options.email) params.set("email", options.email);
|
|
708
|
+
return this.createAuthUrl("/verify-account", params);
|
|
709
|
+
}
|
|
710
|
+
/** Redirect to upgrade account (guest â full account) */
|
|
711
|
+
upgradeAccount(options = {}) {
|
|
712
|
+
const url = this.buildUpgradeAccountUrl(options);
|
|
713
|
+
this.navigate(url, options);
|
|
714
|
+
}
|
|
715
|
+
/** Build upgrade-account URL without redirecting */
|
|
716
|
+
buildUpgradeAccountUrl(options = {}) {
|
|
717
|
+
const params = new URLSearchParams();
|
|
718
|
+
if (options.returnTo) params.set("continue", options.returnTo);
|
|
719
|
+
if (options.clientId) params.set("client_id", options.clientId);
|
|
720
|
+
if (options.scopes?.length) params.set("scope", options.scopes.join(" "));
|
|
721
|
+
return this.createAuthUrl("/upgrade-account", params);
|
|
722
|
+
}
|
|
723
|
+
/** Redirect to passkey setup */
|
|
724
|
+
setupPasskey(options = {}) {
|
|
725
|
+
const url = this.buildSetupPasskeyUrl(options);
|
|
726
|
+
this.navigate(url, options);
|
|
727
|
+
}
|
|
728
|
+
/** Build setup-passkey URL without redirecting */
|
|
729
|
+
buildSetupPasskeyUrl(options = {}) {
|
|
730
|
+
const params = new URLSearchParams();
|
|
731
|
+
if (options.returnTo) params.set("continue", options.returnTo);
|
|
732
|
+
if (options.clientId) params.set("client_id", options.clientId);
|
|
733
|
+
return this.createAuthUrl("/setup-passkey", params);
|
|
734
|
+
}
|
|
735
|
+
/** Redirect to address setup */
|
|
736
|
+
setupAddress(options = {}) {
|
|
737
|
+
const url = this.buildSetupAddressUrl(options);
|
|
738
|
+
this.navigate(url, options);
|
|
739
|
+
}
|
|
740
|
+
/** Build setup-address URL without redirecting */
|
|
741
|
+
buildSetupAddressUrl(options = {}) {
|
|
742
|
+
const params = new URLSearchParams();
|
|
743
|
+
if (options.returnTo) params.set("continue", options.returnTo);
|
|
744
|
+
if (options.clientId) params.set("client_id", options.clientId);
|
|
745
|
+
return this.createAuthUrl("/setup-address", params);
|
|
746
|
+
}
|
|
747
|
+
/** Build a login URL without redirecting (useful for links/buttons) */
|
|
748
|
+
buildLoginUrl(options = {}) {
|
|
749
|
+
const params = new URLSearchParams();
|
|
750
|
+
if (options.returnTo) params.set("continue", options.returnTo);
|
|
751
|
+
if (options.loginHint) params.set("email", options.loginHint);
|
|
752
|
+
if (options.prompt) params.set("prompt", options.prompt);
|
|
753
|
+
if (options.uiLocales) params.set("ui_locales", options.uiLocales);
|
|
754
|
+
return this.createAuthUrl("/login", params);
|
|
755
|
+
}
|
|
756
|
+
/** Build a logout URL without redirecting */
|
|
757
|
+
buildLogoutUrl(options = {}) {
|
|
758
|
+
const params = new URLSearchParams();
|
|
759
|
+
if (options.returnTo) params.set("continue", options.returnTo);
|
|
760
|
+
return this.createAuthUrl("/logout", params);
|
|
761
|
+
}
|
|
762
|
+
/** Revoke a token (access or refresh) */
|
|
763
|
+
async revokeToken(options = {}) {
|
|
764
|
+
this.log("Revoking token...");
|
|
765
|
+
const token = options.token ?? (await this.tokenManager.getTokens())?.accessToken;
|
|
766
|
+
if (!token) {
|
|
767
|
+
throw new AuthError("invalid_token", "No token to revoke");
|
|
768
|
+
}
|
|
769
|
+
const revokeUrl = `${this.config.issuer}/oauth/revoke`;
|
|
770
|
+
const body = new URLSearchParams({
|
|
771
|
+
token,
|
|
772
|
+
client_id: this.config.clientId
|
|
773
|
+
});
|
|
774
|
+
if (options.tokenTypeHint) {
|
|
775
|
+
body.set("token_type_hint", options.tokenTypeHint);
|
|
776
|
+
}
|
|
777
|
+
const response = await fetch(revokeUrl, {
|
|
778
|
+
method: "POST",
|
|
779
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
780
|
+
body
|
|
781
|
+
});
|
|
782
|
+
if (!response.ok) {
|
|
783
|
+
const error = await response.json().catch(() => ({}));
|
|
784
|
+
throw new AuthError(
|
|
785
|
+
error.error ?? "server_error",
|
|
786
|
+
error.error_description ?? "Token revocation failed"
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
this.log("Token revoked successfully");
|
|
790
|
+
}
|
|
791
|
+
/** Check if the user's session is expired */
|
|
792
|
+
isSessionExpired() {
|
|
793
|
+
if (!this.state.tokens) return true;
|
|
794
|
+
return isTokenExpired(this.state.tokens.accessToken);
|
|
795
|
+
}
|
|
796
|
+
/** Check if user needs email verification */
|
|
797
|
+
needsEmailVerification() {
|
|
798
|
+
return this.state.user?.emailVerified === false;
|
|
799
|
+
}
|
|
800
|
+
/** Check if user is a guest account */
|
|
801
|
+
isGuestAccount() {
|
|
802
|
+
return this.state.user?.accountType === "guest";
|
|
803
|
+
}
|
|
639
804
|
tokenSetToInfo(tokenSet) {
|
|
640
805
|
return {
|
|
641
806
|
accessToken: tokenSet.access_token,
|
|
@@ -646,6 +811,28 @@ var DouveryAuthClient = class {
|
|
|
646
811
|
scope: tokenSet.scope?.split(" ") ?? []
|
|
647
812
|
};
|
|
648
813
|
}
|
|
814
|
+
/** Build an AuthUrl object for a given path and params */
|
|
815
|
+
createAuthUrl(path, params) {
|
|
816
|
+
const query = params.toString();
|
|
817
|
+
const url = `${this.config.issuer}${path}${query ? `?${query}` : ""}`;
|
|
818
|
+
return {
|
|
819
|
+
url,
|
|
820
|
+
redirect: () => {
|
|
821
|
+
window.location.href = url;
|
|
822
|
+
},
|
|
823
|
+
open: () => {
|
|
824
|
+
return window.open(url, "_blank");
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
/** Navigate to an auth URL, respecting openInNewTab option */
|
|
829
|
+
navigate(authUrl, options) {
|
|
830
|
+
if (options.openInNewTab) {
|
|
831
|
+
authUrl.open();
|
|
832
|
+
} else {
|
|
833
|
+
authUrl.redirect();
|
|
834
|
+
}
|
|
835
|
+
}
|
|
649
836
|
async fetchUser(accessToken) {
|
|
650
837
|
const discovery = await this.getDiscovery();
|
|
651
838
|
const response = await fetch(discovery.userinfo_endpoint, {
|