@imtbl/auth 2.12.5 → 2.12.6-alpha.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/README.md +163 -0
- package/dist/browser/index.js +80 -28
- package/dist/node/index.cjs +99 -40
- package/dist/node/index.js +79 -27
- package/dist/types/index.d.ts +3 -1
- package/dist/types/login/standalone.d.ts +223 -0
- package/dist/types/logout/index.d.ts +27 -0
- package/dist/types/types.d.ts +32 -3
- package/package.json +6 -6
- package/src/Auth.test.ts +225 -0
- package/src/Auth.ts +31 -25
- package/src/index.ts +27 -0
- package/src/login/standalone.ts +906 -0
- package/src/logout/index.ts +52 -0
- package/src/types.ts +36 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imtbl/auth",
|
|
3
|
-
"version": "2.12.
|
|
3
|
+
"version": "2.12.6-alpha.1",
|
|
4
4
|
"description": "Authentication SDK for Immutable",
|
|
5
5
|
"author": "Immutable",
|
|
6
6
|
"bugs": "https://github.com/immutable/ts-immutable-sdk/issues",
|
|
@@ -25,18 +25,18 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@imtbl/generated-clients": "2.12.
|
|
29
|
-
"@imtbl/metrics": "2.12.
|
|
28
|
+
"@imtbl/generated-clients": "2.12.6-alpha.1",
|
|
29
|
+
"@imtbl/metrics": "2.12.6-alpha.1",
|
|
30
30
|
"localforage": "^1.10.0",
|
|
31
31
|
"oidc-client-ts": "3.4.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@swc/core": "^1.
|
|
34
|
+
"@swc/core": "^1.4.2",
|
|
35
35
|
"@swc/jest": "^0.2.37",
|
|
36
36
|
"@types/jest": "^29.5.12",
|
|
37
|
-
"@types/node": "^
|
|
37
|
+
"@types/node": "^22.10.7",
|
|
38
38
|
"@jest/test-sequencer": "^29.7.0",
|
|
39
|
-
"jest": "^29.
|
|
39
|
+
"jest": "^29.7.0",
|
|
40
40
|
"jest-environment-jsdom": "^29.4.3",
|
|
41
41
|
"ts-node": "^10.9.1",
|
|
42
42
|
"tsup": "^8.3.0",
|
package/src/Auth.test.ts
CHANGED
|
@@ -268,6 +268,231 @@ describe('Auth', () => {
|
|
|
268
268
|
});
|
|
269
269
|
});
|
|
270
270
|
|
|
271
|
+
describe('refreshTokenAndUpdatePromise', () => {
|
|
272
|
+
it('emits TOKEN_REFRESHED event when signinSilent succeeds', async () => {
|
|
273
|
+
const mockOidcUser = {
|
|
274
|
+
id_token: 'new-id',
|
|
275
|
+
access_token: 'new-access',
|
|
276
|
+
refresh_token: 'new-refresh',
|
|
277
|
+
expired: false,
|
|
278
|
+
profile: { sub: 'user-123', email: 'test@example.com', nickname: 'tester' },
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
(decodeJwtPayload as jest.Mock).mockReturnValue({
|
|
282
|
+
username: undefined,
|
|
283
|
+
passport: undefined,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const auth = Object.create(Auth.prototype) as Auth;
|
|
287
|
+
const mockEventEmitter = { emit: jest.fn() };
|
|
288
|
+
const mockUserManager = {
|
|
289
|
+
signinSilent: jest.fn().mockResolvedValue(mockOidcUser),
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
(auth as any).eventEmitter = mockEventEmitter;
|
|
293
|
+
(auth as any).userManager = mockUserManager;
|
|
294
|
+
(auth as any).refreshingPromise = null;
|
|
295
|
+
|
|
296
|
+
const user = await (auth as any).refreshTokenAndUpdatePromise();
|
|
297
|
+
|
|
298
|
+
expect(user).toBeDefined();
|
|
299
|
+
expect(user.accessToken).toBe('new-access');
|
|
300
|
+
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
301
|
+
AuthEvents.TOKEN_REFRESHED,
|
|
302
|
+
expect.objectContaining({
|
|
303
|
+
accessToken: 'new-access',
|
|
304
|
+
refreshToken: 'new-refresh',
|
|
305
|
+
}),
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('does not emit TOKEN_REFRESHED event when signinSilent returns null', async () => {
|
|
310
|
+
const auth = Object.create(Auth.prototype) as Auth;
|
|
311
|
+
const mockEventEmitter = { emit: jest.fn() };
|
|
312
|
+
const mockUserManager = {
|
|
313
|
+
signinSilent: jest.fn().mockResolvedValue(null),
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
(auth as any).eventEmitter = mockEventEmitter;
|
|
317
|
+
(auth as any).userManager = mockUserManager;
|
|
318
|
+
(auth as any).refreshingPromise = null;
|
|
319
|
+
|
|
320
|
+
const user = await (auth as any).refreshTokenAndUpdatePromise();
|
|
321
|
+
|
|
322
|
+
expect(user).toBeNull();
|
|
323
|
+
expect(mockEventEmitter.emit).not.toHaveBeenCalled();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('emits USER_REMOVED event for invalid_grant error', async () => {
|
|
327
|
+
const auth = Object.create(Auth.prototype) as Auth;
|
|
328
|
+
const mockEventEmitter = { emit: jest.fn() };
|
|
329
|
+
const mockUserManager = {
|
|
330
|
+
signinSilent: jest.fn().mockRejectedValue(
|
|
331
|
+
Object.assign(new Error('invalid_grant'), {
|
|
332
|
+
error: 'invalid_grant',
|
|
333
|
+
error_description: 'Unknown or invalid refresh token',
|
|
334
|
+
}),
|
|
335
|
+
),
|
|
336
|
+
removeUser: jest.fn().mockResolvedValue(undefined),
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// Make the error an instance of ErrorResponse
|
|
340
|
+
const { ErrorResponse } = jest.requireActual('oidc-client-ts');
|
|
341
|
+
const errorResponse = new ErrorResponse({
|
|
342
|
+
error: 'invalid_grant',
|
|
343
|
+
error_description: 'Unknown or invalid refresh token',
|
|
344
|
+
});
|
|
345
|
+
mockUserManager.signinSilent.mockRejectedValue(errorResponse);
|
|
346
|
+
|
|
347
|
+
(auth as any).eventEmitter = mockEventEmitter;
|
|
348
|
+
(auth as any).userManager = mockUserManager;
|
|
349
|
+
(auth as any).refreshingPromise = null;
|
|
350
|
+
|
|
351
|
+
await expect((auth as any).refreshTokenAndUpdatePromise()).rejects.toThrow();
|
|
352
|
+
|
|
353
|
+
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
354
|
+
AuthEvents.USER_REMOVED,
|
|
355
|
+
expect.objectContaining({
|
|
356
|
+
reason: 'refresh_failed',
|
|
357
|
+
}),
|
|
358
|
+
);
|
|
359
|
+
expect(mockUserManager.removeUser).toHaveBeenCalled();
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('emits USER_REMOVED event for login_required error', async () => {
|
|
363
|
+
const auth = Object.create(Auth.prototype) as Auth;
|
|
364
|
+
const mockEventEmitter = { emit: jest.fn() };
|
|
365
|
+
const mockUserManager = {
|
|
366
|
+
signinSilent: jest.fn(),
|
|
367
|
+
removeUser: jest.fn().mockResolvedValue(undefined),
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const { ErrorResponse } = jest.requireActual('oidc-client-ts');
|
|
371
|
+
const errorResponse = new ErrorResponse({
|
|
372
|
+
error: 'login_required',
|
|
373
|
+
error_description: 'User must re-authenticate',
|
|
374
|
+
});
|
|
375
|
+
mockUserManager.signinSilent.mockRejectedValue(errorResponse);
|
|
376
|
+
|
|
377
|
+
(auth as any).eventEmitter = mockEventEmitter;
|
|
378
|
+
(auth as any).userManager = mockUserManager;
|
|
379
|
+
(auth as any).refreshingPromise = null;
|
|
380
|
+
|
|
381
|
+
await expect((auth as any).refreshTokenAndUpdatePromise()).rejects.toThrow();
|
|
382
|
+
|
|
383
|
+
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
384
|
+
AuthEvents.USER_REMOVED,
|
|
385
|
+
expect.objectContaining({
|
|
386
|
+
reason: 'refresh_failed',
|
|
387
|
+
}),
|
|
388
|
+
);
|
|
389
|
+
expect(mockUserManager.removeUser).toHaveBeenCalled();
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('emits USER_REMOVED event for network errors', async () => {
|
|
393
|
+
const auth = Object.create(Auth.prototype) as Auth;
|
|
394
|
+
const mockEventEmitter = { emit: jest.fn() };
|
|
395
|
+
const mockUserManager = {
|
|
396
|
+
signinSilent: jest.fn().mockRejectedValue(new Error('Network error: Failed to fetch')),
|
|
397
|
+
removeUser: jest.fn().mockResolvedValue(undefined),
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
(auth as any).eventEmitter = mockEventEmitter;
|
|
401
|
+
(auth as any).userManager = mockUserManager;
|
|
402
|
+
(auth as any).refreshingPromise = null;
|
|
403
|
+
|
|
404
|
+
await expect((auth as any).refreshTokenAndUpdatePromise()).rejects.toThrow();
|
|
405
|
+
|
|
406
|
+
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
407
|
+
AuthEvents.USER_REMOVED,
|
|
408
|
+
expect.objectContaining({
|
|
409
|
+
reason: 'refresh_failed',
|
|
410
|
+
}),
|
|
411
|
+
);
|
|
412
|
+
expect(mockUserManager.removeUser).toHaveBeenCalled();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('emits USER_REMOVED event for server_error OAuth error', async () => {
|
|
416
|
+
const auth = Object.create(Auth.prototype) as Auth;
|
|
417
|
+
const mockEventEmitter = { emit: jest.fn() };
|
|
418
|
+
const mockUserManager = {
|
|
419
|
+
signinSilent: jest.fn(),
|
|
420
|
+
removeUser: jest.fn().mockResolvedValue(undefined),
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const { ErrorResponse } = jest.requireActual('oidc-client-ts');
|
|
424
|
+
const errorResponse = new ErrorResponse({
|
|
425
|
+
error: 'server_error',
|
|
426
|
+
error_description: 'Internal server error',
|
|
427
|
+
});
|
|
428
|
+
mockUserManager.signinSilent.mockRejectedValue(errorResponse);
|
|
429
|
+
|
|
430
|
+
(auth as any).eventEmitter = mockEventEmitter;
|
|
431
|
+
(auth as any).userManager = mockUserManager;
|
|
432
|
+
(auth as any).refreshingPromise = null;
|
|
433
|
+
|
|
434
|
+
await expect((auth as any).refreshTokenAndUpdatePromise()).rejects.toThrow();
|
|
435
|
+
|
|
436
|
+
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
437
|
+
AuthEvents.USER_REMOVED,
|
|
438
|
+
expect.objectContaining({
|
|
439
|
+
reason: 'refresh_failed',
|
|
440
|
+
}),
|
|
441
|
+
);
|
|
442
|
+
expect(mockUserManager.removeUser).toHaveBeenCalled();
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('emits USER_REMOVED event for unknown errors (safer default)', async () => {
|
|
446
|
+
const auth = Object.create(Auth.prototype) as Auth;
|
|
447
|
+
const mockEventEmitter = { emit: jest.fn() };
|
|
448
|
+
const mockUserManager = {
|
|
449
|
+
signinSilent: jest.fn().mockRejectedValue(new Error('Some unknown error')),
|
|
450
|
+
removeUser: jest.fn().mockResolvedValue(undefined),
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
(auth as any).eventEmitter = mockEventEmitter;
|
|
454
|
+
(auth as any).userManager = mockUserManager;
|
|
455
|
+
(auth as any).refreshingPromise = null;
|
|
456
|
+
|
|
457
|
+
await expect((auth as any).refreshTokenAndUpdatePromise()).rejects.toThrow();
|
|
458
|
+
|
|
459
|
+
// Unknown errors should remove user (safer default)
|
|
460
|
+
expect(mockEventEmitter.emit).toHaveBeenCalledWith(
|
|
461
|
+
AuthEvents.USER_REMOVED,
|
|
462
|
+
expect.objectContaining({
|
|
463
|
+
reason: 'refresh_failed',
|
|
464
|
+
}),
|
|
465
|
+
);
|
|
466
|
+
expect(mockUserManager.removeUser).toHaveBeenCalled();
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('does not emit USER_REMOVED event for ErrorTimeout', async () => {
|
|
470
|
+
const auth = Object.create(Auth.prototype) as Auth;
|
|
471
|
+
const mockEventEmitter = { emit: jest.fn() };
|
|
472
|
+
const mockUserManager = {
|
|
473
|
+
signinSilent: jest.fn(),
|
|
474
|
+
removeUser: jest.fn().mockResolvedValue(undefined),
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// Mock ErrorTimeout
|
|
478
|
+
const { ErrorTimeout } = jest.requireActual('oidc-client-ts');
|
|
479
|
+
const timeoutError = new ErrorTimeout('Silent sign-in timed out');
|
|
480
|
+
mockUserManager.signinSilent.mockRejectedValue(timeoutError);
|
|
481
|
+
|
|
482
|
+
(auth as any).eventEmitter = mockEventEmitter;
|
|
483
|
+
(auth as any).userManager = mockUserManager;
|
|
484
|
+
(auth as any).refreshingPromise = null;
|
|
485
|
+
|
|
486
|
+
await expect((auth as any).refreshTokenAndUpdatePromise()).rejects.toThrow();
|
|
487
|
+
|
|
488
|
+
expect(mockEventEmitter.emit).not.toHaveBeenCalledWith(
|
|
489
|
+
AuthEvents.USER_REMOVED,
|
|
490
|
+
expect.anything(),
|
|
491
|
+
);
|
|
492
|
+
expect(mockUserManager.removeUser).not.toHaveBeenCalled();
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
271
496
|
describe('loginWithPopup', () => {
|
|
272
497
|
let mockUserManager: any;
|
|
273
498
|
let originalCryptoRandomUUID: any;
|
package/src/Auth.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
IdTokenPayload,
|
|
31
31
|
isUserZkEvm,
|
|
32
32
|
EvmChain,
|
|
33
|
+
ChainAddress,
|
|
33
34
|
} from './types';
|
|
34
35
|
import EmbeddedLoginPrompt from './login/embeddedLoginPrompt';
|
|
35
36
|
import TypedEventEmitter from './utils/typedEventEmitter';
|
|
@@ -41,6 +42,7 @@ import logger from './utils/logger';
|
|
|
41
42
|
import { isAccessTokenExpiredOrExpiring } from './utils/token';
|
|
42
43
|
import LoginPopupOverlay from './overlay/loginPopupOverlay';
|
|
43
44
|
import { LocalForageAsyncStorage } from './storage/LocalForageAsyncStorage';
|
|
45
|
+
import { buildLogoutUrl } from './logout';
|
|
44
46
|
|
|
45
47
|
const formUrlEncodedHeaders = {
|
|
46
48
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
@@ -75,13 +77,12 @@ const extractTokenErrorMessage = (
|
|
|
75
77
|
return `Token request failed with status ${status}`;
|
|
76
78
|
};
|
|
77
79
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
const toChainAddress = (ethAddress: string, userAdminAddress: string): ChainAddress => ({
|
|
81
|
+
ethAddress: ethAddress as `0x${string}`,
|
|
82
|
+
userAdminAddress: userAdminAddress as `0x${string}`,
|
|
83
|
+
});
|
|
81
84
|
|
|
82
|
-
const
|
|
83
|
-
crossSdkBridgeEnabled ? crossSdkBridgeLogoutEndpoint : logoutEndpoint
|
|
84
|
-
);
|
|
85
|
+
const authorizeEndpoint = '/authorize';
|
|
85
86
|
|
|
86
87
|
const getAuthConfiguration = (config: IAuthConfiguration): UserManagerSettings => {
|
|
87
88
|
const { authenticationDomain, oidcConfiguration } = config;
|
|
@@ -96,14 +97,12 @@ const getAuthConfiguration = (config: IAuthConfiguration): UserManagerSettings =
|
|
|
96
97
|
}
|
|
97
98
|
const userStore = new WebStorageStateStore({ store });
|
|
98
99
|
|
|
99
|
-
const endSessionEndpoint =
|
|
100
|
-
|
|
101
|
-
authenticationDomain
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
endSessionEndpoint.searchParams.set('returnTo', oidcConfiguration.logoutRedirectUri);
|
|
106
|
-
}
|
|
100
|
+
const endSessionEndpoint = buildLogoutUrl({
|
|
101
|
+
clientId: oidcConfiguration.clientId,
|
|
102
|
+
authenticationDomain,
|
|
103
|
+
logoutRedirectUri: oidcConfiguration.logoutRedirectUri,
|
|
104
|
+
crossSdkBridgeEnabled: config.crossSdkBridgeEnabled,
|
|
105
|
+
});
|
|
107
106
|
|
|
108
107
|
return {
|
|
109
108
|
authority: authenticationDomain,
|
|
@@ -114,7 +113,7 @@ const getAuthConfiguration = (config: IAuthConfiguration): UserManagerSettings =
|
|
|
114
113
|
authorization_endpoint: `${authenticationDomain}/authorize`,
|
|
115
114
|
token_endpoint: `${authenticationDomain}/oauth/token`,
|
|
116
115
|
userinfo_endpoint: `${authenticationDomain}/userinfo`,
|
|
117
|
-
end_session_endpoint: endSessionEndpoint
|
|
116
|
+
end_session_endpoint: endSessionEndpoint,
|
|
118
117
|
revocation_endpoint: `${authenticationDomain}/oauth/revoke`,
|
|
119
118
|
},
|
|
120
119
|
automaticSilentRenew: false,
|
|
@@ -573,20 +572,14 @@ export class Auth {
|
|
|
573
572
|
};
|
|
574
573
|
|
|
575
574
|
if (passport?.zkevm_eth_address && passport?.zkevm_user_admin_address) {
|
|
576
|
-
user.zkEvm =
|
|
577
|
-
ethAddress: passport.zkevm_eth_address,
|
|
578
|
-
userAdminAddress: passport.zkevm_user_admin_address,
|
|
579
|
-
};
|
|
575
|
+
user.zkEvm = toChainAddress(passport.zkevm_eth_address, passport.zkevm_user_admin_address);
|
|
580
576
|
}
|
|
581
577
|
|
|
582
578
|
const chains = Object.values(EvmChain).filter((chain) => chain !== EvmChain.ZKEVM);
|
|
583
579
|
for (const chain of chains) {
|
|
584
580
|
const chainMetadata = passport?.[chain as Exclude<EvmChain, EvmChain.ZKEVM>];
|
|
585
581
|
if (chainMetadata?.eth_address && chainMetadata?.user_admin_address) {
|
|
586
|
-
user[chain] =
|
|
587
|
-
ethAddress: chainMetadata.eth_address,
|
|
588
|
-
userAdminAddress: chainMetadata.user_admin_address,
|
|
589
|
-
};
|
|
582
|
+
user[chain] = toChainAddress(chainMetadata.eth_address, chainMetadata.user_admin_address);
|
|
590
583
|
}
|
|
591
584
|
}
|
|
592
585
|
|
|
@@ -779,15 +772,21 @@ export class Auth {
|
|
|
779
772
|
try {
|
|
780
773
|
const newOidcUser = await this.userManager.signinSilent();
|
|
781
774
|
if (newOidcUser) {
|
|
782
|
-
|
|
775
|
+
const user = Auth.mapOidcUserToDomainModel(newOidcUser);
|
|
776
|
+
// Emit TOKEN_REFRESHED event so consumers (e.g., auth-next-client) can sync
|
|
777
|
+
// the new tokens to their session. This is critical for refresh token
|
|
778
|
+
// rotation - without this, the server-side session may have stale tokens.
|
|
779
|
+
this.eventEmitter.emit(AuthEvents.TOKEN_REFRESHED, user);
|
|
780
|
+
resolve(user);
|
|
783
781
|
return;
|
|
784
782
|
}
|
|
785
783
|
resolve(null);
|
|
786
784
|
} catch (err) {
|
|
787
785
|
let passportErrorType = PassportErrorType.AUTHENTICATION_ERROR;
|
|
788
786
|
let errorMessage = 'Failed to refresh token';
|
|
787
|
+
// Default to REMOVING user - safer to log out on unknown errors
|
|
788
|
+
// Only keep user logged in for explicitly known transient errors
|
|
789
789
|
let removeUser = true;
|
|
790
|
-
|
|
791
790
|
if (err instanceof ErrorTimeout) {
|
|
792
791
|
passportErrorType = PassportErrorType.SILENT_LOGIN_ERROR;
|
|
793
792
|
errorMessage = `${errorMessage}: ${err.message}`;
|
|
@@ -802,6 +801,13 @@ export class Auth {
|
|
|
802
801
|
}
|
|
803
802
|
|
|
804
803
|
if (removeUser) {
|
|
804
|
+
// Emit USER_REMOVED event BEFORE removing user so consumers can react
|
|
805
|
+
// (e.g., auth-next-client can clear the NextAuth session)
|
|
806
|
+
this.eventEmitter.emit(AuthEvents.USER_REMOVED, {
|
|
807
|
+
reason: 'refresh_failed',
|
|
808
|
+
error: errorMessage,
|
|
809
|
+
});
|
|
810
|
+
|
|
805
811
|
try {
|
|
806
812
|
await this.userManager.removeUser();
|
|
807
813
|
} catch (removeUserError) {
|
package/src/index.ts
CHANGED
|
@@ -19,9 +19,11 @@ export type {
|
|
|
19
19
|
PassportMetadata,
|
|
20
20
|
PassportChainMetadata,
|
|
21
21
|
ChainAddress,
|
|
22
|
+
ZkEvmInfo,
|
|
22
23
|
IdTokenPayload,
|
|
23
24
|
PKCEData,
|
|
24
25
|
AuthEventMap,
|
|
26
|
+
UserRemovedReason,
|
|
25
27
|
} from './types';
|
|
26
28
|
export {
|
|
27
29
|
isUserZkEvm,
|
|
@@ -40,3 +42,28 @@ export {
|
|
|
40
42
|
} from './errors';
|
|
41
43
|
|
|
42
44
|
export { decodeJwtPayload } from './utils/jwt';
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Standalone Login Functions (stateless, for use with NextAuth or similar)
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
loginWithPopup,
|
|
52
|
+
loginWithEmbedded,
|
|
53
|
+
loginWithRedirect,
|
|
54
|
+
handleLoginCallback,
|
|
55
|
+
type LoginConfig,
|
|
56
|
+
type TokenResponse,
|
|
57
|
+
type StandaloneLoginOptions,
|
|
58
|
+
} from './login/standalone';
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Standalone Logout Functions (stateless, for use with NextAuth or similar)
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
logoutWithRedirect,
|
|
66
|
+
logoutSilent,
|
|
67
|
+
buildLogoutUrl,
|
|
68
|
+
type LogoutConfig,
|
|
69
|
+
} from './login/standalone';
|