@sourceregistry/sveltekit-oidc 1.3.1 → 1.4.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 +30 -33
- package/dist/server/index.js +41 -12
- package/dist/server/types.d.ts +12 -4
- package/dist/server/utils.d.ts +1 -1
- package/dist/server/utils.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,10 +19,7 @@ npm install sveltekit-oidc
|
|
|
19
19
|
|
|
20
20
|
```ts
|
|
21
21
|
// src/lib/server/auth.ts
|
|
22
|
-
import {
|
|
23
|
-
createInMemoryBackChannelLogoutStore,
|
|
24
|
-
createOIDC
|
|
25
|
-
} from 'sveltekit-oidc/server';
|
|
22
|
+
import { createOIDC } from 'sveltekit-oidc/server';
|
|
26
23
|
|
|
27
24
|
export const oidc = createOIDC({
|
|
28
25
|
issuer: 'https://your-idp.example.com',
|
|
@@ -32,22 +29,8 @@ export const oidc = createOIDC({
|
|
|
32
29
|
clientAuthMethod: 'client_secret_basic',
|
|
33
30
|
loginPath: '/auth/login',
|
|
34
31
|
redirectPath: '/auth/callback',
|
|
35
|
-
scope:
|
|
36
|
-
|
|
37
|
-
fetchUserInfo: true,
|
|
38
|
-
backChannelLogoutStore: createInMemoryBackChannelLogoutStore(),
|
|
39
|
-
transformSession(session) {
|
|
40
|
-
return {
|
|
41
|
-
...session,
|
|
42
|
-
groups: session.groups,
|
|
43
|
-
user: session.user
|
|
44
|
-
? {
|
|
45
|
-
...session.user,
|
|
46
|
-
groups: session.groups
|
|
47
|
-
}
|
|
48
|
-
: session.user
|
|
49
|
-
};
|
|
50
|
-
},
|
|
32
|
+
scope: 'openid profile email offline_access',
|
|
33
|
+
backChannelLogoutStore: 'memory',
|
|
51
34
|
cookieOptions: {
|
|
52
35
|
secure: process.env.NODE_ENV === 'production'
|
|
53
36
|
}
|
|
@@ -183,19 +166,17 @@ export const oidc = createOIDC({
|
|
|
183
166
|
});
|
|
184
167
|
```
|
|
185
168
|
|
|
186
|
-
|
|
169
|
+
Wire `App.Locals` so `event.locals.oidc` is fully typed everywhere — `OIDCLocals` infers everything directly from the instance:
|
|
187
170
|
|
|
188
171
|
```ts
|
|
189
172
|
// src/app.d.ts
|
|
190
|
-
import type {
|
|
173
|
+
import type { OIDCLocals } from 'sveltekit-oidc/server';
|
|
191
174
|
import { oidc } from '$lib/server/auth';
|
|
192
175
|
|
|
193
|
-
export type AppClaims = OIDCInferClaims<typeof oidc>;
|
|
194
|
-
|
|
195
176
|
declare global {
|
|
196
177
|
namespace App {
|
|
197
178
|
interface Locals {
|
|
198
|
-
oidc?:
|
|
179
|
+
oidc?: OIDCLocals<typeof oidc>;
|
|
199
180
|
}
|
|
200
181
|
}
|
|
201
182
|
}
|
|
@@ -203,7 +184,16 @@ declare global {
|
|
|
203
184
|
export {};
|
|
204
185
|
```
|
|
205
186
|
|
|
206
|
-
Now `event.locals.oidc?.claims?.roles` is `string[]` and `?.tenant` is `string | undefined` in every hook, load function, and action.
|
|
187
|
+
Now `event.locals.oidc?.claims?.roles` is `string[]` and `?.tenant` is `string | undefined` in every hook, load function, and action — no separate type aliases needed.
|
|
188
|
+
|
|
189
|
+
When you need the claim type explicitly (e.g. in a Svelte component), use `OIDCInferClaims`:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import type { OIDCInferClaims } from 'sveltekit-oidc/server';
|
|
193
|
+
import { oidc } from '$lib/server/auth';
|
|
194
|
+
|
|
195
|
+
type AppClaims = OIDCInferClaims<typeof oidc>;
|
|
196
|
+
```
|
|
207
197
|
|
|
208
198
|
`OIDCContext` is a generic component, but Svelte doesn't support passing explicit type arguments in markup — `TClaims` is inferred from the `session` prop instead. Since `data.session` comes from `oidc.getPublicSession(event)`, it's already typed `OIDCPublicSession<AppClaims> | null`, so the inference flows through automatically:
|
|
209
199
|
|
|
@@ -261,20 +251,17 @@ export const oidc = createOIDC({
|
|
|
261
251
|
});
|
|
262
252
|
```
|
|
263
253
|
|
|
264
|
-
|
|
254
|
+
`app.d.ts` stays a one-liner — `OIDCLocals` picks up both `TClaims` and `TSession` from the instance:
|
|
265
255
|
|
|
266
256
|
```ts
|
|
267
257
|
// src/app.d.ts
|
|
268
|
-
import type {
|
|
258
|
+
import type { OIDCLocals } from 'sveltekit-oidc/server';
|
|
269
259
|
import { oidc } from '$lib/server/auth';
|
|
270
260
|
|
|
271
|
-
export type AppClaims = OIDCInferClaims<typeof oidc>;
|
|
272
|
-
export type AppSession = OIDCInferSession<typeof oidc>;
|
|
273
|
-
|
|
274
261
|
declare global {
|
|
275
262
|
namespace App {
|
|
276
263
|
interface Locals {
|
|
277
|
-
oidc?:
|
|
264
|
+
oidc?: OIDCLocals<typeof oidc>;
|
|
278
265
|
}
|
|
279
266
|
}
|
|
280
267
|
}
|
|
@@ -284,6 +271,16 @@ export {};
|
|
|
284
271
|
|
|
285
272
|
Now `event.locals.oidc?.session?.tenantId` is `string` and `?.permissions` is `string[]` everywhere — and `oidc.requireAuth(event)` / `handleCallback`'s `onsuccess` resolve to `AppSession` directly.
|
|
286
273
|
|
|
274
|
+
Use `OIDCInferClaims` / `OIDCInferSession` when you need the types explicitly:
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
import type { OIDCInferClaims, OIDCInferSession } from 'sveltekit-oidc/server';
|
|
278
|
+
import { oidc } from '$lib/server/auth';
|
|
279
|
+
|
|
280
|
+
type AppClaims = OIDCInferClaims<typeof oidc>;
|
|
281
|
+
type AppSession = OIDCInferSession<typeof oidc>;
|
|
282
|
+
```
|
|
283
|
+
|
|
287
284
|
If `transformSession` is omitted, `TSession` defaults to `OIDCSession<TClaims>` — existing setups keep working unchanged.
|
|
288
285
|
|
|
289
286
|
## Example App
|
|
@@ -309,4 +306,4 @@ Set these environment variables to enable it:
|
|
|
309
306
|
- Use `transformClaims`, `transformUser`, and `transformSession` to project provider-specific claims into your own session shape.
|
|
310
307
|
- `check_session_iframe` monitoring only runs when `monitorSession` is enabled, the provider advertises that endpoint, and the session includes `session_state`.
|
|
311
308
|
- Refresh token handling is automatic when a valid refresh token is present.
|
|
312
|
-
- `event.locals.oidc` is attached by the hook
|
|
309
|
+
- `event.locals.oidc` is attached by the hook; wire it in `app.d.ts` with `OIDCLocals<typeof oidc>` — see [Typed Custom Claims](#typed-custom-claims).
|
package/dist/server/index.js
CHANGED
|
@@ -6,7 +6,34 @@ import { asAuthorizationHeader, createClientSecretJwtAssertion, createPrivateKey
|
|
|
6
6
|
import { normalizeTokens, shouldRefresh } from './session.js';
|
|
7
7
|
import { absoluteUrl, base64UrlEncode, buildCookieOptions, collectGroups, createPKCEPair, normalizeIssuer, normalizeScope, parseProviderError, toPublicSession } from './utils.js';
|
|
8
8
|
export { createInMemoryBackChannelLogoutStore, createInMemorySessionStore } from './store.js';
|
|
9
|
+
import { createInMemoryBackChannelLogoutStore, createInMemorySessionStore } from './store.js';
|
|
10
|
+
function buildLogger(logger) {
|
|
11
|
+
const noop = () => { };
|
|
12
|
+
if (logger === false)
|
|
13
|
+
return { debug: noop, info: noop, warn: noop, error: noop };
|
|
14
|
+
if (logger)
|
|
15
|
+
return {
|
|
16
|
+
debug: (...a) => logger.debug?.(...a),
|
|
17
|
+
info: (...a) => logger.info?.(...a),
|
|
18
|
+
warn: (...a) => logger.warn?.(...a),
|
|
19
|
+
error: (...a) => logger.error?.(...a),
|
|
20
|
+
};
|
|
21
|
+
const p = '[sveltekit-oidc]';
|
|
22
|
+
return {
|
|
23
|
+
debug: (...a) => console.debug(p, ...a),
|
|
24
|
+
info: (...a) => console.info(p, ...a),
|
|
25
|
+
warn: (...a) => console.warn(p, ...a),
|
|
26
|
+
error: (...a) => console.error(p, ...a),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
9
29
|
export function createOIDC(options) {
|
|
30
|
+
const log = buildLogger(options.logger);
|
|
31
|
+
const sessionStore = options.sessionStore === 'memory'
|
|
32
|
+
? createInMemorySessionStore()
|
|
33
|
+
: options.sessionStore;
|
|
34
|
+
const backChannelLogoutStore = options.backChannelLogoutStore === 'memory'
|
|
35
|
+
? createInMemoryBackChannelLogoutStore()
|
|
36
|
+
: options.backChannelLogoutStore;
|
|
10
37
|
const cookieOptions = buildCookieOptions(options.cookieOptions);
|
|
11
38
|
const clockSkewSeconds = options.clockSkewSeconds ?? 30;
|
|
12
39
|
const refreshToleranceSeconds = options.refreshToleranceSeconds ?? 30;
|
|
@@ -20,7 +47,7 @@ export function createOIDC(options) {
|
|
|
20
47
|
let metadataPromise;
|
|
21
48
|
let jwksPromise;
|
|
22
49
|
async function readPersistedSession(cookies) {
|
|
23
|
-
if (!
|
|
50
|
+
if (!sessionStore) {
|
|
24
51
|
const session = cookieStore.readSession(cookies);
|
|
25
52
|
return session ? { session } : null;
|
|
26
53
|
}
|
|
@@ -28,7 +55,7 @@ export function createOIDC(options) {
|
|
|
28
55
|
if (!reference?.id) {
|
|
29
56
|
return null;
|
|
30
57
|
}
|
|
31
|
-
const session = await
|
|
58
|
+
const session = await sessionStore.get(reference.id);
|
|
32
59
|
if (!session) {
|
|
33
60
|
cookieStore.clearSessionReference(cookies);
|
|
34
61
|
return null;
|
|
@@ -39,22 +66,22 @@ export function createOIDC(options) {
|
|
|
39
66
|
};
|
|
40
67
|
}
|
|
41
68
|
async function writePersistedSession(cookies, session, sessionId) {
|
|
42
|
-
if (!
|
|
69
|
+
if (!sessionStore) {
|
|
43
70
|
cookieStore.writeSession(cookies, session);
|
|
44
71
|
return;
|
|
45
72
|
}
|
|
46
73
|
const id = sessionId ?? base64UrlEncode(randomBytes(24));
|
|
47
|
-
await
|
|
74
|
+
await sessionStore.set(id, session);
|
|
48
75
|
cookieStore.writeSessionReference(cookies, { id });
|
|
49
76
|
}
|
|
50
77
|
async function clearPersistedSession(cookies, sessionId) {
|
|
51
|
-
if (!
|
|
78
|
+
if (!sessionStore) {
|
|
52
79
|
cookieStore.clearSession(cookies);
|
|
53
80
|
return;
|
|
54
81
|
}
|
|
55
82
|
const id = sessionId ?? cookieStore.readSessionReference(cookies)?.id;
|
|
56
83
|
if (id) {
|
|
57
|
-
await
|
|
84
|
+
await sessionStore.delete(id);
|
|
58
85
|
}
|
|
59
86
|
cookieStore.clearSessionReference(cookies);
|
|
60
87
|
}
|
|
@@ -237,10 +264,10 @@ export function createOIDC(options) {
|
|
|
237
264
|
});
|
|
238
265
|
}
|
|
239
266
|
async function isRevoked(session) {
|
|
240
|
-
if (!session || !
|
|
267
|
+
if (!session || !backChannelLogoutStore) {
|
|
241
268
|
return false;
|
|
242
269
|
}
|
|
243
|
-
return
|
|
270
|
+
return backChannelLogoutStore.isRevoked(session);
|
|
244
271
|
}
|
|
245
272
|
async function maybeRefreshSession(event, persisted) {
|
|
246
273
|
const session = persisted?.session ?? null;
|
|
@@ -285,7 +312,8 @@ export function createOIDC(options) {
|
|
|
285
312
|
await writePersistedSession(event.cookies, finalSession, persisted?.id);
|
|
286
313
|
return finalSession;
|
|
287
314
|
}
|
|
288
|
-
catch {
|
|
315
|
+
catch (err) {
|
|
316
|
+
log.error('Token refresh failed — clearing session', err);
|
|
289
317
|
await clearPersistedSession(event.cookies, persisted?.id);
|
|
290
318
|
return null;
|
|
291
319
|
}
|
|
@@ -337,7 +365,8 @@ export function createOIDC(options) {
|
|
|
337
365
|
const code = event.url.searchParams.get('code');
|
|
338
366
|
if (!stateCookie || !state || !code || stateCookie.state !== state) {
|
|
339
367
|
cookieStore.clearState(event.cookies);
|
|
340
|
-
|
|
368
|
+
log.warn('Invalid or expired callback state — restarting login flow');
|
|
369
|
+
return signIn(event);
|
|
341
370
|
}
|
|
342
371
|
cookieStore.clearState(event.cookies);
|
|
343
372
|
const tokenResponse = await exchangeCode({
|
|
@@ -415,7 +444,7 @@ export function createOIDC(options) {
|
|
|
415
444
|
if (!metadata.backchannel_logout_supported) {
|
|
416
445
|
throw error(400, { message: 'Provider does not advertise back-channel logout support' });
|
|
417
446
|
}
|
|
418
|
-
if (!
|
|
447
|
+
if (!backChannelLogoutStore) {
|
|
419
448
|
throw error(500, {
|
|
420
449
|
message: 'backChannelLogoutStore is required for back-channel logout with cookie sessions'
|
|
421
450
|
});
|
|
@@ -426,7 +455,7 @@ export function createOIDC(options) {
|
|
|
426
455
|
throw error(400, { message: 'logout_token is required' });
|
|
427
456
|
}
|
|
428
457
|
const claims = await validateBackChannelLogoutToken(logoutToken);
|
|
429
|
-
await
|
|
458
|
+
await backChannelLogoutStore.revoke({
|
|
430
459
|
issuer: claims.iss,
|
|
431
460
|
clientId: options.clientId,
|
|
432
461
|
sid: claims.sid,
|
package/dist/server/types.d.ts
CHANGED
|
@@ -133,6 +133,12 @@ export type OIDCSessionManagementConfig = {
|
|
|
133
133
|
backChannelLogoutSupported: boolean;
|
|
134
134
|
backChannelLogoutSessionSupported: boolean;
|
|
135
135
|
};
|
|
136
|
+
export type OIDCLogger = {
|
|
137
|
+
debug?: (...args: unknown[]) => void;
|
|
138
|
+
info?: (...args: unknown[]) => void;
|
|
139
|
+
warn?: (...args: unknown[]) => void;
|
|
140
|
+
error?: (...args: unknown[]) => void;
|
|
141
|
+
};
|
|
136
142
|
export type OIDCOptions<TClaims extends OIDCUserClaims = OIDCUserClaims, TSession extends OIDCSession<TClaims> = OIDCSession<TClaims>> = {
|
|
137
143
|
issuer?: string;
|
|
138
144
|
discoveryUrl?: string;
|
|
@@ -144,7 +150,7 @@ export type OIDCOptions<TClaims extends OIDCUserClaims = OIDCUserClaims, TSessio
|
|
|
144
150
|
loginPath?: string;
|
|
145
151
|
redirectPath?: string;
|
|
146
152
|
postLogoutRedirectUri?: string;
|
|
147
|
-
scope?: string[];
|
|
153
|
+
scope?: string | string[];
|
|
148
154
|
audience?: string;
|
|
149
155
|
fetchUserInfo?: boolean;
|
|
150
156
|
sessionCookieName?: string;
|
|
@@ -155,8 +161,8 @@ export type OIDCOptions<TClaims extends OIDCUserClaims = OIDCUserClaims, TSessio
|
|
|
155
161
|
refreshToleranceSeconds?: number;
|
|
156
162
|
defaultLoginRedirect?: string;
|
|
157
163
|
defaultLogoutRedirect?: string;
|
|
158
|
-
sessionStore?: OIDCSessionStore<TClaims, TSession
|
|
159
|
-
backChannelLogoutStore?: OIDCBackChannelLogoutStore<TClaims, TSession
|
|
164
|
+
sessionStore?: OIDCSessionStore<TClaims, TSession> | 'memory';
|
|
165
|
+
backChannelLogoutStore?: OIDCBackChannelLogoutStore<TClaims, TSession> | 'memory';
|
|
160
166
|
transformClaims?: (claims: OIDCUserClaims) => MaybePromise<TClaims>;
|
|
161
167
|
transformUser?: (user: OIDCUserClaims | undefined, context: {
|
|
162
168
|
claims?: TClaims;
|
|
@@ -169,11 +175,12 @@ export type OIDCOptions<TClaims extends OIDCUserClaims = OIDCUserClaims, TSessio
|
|
|
169
175
|
isRefresh: boolean;
|
|
170
176
|
}) => MaybePromise<TSession>;
|
|
171
177
|
endpoints?: Partial<OIDCDiscoveryDocument>;
|
|
178
|
+
logger?: OIDCLogger | false;
|
|
172
179
|
};
|
|
173
180
|
export type OIDCLoginOptions = {
|
|
174
181
|
returnTo?: string;
|
|
175
182
|
prompt?: 'login' | 'consent' | 'none' | 'select_account';
|
|
176
|
-
scope?: string[];
|
|
183
|
+
scope?: string | string[];
|
|
177
184
|
extraParams?: Record<string, string>;
|
|
178
185
|
};
|
|
179
186
|
export type OIDCLogoutOptions = {
|
|
@@ -256,3 +263,4 @@ export type OIDCInstance<TClaims extends OIDCUserClaims = OIDCUserClaims, TSessi
|
|
|
256
263
|
};
|
|
257
264
|
export type OIDCInferClaims<T> = T extends OIDCInstance<infer TClaims> ? TClaims : OIDCUserClaims;
|
|
258
265
|
export type OIDCInferSession<T> = T extends OIDCInstance<infer _TClaims, infer TSession> ? TSession : OIDCSession<OIDCUserClaims>;
|
|
266
|
+
export type OIDCLocals<T extends OIDCInstance<any, any>> = OIDCHandleLocals<OIDCInferClaims<T>, OIDCInferSession<T>>;
|
package/dist/server/utils.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare function createPKCEPair(length?: number): {
|
|
|
6
6
|
challenge: string;
|
|
7
7
|
};
|
|
8
8
|
export declare function normalizeIssuer(issuer: string): string;
|
|
9
|
-
export declare function normalizeScope(scope?: string[]): string[];
|
|
9
|
+
export declare function normalizeScope(scope?: string | string[]): string[];
|
|
10
10
|
export declare function normalizeStringArray(value: unknown): string[];
|
|
11
11
|
export declare function collectGroups<TClaims extends OIDCUserClaims = OIDCUserClaims>(...sources: Array<TClaims | undefined>): string[];
|
|
12
12
|
export declare function createSignedValue(value: string, secret: string): string;
|
package/dist/server/utils.js
CHANGED
|
@@ -13,7 +13,10 @@ export function normalizeIssuer(issuer) {
|
|
|
13
13
|
return issuer.endsWith('/') ? issuer.slice(0, -1) : issuer;
|
|
14
14
|
}
|
|
15
15
|
export function normalizeScope(scope) {
|
|
16
|
-
|
|
16
|
+
if (!scope)
|
|
17
|
+
return ['openid', 'profile', 'email'];
|
|
18
|
+
const arr = typeof scope === 'string' ? scope.trim().split(/\s+/) : scope;
|
|
19
|
+
return arr.length ? [...new Set(arr)] : ['openid', 'profile', 'email'];
|
|
17
20
|
}
|
|
18
21
|
export function normalizeStringArray(value) {
|
|
19
22
|
if (!value)
|