@owlmeans/server-oidc-provider 0.1.2 → 0.1.4

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 CHANGED
@@ -1,848 +1,65 @@
1
1
  # @owlmeans/server-oidc-provider
2
2
 
3
- Server-side OpenID Connect Provider implementation for OwlMeans applications. This package provides a complete OIDC identity provider service built on the industry-standard `oidc-provider` library, with seamless integration into the OwlMeans server ecosystem.
3
+ Embedded OIDC identity provider wraps the `oidc-provider` library as an OwlMeans service.
4
4
 
5
- ## Overview
6
-
7
- The `@owlmeans/server-oidc-provider` package delivers a full-featured OIDC identity provider with:
5
+ > Use this package only when your service IS the identity provider (Keycloak alternative). For consuming an external IdP, use [`@owlmeans/server-oidc-rp`](../server-oidc-rp).
8
6
 
9
- - **Complete OIDC Provider**: Full OpenID Connect 1.0 and OAuth 2.0 implementation
10
- - **Fastify Integration**: Seamless integration with OwlMeans server API framework
11
- - **Account Management**: Pluggable account service for user authentication
12
- - **Adapter Support**: Configurable storage adapters for sessions and tokens
13
- - **Client Management**: Dynamic client registration and configuration
14
- - **Interaction Flows**: Customizable authentication and consent flows
15
- - **Security Features**: JWT signing, HTTPS support, and secure cookie handling
16
- - **Proxy Support**: Works behind reverse proxies and load balancers
7
+ ## Overview
17
8
 
18
- This package is part of the OwlMeans OIDC ecosystem:
19
- - **@owlmeans/oidc**: Core OIDC types and utilities
20
- - **@owlmeans/server-oidc-provider**: Server-side OIDC provider *(this package)*
21
- - **@owlmeans/server-oidc-rp**: Server-side relying party
22
- - **@owlmeans/web-oidc-provider**: Web UI for OIDC provider
23
- - **@owlmeans/web-oidc-rp**: Web-based relying party
9
+ - `createOidcProviderService(alias?)` factory for the embedded provider service
10
+ - `appendOidcProviderService(context, alias?)` registers the service on a context
11
+ - `createOidcProviderMiddleware(webAlias?, oidcAlias?)` — middleware mounting the OIDC endpoints (authorize, token, userinfo, jwks)
12
+ - Hosts the interaction UI route alias `oidc:interaction`
24
13
 
25
14
  ## Installation
26
15
 
27
16
  ```bash
28
- npm install @owlmeans/server-oidc-provider oidc-provider fastify
17
+ bun add @owlmeans/server-oidc-provider
29
18
  ```
30
19
 
31
- ## Dependencies
32
-
33
- This package requires:
34
- - `oidc-provider`: Industry-standard OIDC provider implementation
35
- - `@owlmeans/server-api`: Server API framework with Fastify
36
- - `@owlmeans/server-context`: Server context management
37
- - `@owlmeans/oidc`: Core OIDC functionality
38
- - `@owlmeans/config`: Configuration management
39
- - `jose`: JWT operations
40
- - `fastify`: HTTP server framework (peer dependency)
41
-
42
- ## Core Concepts
43
-
44
- ### OIDC Provider Service
45
-
46
- The OIDC provider service manages the complete lifecycle of an OpenID Connect identity provider, including client management, user authentication, token issuance, and session management.
47
-
48
- ### Account Service
49
-
50
- Pluggable account service interface that handles user authentication and account loading for the OIDC provider.
51
-
52
- ### Adapter Service
20
+ ## Usage
53
21
 
54
- Configurable storage adapter service for persisting OIDC sessions, authorization codes, access tokens, and other provider data.
55
-
56
- ### Interaction Flows
57
-
58
- Customizable authentication and consent flows that work with the OwlMeans client module system.
59
-
60
- ## API Reference
61
-
62
- ### Types
63
-
64
- #### `OidcProviderService`
65
-
66
- Main OIDC provider service interface.
22
+ Register the service and mount the middleware:
67
23
 
68
24
  ```typescript
69
- interface OidcProviderService extends InitializedService {
70
- oidc: Provider // oidc-provider instance
71
- update(api: ApiServer): Promise<void> // Update provider with API server
72
- instance(): Provider // Get provider instance
73
- getInteraction(id: string): Promise<Interaction | null> // Get interaction by ID
74
- }
75
- ```
76
-
77
- #### `OidcConfig`
78
-
79
- Configuration interface for OIDC provider.
25
+ import {
26
+ createOidcProviderService,
27
+ appendOidcProviderService,
28
+ createOidcProviderMiddleware
29
+ } from '@owlmeans/server-oidc-provider'
80
30
 
81
- ```typescript
82
- interface OidcConfig extends OidcSharedConfig {
83
- authService?: string // Authentication service name
84
- basePath?: string // Base path for OIDC endpoints
85
- frontBase?: string // Frontend base URL
86
- clients: ClientMetadata[] // OIDC client configurations
87
- customConfiguration?: Configuration // Custom oidc-provider config
88
- behindProxy?: boolean // Behind reverse proxy flag
89
- defaultKeys: { // Default signing keys
90
- RS256: {
91
- pk: string // Private key (PEM format)
92
- pub?: string // Public key (PEM format)
93
- }
94
- }
95
- accountService?: string // Account service name
96
- adapterService?: string // Adapter service name
97
- }
98
- ```
99
-
100
- #### `OidcAccountService`
101
-
102
- Interface for user account management.
103
-
104
- ```typescript
105
- interface OidcAccountService extends InitializedService {
106
- loadById<C extends Config, T extends Context<C>>(
107
- ctx: T,
108
- id: string
109
- ): Promise<Account | undefined>
110
- }
31
+ appendOidcProviderService<C, T>(context)
32
+ context.registerMiddleware(createOidcProviderMiddleware())
111
33
  ```
112
34
 
