@svadmin/sso 0.8.4 → 0.8.6
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/package.json +2 -2
- package/src/auth-provider.ts +32 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svadmin/sso",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
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",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"src"
|
|
32
32
|
],
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@svadmin/core": "^0.
|
|
34
|
+
"@svadmin/core": "^0.22.1"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"typescript": "^5.7.0"
|
package/src/auth-provider.ts
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* ```ts
|
|
17
17
|
* import { createSSOAuthProvider } from '@svadmin/sso';
|
|
18
18
|
* import { setAuthProvider } from '@svadmin/core';
|
|
19
|
+
|
|
20
|
+
|
|
19
21
|
*
|
|
20
22
|
* const authProvider = createSSOAuthProvider({
|
|
21
23
|
* issuer: 'https://your-tenant.okta.com',
|
|
@@ -29,6 +31,19 @@
|
|
|
29
31
|
|
|
30
32
|
import type { AuthProvider, Identity } from '@svadmin/core';
|
|
31
33
|
|
|
34
|
+
// Safe base64url decode for JWT payloads
|
|
35
|
+
function decodeJwtPayload(token: string): any {
|
|
36
|
+
if (!token) return null;
|
|
37
|
+
const parts = token.split('.');
|
|
38
|
+
if (parts.length < 2) return null;
|
|
39
|
+
const payloadStr = parts[1];
|
|
40
|
+
const padded = payloadStr + '='.repeat((4 - (payloadStr.length % 4)) % 4);
|
|
41
|
+
const binString = atob(padded.replace(/-/g, '+').replace(/_/g, '/'));
|
|
42
|
+
const bytes = Uint8Array.from(binString, (m) => m.codePointAt(0)!);
|
|
43
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
32
47
|
// ─── Types ────────────────────────────────────────────────────
|
|
33
48
|
|
|
34
49
|
export interface SSOConfig {
|
|
@@ -116,11 +131,10 @@ const STORAGE_PREFIX = 'svadmin_sso_';
|
|
|
116
131
|
|
|
117
132
|
function getStorage(config: SSOConfig): TokenStorage {
|
|
118
133
|
if (config.storage && typeof config.storage === 'object') return config.storage;
|
|
119
|
-
const backend = config.storage === 'session' ? sessionStorage : localStorage;
|
|
120
134
|
return {
|
|
121
|
-
getItem: (key) =>
|
|
122
|
-
setItem: (key, value) =>
|
|
123
|
-
removeItem: (key) =>
|
|
135
|
+
getItem: (key) => typeof window !== 'undefined' ? (config.storage === 'session' ? sessionStorage : localStorage).getItem(key) : null,
|
|
136
|
+
setItem: (key, value) => { if (typeof window !== 'undefined') (config.storage === 'session' ? sessionStorage : localStorage).setItem(key, value); },
|
|
137
|
+
removeItem: (key) => { if (typeof window !== 'undefined') (config.storage === 'session' ? sessionStorage : localStorage).removeItem(key); },
|
|
124
138
|
};
|
|
125
139
|
}
|
|
126
140
|
|
|
@@ -264,6 +278,9 @@ export function createSSOAuthProvider(config: SSOConfig): AuthProvider {
|
|
|
264
278
|
|
|
265
279
|
return {
|
|
266
280
|
async login() {
|
|
281
|
+
if (typeof window === 'undefined') {
|
|
282
|
+
return { success: false, error: { message: 'SSO login requires a browser environment' } };
|
|
283
|
+
}
|
|
267
284
|
const cfg = await discover();
|
|
268
285
|
const { verifier, challenge } = await createPKCEChallenge();
|
|
269
286
|
const state = generateRandomString(32);
|
|
@@ -293,7 +310,7 @@ export function createSSOAuthProvider(config: SSOConfig): AuthProvider {
|
|
|
293
310
|
const tokens = getTokens();
|
|
294
311
|
clearTokens();
|
|
295
312
|
|
|
296
|
-
if (cfg.end_session_endpoint && tokens?.id_token) {
|
|
313
|
+
if (typeof window !== 'undefined' && cfg.end_session_endpoint && tokens?.id_token) {
|
|
297
314
|
const params = new URLSearchParams({
|
|
298
315
|
id_token_hint: tokens.id_token,
|
|
299
316
|
...(config.postLogoutRedirectUri
|
|
@@ -311,6 +328,15 @@ export function createSSOAuthProvider(config: SSOConfig): AuthProvider {
|
|
|
311
328
|
},
|
|
312
329
|
|
|
313
330
|
async check() {
|
|
331
|
+
// SSR: no tokens, no callback — return unauthenticated
|
|
332
|
+
if (typeof window === 'undefined') {
|
|
333
|
+
const tokens = getTokens();
|
|
334
|
+
if (tokens && (!tokens.expires_at || tokens.expires_at > Math.floor(Date.now() / 1000))) {
|
|
335
|
+
return { authenticated: true };
|
|
336
|
+
}
|
|
337
|
+
return { authenticated: false, logout: true };
|
|
338
|
+
}
|
|
339
|
+
|
|
314
340
|
// Handle callback — exchange authorization code for tokens
|
|
315
341
|
const url = new URL(window.location.href);
|
|
316
342
|
const code = url.searchParams.get('code');
|
|
@@ -382,7 +408,7 @@ export function createSSOAuthProvider(config: SSOConfig): AuthProvider {
|
|
|
382
408
|
// Try to extract roles/permissions from the ID token
|
|
383
409
|
if (tokens.id_token) {
|
|
384
410
|
try {
|
|
385
|
-
const payload =
|
|
411
|
+
const payload = decodeJwtPayload(tokens.id_token);
|
|
386
412
|
return payload.roles ?? payload.groups ?? payload.permissions ?? null;
|
|
387
413
|
} catch {
|
|
388
414
|
return null;
|