@svadmin/sso 0.11.0 → 0.11.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.
- package/dist/auth-provider.d.ts +2 -0
- package/dist/auth-provider.js +57 -19
- package/dist/session-manager.d.ts +1 -0
- package/dist/session-manager.js +16 -1
- package/package.json +3 -3
package/dist/auth-provider.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface SSOConfig {
|
|
|
17
17
|
scopes?: string[];
|
|
18
18
|
/** Where to redirect after logout */
|
|
19
19
|
postLogoutRedirectUri?: string;
|
|
20
|
+
/** Optional public RP-initiated logout endpoint override. */
|
|
21
|
+
endSessionEndpoint?: string;
|
|
20
22
|
/** Token storage backend. Default: 'local' (localStorage) */
|
|
21
23
|
storage?: 'local' | 'session' | TokenStorage;
|
|
22
24
|
/** Storage namespace. Defaults to an issuer/client-specific key. */
|
package/dist/auth-provider.js
CHANGED
|
@@ -8,6 +8,35 @@ import { createAuthenticatedFetch as buildAuthenticatedFetch } from './authentic
|
|
|
8
8
|
import { createSSOAuthNetworkError, createSSOAuthResponseError, isTerminalSessionError, SSOAuthError, } from './errors.js';
|
|
9
9
|
import { createSessionManager, } from './session-manager.js';
|
|
10
10
|
const DEFAULT_STORAGE_KEY = 'svadmin_sso';
|
|
11
|
+
const REQUIRED_OIDC_ENDPOINTS = [
|
|
12
|
+
'authorization_endpoint',
|
|
13
|
+
'token_endpoint',
|
|
14
|
+
'userinfo_endpoint',
|
|
15
|
+
];
|
|
16
|
+
function readOIDCConfig(body) {
|
|
17
|
+
const discovery = typeof body === 'object' && body !== null && !Array.isArray(body)
|
|
18
|
+
? body
|
|
19
|
+
: {};
|
|
20
|
+
const missingEndpoints = REQUIRED_OIDC_ENDPOINTS.filter((endpoint) => {
|
|
21
|
+
const value = discovery[endpoint];
|
|
22
|
+
return typeof value !== 'string' || value.length === 0;
|
|
23
|
+
});
|
|
24
|
+
if (missingEndpoints.length > 0) {
|
|
25
|
+
throw new SSOAuthError(`OIDC discovery returned incomplete endpoints: missing ${missingEndpoints.join(', ')}`, 502, {
|
|
26
|
+
code: 'invalid_discovery_document',
|
|
27
|
+
body,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
authorization_endpoint: discovery.authorization_endpoint,
|
|
32
|
+
token_endpoint: discovery.token_endpoint,
|
|
33
|
+
userinfo_endpoint: discovery.userinfo_endpoint,
|
|
34
|
+
...(typeof discovery.end_session_endpoint === 'string'
|
|
35
|
+
&& discovery.end_session_endpoint.length > 0
|
|
36
|
+
? { end_session_endpoint: discovery.end_session_endpoint }
|
|
37
|
+
: {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
11
40
|
function decodeJwtPayload(token) {
|
|
12
41
|
if (!token)
|
|
13
42
|
return null;
|
|
@@ -181,7 +210,10 @@ export function createSSOAuthProvider(config) {
|
|
|
181
210
|
if (oidcConfig)
|
|
182
211
|
return oidcConfig;
|
|
183
212
|
if (config.manualEndpoints) {
|
|
184
|
-
oidcConfig =
|
|
213
|
+
oidcConfig = {
|
|
214
|
+
...config.manualEndpoints,
|
|
215
|
+
...(config.endSessionEndpoint ? { end_session_endpoint: config.endSessionEndpoint } : {}),
|
|
216
|
+
};
|
|
185
217
|
return oidcConfig;
|
|
186
218
|
}
|
|
187
219
|
const url = `${config.issuer.replace(/\/$/, '')}/.well-known/openid-configuration`;
|
|
@@ -205,15 +237,10 @@ export function createSSOAuthProvider(config) {
|
|
|
205
237
|
cause: error,
|
|
206
238
|
});
|
|
207
239
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
code: 'invalid_discovery_document',
|
|
213
|
-
body: discovered,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
oidcConfig = discovered;
|
|
240
|
+
const discoveredConfig = readOIDCConfig(discovered);
|
|
241
|
+
oidcConfig = config.endSessionEndpoint
|
|
242
|
+
? { ...discoveredConfig, end_session_endpoint: config.endSessionEndpoint }
|
|
243
|
+
: discoveredConfig;
|
|
217
244
|
return oidcConfig;
|
|
218
245
|
}
|
|
219
246
|
async function requestToken(endpoint, body, fallbackMessage) {
|
|
@@ -387,14 +414,16 @@ export function createSSOAuthProvider(config) {
|
|
|
387
414
|
}
|
|
388
415
|
try {
|
|
389
416
|
const endpoints = await discover();
|
|
390
|
-
if (typeof window !== 'undefined' && endpoints.end_session_endpoint
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
417
|
+
if (typeof window !== 'undefined' && endpoints.end_session_endpoint) {
|
|
418
|
+
const logoutUrl = new URL(endpoints.end_session_endpoint, window.location.href);
|
|
419
|
+
logoutUrl.searchParams.set('client_id', config.clientId);
|
|
420
|
+
logoutUrl.searchParams.delete('id_token_hint');
|
|
421
|
+
if (session?.id_token)
|
|
422
|
+
logoutUrl.searchParams.set('id_token_hint', session.id_token);
|
|
423
|
+
if (config.postLogoutRedirectUri) {
|
|
424
|
+
logoutUrl.searchParams.set('post_logout_redirect_uri', config.postLogoutRedirectUri);
|
|
425
|
+
}
|
|
426
|
+
window.location.href = logoutUrl.href;
|
|
398
427
|
return { success: true };
|
|
399
428
|
}
|
|
400
429
|
}
|
|
@@ -407,7 +436,16 @@ export function createSSOAuthProvider(config) {
|
|
|
407
436
|
};
|
|
408
437
|
},
|
|
409
438
|
async check() {
|
|
410
|
-
|
|
439
|
+
let existingSession = sessions.getSession();
|
|
440
|
+
if (!existingSession) {
|
|
441
|
+
try {
|
|
442
|
+
await sessions.clearInvalidSession();
|
|
443
|
+
existingSession = sessions.getSession();
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
return { authenticated: false, error: getErrorResult(error) };
|
|
447
|
+
}
|
|
448
|
+
}
|
|
411
449
|
if (typeof window === 'undefined') {
|
|
412
450
|
const now = Math.floor(Date.now() / 1000);
|
|
413
451
|
return existingSession && (existingSession.expires_at === undefined || existingSession.expires_at > now)
|
|
@@ -31,6 +31,7 @@ export interface TokenStorageLike {
|
|
|
31
31
|
}
|
|
32
32
|
export interface SessionManager {
|
|
33
33
|
getSession: () => SSOSession | null;
|
|
34
|
+
clearInvalidSession: () => Promise<void>;
|
|
34
35
|
getAuthGeneration: () => number;
|
|
35
36
|
saveSession: (session: SSOSession, event?: AuthStateChangeEvent) => void;
|
|
36
37
|
clearSession: () => void;
|
package/dist/session-manager.js
CHANGED
|
@@ -306,6 +306,17 @@ export function createSessionManager(options) {
|
|
|
306
306
|
});
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
|
+
function clearInvalidSession() {
|
|
310
|
+
const initialSnapshot = getSessionSnapshot();
|
|
311
|
+
if (initialSnapshot.raw === null || initialSnapshot.session)
|
|
312
|
+
return Promise.resolve();
|
|
313
|
+
return runAuthMutation(async () => {
|
|
314
|
+
const currentRaw = locallySignedOut ? null : options.storage.getItem(keys.tokens);
|
|
315
|
+
if (currentRaw === null || parseSession(currentRaw))
|
|
316
|
+
return;
|
|
317
|
+
clearTokenSession();
|
|
318
|
+
});
|
|
319
|
+
}
|
|
309
320
|
function resolveRefreshRace(expectedRaw) {
|
|
310
321
|
const currentRaw = locallySignedOut ? null : options.storage.getItem(keys.tokens);
|
|
311
322
|
if (currentRaw === expectedRaw)
|
|
@@ -432,8 +443,11 @@ export function createSessionManager(options) {
|
|
|
432
443
|
}
|
|
433
444
|
async function getAccessToken(accessOptions = {}) {
|
|
434
445
|
const snapshot = getSessionSnapshot();
|
|
435
|
-
if (!snapshot.raw || !snapshot.session)
|
|
446
|
+
if (!snapshot.raw || !snapshot.session) {
|
|
447
|
+
if (snapshot.raw)
|
|
448
|
+
await clearInvalidSession();
|
|
436
449
|
return null;
|
|
450
|
+
}
|
|
437
451
|
const { raw: initialRaw, session } = snapshot;
|
|
438
452
|
const minValiditySeconds = Math.max(0, accessOptions.minValiditySeconds ?? 0);
|
|
439
453
|
const now = Math.floor(Date.now() / 1000);
|
|
@@ -532,6 +546,7 @@ export function createSessionManager(options) {
|
|
|
532
546
|
scheduleRefresh(parseSession(lastObservedRaw));
|
|
533
547
|
return {
|
|
534
548
|
getSession,
|
|
549
|
+
clearInvalidSession,
|
|
535
550
|
getAuthGeneration: () => authGeneration,
|
|
536
551
|
saveSession,
|
|
537
552
|
clearSession,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/sso",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OIDC/OAuth2 SSO AuthProvider plugin for svadmin — supports Okta, Azure AD, Cognito, Keycloak, Google",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"dist"
|
|
33
33
|
],
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@svadmin/core": "^0.32.
|
|
35
|
+
"@svadmin/core": "^0.32.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"typescript": "^
|
|
38
|
+
"typescript": "^6.0.3"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "bun ../../scripts/build-node-package.ts"
|