113
- #### `OidcAdapterService`
35
+ The provider exposes the standard OIDC endpoints under the configured base path; the interaction screen is mounted at the path defined by `INTERACTION_PATH` from `@owlmeans/oidc`.
114
36
 
115
- Interface for storage adapter management.
37
+ ## API
116
38
 
117
- ```typescript
118
- interface OidcAdapterService extends InitializedService {
119
- instance(name: string): Adapter
120
- }
121
- ```
39
+ ### `createOidcProviderService(alias?): OidcProviderService`
122
40
 
123
- #### `Config`
41
+ Creates the embedded OIDC provider service. `alias` defaults to `DEFAULT_ALIAS` (`'oidc-provider'`).
124
42
 
125
- Extended server configuration with OIDC settings.
43
+ ### `appendOidcProviderService<C, T>(context, alias?): T`
126
44
 
127
- ```typescript
128
- interface Config extends ServerConfig, OidcConfigAppend {
129
- debug: ServerConfig["debug"] & {
130
- oidc?: boolean // General OIDC debugging
131
- oidcServer?: boolean // Server-specific debugging
132
- oidcData?: boolean // Data operation debugging
133
- }
134
- }
135
- ```
45
+ Registers the provider service on the context.
136
46
 
137
- ### Factory Functions
138
-
139
- #### `createOidcProviderService(alias?: string): OidcProviderService`
140
-
141
- Creates an OIDC provider service instance.
142
-
143
- **Parameters:**
144
- - `alias`: Service alias (default: 'oidc-provider')
145
-
146
- **Returns:** OidcProviderService instance
147
-
148
- **Features:**
149
- - Automatic provider configuration based on context settings
150
- - Integration with account and adapter services
151
- - Fastify middleware integration
152
- - JWT key management
153
- - Client configuration management
154
-
155
- **Example:**
156
- ```typescript
157
- import { createOidcProviderService } from '@owlmeans/server-oidc-provider'
158
-
159
- const oidcProvider = createOidcProviderService('main-oidc')
160
- ```
47
+ ### `createOidcProviderMiddleware(webAlias?, oidcAlias?)`
161
48
 
162
- #### `createOidcProviderMiddleware(web?: string, oidc?: string): Middleware`
163
-
164
- Creates middleware to integrate OIDC provider with API server.
165
-
166
- **Parameters:**
167
- - `web`: API server service alias (default: 'api-server')
168
- - `oidc`: OIDC provider service alias (default: 'oidc-provider')
169
-
170
- **Returns:** Context middleware
171
-
172
- **Example:**
173
- ```typescript
174
- import { createOidcProviderMiddleware } from '@owlmeans/server-oidc-provider'
175
-
176
- const oidcMiddleware = createOidcProviderMiddleware('api', 'oidc')
177
- context.registerMiddleware(oidcMiddleware)
178
- ```
179
-
180
- ### Service Methods
181
-
182
- #### `update(api: ApiServer): Promise<void>`
183
-
184
- Updates the OIDC provider with the API server, registering all OIDC endpoints.
185
-
186
- **Parameters:**
187
- - `api`: API server instance to integrate with
188
-
189
- **Process:**
190
- 1. Configures oidc-provider instance with context settings
191
- 2. Sets up account and adapter services
192
- 3. Configures interaction flows
193
- 4. Registers OIDC endpoints with Fastify server
194
-
195
- **Example:**
196
- ```typescript
197
- const oidcService = context.service<OidcProviderService>('oidc-provider')
198
- const apiServer = context.service<ApiServer>('api')
199
-
200
- await oidcService.update(apiServer)
201
- ```
202
-
203
- #### `instance(): Provider`
204
-
205
- Gets the underlying oidc-provider instance for direct access.
206
-
207
- **Returns:** oidc-provider Provider instance
208
-
209
- **Example:**
210
- ```typescript
211
- const provider = oidcService.instance()
212
-
213
- // Access oidc-provider methods directly
214
- const client = await provider.Client.find('client-id')
215
- const interaction = await provider.interactionDetails(req, res)
216
- ```
217
-
218
- #### `getInteraction(id: string): Promise<Interaction | null>`
219
-
220
- Retrieves an interaction by its unique identifier.
221
-
222
- **Parameters:**
223
- - `id`: Interaction identifier
224
-
225
- **Returns:** Promise resolving to Interaction object or null
226
-
227
- **Example:**
228
- ```typescript
229
- const interaction = await oidcService.getInteraction('interaction-uuid')
230
- if (interaction) {
231
- console.log('Interaction details:', interaction.params)
232
- }
233
- ```
49
+ Returns middleware that mounts the OIDC authorize/token/userinfo/jwks endpoints onto the web app.
234
50
 
235
51
  ### Constants
236
52
 
