@enterprisestandard/react 0.0.5-beta.20260115.1 → 0.0.5-beta.20260115.3

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/iam.js ADDED
@@ -0,0 +1,680 @@
1
+ import { groupResourceSchema, userSchema } from './types/scim-schema.js';
2
+ const SCIM_CONTENT_TYPE = 'application/scim+json';
3
+ /**
4
+ * Send SCIM error response
5
+ */
6
+ function scimErrorResponse(status, detail, scimType) {
7
+ return new Response(JSON.stringify({
8
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
9
+ status: String(status),
10
+ scimType,
11
+ detail,
12
+ }), {
13
+ status,
14
+ headers: { 'Content-Type': SCIM_CONTENT_TYPE },
15
+ });
16
+ }
17
+ /**
18
+ * Send SCIM list response
19
+ */
20
+ function scimListResponse(resources) {
21
+ return new Response(JSON.stringify({
22
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:ListResponse'],
23
+ totalResults: resources.length,
24
+ startIndex: 1,
25
+ itemsPerPage: resources.length,
26
+ Resources: resources,
27
+ }), {
28
+ status: 200,
29
+ headers: { 'Content-Type': SCIM_CONTENT_TYPE },
30
+ });
31
+ }
32
+ /**
33
+ * Send SCIM resource response
34
+ */
35
+ function scimResourceResponse(resource, status = 200) {
36
+ return new Response(JSON.stringify(resource), {
37
+ status,
38
+ headers: { 'Content-Type': SCIM_CONTENT_TYPE },
39
+ });
40
+ }
41
+ /**
42
+ * Convert StoredGroup to GroupResource for SCIM response
43
+ */
44
+ function storedGroupToResource(group) {
45
+ return {
46
+ schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
47
+ id: group.id,
48
+ externalId: group.externalId,
49
+ displayName: group.displayName,
50
+ members: group.members,
51
+ meta: {
52
+ resourceType: 'Group',
53
+ created: group.createdAt.toISOString(),
54
+ lastModified: group.updatedAt.toISOString(),
55
+ },
56
+ };
57
+ }
58
+ /**
59
+ * Generate a UUID for new resources
60
+ */
61
+ function generateId() {
62
+ return crypto.randomUUID();
63
+ }
64
+ /**
65
+ * Creates an IAM service instance.
66
+ *
67
+ * - If `url` is configured, enables outbound SCIM operations to external IAM
68
+ * - If `group_store` is configured, enables inbound SCIM operations from external IAM
69
+ *
70
+ * @param config - IAM configuration
71
+ * @param workload - Workload instance for authentication
72
+ * @returns IAM service instance
73
+ */
74
+ export function iam(config, workload) {
75
+ const { url, group_store } = config;
76
+ /**
77
+ * Build headers for outgoing SCIM request using workload auth
78
+ */
79
+ async function buildHeaders() {
80
+ const token = await workload.getToken();
81
+ return new Headers({
82
+ 'Content-Type': SCIM_CONTENT_TYPE,
83
+ Accept: SCIM_CONTENT_TYPE,
84
+ Authorization: `Bearer ${token}`,
85
+ });
86
+ }
87
+ /**
88
+ * Make an outgoing SCIM request to external IAM
89
+ */
90
+ async function scimRequest(method, endpoint, body, validator) {
91
+ if (!url) {
92
+ return {
93
+ success: false,
94
+ error: {
95
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
96
+ status: '500',
97
+ detail: 'IAM URL not configured for outgoing requests',
98
+ },
99
+ status: 500,
100
+ };
101
+ }
102
+ const requestUrl = `${url}${endpoint}`;
103
+ try {
104
+ const headers = await buildHeaders();
105
+ const response = await fetch(requestUrl, {
106
+ method,
107
+ headers,
108
+ body: body ? JSON.stringify(body) : undefined,
109
+ });
110
+ const responseData = await response.json();
111
+ if (!response.ok) {
112
+ return {
113
+ success: false,
114
+ error: responseData,
115
+ status: response.status,
116
+ };
117
+ }
118
+ // Validate response if validator provided
119
+ if (validator) {
120
+ const validationResult = await validator['~standard'].validate(responseData);
121
+ if ('issues' in validationResult) {
122
+ return {
123
+ success: false,
124
+ error: {
125
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
126
+ status: '400',
127
+ scimType: 'invalidValue',
128
+ detail: `Response validation failed: ${validationResult.issues?.map((i) => i.message).join('; ')}`,
129
+ },
130
+ status: 400,
131
+ };
132
+ }
133
+ return {
134
+ success: true,
135
+ data: validationResult.value,
136
+ status: response.status,
137
+ };
138
+ }
139
+ return {
140
+ success: true,
141
+ data: responseData,
142
+ status: response.status,
143
+ };
144
+ }
145
+ catch (error) {
146
+ return {
147
+ success: false,
148
+ error: {
149
+ schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
150
+ status: '500',
151
+ detail: error instanceof Error ? error.message : 'Unknown error occurred',
152
+ },
153
+ status: 500,
154
+ };
155
+ }
156
+ }
157
+ /**
158
+ * Get the configured SCIM base URL
159
+ */
160
+ function getBaseUrl() {
161
+ return url;
162
+ }
163
+ // Build groups_outbound if url is configured
164
+ let groups_outbound;
165
+ let createUser;
166
+ if (url) {
167
+ /**
168
+ * Create a new user/account in the external IAM provider
169
+ */
170
+ createUser = async (user, options) => {
171
+ const userPayload = {
172
+ ...user,
173
+ schemas: user.schemas ?? [
174
+ 'urn:ietf:params:scim:schemas:core:2.0:User',
175
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
176
+ ],
177
+ };
178
+ const validator = options?.validation ?? userSchema('es-iam');
179
+ return scimRequest('POST', '/Users', userPayload, validator);
180
+ };
181
+ /**
182
+ * Create a new group in the external IAM provider
183
+ */
184
+ async function createGroup(displayName, options) {
185
+ const groupPayload = {
186
+ schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'],
187
+ displayName,
188
+ externalId: options?.externalId,
189
+ members: options?.members,
190
+ };
191
+ const validator = options?.validation ?? groupResourceSchema('es-iam');
192
+ return scimRequest('POST', '/Groups', groupPayload, validator);
193
+ }
194
+ groups_outbound = {
195
+ createGroup,
196
+ };
197
+ }
198
+ // Build groups_inbound if group_store is configured
199
+ let groups_inbound;
200
+ if (group_store) {
201
+ // Capture reference to avoid undefined checks in nested functions
202
+ const store = group_store;
203
+ /**
204
+ * Validate authorization header for inbound requests
205
+ */
206
+ const validateAuth = async (request) => {
207
+ const auth = request.headers.get('Authorization');
208
+ if (!auth || !auth.startsWith('Bearer ')) {
209
+ return false;
210
+ }
211
+ // Validate the token using workload
212
+ try {
213
+ const token = auth.substring(7);
214
+ const result = await workload.validateToken(token);
215
+ return result.valid;
216
+ }
217
+ catch {
218
+ return false;
219
+ }
220
+ };
221
+ /**
222
+ * Handle inbound SCIM requests for group management
223
+ */
224
+ const handler = async (request, handlerConfig) => {
225
+ // Validate auth
226
+ const isAuthorized = await validateAuth(request);
227
+ if (!isAuthorized) {
228
+ return scimErrorResponse(401, 'Authorization required');
229
+ }
230
+ const urlObj = new URL(request.url);
231
+ const basePath = handlerConfig?.basePath ?? '/Groups';
232
+ let path = urlObj.pathname;
233
+ // Remove base path to get the resource path
234
+ if (path.startsWith(basePath)) {
235
+ path = path.substring(basePath.length);
236
+ }
237
+ // Parse group ID from path
238
+ const groupIdMatch = path.match(/^\/([^/]+)$/);
239
+ const groupId = groupIdMatch?.[1];
240
+ const method = request.method;
241
+ try {
242
+ // Route to appropriate handler
243
+ if (groupId) {
244
+ // Operations on specific group
245
+ switch (method) {
246
+ case 'GET':
247
+ return await handleGetGroup(groupId);
248
+ case 'PUT':
249
+ return await handleReplaceGroup(request, groupId);
250
+ case 'PATCH':
251
+ return await handlePatchGroup(request, groupId);
252
+ case 'DELETE':
253
+ return await handleDeleteGroup(groupId);
254
+ default:
255
+ return scimErrorResponse(405, 'Method not allowed');
256
+ }
257
+ }
258
+ else if (path === '' || path === '/') {
259
+ // Operations on groups collection
260
+ switch (method) {
261
+ case 'GET':
262
+ return await handleListGroups();
263
+ case 'POST':
264
+ return await handleCreateGroup(request);
265
+ default:
266
+ return scimErrorResponse(405, 'Method not allowed');
267
+ }
268
+ }
269
+ return scimErrorResponse(404, 'Resource not found');
270
+ }
271
+ catch (error) {
272
+ console.error('Groups inbound handler error:', error);
273
+ return scimErrorResponse(500, error instanceof Error ? error.message : 'Internal server error');
274
+ }
275
+ };
276
+ /**
277
+ * List all groups
278
+ */
279
+ const handleListGroups = async () => {
280
+ const groups = await store.list();
281
+ const resources = groups.map(storedGroupToResource);
282
+ return scimListResponse(resources);
283
+ };
284
+ /**
285
+ * Get a group by ID
286
+ */
287
+ const handleGetGroup = async (id) => {
288
+ const group = await store.get(id);
289
+ if (!group) {
290
+ return scimErrorResponse(404, `Group ${id} not found`, 'invalidValue');
291
+ }
292
+ return scimResourceResponse(storedGroupToResource(group));
293
+ };
294
+ /**
295
+ * Create a new group
296
+ */
297
+ const handleCreateGroup = async (request) => {
298
+ const body = (await request.json());
299
+ if (!body.displayName) {
300
+ return scimErrorResponse(400, 'displayName is required', 'invalidValue');
301
+ }
302
+ const now = new Date();
303
+ const storedGroup = {
304
+ id: generateId(),
305
+ displayName: body.displayName,
306
+ externalId: body.externalId,
307
+ members: body.members,
308
+ createdAt: now,
309
+ updatedAt: now,
310
+ };
311
+ await store.upsert(storedGroup);
312
+ return scimResourceResponse(storedGroupToResource(storedGroup), 201);
313
+ };
314
+ /**
315
+ * Replace a group (PUT)
316
+ */
317
+ const handleReplaceGroup = async (request, id) => {
318
+ const existing = await store.get(id);
319
+ if (!existing) {
320
+ return scimErrorResponse(404, `Group ${id} not found`, 'invalidValue');
321
+ }
322
+ const body = (await request.json());
323
+ const updatedGroup = {
324
+ ...existing,
325
+ displayName: body.displayName ?? existing.displayName,
326
+ externalId: body.externalId,
327
+ members: body.members,
328
+ updatedAt: new Date(),
329
+ };
330
+ await store.upsert(updatedGroup);
331
+ return scimResourceResponse(storedGroupToResource(updatedGroup));
332
+ };
333
+ /**
334
+ * Patch a group (PATCH)
335
+ */
336
+ const handlePatchGroup = async (request, id) => {
337
+ const existing = await store.get(id);
338
+ if (!existing) {
339
+ return scimErrorResponse(404, `Group ${id} not found`, 'invalidValue');
340
+ }
341
+ const body = (await request.json());
342
+ const operations = body.Operations ?? [];
343
+ const updated = { ...existing };
344
+ for (const op of operations) {
345
+ if (op.op === 'replace' && op.path && op.value !== undefined) {
346
+ if (op.path === 'displayName') {
347
+ updated.displayName = op.value;
348
+ }
349
+ }
350
+ else if (op.op === 'add' && op.path && op.value !== undefined) {
351
+ if (op.path === 'members') {
352
+ const newMembers = op.value;
353
+ updated.members = [...(updated.members ?? []), ...newMembers];
354
+ }
355
+ }
356
+ else if (op.op === 'remove' && op.path) {
357
+ if (op.path.startsWith('members[')) {
358
+ // Parse member filter like members[value eq "user-id"]
359
+ const match = op.path.match(/members\[value eq "([^"]+)"\]/);
360
+ if (match) {
361
+ updated.members = (updated.members ?? []).filter((m) => m.value !== match[1]);
362
+ }
363
+ }
364
+ }
365
+ }
366
+ updated.updatedAt = new Date();
367
+ await store.upsert(updated);
368
+ return scimResourceResponse(storedGroupToResource(updated));
369
+ };
370
+ /**
371
+ * Delete a group
372
+ */
373
+ const handleDeleteGroup = async (id) => {
374
+ const existing = await store.get(id);
375
+ if (!existing) {
376
+ return scimErrorResponse(404, `Group ${id} not found`, 'invalidValue');
377
+ }
378
+ await store.delete(id);
379
+ return new Response(null, { status: 204 });
380
+ };
381
+ groups_inbound = {
382
+ handler,
383
+ };
384
+ }
385
+ // Build users_inbound if user_store is configured
386
+ let users_inbound;
387
+ if (config.user_store) {
388
+ // Capture reference to avoid undefined checks in nested functions
389
+ const store = config.user_store;
390
+ /**
391
+ * Validate authorization header for inbound requests
392
+ */
393
+ async function validateAuth(request) {
394
+ const auth = request.headers.get('Authorization');
395
+ if (!auth || !auth.startsWith('Bearer ')) {
396
+ return false;
397
+ }
398
+ // Validate the token using workload
399
+ try {
400
+ const token = auth.substring(7);
401
+ const result = await workload.validateToken(token);
402
+ return result.valid;
403
+ }
404
+ catch {
405
+ return false;
406
+ }
407
+ }
408
+ /**
409
+ * Convert StoredUser to SCIM User for response
410
+ */
411
+ const storedUserToScimUser = (storedUser) => {
412
+ return {
413
+ schemas: [
414
+ 'urn:ietf:params:scim:schemas:core:2.0:User',
415
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
416
+ ],
417
+ id: storedUser.id,
418
+ userName: storedUser.userName || storedUser.email || storedUser.id,
419
+ displayName: storedUser.name || storedUser.userName || storedUser.email,
420
+ name: storedUser.name
421
+ ? {
422
+ givenName: storedUser.name.split(' ')[0],
423
+ familyName: storedUser.name.split(' ').slice(1).join(' ') || undefined,
424
+ }
425
+ : undefined,
426
+ emails: storedUser.email ? [{ value: storedUser.email, primary: true }] : [],
427
+ active: true,
428
+ meta: {
429
+ resourceType: 'User',
430
+ created: storedUser.createdAt.toISOString(),
431
+ lastModified: storedUser.updatedAt.toISOString(),
432
+ },
433
+ };
434
+ };
435
+ /**
436
+ * Convert SCIM User to StoredUser for storage
437
+ */
438
+ const scimUserToStoredUser = (scimUser) => {
439
+ const now = new Date();
440
+ const primaryEmail = scimUser.emails?.find((e) => e.primary)?.value || scimUser.emails?.[0]?.value;
441
+ const name = scimUser.name
442
+ ? `${scimUser.name.givenName || ''} ${scimUser.name.familyName || ''}`.trim()
443
+ : scimUser.displayName;
444
+ // Create minimal SSO data for IAM-provisioned users
445
+ const userId = scimUser.id || generateId();
446
+ const userName = scimUser.userName || primaryEmail || userId;
447
+ return {
448
+ id: userId,
449
+ userName,
450
+ name: name || scimUser.displayName || userName,
451
+ email: primaryEmail || userName,
452
+ avatarUrl: scimUser.profileUrl,
453
+ sso: {
454
+ profile: {
455
+ sub: userId,
456
+ iss: 'iam-provisioned',
457
+ aud: 'iam-provisioned',
458
+ exp: Math.floor(Date.now() / 1000) + 3600,
459
+ iat: Math.floor(Date.now() / 1000),
460
+ email: primaryEmail || userName,
461
+ email_verified: true,
462
+ name: name || scimUser.displayName || userName,
463
+ preferred_username: userName,
464
+ },
465
+ tenant: {
466
+ id: 'iam-provisioned',
467
+ name: 'IAM Provisioned',
468
+ },
469
+ scope: 'openid profile email',
470
+ tokenType: 'Bearer',
471
+ expires: new Date(Date.now() + 3600 * 1000),
472
+ },
473
+ createdAt: scimUser.meta?.created ? new Date(scimUser.meta.created) : now,
474
+ updatedAt: scimUser.meta?.lastModified ? new Date(scimUser.meta.lastModified) : now,
475
+ };
476
+ };
477
+ /**
478
+ * Handle inbound SCIM requests for user management
479
+ */
480
+ const handler = async (request, handlerConfig) => {
481
+ // Validate auth
482
+ const isAuthorized = await validateAuth(request);
483
+ if (!isAuthorized) {
484
+ return scimErrorResponse(401, 'Authorization required');
485
+ }
486
+ const urlObj = new URL(request.url);
487
+ const basePath = handlerConfig?.basePath ?? '/Users';
488
+ let path = urlObj.pathname;
489
+ // Remove base path to get the resource path
490
+ if (path.startsWith(basePath)) {
491
+ path = path.substring(basePath.length);
492
+ }
493
+ // Parse user ID from path
494
+ const userIdMatch = path.match(/^\/([^/]+)$/);
495
+ const userId = userIdMatch?.[1];
496
+ const method = request.method;
497
+ try {
498
+ // Route to appropriate handler
499
+ if (userId) {
500
+ // Operations on specific user
501
+ switch (method) {
502
+ case 'GET':
503
+ return await handleGetUser(userId);
504
+ case 'PUT':
505
+ return await handleReplaceUser(request, userId);
506
+ case 'PATCH':
507
+ return await handlePatchUser(request, userId);
508
+ case 'DELETE':
509
+ return await handleDeleteUser(userId);
510
+ default:
511
+ return scimErrorResponse(405, 'Method not allowed');
512
+ }
513
+ }
514
+ else if (path === '' || path === '/') {
515
+ // Operations on users collection
516
+ switch (method) {
517
+ case 'GET':
518
+ return await handleListUsers();
519
+ case 'POST':
520
+ return await handleCreateUser(request);
521
+ default:
522
+ return scimErrorResponse(405, 'Method not allowed');
523
+ }
524
+ }
525
+ return scimErrorResponse(404, 'Resource not found');
526
+ }
527
+ catch (error) {
528
+ console.error('Users inbound handler error:', error);
529
+ return scimErrorResponse(500, error instanceof Error ? error.message : 'Internal server error');
530
+ }
531
+ };
532
+ /**
533
+ * List all users
534
+ */
535
+ const handleListUsers = async () => {
536
+ // Note: UserStore doesn't have a list method, so we'd need to implement it
537
+ // For now, return empty list - this would need to be enhanced based on UserStore interface
538
+ return scimListResponse([]);
539
+ };
540
+ /**
541
+ * Get a user by ID
542
+ */
543
+ const handleGetUser = async (id) => {
544
+ const user = await store.get(id);
545
+ if (!user) {
546
+ return scimErrorResponse(404, `User ${id} not found`, 'invalidValue');
547
+ }
548
+ return scimResourceResponse(storedUserToScimUser(user));
549
+ };
550
+ /**
551
+ * Create a new user
552
+ */
553
+ const handleCreateUser = async (request) => {
554
+ const body = (await request.json());
555
+ if (!body.userName && !body.emails?.[0]?.value) {
556
+ return scimErrorResponse(400, 'userName or email is required', 'invalidValue');
557
+ }
558
+ const storedUser = scimUserToStoredUser(body);
559
+ await store.upsert(storedUser);
560
+ return scimResourceResponse(storedUserToScimUser(storedUser), 201);
561
+ };
562
+ /**
563
+ * Replace a user (PUT)
564
+ */
565
+ const handleReplaceUser = async (request, id) => {
566
+ const existing = await store.get(id);
567
+ if (!existing) {
568
+ return scimErrorResponse(404, `User ${id} not found`, 'invalidValue');
569
+ }
570
+ const body = (await request.json());
571
+ const updatedUser = scimUserToStoredUser({ ...body, id });
572
+ updatedUser.createdAt = existing.createdAt;
573
+ updatedUser.updatedAt = new Date();
574
+ await store.upsert(updatedUser);
575
+ return scimResourceResponse(storedUserToScimUser(updatedUser));
576
+ };
577
+ /**
578
+ * Patch a user (PATCH)
579
+ */
580
+ const handlePatchUser = async (request, id) => {
581
+ const existing = await store.get(id);
582
+ if (!existing) {
583
+ return scimErrorResponse(404, `User ${id} not found`, 'invalidValue');
584
+ }
585
+ const body = (await request.json());
586
+ const operations = body.Operations ?? [];
587
+ const updated = { ...existing };
588
+ for (const op of operations) {
589
+ if (op.op === 'replace' && op.path && op.value !== undefined) {
590
+ if (op.path === 'displayName') {
591
+ updated.name = op.value;
592
+ }
593
+ else if (op.path === 'userName') {
594
+ updated.userName = op.value;
595
+ }
596
+ else if (op.path.startsWith('name.')) {
597
+ const namePart = op.path.split('.')[1];
598
+ if (!updated.name)
599
+ updated.name = '';
600
+ // Simple name handling - in production you'd want more sophisticated parsing
601
+ if (namePart === 'givenName') {
602
+ updated.name = `${op.value} ${updated.name.split(' ').slice(1).join(' ')}`.trim();
603
+ }
604
+ else if (namePart === 'familyName') {
605
+ updated.name = `${updated.name.split(' ')[0]} ${op.value}`.trim();
606
+ }
607
+ }
608
+ else if (op.path === 'emails') {
609
+ // Note: StoredUser doesn't have emails array, only email string
610
+ // We'll extract the primary email and update the email field
611
+ const emails = op.value;
612
+ const primaryEmail = emails?.find((e) => e.primary)?.value || emails?.[0]?.value;
613
+ if (primaryEmail)
614
+ updated.email = primaryEmail;
615
+ }
616
+ }
617
+ else if (op.op === 'add' && op.path && op.value !== undefined) {
618
+ if (op.path === 'emails') {
619
+ // Note: StoredUser doesn't have emails array, only email string
620
+ // We'll extract the primary email and update the email field
621
+ const newEmails = op.value;
622
+ const primaryEmail = newEmails?.find((e) => e.primary)?.value || newEmails?.[0]?.value;
623
+ if (primaryEmail)
624
+ updated.email = primaryEmail;
625
+ }
626
+ }
627
+ else if (op.op === 'remove' && op.path) {
628
+ if (op.path === 'displayName') {
629
+ updated.name = '';
630
+ }
631
+ }
632
+ }
633
+ updated.updatedAt = new Date();
634
+ await store.upsert(updated);
635
+ return scimResourceResponse(storedUserToScimUser(updated));
636
+ };
637
+ /**
638
+ * Delete a user
639
+ */
640
+ const handleDeleteUser = async (id) => {
641
+ const existing = await store.get(id);
642
+ if (!existing) {
643
+ return scimErrorResponse(404, `User ${id} not found`, 'invalidValue');
644
+ }
645
+ await store.delete(id);
646
+ return new Response(null, { status: 204 });
647
+ };
648
+ users_inbound = {
649
+ handler,
650
+ };
651
+ }
652
+ /**
653
+ * Top-level handler that routes to users_inbound or groups_inbound based on path
654
+ */
655
+ async function topLevelHandler(request, handlerConfig) {
656
+ const urlObj = new URL(request.url);
657
+ const path = urlObj.pathname;
658
+ const usersUrl = handlerConfig?.usersUrl ?? config.usersUrl ?? '/api/iam/Users';
659
+ const groupsUrl = handlerConfig?.groupsUrl ?? config.groupsUrl ?? '/api/iam/Groups';
660
+ // Route to users handler if path matches usersUrl
661
+ if (path.startsWith(usersUrl) && users_inbound) {
662
+ return users_inbound.handler(request, { basePath: usersUrl });
663
+ }
664
+ // Route to groups handler if path matches groupsUrl
665
+ if (path.startsWith(groupsUrl) && groups_inbound) {
666
+ return groups_inbound.handler(request, { basePath: groupsUrl });
667
+ }
668
+ // If neither matches, return 404
669
+ return scimErrorResponse(404, 'Resource not found');
670
+ }
671
+ return {
672
+ ...config,
673
+ createUser,
674
+ getBaseUrl,
675
+ groups_outbound,
676
+ groups_inbound,
677
+ users_inbound,
678
+ handler: topLevelHandler,
679
+ };
680
+ }
package/dist/index.d.ts CHANGED
@@ -24,11 +24,11 @@ export type { GroupStore, StoredGroup } from './group-store';
24
24
  export { InMemoryGroupStore } from './group-store';
25
25
  export type { CreateGroupOptions, CreateUserOptions, GroupsInboundHandlerConfig, IAM, IAMConfig, IAMGroupsInbound, IAMGroupsOutbound, IAMHandlerConfig, IAMUsersInbound, ScimError, ScimListResponse, ScimResult, UsersInboundHandlerConfig, } from './iam';
26
26
  export { iam } from './iam';
27
- export * from './sso-server';
28
27
  export type { SessionStore } from './session-store';
29
28
  export { InMemorySessionStore } from './session-store';
30
29
  export type { SSO, SSOConfig, SSOHandlerConfig } from './sso';
31
30
  export { sso } from './sso';
31
+ export * from './sso-server';
32
32
  export type { CreateTenantRequest, CreateTenantResponse, EnvironmentType, StoredTenant, TenantStatus, TenantStore, TenantWebhookPayload, } from './tenant';
33
33
  export { InMemoryTenantStore, parseTenantRequest, sendTenantWebhook, serializeESConfig, TenantRequestError, } from './tenant';
34
34
  export type { BaseUser } from './types/base-user';