@better-auth/scim 1.5.0-beta.13 → 1.5.0-beta.16
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/.turbo/turbo-build.log +7 -7
- package/dist/index.d.mts +246 -32
- package/dist/index.mjs +175 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +15 -0
- package/src/routes.ts +277 -14
- package/src/scim.management.test.ts +810 -0
- package/src/scim.test.ts +16 -291
- package/src/types.ts +8 -0
- package/vitest.config.ts +6 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
|
|
2
|
-
> @better-auth/scim@1.5.0-beta.
|
|
2
|
+
> @better-auth/scim@1.5.0-beta.16 build /home/runner/work/better-auth/better-auth/packages/scim
|
|
3
3
|
> tsdown
|
|
4
4
|
|
|
5
|
-
[34mℹ[39m tsdown [2mv0.20.
|
|
5
|
+
[34mℹ[39m tsdown [2mv0.20.3[22m powered by rolldown [2mv1.0.0-rc.3[22m
|
|
6
6
|
[34mℹ[39m config file: [4m/home/runner/work/better-auth/better-auth/packages/scim/tsdown.config.ts[24m
|
|
7
7
|
[34mℹ[39m entry: [34msrc/index.ts, src/client.ts[39m
|
|
8
8
|
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
9
9
|
[34mℹ[39m Build start
|
|
10
|
-
[34mℹ[39m [2mdist/[22m[1mindex.mjs[22m [2m
|
|
10
|
+
[34mℹ[39m [2mdist/[22m[1mindex.mjs[22m [2m 45.12 kB[22m [2m│ gzip: 8.92 kB[22m
|
|
11
11
|
[34mℹ[39m [2mdist/[22m[1mclient.mjs[22m [2m 0.19 kB[22m [2m│ gzip: 0.17 kB[22m
|
|
12
|
-
[34mℹ[39m [2mdist/[22mindex.mjs.map [2m
|
|
12
|
+
[34mℹ[39m [2mdist/[22mindex.mjs.map [2m 90.39 kB[22m [2m│ gzip: 17.19 kB[22m
|
|
13
13
|
[34mℹ[39m [2mdist/[22mclient.mjs.map [2m 0.46 kB[22m [2m│ gzip: 0.30 kB[22m
|
|
14
|
-
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [
|
|
14
|
+
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [2m115.17 kB[22m [2m│ gzip: 4.89 kB[22m
|
|
15
15
|
[34mℹ[39m [2mdist/[22m[32m[1mclient.d.mts[22m[39m [2m 0.24 kB[22m [2m│ gzip: 0.20 kB[22m
|
|
16
|
-
[34mℹ[39m 6 files, total:
|
|
16
|
+
[34mℹ[39m 6 files, total: 251.57 kB
|
|
17
17
|
[33m[PLUGIN_TIMINGS] Warning:[0m Your build spent significant time in plugin `rolldown-plugin-dts:generate`. See https://rolldown.rs/options/checks#plugintimings for more details.
|
|
18
18
|
|
|
19
|
-
[32m✔[39m Build complete in [
|
|
19
|
+
[32m✔[39m Build complete in [32m11100ms[39m
|
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import * as better_auth0 from "better-auth";
|
|
|
2
2
|
import { User } from "better-auth";
|
|
3
3
|
import * as better_call0 from "better-call";
|
|
4
4
|
import { Member } from "better-auth/plugins";
|
|
5
|
-
import * as
|
|
5
|
+
import * as zod from "zod";
|
|
6
6
|
|
|
7
7
|
//#region src/types.d.ts
|
|
8
8
|
interface SCIMProvider {
|
|
@@ -10,6 +10,7 @@ interface SCIMProvider {
|
|
|
10
10
|
providerId: string;
|
|
11
11
|
scimToken: string;
|
|
12
12
|
organizationId?: string;
|
|
13
|
+
userId?: string;
|
|
13
14
|
}
|
|
14
15
|
type SCIMName = {
|
|
15
16
|
formatted?: string;
|
|
@@ -21,6 +22,13 @@ type SCIMEmail = {
|
|
|
21
22
|
primary?: boolean;
|
|
22
23
|
};
|
|
23
24
|
type SCIMOptions = {
|
|
25
|
+
/**
|
|
26
|
+
* SCIM provider ownership configuration. When enabled, each provider
|
|
27
|
+
* connection is linked to the user who generated its token
|
|
28
|
+
*/
|
|
29
|
+
providerOwnership?: {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
};
|
|
24
32
|
/**
|
|
25
33
|
* Default list of SCIM providers for testing
|
|
26
34
|
* These will take precedence over the database when present
|
|
@@ -79,9 +87,9 @@ declare const scim: (options?: SCIMOptions) => {
|
|
|
79
87
|
endpoints: {
|
|
80
88
|
generateSCIMToken: better_call0.StrictEndpoint<"/scim/generate-token", {
|
|
81
89
|
method: "POST";
|
|
82
|
-
body:
|
|
83
|
-
providerId:
|
|
84
|
-
organizationId:
|
|
90
|
+
body: zod.ZodObject<{
|
|
91
|
+
providerId: zod.ZodString;
|
|
92
|
+
organizationId: zod.ZodOptional<zod.ZodString>;
|
|
85
93
|
}, better_auth0.$strip>;
|
|
86
94
|
metadata: {
|
|
87
95
|
openapi: {
|
|
@@ -133,6 +141,208 @@ declare const scim: (options?: SCIMOptions) => {
|
|
|
133
141
|
}, {
|
|
134
142
|
scimToken: string;
|
|
135
143
|
}>;
|
|
144
|
+
listSCIMProviderConnections: better_call0.StrictEndpoint<"/scim/list-provider-connections", {
|
|
145
|
+
method: "GET";
|
|
146
|
+
use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
|
|
147
|
+
session: {
|
|
148
|
+
session: Record<string, any> & {
|
|
149
|
+
id: string;
|
|
150
|
+
createdAt: Date;
|
|
151
|
+
updatedAt: Date;
|
|
152
|
+
userId: string;
|
|
153
|
+
expiresAt: Date;
|
|
154
|
+
token: string;
|
|
155
|
+
ipAddress?: string | null | undefined;
|
|
156
|
+
userAgent?: string | null | undefined;
|
|
157
|
+
};
|
|
158
|
+
user: Record<string, any> & {
|
|
159
|
+
id: string;
|
|
160
|
+
createdAt: Date;
|
|
161
|
+
updatedAt: Date;
|
|
162
|
+
email: string;
|
|
163
|
+
emailVerified: boolean;
|
|
164
|
+
name: string;
|
|
165
|
+
image?: string | null | undefined;
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
}>)[];
|
|
169
|
+
metadata: {
|
|
170
|
+
openapi: {
|
|
171
|
+
operationId: string;
|
|
172
|
+
summary: string;
|
|
173
|
+
description: string;
|
|
174
|
+
responses: {
|
|
175
|
+
"200": {
|
|
176
|
+
description: string;
|
|
177
|
+
content: {
|
|
178
|
+
"application/json": {
|
|
179
|
+
schema: {
|
|
180
|
+
type: "object";
|
|
181
|
+
properties: {
|
|
182
|
+
providers: {
|
|
183
|
+
type: string;
|
|
184
|
+
items: {
|
|
185
|
+
type: string;
|
|
186
|
+
properties: {
|
|
187
|
+
id: {
|
|
188
|
+
type: string;
|
|
189
|
+
};
|
|
190
|
+
providerId: {
|
|
191
|
+
type: string;
|
|
192
|
+
};
|
|
193
|
+
organizationId: {
|
|
194
|
+
type: string;
|
|
195
|
+
nullable: boolean;
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
}, {
|
|
209
|
+
providers: {
|
|
210
|
+
id: string;
|
|
211
|
+
providerId: string;
|
|
212
|
+
organizationId: string | null;
|
|
213
|
+
}[];
|
|
214
|
+
}>;
|
|
215
|
+
getSCIMProviderConnection: better_call0.StrictEndpoint<"/scim/get-provider-connection", {
|
|
216
|
+
method: "GET";
|
|
217
|
+
use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
|
|
218
|
+
session: {
|
|
219
|
+
session: Record<string, any> & {
|
|
220
|
+
id: string;
|
|
221
|
+
createdAt: Date;
|
|
222
|
+
updatedAt: Date;
|
|
223
|
+
userId: string;
|
|
224
|
+
expiresAt: Date;
|
|
225
|
+
token: string;
|
|
226
|
+
ipAddress?: string | null | undefined;
|
|
227
|
+
userAgent?: string | null | undefined;
|
|
228
|
+
};
|
|
229
|
+
user: Record<string, any> & {
|
|
230
|
+
id: string;
|
|
231
|
+
createdAt: Date;
|
|
232
|
+
updatedAt: Date;
|
|
233
|
+
email: string;
|
|
234
|
+
emailVerified: boolean;
|
|
235
|
+
name: string;
|
|
236
|
+
image?: string | null | undefined;
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
}>)[];
|
|
240
|
+
query: zod.ZodObject<{
|
|
241
|
+
providerId: zod.ZodString;
|
|
242
|
+
}, better_auth0.$strip>;
|
|
243
|
+
metadata: {
|
|
244
|
+
openapi: {
|
|
245
|
+
operationId: string;
|
|
246
|
+
summary: string;
|
|
247
|
+
description: string;
|
|
248
|
+
responses: {
|
|
249
|
+
"200": {
|
|
250
|
+
description: string;
|
|
251
|
+
content: {
|
|
252
|
+
"application/json": {
|
|
253
|
+
schema: {
|
|
254
|
+
type: "object";
|
|
255
|
+
properties: {
|
|
256
|
+
id: {
|
|
257
|
+
type: string;
|
|
258
|
+
};
|
|
259
|
+
providerId: {
|
|
260
|
+
type: string;
|
|
261
|
+
};
|
|
262
|
+
organizationId: {
|
|
263
|
+
type: string;
|
|
264
|
+
nullable: boolean;
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
"404": {
|
|
272
|
+
description: string;
|
|
273
|
+
};
|
|
274
|
+
"403": {
|
|
275
|
+
description: string;
|
|
276
|
+
};
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
}, {
|
|
281
|
+
id: string;
|
|
282
|
+
providerId: string;
|
|
283
|
+
organizationId: string | null;
|
|
284
|
+
}>;
|
|
285
|
+
deleteSCIMProviderConnection: better_call0.StrictEndpoint<"/scim/delete-provider-connection", {
|
|
286
|
+
method: "POST";
|
|
287
|
+
use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
|
|
288
|
+
session: {
|
|
289
|
+
session: Record<string, any> & {
|
|
290
|
+
id: string;
|
|
291
|
+
createdAt: Date;
|
|
292
|
+
updatedAt: Date;
|
|
293
|
+
userId: string;
|
|
294
|
+
expiresAt: Date;
|
|
295
|
+
token: string;
|
|
296
|
+
ipAddress?: string | null | undefined;
|
|
297
|
+
userAgent?: string | null | undefined;
|
|
298
|
+
};
|
|
299
|
+
user: Record<string, any> & {
|
|
300
|
+
id: string;
|
|
301
|
+
createdAt: Date;
|
|
302
|
+
updatedAt: Date;
|
|
303
|
+
email: string;
|
|
304
|
+
emailVerified: boolean;
|
|
305
|
+
name: string;
|
|
306
|
+
image?: string | null | undefined;
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
}>)[];
|
|
310
|
+
body: zod.ZodObject<{
|
|
311
|
+
providerId: zod.ZodString;
|
|
312
|
+
}, better_auth0.$strip>;
|
|
313
|
+
metadata: {
|
|
314
|
+
openapi: {
|
|
315
|
+
operationId: string;
|
|
316
|
+
summary: string;
|
|
317
|
+
description: string;
|
|
318
|
+
responses: {
|
|
319
|
+
"200": {
|
|
320
|
+
description: string;
|
|
321
|
+
content: {
|
|
322
|
+
"application/json": {
|
|
323
|
+
schema: {
|
|
324
|
+
type: "object";
|
|
325
|
+
properties: {
|
|
326
|
+
success: {
|
|
327
|
+
type: string;
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
};
|
|
331
|
+
};
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
"404": {
|
|
335
|
+
description: string;
|
|
336
|
+
};
|
|
337
|
+
"403": {
|
|
338
|
+
description: string;
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
};
|
|
342
|
+
};
|
|
343
|
+
}, {
|
|
344
|
+
success: boolean;
|
|
345
|
+
}>;
|
|
136
346
|
getSCIMUser: better_call0.StrictEndpoint<"/scim/v2/Users/:userId", {
|
|
137
347
|
method: "GET";
|
|
138
348
|
metadata: {
|
|
@@ -411,17 +621,17 @@ declare const scim: (options?: SCIMOptions) => {
|
|
|
411
621
|
}>;
|
|
412
622
|
createSCIMUser: better_call0.StrictEndpoint<"/scim/v2/Users", {
|
|
413
623
|
method: "POST";
|
|
414
|
-
body:
|
|
415
|
-
userName:
|
|
416
|
-
externalId:
|
|
417
|
-
name:
|
|
418
|
-
formatted:
|
|
419
|
-
givenName:
|
|
420
|
-
familyName:
|
|
624
|
+
body: zod.ZodObject<{
|
|
625
|
+
userName: zod.ZodString;
|
|
626
|
+
externalId: zod.ZodOptional<zod.ZodString>;
|
|
627
|
+
name: zod.ZodOptional<zod.ZodObject<{
|
|
628
|
+
formatted: zod.ZodOptional<zod.ZodString>;
|
|
629
|
+
givenName: zod.ZodOptional<zod.ZodString>;
|
|
630
|
+
familyName: zod.ZodOptional<zod.ZodString>;
|
|
421
631
|
}, better_auth0.$strip>>;
|
|
422
|
-
emails:
|
|
423
|
-
value:
|
|
424
|
-
primary:
|
|
632
|
+
emails: zod.ZodOptional<zod.ZodArray<zod.ZodObject<{
|
|
633
|
+
value: zod.ZodEmail;
|
|
634
|
+
primary: zod.ZodOptional<zod.ZodBoolean>;
|
|
425
635
|
}, better_auth0.$strip>>>;
|
|
426
636
|
}, better_auth0.$strip>;
|
|
427
637
|
metadata: {
|
|
@@ -700,16 +910,16 @@ declare const scim: (options?: SCIMOptions) => {
|
|
|
700
910
|
}>;
|
|
701
911
|
patchSCIMUser: better_call0.StrictEndpoint<"/scim/v2/Users/:userId", {
|
|
702
912
|
method: "PATCH";
|
|
703
|
-
body:
|
|
704
|
-
schemas:
|
|
705
|
-
Operations:
|
|
706
|
-
op:
|
|
913
|
+
body: zod.ZodObject<{
|
|
914
|
+
schemas: zod.ZodArray<zod.ZodString>;
|
|
915
|
+
Operations: zod.ZodArray<zod.ZodObject<{
|
|
916
|
+
op: zod.ZodPipe<zod.ZodDefault<zod.ZodString>, zod.ZodEnum<{
|
|
707
917
|
add: "add";
|
|
708
918
|
remove: "remove";
|
|
709
919
|
replace: "replace";
|
|
710
920
|
}>>;
|
|
711
|
-
path:
|
|
712
|
-
value:
|
|
921
|
+
path: zod.ZodOptional<zod.ZodString>;
|
|
922
|
+
value: zod.ZodAny;
|
|
713
923
|
}, better_auth0.$strip>>;
|
|
714
924
|
}, better_auth0.$strip>;
|
|
715
925
|
metadata: {
|
|
@@ -1076,17 +1286,17 @@ declare const scim: (options?: SCIMOptions) => {
|
|
|
1076
1286
|
}, void>;
|
|
1077
1287
|
updateSCIMUser: better_call0.StrictEndpoint<"/scim/v2/Users/:userId", {
|
|
1078
1288
|
method: "PUT";
|
|
1079
|
-
body:
|
|
1080
|
-
userName:
|
|
1081
|
-
externalId:
|
|
1082
|
-
name:
|
|
1083
|
-
formatted:
|
|
1084
|
-
givenName:
|
|
1085
|
-
familyName:
|
|
1289
|
+
body: zod.ZodObject<{
|
|
1290
|
+
userName: zod.ZodString;
|
|
1291
|
+
externalId: zod.ZodOptional<zod.ZodString>;
|
|
1292
|
+
name: zod.ZodOptional<zod.ZodObject<{
|
|
1293
|
+
formatted: zod.ZodOptional<zod.ZodString>;
|
|
1294
|
+
givenName: zod.ZodOptional<zod.ZodString>;
|
|
1295
|
+
familyName: zod.ZodOptional<zod.ZodString>;
|
|
1086
1296
|
}, better_auth0.$strip>>;
|
|
1087
|
-
emails:
|
|
1088
|
-
value:
|
|
1089
|
-
primary:
|
|
1297
|
+
emails: zod.ZodOptional<zod.ZodArray<zod.ZodObject<{
|
|
1298
|
+
value: zod.ZodEmail;
|
|
1299
|
+
primary: zod.ZodOptional<zod.ZodBoolean>;
|
|
1090
1300
|
}, better_auth0.$strip>>>;
|
|
1091
1301
|
}, better_auth0.$strip>;
|
|
1092
1302
|
metadata: {
|
|
@@ -1365,8 +1575,8 @@ declare const scim: (options?: SCIMOptions) => {
|
|
|
1365
1575
|
}>;
|
|
1366
1576
|
listSCIMUsers: better_call0.StrictEndpoint<"/scim/v2/Users", {
|
|
1367
1577
|
method: "GET";
|
|
1368
|
-
query:
|
|
1369
|
-
filter:
|
|
1578
|
+
query: zod.ZodOptional<zod.ZodObject<{
|
|
1579
|
+
filter: zod.ZodOptional<zod.ZodString>;
|
|
1370
1580
|
}, better_auth0.$strip>>;
|
|
1371
1581
|
metadata: {
|
|
1372
1582
|
allowedMediaTypes: string[];
|
|
@@ -3205,6 +3415,10 @@ declare const scim: (options?: SCIMOptions) => {
|
|
|
3205
3415
|
schema: {
|
|
3206
3416
|
scimProvider: {
|
|
3207
3417
|
fields: {
|
|
3418
|
+
userId?: {
|
|
3419
|
+
type: "string";
|
|
3420
|
+
required: false;
|
|
3421
|
+
} | undefined;
|
|
3208
3422
|
providerId: {
|
|
3209
3423
|
type: "string";
|
|
3210
3424
|
required: true;
|
package/dist/index.mjs
CHANGED
|
@@ -618,6 +618,55 @@ const generateSCIMTokenBodySchema = z.object({
|
|
|
618
618
|
providerId: z.string().meta({ description: "Unique provider identifier" }),
|
|
619
619
|
organizationId: z.string().optional().meta({ description: "Optional organization id" })
|
|
620
620
|
});
|
|
621
|
+
const getSCIMProviderConnectionQuerySchema = z.object({ providerId: z.string() });
|
|
622
|
+
const deleteSCIMProviderConnectionBodySchema = z.object({ providerId: z.string() });
|
|
623
|
+
async function getSCIMUserOrgIds(ctx, userId) {
|
|
624
|
+
const members = await ctx.context.adapter.findMany({
|
|
625
|
+
model: "member",
|
|
626
|
+
where: [{
|
|
627
|
+
field: "userId",
|
|
628
|
+
value: userId
|
|
629
|
+
}]
|
|
630
|
+
});
|
|
631
|
+
return new Set(members.map((m) => m.organizationId));
|
|
632
|
+
}
|
|
633
|
+
function normalizeSCIMProvider(provider) {
|
|
634
|
+
return {
|
|
635
|
+
id: provider.id,
|
|
636
|
+
providerId: provider.providerId,
|
|
637
|
+
organizationId: provider.organizationId ?? null
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
async function findOrganizationMember(ctx, userId, organizationId) {
|
|
641
|
+
return ctx.context.adapter.findOne({
|
|
642
|
+
model: "member",
|
|
643
|
+
where: [{
|
|
644
|
+
field: "userId",
|
|
645
|
+
value: userId
|
|
646
|
+
}, {
|
|
647
|
+
field: "organizationId",
|
|
648
|
+
value: organizationId
|
|
649
|
+
}]
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
async function assertSCIMProviderAccess(ctx, userId, provider) {
|
|
653
|
+
if (provider.organizationId) {
|
|
654
|
+
if (!ctx.context.hasPlugin("organization")) throw new APIError("FORBIDDEN", { message: "Organization plugin is required to access this SCIM provider" });
|
|
655
|
+
if (!await findOrganizationMember(ctx, userId, provider.organizationId)) throw new APIError("FORBIDDEN", { message: "You must be a member of the organization to access this provider" });
|
|
656
|
+
} else if (provider.userId && provider.userId !== userId) throw new APIError("FORBIDDEN", { message: "You must be the owner to access this provider" });
|
|
657
|
+
}
|
|
658
|
+
async function checkSCIMProviderAccess(ctx, userId, providerId) {
|
|
659
|
+
const provider = await ctx.context.adapter.findOne({
|
|
660
|
+
model: "scimProvider",
|
|
661
|
+
where: [{
|
|
662
|
+
field: "providerId",
|
|
663
|
+
value: providerId
|
|
664
|
+
}]
|
|
665
|
+
});
|
|
666
|
+
if (!provider) throw new APIError("NOT_FOUND", { message: "SCIM provider not found" });
|
|
667
|
+
await assertSCIMProviderAccess(ctx, userId, provider);
|
|
668
|
+
return provider;
|
|
669
|
+
}
|
|
621
670
|
const generateSCIMToken = (opts) => createAuthEndpoint("/scim/generate-token", {
|
|
622
671
|
method: "POST",
|
|
623
672
|
body: generateSCIMTokenBodySchema,
|
|
@@ -643,16 +692,7 @@ const generateSCIMToken = (opts) => createAuthEndpoint("/scim/generate-token", {
|
|
|
643
692
|
if (organizationId && !ctx.context.hasPlugin("organization")) throw new APIError("BAD_REQUEST", { message: "Restricting a token to an organization requires the organization plugin" });
|
|
644
693
|
let member = null;
|
|
645
694
|
if (organizationId) {
|
|
646
|
-
member = await ctx.
|
|
647
|
-
model: "member",
|
|
648
|
-
where: [{
|
|
649
|
-
field: "userId",
|
|
650
|
-
value: user.id
|
|
651
|
-
}, {
|
|
652
|
-
field: "organizationId",
|
|
653
|
-
value: organizationId
|
|
654
|
-
}]
|
|
655
|
-
});
|
|
695
|
+
member = await findOrganizationMember(ctx, user.id, organizationId);
|
|
656
696
|
if (!member) throw new APIError("FORBIDDEN", { message: "You are not a member of the organization" });
|
|
657
697
|
}
|
|
658
698
|
const scimProvider = await ctx.context.adapter.findOne({
|
|
@@ -665,13 +705,16 @@ const generateSCIMToken = (opts) => createAuthEndpoint("/scim/generate-token", {
|
|
|
665
705
|
value: organizationId
|
|
666
706
|
}] : []]
|
|
667
707
|
});
|
|
668
|
-
if (scimProvider)
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
708
|
+
if (scimProvider) {
|
|
709
|
+
await assertSCIMProviderAccess(ctx, user.id, scimProvider);
|
|
710
|
+
await ctx.context.adapter.delete({
|
|
711
|
+
model: "scimProvider",
|
|
712
|
+
where: [{
|
|
713
|
+
field: "id",
|
|
714
|
+
value: scimProvider.id
|
|
715
|
+
}]
|
|
716
|
+
});
|
|
717
|
+
}
|
|
675
718
|
const baseToken = generateRandomString(24);
|
|
676
719
|
const scimToken = base64Url.encode(`${baseToken}:${providerId}${organizationId ? `:${organizationId}` : ""}`);
|
|
677
720
|
if (opts.beforeSCIMTokenGenerated) await opts.beforeSCIMTokenGenerated({
|
|
@@ -684,7 +727,8 @@ const generateSCIMToken = (opts) => createAuthEndpoint("/scim/generate-token", {
|
|
|
684
727
|
data: {
|
|
685
728
|
providerId,
|
|
686
729
|
organizationId,
|
|
687
|
-
scimToken: await storeSCIMToken(ctx, opts, baseToken)
|
|
730
|
+
scimToken: await storeSCIMToken(ctx, opts, baseToken),
|
|
731
|
+
...opts.providerOwnership?.enabled ? { userId: user.id } : {}
|
|
688
732
|
}
|
|
689
733
|
});
|
|
690
734
|
if (opts.afterSCIMTokenGenerated) await opts.afterSCIMTokenGenerated({
|
|
@@ -696,6 +740,110 @@ const generateSCIMToken = (opts) => createAuthEndpoint("/scim/generate-token", {
|
|
|
696
740
|
ctx.setStatus(201);
|
|
697
741
|
return ctx.json({ scimToken });
|
|
698
742
|
});
|
|
743
|
+
const listSCIMProviderConnections = () => createAuthEndpoint("/scim/list-provider-connections", {
|
|
744
|
+
method: "GET",
|
|
745
|
+
use: [sessionMiddleware],
|
|
746
|
+
metadata: { openapi: {
|
|
747
|
+
operationId: "listSCIMProviderConnections",
|
|
748
|
+
summary: "List SCIM providers",
|
|
749
|
+
description: "Returns SCIM providers for organizations the user is a member of.",
|
|
750
|
+
responses: { "200": {
|
|
751
|
+
description: "List of SCIM providers",
|
|
752
|
+
content: { "application/json": { schema: {
|
|
753
|
+
type: "object",
|
|
754
|
+
properties: { providers: {
|
|
755
|
+
type: "array",
|
|
756
|
+
items: {
|
|
757
|
+
type: "object",
|
|
758
|
+
properties: {
|
|
759
|
+
id: { type: "string" },
|
|
760
|
+
providerId: { type: "string" },
|
|
761
|
+
organizationId: {
|
|
762
|
+
type: "string",
|
|
763
|
+
nullable: true
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
} }
|
|
768
|
+
} } }
|
|
769
|
+
} }
|
|
770
|
+
} }
|
|
771
|
+
}, async (ctx) => {
|
|
772
|
+
const userId = ctx.context.session.user.id;
|
|
773
|
+
const userOrgIds = ctx.context.hasPlugin("organization") ? await getSCIMUserOrgIds(ctx, userId) : /* @__PURE__ */ new Set();
|
|
774
|
+
const providers = (await ctx.context.adapter.findMany({ model: "scimProvider" })).filter((p) => {
|
|
775
|
+
if (p.organizationId) return userOrgIds.has(p.organizationId);
|
|
776
|
+
if (p.userId === userId) return true;
|
|
777
|
+
return !p.userId;
|
|
778
|
+
}).map((p) => normalizeSCIMProvider(p));
|
|
779
|
+
return ctx.json({ providers });
|
|
780
|
+
});
|
|
781
|
+
const getSCIMProviderConnection = () => createAuthEndpoint("/scim/get-provider-connection", {
|
|
782
|
+
method: "GET",
|
|
783
|
+
use: [sessionMiddleware],
|
|
784
|
+
query: getSCIMProviderConnectionQuerySchema,
|
|
785
|
+
metadata: { openapi: {
|
|
786
|
+
operationId: "getSCIMProviderConnection",
|
|
787
|
+
summary: "Get SCIM provider details",
|
|
788
|
+
description: "Returns details for a specific SCIM provider",
|
|
789
|
+
responses: {
|
|
790
|
+
"200": {
|
|
791
|
+
description: "SCIM provider details",
|
|
792
|
+
content: { "application/json": { schema: {
|
|
793
|
+
type: "object",
|
|
794
|
+
properties: {
|
|
795
|
+
id: { type: "string" },
|
|
796
|
+
providerId: { type: "string" },
|
|
797
|
+
organizationId: {
|
|
798
|
+
type: "string",
|
|
799
|
+
nullable: true
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
} } }
|
|
803
|
+
},
|
|
804
|
+
"404": { description: "Provider not found" },
|
|
805
|
+
"403": { description: "Access denied" }
|
|
806
|
+
}
|
|
807
|
+
} }
|
|
808
|
+
}, async (ctx) => {
|
|
809
|
+
const { providerId } = ctx.query;
|
|
810
|
+
const userId = ctx.context.session.user.id;
|
|
811
|
+
const provider = await checkSCIMProviderAccess(ctx, userId, providerId);
|
|
812
|
+
return ctx.json(normalizeSCIMProvider(provider));
|
|
813
|
+
});
|
|
814
|
+
const deleteSCIMProviderConnection = () => createAuthEndpoint("/scim/delete-provider-connection", {
|
|
815
|
+
method: "POST",
|
|
816
|
+
use: [sessionMiddleware],
|
|
817
|
+
body: deleteSCIMProviderConnectionBodySchema,
|
|
818
|
+
metadata: { openapi: {
|
|
819
|
+
operationId: "deleteSCIMProviderConnection",
|
|
820
|
+
summary: "Delete SCIM provider",
|
|
821
|
+
description: "Deletes a SCIM provider and invalidates its token",
|
|
822
|
+
responses: {
|
|
823
|
+
"200": {
|
|
824
|
+
description: "SCIM provider deleted successfully",
|
|
825
|
+
content: { "application/json": { schema: {
|
|
826
|
+
type: "object",
|
|
827
|
+
properties: { success: { type: "boolean" } }
|
|
828
|
+
} } }
|
|
829
|
+
},
|
|
830
|
+
"404": { description: "Provider not found" },
|
|
831
|
+
"403": { description: "Access denied" }
|
|
832
|
+
}
|
|
833
|
+
} }
|
|
834
|
+
}, async (ctx) => {
|
|
835
|
+
const { providerId } = ctx.body;
|
|
836
|
+
const userId = ctx.context.session.user.id;
|
|
837
|
+
await checkSCIMProviderAccess(ctx, userId, providerId);
|
|
838
|
+
await ctx.context.adapter.delete({
|
|
839
|
+
model: "scimProvider",
|
|
840
|
+
where: [{
|
|
841
|
+
field: "providerId",
|
|
842
|
+
value: providerId
|
|
843
|
+
}]
|
|
844
|
+
});
|
|
845
|
+
return ctx.json({ success: true });
|
|
846
|
+
});
|
|
699
847
|
const createSCIMUser = (authMiddleware) => createAuthEndpoint("/scim/v2/Users", {
|
|
700
848
|
method: "POST",
|
|
701
849
|
body: APIUserSchema,
|
|
@@ -1276,6 +1424,7 @@ const parseSCIMAPIUserFilter = (filter) => {
|
|
|
1276
1424
|
const scim = (options) => {
|
|
1277
1425
|
const opts = {
|
|
1278
1426
|
storeSCIMToken: "plain",
|
|
1427
|
+
providerOwnership: { enabled: false },
|
|
1279
1428
|
...options
|
|
1280
1429
|
};
|
|
1281
1430
|
const authMiddleware = authMiddlewareFactory(opts);
|
|
@@ -1283,6 +1432,9 @@ const scim = (options) => {
|
|
|
1283
1432
|
id: "scim",
|
|
1284
1433
|
endpoints: {
|
|
1285
1434
|
generateSCIMToken: generateSCIMToken(opts),
|
|
1435
|
+
listSCIMProviderConnections: listSCIMProviderConnections(),
|
|
1436
|
+
getSCIMProviderConnection: getSCIMProviderConnection(),
|
|
1437
|
+
deleteSCIMProviderConnection: deleteSCIMProviderConnection(),
|
|
1286
1438
|
getSCIMUser: getSCIMUser(authMiddleware),
|
|
1287
1439
|
createSCIMUser: createSCIMUser(authMiddleware),
|
|
1288
1440
|
patchSCIMUser: patchSCIMUser(authMiddleware),
|
|
@@ -1309,7 +1461,11 @@ const scim = (options) => {
|
|
|
1309
1461
|
organizationId: {
|
|
1310
1462
|
type: "string",
|
|
1311
1463
|
required: false
|
|
1312
|
-
}
|
|
1464
|
+
},
|
|
1465
|
+
...opts.providerOwnership?.enabled ? { userId: {
|
|
1466
|
+
type: "string",
|
|
1467
|
+
required: false
|
|
1468
|
+
} } : {}
|
|
1313
1469
|
} } },
|
|
1314
1470
|
options
|
|
1315
1471
|
};
|