237
- ```typescript
238
- const DEFAULT_ALIAS = 'oidc-provider' // Default service alias
239
- const OIDC_ACCOUNT_SERVICE = 'oidc-account-service' // Default account service name
240
- ```
241
-
242
- ## Usage Examples
243
-
244
- ### Basic OIDC Provider Setup
245
-
246
- ```typescript
247
- import { createOidcProviderService, createOidcProviderMiddleware } from '@owlmeans/server-oidc-provider'
248
- import { makeServerContext } from '@owlmeans/server-context'
249
- import { createApiServer } from '@owlmeans/server-api'
250
-
251
- // Create server configuration with OIDC settings
252
- const config = {
253
- service: 'auth-server',
254
- oidc: {
255
- basePath: 'oidc',
256
- clients: [
257
- {
258
- client_id: 'my-web-app',
259
- client_secret: 'secure-client-secret',
260
- redirect_uris: ['https://myapp.com/callback'],
261
- response_types: ['code'],
262
- grant_types: ['authorization_code', 'refresh_token'],
263
- scope: 'openid profile email'
264
- }
265
- ],
266
- defaultKeys: {
267
- RS256: {
268
- pk: `-----BEGIN PRIVATE KEY-----
269
- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
270
- -----END PRIVATE KEY-----`,
271
- pub: `-----BEGIN PUBLIC KEY-----
272
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtfGqPGwp...
273
- -----END PUBLIC KEY-----`
274
- }
275
- }
276
- }
277
- }
278
-
279
- // Create context and services
280
- const context = makeServerContext(config)
281
- const apiServer = createApiServer('api')
282
- const oidcProvider = createOidcProviderService('oidc')
53
+ - `DEFAULT_ALIAS` — `'oidc-provider'`
54
+ - `OIDC_ACCOUNT_SERVICE` `'oidc-account-service'` alias for the user account adapter
283
55
 
284
- // Register services and middleware
285
- context.registerService(apiServer)
286
- context.registerService(oidcProvider)
287
- context.registerMiddleware(createOidcProviderMiddleware('api', 'oidc'))
288
-
289
- // Initialize and start
290
- await context.configure().init()
291
- await apiServer.listen()
292
-
293
- // OIDC endpoints now available at /oidc/*
294
- ```
295
-
296
- ### Custom Account Service Implementation
297
-
298
- ```typescript
299
- import { createService } from '@owlmeans/context'
300
- import type { OidcAccountService } from '@owlmeans/server-oidc-provider'
301
-
302
- // Implement account service
303
- const accountService: OidcAccountService = createService('oidc-account-service', {
304
- loadById: async (ctx, id) => {
305
- // Load user account from your database
306
- const user = await getUserById(id)
307
-
308
- if (!user) {
309
- return undefined
310
- }
311
-
312
- // Return oidc-provider Account object
313
- return {
314
- accountId: user.id,
315
-
316
- // Standard claims
317
- claims: async (use, scope, claims, rejected) => {
318
- const standardClaims = {
319
- sub: user.id,
320
- email: user.email,
321
- email_verified: user.emailVerified,
322
- name: user.fullName,
323
- given_name: user.firstName,
324
- family_name: user.lastName,
325
- picture: user.avatar
326
- }
327
-
328
- // Filter claims based on scope and requested claims
329
- const result = {}
330
- if (scope.includes('email')) {
331
- result.email = standardClaims.email
332
- result.email_verified = standardClaims.email_verified
333
- }
334
- if (scope.includes('profile')) {
335
- result.name = standardClaims.name
336
- result.given_name = standardClaims.given_name
337
- result.family_name = standardClaims.family_name
338
- result.picture = standardClaims.picture
339
- }
340
-
341
- return result
342
- }
343
- }
344
- }
345
- })
346
-
347
- // Register account service
348
- context.registerService(accountService)
349
- ```
350
-
351
- ### Custom Storage Adapter
352
-
353
- ```typescript
354
- import { createService } from '@owlmeans/context'
355
- import type { OidcAdapterService } from '@owlmeans/server-oidc-provider'
356
-
357
- // Implement Redis-based adapter service
358
- const adapterService: OidcAdapterService = createService('oidc-adapter-service', {
359
- instance: (name) => {
360
- // Return adapter for specific model (Session, AccessToken, etc.)
361
- return {
362
- async upsert(id, payload, expiresIn) {
363
- const key = `oidc:${name}:${id}`
364
- await redis.setex(key, expiresIn, JSON.stringify(payload))
365
- },
366
-
367
- async find(id) {
368
- const key = `oidc:${name}:${id}`
369
- const data = await redis.get(key)
370
- return data ? JSON.parse(data) : undefined
371
- },
372
-
373
- async findByUserCode(userCode) {
374
- // Implement user code lookup for device flow
375
- const keys = await redis.keys(`oidc:${name}:*`)
376
- for (const key of keys) {
377
- const data = await redis.get(key)
378
- const payload = JSON.parse(data)
379
- if (payload.userCode === userCode) {
380
- return payload
381
- }
382
- }
383
- return undefined
384
- },
385
-
386
- async findByUid(uid) {
387
- // Implement UID-based lookup
388
- const keys = await redis.keys(`oidc:${name}:*`)
389
- for (const key of keys) {
390
- const data = await redis.get(key)
391
- const payload = JSON.parse(data)
392
- if (payload.uid === uid) {
393
- return payload
394
- }
395
- }
396
- return undefined
397
- },
398
-
399
- async destroy(id) {
400
- const key = `oidc:${name}:${id}`
401
- await redis.del(key)
402
- },
403
-
404
- async revokeByGrantId(grantId) {
405
- const keys = await redis.keys(`oidc:${name}:*`)
406
- for (const key of keys) {
407
- const data = await redis.get(key)
408
- const payload = JSON.parse(data)
409
- if (payload.grantId === grantId) {
410
- await redis.del(key)
411
- }
412
- }
413
- },
414
-
415
- async consume(id) {
416
- const key = `oidc:${name}:${id}`
417
- const data = await redis.get(key)
418
- if (data) {
419
- const payload = JSON.parse(data)
420
- payload.consumed = Math.floor(Date.now() / 1000)
421
- await redis.setex(key, payload.exp - Math.floor(Date.now() / 1000), JSON.stringify(payload))
422
- }
423
- }
424
- }
425
- }
426
- })
427
-
428
- // Register adapter service
429
- context.registerService(adapterService)
430
- ```
431
-
432
- ### Advanced OIDC Configuration
433
-
434
- ```typescript
435
- const advancedConfig = {
436
- oidc: {
437
- basePath: 'auth',
438
- behindProxy: true,
439
- accountService: 'custom-account-service',
440
- adapterService: 'redis-adapter-service',
441
-
442
- clients: [
443
- // Web application
444
- {
445
- client_id: 'web-app',
446
- client_secret: 'web-app-secret',
447
- redirect_uris: [
448
- 'https://app.example.com/callback',
449
- 'https://staging.app.example.com/callback'
450
- ],
451
- post_logout_redirect_uris: [
452
- 'https://app.example.com/logout'
453
- ],
454
- response_types: ['code'],
455
- grant_types: ['authorization_code', 'refresh_token'],
456
- scope: 'openid profile email offline_access',
457
- token_endpoint_auth_method: 'client_secret_basic'
458
- },
459
-
460
- // Mobile application (public client)
461
- {
462
- client_id: 'mobile-app',
463
- redirect_uris: ['com.example.app://callback'],
464
- response_types: ['code'],
465
- grant_types: ['authorization_code', 'refresh_token'],
466
- scope: 'openid profile email offline_access',
467
- token_endpoint_auth_method: 'none', // Public client
468
- require_auth_time: true
469
- },
470
-
471
- // SPA (Single Page Application)
472
- {
473
- client_id: 'spa-app',
474
- redirect_uris: ['https://spa.example.com/callback'],
475
- response_types: ['code'],
476
- grant_types: ['authorization_code'],
477
- scope: 'openid profile email',
478
- token_endpoint_auth_method: 'none',
479
- require_auth_time: false
480
- }
481
- ],
482
-
483
- // Custom oidc-provider configuration
484
- customConfiguration: {
485
- features: {
486
- devInteractions: { enabled: false },
487
- deviceFlow: { enabled: true },
488
- revocation: { enabled: true },
489
- introspection: { enabled: true },
490
- userinfo: { enabled: true }
491
- },
492
-
493
- ttl: {
494
- AccessToken: 60 * 60, // 1 hour
495
- AuthorizationCode: 10 * 60, // 10 minutes
496
- IdToken: 60 * 60, // 1 hour
497
- DeviceCode: 10 * 60, // 10 minutes
498
- RefreshToken: 14 * 24 * 60 * 60 // 14 days
499
- },
500
-
501
- claims: {
502
- openid: ['sub'],
503
- email: ['email', 'email_verified'],
504
- profile: ['name', 'family_name', 'given_name', 'picture']
505
- },
506
-
507
- scopes: ['openid', 'email', 'profile', 'offline_access']
508
- }
509
- }
510
- }
511
- ```
512
-
513
- ### Interaction Flow Integration
514
-
515
- ```typescript
516
- import { modules } from '@owlmeans/oidc'
517
- import { module, handleRequest } from '@owlmeans/module'
518
- import { route, frontend } from '@owlmeans/route'
519
-
520
- // Register OIDC interaction modules
521
- context.registerModules(modules)
522
-
523
- // Custom login interaction handler
524
- const loginModule = module(
525
- route('oidc-login', '/oidc/interaction/:uid/login', frontend()),
526
- {
527
- handle: handleRequest(async (req, ctx) => {
528
- const { uid } = req.params
529
- const oidcService = ctx.service<OidcProviderService>('oidc-provider')
530
-
531
- // Get interaction details
532
- const interaction = await oidcService.getInteraction(uid)
533
-
534
- if (!interaction) {
535
- throw new Error('Interaction not found')
536
- }
537
-
538
- // Return login form data
539
- return {
540
- interaction: {
541
- uid: interaction.uid,
542
- params: interaction.params,
543
- prompt: interaction.prompt
544
- },
545
- client: interaction.params.client_id
546
- }
547
- })
548
- }
549
- )
550
-
551
- context.registerModule(loginModule)
552
- ```
553
-
554
- ### Multi-Tenant OIDC Setup
555
-
556
- ```typescript
557
- // Multi-tenant configuration with entity-specific clients
558
- const multiTenantConfig = {
559
- oidc: {
560
- clients: [
561
- // Tenant A
562
- {
563
- client_id: 'tenant-a-web',
564
- client_secret: 'tenant-a-secret',
565
- redirect_uris: ['https://tenant-a.example.com/callback'],
566
- response_types: ['code'],
567
- grant_types: ['authorization_code', 'refresh_token'],
568
- scope: 'openid profile email'
569
- },
570
-
571
- // Tenant B
572
- {
573
- client_id: 'tenant-b-web',
574
- client_secret: 'tenant-b-secret',
575
- redirect_uris: ['https://tenant-b.example.com/callback'],
576
- response_types: ['code'],
577
- grant_types: ['authorization_code', 'refresh_token'],
578
- scope: 'openid profile email'
579
- }
580
- ],
581
-
582
- // Tenant-aware account service
583
- accountService: 'multi-tenant-account-service'
584
- }
585
- }
586
-
587
- // Multi-tenant account service
588
- const multiTenantAccountService = createService('multi-tenant-account-service', {
589
- loadById: async (ctx, id) => {
590
- // Extract tenant from ID or use other tenant identification
591
- const [tenantId, userId] = id.split(':')
592
-
593
- const user = await getUserByIdAndTenant(userId, tenantId)
594
-
595
- if (!user) {
596
- return undefined
597
- }
598
-
599
- return {
600
- accountId: id,
601
- claims: async (use, scope) => {
602
- return {
603
- sub: id,
604
- email: user.email,
605
- name: user.name,
606
- tenant: tenantId // Custom claim
607
- }
608
- }
609
- }
610
- }
611
- })
612
- ```
613
-
614
- ### Development and Testing Configuration
615
-
616
- ```typescript
617
- // Development configuration with debugging
618
- const devConfig = {
619
- debug: {
620
- oidc: true,
621
- oidcServer: true,
622
- oidcData: true
623
- },
624
-
625
- oidc: {
626
- customConfiguration: {
627
- features: {
628
- devInteractions: { enabled: true }, // Enable dev interactions
629
- },
630
-
631
- // More permissive settings for development
632
- conformIdTokenClaims: false,
633
-
634
- // Custom cookies for development
635
- cookies: {
636
- keys: ['dev-cookie-secret'],
637
- secure: false, // Allow non-HTTPS in development
638
- sameSite: 'lax'
639
- }
640
- }
641
- }
642
- }
643
- ```
644
-
645
- ## Advanced Features
646
-
647
- ### Custom Claims and Scopes
648
-
649
- ```typescript
650
- const customConfig = {
651
- oidc: {
652
- customConfiguration: {
653
- // Define custom scopes
654
- scopes: ['openid', 'email', 'profile', 'admin', 'api:read', 'api:write'],
655
-
656
- // Map scopes to claims
657
- claims: {
658
- openid: ['sub'],
659
- email: ['email', 'email_verified'],
660
- profile: ['name', 'family_name', 'given_name', 'picture', 'locale'],
661
- admin: ['role', 'permissions'],
662
- 'api:read': ['api_access'],
663
- 'api:write': ['api_access', 'write_permissions']
664
- },
665
-
666
- // Custom claim mapping
667
- extraParams: ['tenant_id', 'organization'],
668
-
669
- // Custom token format
670
- formats: {
671
- AccessToken: 'jwt',
672
- ClientCredentials: 'jwt'
673
- }
674
- }
675
- }
676
- }
677
- ```
678
-
679
- ### Dynamic Client Registration
680
-
681
- ```typescript
682
- const dynamicClientConfig = {
683
- oidc: {
684
- customConfiguration: {
685
- features: {
686
- registration: { enabled: true },
687
- registrationManagement: { enabled: true }
688
- },
689
-
690
- // Client validation
691
- clientDefaults: {
692
- grant_types: ['authorization_code'],
693
- response_types: ['code'],
694
- token_endpoint_auth_method: 'client_secret_basic'
695
- }
696
- }
697
- }
698
- }
699
-
700
- // Handle dynamic client registration
701
- const clientRegistrationModule = module(
702
- route('client-registration', '/oidc/reg', backend('POST')),
703
- {
704
- handle: handleRequest(async (req, ctx) => {
705
- const oidcService = ctx.service<OidcProviderService>('oidc-provider')
706
- const provider = oidcService.instance()
707
-
708
- // Custom client validation logic
709
- const clientMetadata = req.body
710
-
711
- // Register client with oidc-provider
712
- const client = await provider.Client.create(clientMetadata)
713
-
714
- return {
715
- client_id: client.clientId,
716
- client_secret: client.clientSecret,
717
- registration_access_token: client.registrationAccessToken
718
- }
719
- })
720
- }
721
- )
722
- ```
723
-
724
- ### Integration with External Identity Providers
725
-
726
- ```typescript
727
- // Federation with external IdPs
728
- const federatedConfig = {
729
- oidc: {
730
- customConfiguration: {
731
- // Custom interaction handling for federation
732
- interactions: {
733
- url: async (ctx, interaction) => {
734
- // Custom logic to determine if user should be redirected to external IdP
735
- if (interaction.params.federated_idp) {
736
- return `/oidc/federate/${interaction.params.federated_idp}/${interaction.uid}`
737
- }
738
-
739
- return `/oidc/interaction/${interaction.uid}`
740
- }
741
- }
742
- }
743
- }
744
- }
745
- ```
746
-
747
- ## Security Considerations
748
-
749
- ### Key Management
750
- - Use strong RSA keys (2048-bit minimum) for JWT signing
751
- - Rotate signing keys periodically
752
- - Store private keys securely using environment variables or secret management
753
- - Use different keys for different environments
754
-
755
- ### HTTPS and Proxy Configuration
756
- - Always use HTTPS in production
757
- - Configure `behindProxy` correctly when behind reverse proxies
758
- - Use secure cookie settings in production
759
- - Implement proper CORS policies
760
-
761
- ### Client Security
762
- - Use strong client secrets for confidential clients
763
- - Validate redirect URIs strictly
764
- - Implement proper logout handling
765
- - Use appropriate token lifetimes
766
-
767
- ### Session Management
768
- - Use secure session storage
769
- - Implement proper session cleanup
770
- - Monitor for session hijacking attempts
771
- - Use appropriate session timeouts
772
-
773
- ## Best Practices
774
-
775
- 1. **Configuration**: Use environment-specific configurations
776
- 2. **Account Service**: Implement robust user authentication
777
- 3. **Adapter Service**: Use persistent storage for production
778
- 4. **Client Management**: Validate client configurations thoroughly
779
- 5. **Security**: Follow OIDC security best practices
780
- 6. **Monitoring**: Implement logging and monitoring
781
- 7. **Testing**: Test all authentication flows thoroughly
782
-
783
- ## Error Handling
784
-
785
- ```typescript
786
- try {
787
- const oidcService = createOidcProviderService()
788
- await oidcService.update(apiServer)
789
- } catch (error) {
790
- console.error('OIDC Provider setup failed:', error)
791
-
792
- // Handle specific configuration errors
793
- if (error.message.includes('missing key')) {
794
- console.error('JWT signing key not configured')
795
- }
796
-
797
- if (error.message.includes('client')) {
798
- console.error('Client configuration invalid')
799
- }
800
- }
801
- ```
802
-
803
- ## Integration with OwlMeans Ecosystem
804
-
805
- ### Server API Integration
806
- ```typescript
807
- import { createApiServer } from '@owlmeans/server-api'
808
-
809
- // OIDC provider integrates seamlessly with server API
810
- const apiServer = createApiServer('api')
811
- const oidcProvider = createOidcProviderService('oidc')
812
-
813
- context.registerService(apiServer)
814
- context.registerService(oidcProvider)
815
- ```
816
-
817
- ### Authentication Integration
818
- ```typescript
819
- import { makeAuthService } from '@owlmeans/server-auth'
820
-
821
- // Use OIDC tokens for API authentication
822
- const authService = makeAuthService('auth')
823
- // Configure auth service to validate OIDC tokens
824
- ```
825
-
826
- ### Context Integration
827
- ```typescript
828
- import { makeServerContext } from '@owlmeans/server-context'
829
-
830
- // Full integration with server context management
831
- const context = makeServerContext(configWithOidc)
832
- ```
833
-
834
- ## Performance Considerations
56
+ ### Types
835
57
 
