@axa-fr/oidc-client 7.27.16 → 7.27.18
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 +35 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +245 -205
- package/dist/index.umd.cjs +2 -2
- package/dist/login.d.ts.map +1 -1
- package/dist/login.spec.d.ts +2 -0
- package/dist/login.spec.d.ts.map +1 -0
- package/dist/logout.d.ts +14 -0
- package/dist/logout.d.ts.map +1 -1
- package/dist/oidc.d.ts +20 -0
- package/dist/oidc.d.ts.map +1 -1
- package/dist/oidcClient.d.ts +20 -0
- package/dist/oidcClient.d.ts.map +1 -1
- package/dist/oidcStateError.d.ts +33 -0
- package/dist/oidcStateError.d.ts.map +1 -0
- package/dist/oidcStateError.spec.d.ts +2 -0
- package/dist/oidcStateError.spec.d.ts.map +1 -0
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/login.spec.ts +151 -0
- package/src/login.ts +23 -2
- package/src/logout.spec.ts +208 -1
- package/src/logout.ts +149 -80
- package/src/oidc.ts +30 -1
- package/src/oidcClient.ts +26 -0
- package/src/oidcStateError.spec.ts +33 -0
- package/src/oidcStateError.ts +50 -0
- package/src/renewTokens.ts +13 -0
- package/src/version.ts +1 -1
package/src/oidc.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { tryKeepSessionAsync } from './keepSession';
|
|
13
13
|
import { ILOidcLocation, OidcLocation } from './location';
|
|
14
14
|
import { defaultLoginAsync, loginCallbackAsync } from './login.js';
|
|
15
|
-
import { destroyAsync, logoutAsync } from './logout.js';
|
|
15
|
+
import { clearSessionAsync, destroyAsync, logoutAsync } from './logout.js';
|
|
16
16
|
import { TokenRenewMode, Tokens } from './parseTokens.js';
|
|
17
17
|
import { autoRenewTokens, renewTokensAndStartTimerAsync } from './renewTokens.js';
|
|
18
18
|
import { fetchFromIssuer } from './requests.js';
|
|
@@ -99,6 +99,14 @@ export class Oidc {
|
|
|
99
99
|
public checkSessionIFrame: CheckSessionIFrame;
|
|
100
100
|
public getFetch: () => Fetch;
|
|
101
101
|
public location: ILOidcLocation;
|
|
102
|
+
/**
|
|
103
|
+
* `true` while {@link logoutAsync} is executing or has scheduled a
|
|
104
|
+
* navigation to the identity provider's end-session endpoint that has not
|
|
105
|
+
* yet committed. Consumers (UI guards, silent-renew handlers, 401 retry
|
|
106
|
+
* interceptors, …) should check this flag and skip starting a new auth
|
|
107
|
+
* flow when it is set, even if `tokens` is null.
|
|
108
|
+
*/
|
|
109
|
+
public isLoggingOut = false;
|
|
102
110
|
constructor(
|
|
103
111
|
configuration: OidcConfiguration,
|
|
104
112
|
configurationName = 'default',
|
|
@@ -477,6 +485,27 @@ Please checkout that you are using OIDC hook inside a <OidcProvider configuratio
|
|
|
477
485
|
return await destroyAsync(this)(status);
|
|
478
486
|
}
|
|
479
487
|
|
|
488
|
+
/**
|
|
489
|
+
* Drops the local OIDC session (tokens, user info, service-worker storage)
|
|
490
|
+
* and broadcasts `logout_from_same_tab`, without contacting the identity
|
|
491
|
+
* provider's `end_session_endpoint` and without revoking tokens.
|
|
492
|
+
*
|
|
493
|
+
* Use this for SPA-only logouts, service-worker-only flows, or
|
|
494
|
+
* error-recovery paths where a full IdP logout is not needed or not
|
|
495
|
+
* desirable. For a standard OIDC RP-initiated logout use
|
|
496
|
+
* {@link logoutAsync} instead.
|
|
497
|
+
*/
|
|
498
|
+
clearSessionPromise: Promise<void> = null;
|
|
499
|
+
async clearSessionAsync(): Promise<void> {
|
|
500
|
+
if (this.clearSessionPromise) {
|
|
501
|
+
return this.clearSessionPromise;
|
|
502
|
+
}
|
|
503
|
+
this.clearSessionPromise = clearSessionAsync(this, oidcDatabase)();
|
|
504
|
+
return this.clearSessionPromise.finally(() => {
|
|
505
|
+
this.clearSessionPromise = null;
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
480
509
|
async logoutSameTabAsync(clientId: string, sub: any) {
|
|
481
510
|
// @ts-ignore
|
|
482
511
|
if (
|
package/src/oidcClient.ts
CHANGED
|
@@ -84,6 +84,32 @@ export class OidcClient {
|
|
|
84
84
|
return this._oidc.logoutAsync(callbackPathOrUrl, extras);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Drops the local OIDC session (tokens, user info, service-worker storage)
|
|
89
|
+
* and notifies same-tab listeners via the `logout_from_same_tab` event,
|
|
90
|
+
* without contacting the identity provider's `end_session_endpoint` and
|
|
91
|
+
* without revoking tokens.
|
|
92
|
+
*
|
|
93
|
+
* Use this for SPA-only logouts, service-worker-only flows, or
|
|
94
|
+
* error-recovery paths. For a standard OIDC RP-initiated logout (with
|
|
95
|
+
* token revocation and navigation to the IdP's end-session endpoint) use
|
|
96
|
+
* {@link logoutAsync} instead.
|
|
97
|
+
*/
|
|
98
|
+
clearSessionAsync(): Promise<void> {
|
|
99
|
+
return this._oidc.clearSessionAsync();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* `true` while a logout flow is in progress: between the moment
|
|
104
|
+
* {@link logoutAsync} starts and the moment the browser navigates away to
|
|
105
|
+
* the identity provider's end-session endpoint. UI guards and silent-renew
|
|
106
|
+
* handlers should check this flag to avoid kicking off a new auth flow
|
|
107
|
+
* during that window.
|
|
108
|
+
*/
|
|
109
|
+
get isLoggingOut(): boolean {
|
|
110
|
+
return this._oidc.isLoggingOut === true;
|
|
111
|
+
}
|
|
112
|
+
|
|
87
113
|
silentLoginCallbackAsync(): Promise<void> {
|
|
88
114
|
return this._oidc.silentLoginCallbackAsync();
|
|
89
115
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { isOidcStateError, OidcStateError, OidcStateErrorCode } from './oidcStateError';
|
|
4
|
+
|
|
5
|
+
describe('OidcStateError', () => {
|
|
6
|
+
it('exposes the well-known state error codes', () => {
|
|
7
|
+
expect(OidcStateErrorCode.STATE_MISSING).toBe('STATE_MISSING');
|
|
8
|
+
expect(OidcStateErrorCode.STATE_MISMATCH).toBe('STATE_MISMATCH');
|
|
9
|
+
expect(OidcStateErrorCode.NONCE_MISSING).toBe('NONCE_MISSING');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('is an Error subclass with name "OidcStateError"', () => {
|
|
13
|
+
const err = new OidcStateError(OidcStateErrorCode.STATE_MISSING, 'missing');
|
|
14
|
+
expect(err).toBeInstanceOf(Error);
|
|
15
|
+
expect(err).toBeInstanceOf(OidcStateError);
|
|
16
|
+
expect(err.name).toBe('OidcStateError');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('preserves the code and message passed to the constructor', () => {
|
|
20
|
+
const err = new OidcStateError(OidcStateErrorCode.STATE_MISMATCH, 'mismatch happened');
|
|
21
|
+
expect(err.code).toBe('STATE_MISMATCH');
|
|
22
|
+
expect(err.message).toBe('mismatch happened');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('is detectable via isOidcStateError', () => {
|
|
26
|
+
const err = new OidcStateError(OidcStateErrorCode.NONCE_MISSING, 'nonce missing');
|
|
27
|
+
expect(isOidcStateError(err)).toBe(true);
|
|
28
|
+
expect(isOidcStateError(new Error('boom'))).toBe(false);
|
|
29
|
+
expect(isOidcStateError(null)).toBe(false);
|
|
30
|
+
expect(isOidcStateError(undefined)).toBe(false);
|
|
31
|
+
expect(isOidcStateError({ code: 'STATE_MISSING' })).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable, machine-readable codes for OIDC state / nonce failures occurring
|
|
3
|
+
* between the authorization redirect and the callback handling.
|
|
4
|
+
*
|
|
5
|
+
* These codes let consumers react to specific failure modes without having to
|
|
6
|
+
* pattern-match against error message strings.
|
|
7
|
+
*/
|
|
8
|
+
export const OidcStateErrorCode = {
|
|
9
|
+
/** No state was found in storage when handling the callback. */
|
|
10
|
+
STATE_MISSING: 'STATE_MISSING',
|
|
11
|
+
/** The state returned by the server does not match the stored one. */
|
|
12
|
+
STATE_MISMATCH: 'STATE_MISMATCH',
|
|
13
|
+
/** No nonce was found in storage when handling the callback / renewal. */
|
|
14
|
+
NONCE_MISSING: 'NONCE_MISSING',
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
// Companion type that mirrors the const above. This is the standard TS
|
|
18
|
+
// "string-enum-like" pattern; we intentionally reuse the same name so that
|
|
19
|
+
// `OidcStateErrorCode` works as both a value namespace and a type for
|
|
20
|
+
// consumers.
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
|
22
|
+
export type OidcStateErrorCode = (typeof OidcStateErrorCode)[keyof typeof OidcStateErrorCode];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Typed error thrown when the OIDC login state or nonce is missing,
|
|
26
|
+
* corrupted, or does not match the value returned by the authorization server.
|
|
27
|
+
*
|
|
28
|
+
* Consumers can use `instanceof OidcStateError` and inspect `code` instead of
|
|
29
|
+
* relying on the (unstable) error message text.
|
|
30
|
+
*/
|
|
31
|
+
export class OidcStateError extends Error {
|
|
32
|
+
readonly code: OidcStateErrorCode;
|
|
33
|
+
|
|
34
|
+
constructor(code: OidcStateErrorCode, message: string) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = 'OidcStateError';
|
|
37
|
+
this.code = code;
|
|
38
|
+
|
|
39
|
+
// Keep prototype chain intact when transpiled to ES5.
|
|
40
|
+
Object.setPrototypeOf(this, OidcStateError.prototype);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Type guard for {@link OidcStateError}. Useful in callers that want to react
|
|
46
|
+
* specifically to state/nonce failures.
|
|
47
|
+
*/
|
|
48
|
+
export const isOidcStateError = (value: unknown): value is OidcStateError => {
|
|
49
|
+
return value instanceof OidcStateError;
|
|
50
|
+
};
|
package/src/renewTokens.ts
CHANGED
|
@@ -446,6 +446,19 @@ const synchroniseTokensAsync =
|
|
|
446
446
|
);
|
|
447
447
|
|
|
448
448
|
if (tokenResponse.success) {
|
|
449
|
+
// Guard against a missing/corrupted nonce reaching id_token validation.
|
|
450
|
+
// Without this guard, accessing `nonce.nonce` would throw a TypeError
|
|
451
|
+
// when the underlying storage has been cleared (private mode, manual
|
|
452
|
+
// clearing, browser eviction). We prefer a defined SESSION_LOST result
|
|
453
|
+
// so silent renew stays non-throwing for consumers.
|
|
454
|
+
// See https://github.com/AxaFrance/oidc-client/issues/1678
|
|
455
|
+
if (!nonce || !nonce.nonce) {
|
|
456
|
+
updateTokens(null);
|
|
457
|
+
oidc.publishEvent(eventNames.refreshTokensAsync_error, {
|
|
458
|
+
message: 'refresh token: nonce missing from storage',
|
|
459
|
+
});
|
|
460
|
+
return { tokens: null, status: 'SESSION_LOST' };
|
|
461
|
+
}
|
|
449
462
|
const { isValid, reason } = isTokensOidcValid(
|
|
450
463
|
tokenResponse.data,
|
|
451
464
|
nonce.nonce,
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '7.27.
|
|
1
|
+
export default '7.27.18';
|