836
- - **Adapter Choice**: Use Redis or other fast storage for sessions
837
- - **Token Formats**: Consider JWT vs opaque tokens based on use case
838
- - **Caching**: Implement appropriate caching for user accounts
839
- - **Database Optimization**: Optimize account and client lookups
840
- - **Connection Pooling**: Use connection pooling for database operations
58
+ `OidcProviderService` and provider config types are exported from the root entry.
841
59
 
842
60
  ## Related Packages
843
61
 
844
- - [`@owlmeans/oidc`](../oidc) - Core OIDC types and utilities
845
- - [`@owlmeans/server-api`](../server-api) - Server API framework
846
- - [`@owlmeans/server-context`](../server-context) - Server context management
847
- - [`@owlmeans/server-oidc-rp`](../server-oidc-rp) - Server-side relying party
848
- - [`@owlmeans/web-oidc-provider`](../web-oidc-provider) - Web UI for OIDC provider
62
+ - [`@owlmeans/oidc`](../oidc) shared protocol constants and module declarations
63
+ - [`@owlmeans/server-oidc-rp`](../server-oidc-rp) — relying-party counterpart for consuming external IdPs
64
+ - [`@owlmeans/server-api`](../server-api) the underlying server API the middleware mounts onto
65
+ - [`@owlmeans/web-oidc-provider`](../web-oidc-provider) browser UI for the provider's interaction screens
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAA0C,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAS9G,eAAO,MAAM,yBAAyB,GAAI,QAAO,MAAsB,KAAG,mBA6FzE,CAAA;AAED,eAAO,MAAM,yBAAyB,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,EAC9E,KAAK,CAAC,EAAE,QAAO,MAAsB,KACpC,CAOF,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAA0C,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAS9G,eAAO,MAAM,yBAAyB,GAAI,QAAO,MAAsB,KAAG,mBAwFzE,CAAA;AAED,eAAO,MAAM,yBAAyB,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,EAC9E,KAAK,CAAC,EAAE,QAAO,MAAsB,KACpC,CAOF,CAAA"}
package/build/service.js CHANGED
@@ -26,7 +26,7 @@ export const createOidcProviderService = (alias = DEFAULT_ALIAS) => {
26
26
  },
27
27
  interactions: {
28
28
  url: async (_, interaction) => {
29
- const module = context.module(INTERACTION);
29
+ const module = context.entrypoint(INTERACTION);
30
30
  const [uri] = await module.call({ params: { uid: interaction.uid } });
31
31
  return uri;
32
32
  }
@@ -35,18 +35,22 @@ export const createOidcProviderService = (alias = DEFAULT_ALIAS) => {
35
35
  oidc.proxy = cfg.behindProxy ?? unsecure;
36
36
  const base = SEP + (cfg.basePath ?? DEFAULT_PATH);
37
37
  api.server.use(base, oidc.callback());
38
- oidc.use(async (ctx, next) => {
39
- await next();
40
- const csp = ctx.response.headers['content-security-policy'];
41
- if (csp != null) {
42
- // @TODO Make it a little bit nicer - preferably using helmet :)
43
- ctx.response.set('Content-Security-Policy', csp.replace(/form-action 'self'/, 'form-action *'));
38
+ // oidc-provider v9: provider.use() middleware no longer runs post-response after a matched
39
+ // route. CSP rewrite is handled at the Fastify layer via an onSend hook instead.
40
+ api.server.addHook('onSend', async (request, reply, payload) => {
41
+ if (!request.url.startsWith(base)) {
42
+ return payload;
44
43
  }
44
+ const csp = reply.getHeader('content-security-policy');
45
+ if (typeof csp === 'string' && csp.includes("form-action 'self'")) {
46
+ reply.header('Content-Security-Policy', csp.replace(/form-action 'self'/, 'form-action *'));
47
+ }
48
+ return payload;
45
49
  });
46
50
  if (context.cfg.debug?.all || context.cfg.debug?.oidc) {
47
51
  oidc.on('grant.error', (_, error) => {
48
52
  console.warn('GRANT ERROR .......: ');
49
- console.info(oidc.issuer, _.request.toJSON(), _.body);
53
+ console.info(oidc.issuer);
50
54
  console.error('!!!! GRANT ERROR: ', error);
51
55
  });
52
56
  oidc.on('server_error', (ctx, error) => {
@@ -59,15 +63,6 @@ export const createOidcProviderService = (alias = DEFAULT_ALIAS) => {
59
63
  console.info(ctx.oidc.grant);
60
64
  console.error('!!!! USER INFO ERROR: ', error);
61
65
  });
62
- oidc.use(async (_, next) => {
63
- console.log('OIDC REQ .......: ');
64
- console.log(_.request.toJSON());
65
- await next();
66
- console.log(_.oidc);
67
- console.log('OIDC RES .......: ');
68
- console.log(_.response.toJSON());
69
- console.log('OIDC RESPONSE BODY .......: ', _.response.body);
70
- });
71
66
  }
72
67
  _initializedOidc = service.oidc = oidc;
73
68
  },
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
@@ -30,7 +30,7 @@ export const combineConfig = async (context, _unsecure) => {
30
30
  },
31
31
  jwks: {
32
32
  keys: [
33
- await jose.exportJWK(await jose.importPKCS8(cfg.defaultKeys.RS256.pk, 'RS256'))
33
+ await jose.exportJWK(await jose.importPKCS8(cfg.defaultKeys.RS256.pk, 'RS256', { extractable: true }))
34
34
  ]
35
35
  }
36
36
  };
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAE5B,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAAE,OAAgB,EAAE,SAAkB,EAA0B,EAAE;IAClG,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAA;IAE5B,MAAM,aAAa,GAAkB;QACnC,GAAG,GAAG,CAAC,mBAAmB;QAC1B,OAAO,EAAE;YACP,GAAG,GAAG,CAAC,OAAO;YACd,GAAG,CAAC,GAAG,CAAC,mBAAmB,EAAE,OAAO,IAAI,EAAE,CAAC;SAC5C,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC9C,MAAM,EAAE;YACN,KAAK,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,GAAG,CAAC,mBAAmB,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;YACnF,OAAO,EAAE;gBACP,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,oBAAoB;gBAC3F,GAAG,GAAG,CAAC,mBAAmB,EAAE,MAAM,EAAE,OAAO,IAAI,EAAE;aAClD;YACD,GAAG,GAAG,CAAC,mBAAmB,EAAE,MAAM;SACnC;QACD,MAAM,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,GAAG,CAAC,mBAAmB,EAAE,MAAM,IAAI,EAAE,CAAC;QACzF,QAAQ,EAAE;YACR,GAAG,GAAG,CAAC,mBAAmB,EAAE,QAAQ;YACpC,eAAe,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;YACnC,qBAAqB;YACrB,eAAe;YACf,kEAAkE;YAClE,gCAAgC;YAChC,mBAAmB;YACnB,2DAA2D;YAC3D,KAAK;SACN;QACD,IAAI,EAAE;YACJ,IAAI,EAAE;gBACJ,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;aAChF;SACF;KACF,CAAA;IAED,OAAO,aAAa,CAAA;AACtB,CAAC,CAAA"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAE5B,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAAE,OAAgB,EAAE,SAAkB,EAA0B,EAAE;IAClG,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAA;IAE5B,MAAM,aAAa,GAAkB;QACnC,GAAG,GAAG,CAAC,mBAAmB;QAC1B,OAAO,EAAE;YACP,GAAG,GAAG,CAAC,OAAO;YACd,GAAG,CAAC,GAAG,CAAC,mBAAmB,EAAE,OAAO,IAAI,EAAE,CAAC;SAC5C,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC9C,MAAM,EAAE;YACN,KAAK,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,GAAG,CAAC,mBAAmB,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;YACnF,OAAO,EAAE;gBACP,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,oBAAoB;gBAC3F,GAAG,GAAG,CAAC,mBAAmB,EAAE,MAAM,EAAE,OAAO,IAAI,EAAE;aAClD;YACD,GAAG,GAAG,CAAC,mBAAmB,EAAE,MAAM;SACnC;QACD,MAAM,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,GAAG,CAAC,mBAAmB,EAAE,MAAM,IAAI,EAAE,CAAC;QACzF,QAAQ,EAAE;YACR,GAAG,GAAG,CAAC,mBAAmB,EAAE,QAAQ;YACpC,eAAe,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;YACnC,qBAAqB;YACrB,eAAe;YACf,kEAAkE;YAClE,gCAAgC;YAChC,mBAAmB;YACnB,2DAA2D;YAC3D,KAAK;SACN;QACD,IAAI,EAAE;YACJ,IAAI,EAAE;gBACJ,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;aACvG;SACF;KACF,CAAA;IAED,OAAO,aAAa,CAAA;AACtB,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@owlmeans/server-oidc-provider",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsc -b",
8
8
  "dev": "sleep 306 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
9
- "watch": "tsc -b -w --preserveWatchOutput --pretty"
9
+ "watch": "tsc -b -w --preserveWatchOutput --pretty",
10
+ "test": "bun test ./tests"
10
11
  },
11
12
  "main": "build/index.js",
12
13
  "module": "build/index.js",
@@ -21,27 +22,30 @@
21
22
  }
22
23
  },
23
24
  "devDependencies": {
25
+ "@owlmeans/dep-config": "workspace:*",
26
+ "@owlmeans/context": "^0.1.4",
27
+ "@types/bun": "^1.3.0",
24
28
  "@types/node": "^24.10.1",
25
- "@types/oidc-provider": "^8.5.2",
29
+ "@types/oidc-provider": "9.5.0",
26
30
  "nodemon": "^3.1.11",
27
31
  "npm-check": "^6.0.1",
28
- "typescript": "^5.8.3"
32
+ "typescript": "^6.0.2"
29
33
  },
30
34
  "peerDependencies": {
31
35
  "fastify": "*"
32
36
  },
33
37
  "dependencies": {
34
38
  "@noble/hashes": "^1.5.0",
35
- "@owlmeans/client-module": "^0.1.2",
36
- "@owlmeans/config": "^0.1.2",
37
- "@owlmeans/context": "^0.1.2",
38
- "@owlmeans/oidc": "^0.1.2",
39
- "@owlmeans/route": "^0.1.2",
40
- "@owlmeans/server-api": "^0.1.2",
41
- "@owlmeans/server-context": "^0.1.2",
39
+ "@owlmeans/client-entrypoint": "^0.1.4",
40
+ "@owlmeans/config": "^0.1.4",
41
+ "@owlmeans/context": "^0.1.4",
42
+ "@owlmeans/oidc": "^0.1.4",
43
+ "@owlmeans/route": "^0.1.4",
44
+ "@owlmeans/server-api": "^0.1.4",
45
+ "@owlmeans/server-context": "^0.1.4",
42
46
  "@scure/base": "^1.1.9",
43
- "jose": "5.9.6",
44
- "oidc-provider": "8.5.2"
47
+ "jose": "6.2.3",
48
+ "oidc-provider": "9.8.4"
45
49
  },
46
50
  "publishConfig": {
47
51
  "access": "public"
package/src/service.ts CHANGED
@@ -4,7 +4,7 @@ import { DEFAULT_PATH, INTERACTION } from '@owlmeans/oidc'
4
4
  import type { Config, Context, OidcAccountService, OidcAdapterService, OidcProviderService } from './types.js'
5
5
  import Provider from 'oidc-provider'
6
6
  import type { BasicRoute } from '@owlmeans/route'
7
- import type { ClientModule } from '@owlmeans/client-module'
7
+ import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
8
8
  import { SEP } from '@owlmeans/route'
9
9
  import { makeSecurityHelper } from '@owlmeans/config'
10
10
  import { combineConfig } from './utils/config.js'
@@ -38,7 +38,7 @@ export const createOidcProviderService = (alias: string = DEFAULT_ALIAS): OidcPr
38
38
 
39
39
  interactions: {
40
40
  url: async (_, interaction) => {
41
- const module = context.module<ClientModule>(INTERACTION)
41
+ const module = context.entrypoint<ClientEntrypoint>(INTERACTION)
42
42
  const [uri] = await module.call<string>({ params: { uid: interaction.uid } })
43
43
  return uri
44
44
  }
@@ -49,20 +49,25 @@ export const createOidcProviderService = (alias: string = DEFAULT_ALIAS): OidcPr
49
49
  const base = SEP + (cfg.basePath ?? DEFAULT_PATH)
50
50
 
51
51
  api.server.use(base, oidc.callback())
52
- oidc.use(async (ctx, next) => {
53
- await next()
54
- const csp = ctx.response.headers['content-security-policy']
55
- if (csp != null) {
56
- // @TODO Make it a little bit nicer - preferably using helmet :)
57
52
 
58
- ctx.response.set('Content-Security-Policy', csp.replace(/form-action 'self'/, 'form-action *'))
53
+ // oidc-provider v9: provider.use() middleware no longer runs post-response after a matched
54
+ // route. CSP rewrite is handled at the Fastify layer via an onSend hook instead.
55
+ api.server.addHook('onSend', async (request, reply, payload) => {
56
+ if (!request.url.startsWith(base)) {
57
+ return payload
59
58
  }
59
+ const csp = reply.getHeader('content-security-policy')
60
+ if (typeof csp === 'string' && csp.includes("form-action 'self'")) {
61
+ reply.header('Content-Security-Policy', csp.replace(/form-action 'self'/, 'form-action *'))
62
+ }
63
+ return payload
60
64
  })
65
+
61
66
  if (context.cfg.debug?.all || context.cfg.debug?.oidc) {
62
67
 
63
68
  oidc.on('grant.error', (_, error) => {
64
69
  console.warn('GRANT ERROR .......: ')
65
- console.info(oidc.issuer, _.request.toJSON(), _.body)
70
+ console.info(oidc.issuer)
66
71
  console.error('!!!! GRANT ERROR: ', error)
67
72
  })
68
73
 
@@ -78,16 +83,6 @@ export const createOidcProviderService = (alias: string = DEFAULT_ALIAS): OidcPr
78
83
  console.error('!!!! USER INFO ERROR: ', error)
79
84
  })
80
85
 
81
- oidc.use(async (_, next) => {
82
- console.log('OIDC REQ .......: ')
83
- console.log(_.request.toJSON())
84
- await next()
85
- console.log(_.oidc)
86
- console.log('OIDC RES .......: ')
87
- console.log(_.response.toJSON())
88
- console.log('OIDC RESPONSE BODY .......: ', _.response.body)
89
- })
90
-
91
86
  }
92
87
 
93
88
  _initializedOidc = service.oidc = oidc
@@ -34,7 +34,7 @@ export const combineConfig = async (context: Context, _unsecure: boolean): Promi
34
34
  },
35
35
  jwks: {
36
36
  keys: [
37
- await jose.exportJWK(await jose.importPKCS8(cfg.defaultKeys.RS256.pk, 'RS256'))
37
+ await jose.exportJWK(await jose.importPKCS8(cfg.defaultKeys.RS256.pk, 'RS256', { extractable: true }))
38
38
  ]
39
39
  }
40
40
  }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.spec.d.ts","sourceRoot":"","sources":["config.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,41 @@
1
+ import { describe, it, expect } from 'bun:test'
2
+ import { generateKeyPairSync } from 'node:crypto'
3
+ import { AppType, Layer, makeBasicContext } from '@owlmeans/context'
4
+ import type { BasicContext } from '@owlmeans/context'
5
+ import { combineConfig } from '../src/utils/config.js'
6
+ import type { Config } from '../src/types.js'
7
+
8
+ const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 })
9
+ const TEST_PKCS8_PEM = privateKey.export({ type: 'pkcs8', format: 'pem' }).toString()
10
+
11
+ const makeTestContext = () => {
12
+ const cfg: Config = {
13
+ ready: false,
14
+ service: 'server-oidc-provider-tests',
15
+ layer: Layer.Service,
16
+ type: AppType.Backend,
17
+ services: {},
18
+ debug: { all: false },
19
+ oidc: {
20
+ clients: [],
21
+ defaultKeys: {
22
+ RS256: { pk: TEST_PKCS8_PEM },
23
+ },
24
+ },
25
+ } as unknown as Config
26
+
27
+ return makeBasicContext(cfg) as BasicContext<Config>
28
+ }
29
+
30
+ describe('combineConfig (jose v6 extractable key)', () => {
31
+ it('produces a JWKS with an RS256 key', async () => {
32
+ const context = makeTestContext()
33
+ const config = await combineConfig(context as any, true)
34
+
35
+ expect(config.jwks).toBeDefined()
36
+ expect(Array.isArray(config.jwks?.keys)).toBe(true)
37
+ const keys = config.jwks?.keys ?? []
38
+ expect(keys.length).toBeGreaterThan(0)
39
+ expect(keys[0].kty).toBe('RSA')
40
+ })
41
+ })
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": [
3
+ "@owlmeans/dep-config/tsconfig.base.json",
4
+ "@owlmeans/dep-config/tsconfig.node.json"
5
+ ],
6
+ "compilerOptions": {
7
+ "types": ["bun"],
8
+ "rootDir": "../",
9
+ "noEmit": true
10
+ },
11
+ "include": ["./**/*", "../src/**/*"]
12
+ }
package/tsconfig.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "extends": [
3
- "../tsconfig.default.json",
3
+ "@owlmeans/dep-config/tsconfig.base.json",
4
+ "@owlmeans/dep-config/tsconfig.node.json"
4
5
  ],
5
6
  "compilerOptions": {
6
- "rootDir": "./src/", /* Specify the root folder within your source files. */
7
- "outDir": "./build/", /* Specify an output folder for all emitted files. */
8
- "moduleResolution": "Bundler",
7
+ "rootDir": "./src/",
8
+ "outDir": "./build/"
9
9
  },
10
10
  "exclude": [
11
11
  "./dist/**/*",
12
12
  "./build/**/*",
13
+ "./tests/**/*",
13
14
  "./*.ts"
14
15
  ]
15
- }
16
+ }