@voltro/plugin-scim 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2006 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.js +166 -145
- package/package.json +5 -4
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,8 @@ import { VoltroPlugin } from '@voltro/protocol';
|
|
|
9
9
|
export declare const applyScimGroupPatch: <G extends GroupLike>(group: G, body: Record<string, unknown>) => G;
|
|
10
10
|
|
|
11
11
|
/** Apply a SCIM PATCH (the `active=false` deactivation IdPs send is the case
|
|
12
|
-
* that matters most). Supports `replace` ops on `active` / `userName
|
|
12
|
+
* that matters most). Supports `replace`/`add` ops on `active` / `userName`,
|
|
13
|
+
* path-targeted or as a pathless partial resource. */
|
|
13
14
|
export declare const applyScimPatch: (row: ScimUserRow, body: Record<string, unknown>) => ScimUserRow;
|
|
14
15
|
|
|
15
16
|
export declare const dataStoreScimStore: (store: DataStore) => ScimStore;
|
|
@@ -96,6 +97,19 @@ export declare interface ScimPluginOptions {
|
|
|
96
97
|
readonly token: string;
|
|
97
98
|
/** Mount prefix. Default `/scim/v2`. */
|
|
98
99
|
readonly basePath?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Namespace for this plugin's HTTP mount + inspect surface. Default `scim`.
|
|
102
|
+
*
|
|
103
|
+
* Set it when your app already publishes under that name — an exact tag
|
|
104
|
+
* collision is fatal at codegen, and this is the way out. Orthogonal to
|
|
105
|
+
* `name` below: `alias` REPLACES the namespace, `name` distinguishes two
|
|
106
|
+
* installations within it.
|
|
107
|
+
*/
|
|
108
|
+
readonly alias?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Discriminator for a SECOND installation of this plugin, when one app runs
|
|
111
|
+
* two (`@voltro/plugin-scim#eu`). Not a rename — for that use `alias`.
|
|
112
|
+
*/
|
|
99
113
|
readonly name?: string;
|
|
100
114
|
}
|
|
101
115
|
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { Effect as e } from "effect";
|
|
2
|
-
import { definePlugin as t,
|
|
3
|
-
import { checkBearer as
|
|
4
|
-
import { boolean as
|
|
2
|
+
import { definePlugin as t, pluginInstanceName as n, tsMs as r } from "@voltro/protocol";
|
|
3
|
+
import { checkBearer as i } from "@voltro/protocol/session";
|
|
4
|
+
import { boolean as a, eq as o, generateId as s, id as c, json as l, queryFor as u, table as d, text as f, timestamp as p } from "@voltro/database";
|
|
5
5
|
//#region src/scim.ts
|
|
6
|
-
var
|
|
7
|
-
|
|
6
|
+
var m = "urn:ietf:params:scim:schemas:core:2.0:User", h = "urn:ietf:params:scim:schemas:core:2.0:Group", g = "urn:ietf:params:scim:api:messages:2.0:ListResponse", _ = "urn:ietf:params:scim:api:messages:2.0:Error", v = (e) => new Date(e).toISOString(), y = (e, t) => {
|
|
7
|
+
if (typeof e == "boolean") return e;
|
|
8
|
+
if (typeof e == "number") return Number.isFinite(e) && e !== 0;
|
|
9
|
+
if (typeof e == "string") {
|
|
10
|
+
let t = e.trim().toLowerCase();
|
|
11
|
+
if (t === "true" || t === "1") return !0;
|
|
12
|
+
if (t === "false" || t === "0" || t === "") return !1;
|
|
13
|
+
}
|
|
14
|
+
return t;
|
|
15
|
+
}, b = (e) => {
|
|
16
|
+
let t = (e ?? "").trim();
|
|
17
|
+
return (t.toLowerCase().startsWith("urn:") ? t.slice(t.lastIndexOf(":") + 1) : t).toLowerCase();
|
|
18
|
+
}, x = (e, t) => ({
|
|
19
|
+
schemas: [m],
|
|
8
20
|
id: e.id,
|
|
9
21
|
...e.externalId ? { externalId: e.externalId } : {},
|
|
10
22
|
userName: e.userName,
|
|
@@ -19,55 +31,58 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
19
31
|
active: e.active,
|
|
20
32
|
meta: {
|
|
21
33
|
resourceType: "User",
|
|
22
|
-
created:
|
|
23
|
-
lastModified:
|
|
34
|
+
created: v(e.createdAt),
|
|
35
|
+
lastModified: v(e.updatedAt),
|
|
24
36
|
location: `${t}/${e.id}`
|
|
25
37
|
}
|
|
26
|
-
}),
|
|
38
|
+
}), S = (e) => {
|
|
27
39
|
let t = e.name ?? {}, n = e.emails ?? [], r = n.find((e) => e.primary)?.value ?? n[0]?.value ?? null;
|
|
28
40
|
return {
|
|
29
41
|
userName: String(e.userName ?? ""),
|
|
30
42
|
givenName: t.givenName ?? null,
|
|
31
43
|
familyName: t.familyName ?? null,
|
|
32
44
|
email: r,
|
|
33
|
-
active: e.active === void 0 ||
|
|
45
|
+
active: e.active === void 0 || e.active === null || y(e.active, !1),
|
|
34
46
|
externalId: e.externalId ?? null
|
|
35
47
|
};
|
|
36
|
-
},
|
|
48
|
+
}, C = (e, t) => {
|
|
37
49
|
let n = t.Operations ?? t.operations ?? [], r = { ...e };
|
|
38
|
-
for (let e of n)
|
|
39
|
-
|
|
50
|
+
for (let e of n) {
|
|
51
|
+
let t = (e.op ?? "").toLowerCase();
|
|
52
|
+
if (t !== "replace" && t !== "add") continue;
|
|
53
|
+
let n = b(e.path);
|
|
54
|
+
if (n === "active") r = {
|
|
40
55
|
...r,
|
|
41
|
-
active:
|
|
56
|
+
active: y(e.value, !1)
|
|
42
57
|
};
|
|
43
|
-
else if (
|
|
58
|
+
else if (n === "username") r = {
|
|
44
59
|
...r,
|
|
45
60
|
userName: String(e.value)
|
|
46
61
|
};
|
|
47
|
-
else if (
|
|
48
|
-
let
|
|
49
|
-
"active"
|
|
62
|
+
else if (n === "" && e.value && typeof e.value == "object") for (let [t, n] of Object.entries(e.value)) {
|
|
63
|
+
let e = t.toLowerCase();
|
|
64
|
+
e === "active" ? r = {
|
|
50
65
|
...r,
|
|
51
|
-
active:
|
|
52
|
-
}
|
|
66
|
+
active: y(n, !1)
|
|
67
|
+
} : e === "username" && (r = {
|
|
53
68
|
...r,
|
|
54
|
-
userName: String(
|
|
69
|
+
userName: String(n)
|
|
55
70
|
});
|
|
56
71
|
}
|
|
57
72
|
}
|
|
58
73
|
return r;
|
|
59
|
-
},
|
|
60
|
-
schemas: [
|
|
74
|
+
}, w = (e, t) => ({
|
|
75
|
+
schemas: [g],
|
|
61
76
|
totalResults: t?.totalResults ?? e.length,
|
|
62
77
|
startIndex: t?.startIndex ?? 1,
|
|
63
78
|
itemsPerPage: e.length,
|
|
64
79
|
Resources: e
|
|
65
|
-
}),
|
|
66
|
-
schemas: [
|
|
80
|
+
}), T = (e, t, n) => ({
|
|
81
|
+
schemas: [_],
|
|
67
82
|
status: String(e),
|
|
68
83
|
...n ? { scimType: n } : {},
|
|
69
84
|
detail: t
|
|
70
|
-
}),
|
|
85
|
+
}), E = (e, t) => {
|
|
71
86
|
if (!e) return { kind: "none" };
|
|
72
87
|
let n = /^\s*([A-Za-z][A-Za-z0-9]*)\s+eq\s+"([^"]*)"\s*$/i.exec(e);
|
|
73
88
|
if (!n) return { kind: "invalid" };
|
|
@@ -77,13 +92,13 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
77
92
|
attribute: r,
|
|
78
93
|
value: n[2]
|
|
79
94
|
};
|
|
80
|
-
},
|
|
95
|
+
}, D = (e) => {
|
|
81
96
|
let t = Number(e.get("startIndex")), n = Number(e.get("count"));
|
|
82
97
|
return {
|
|
83
98
|
startIndex: Number.isInteger(t) && t >= 1 ? t : 1,
|
|
84
99
|
count: e.get("count") !== null && Number.isInteger(n) && n >= 0 ? n : null
|
|
85
100
|
};
|
|
86
|
-
},
|
|
101
|
+
}, O = (e, t) => {
|
|
87
102
|
let n = t.Operations ?? t.operations ?? [], r = (e) => (Array.isArray(e) ? e : e === void 0 ? [] : [e]).map((e) => String(e?.value ?? e)).filter((e) => e !== "" && e !== "undefined"), i = e;
|
|
88
103
|
for (let e of n) {
|
|
89
104
|
let t = (e.op ?? "").toLowerCase(), n = e.path ?? "", a = /^members\[value\s+eq\s+"([^"]*)"\]$/i.exec(n);
|
|
@@ -123,7 +138,7 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
123
138
|
}
|
|
124
139
|
}
|
|
125
140
|
return i;
|
|
126
|
-
},
|
|
141
|
+
}, k = (e) => ({
|
|
127
142
|
schemas: ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
|
|
128
143
|
documentationUri: "https://voltro.dev",
|
|
129
144
|
patch: { supported: !0 },
|
|
@@ -148,7 +163,7 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
148
163
|
resourceType: "ServiceProviderConfig",
|
|
149
164
|
location: e
|
|
150
165
|
}
|
|
151
|
-
}),
|
|
166
|
+
}), A = (e, t = {}) => ({
|
|
152
167
|
name: e,
|
|
153
168
|
type: "string",
|
|
154
169
|
multiValued: !1,
|
|
@@ -158,56 +173,56 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
158
173
|
returned: "default",
|
|
159
174
|
uniqueness: "none",
|
|
160
175
|
...t
|
|
161
|
-
}),
|
|
176
|
+
}), j = (e) => [{
|
|
162
177
|
schemas: ["urn:ietf:params:scim:schemas:core:2.0:Schema"],
|
|
163
|
-
id:
|
|
178
|
+
id: m,
|
|
164
179
|
name: "User",
|
|
165
180
|
description: "SCIM core User",
|
|
166
181
|
attributes: [
|
|
167
|
-
|
|
182
|
+
A("userName", {
|
|
168
183
|
required: !0,
|
|
169
184
|
uniqueness: "server"
|
|
170
185
|
}),
|
|
171
|
-
|
|
186
|
+
A("name", {
|
|
172
187
|
type: "complex",
|
|
173
|
-
subAttributes: [
|
|
188
|
+
subAttributes: [A("givenName"), A("familyName")]
|
|
174
189
|
}),
|
|
175
|
-
|
|
190
|
+
A("emails", {
|
|
176
191
|
type: "complex",
|
|
177
192
|
multiValued: !0,
|
|
178
|
-
subAttributes: [
|
|
193
|
+
subAttributes: [A("value"), A("primary", { type: "boolean" })]
|
|
179
194
|
}),
|
|
180
|
-
|
|
181
|
-
|
|
195
|
+
A("active", { type: "boolean" }),
|
|
196
|
+
A("externalId")
|
|
182
197
|
],
|
|
183
198
|
meta: {
|
|
184
199
|
resourceType: "Schema",
|
|
185
|
-
location: `${e}/${
|
|
200
|
+
location: `${e}/${m}`
|
|
186
201
|
}
|
|
187
202
|
}, {
|
|
188
203
|
schemas: ["urn:ietf:params:scim:schemas:core:2.0:Schema"],
|
|
189
|
-
id:
|
|
204
|
+
id: h,
|
|
190
205
|
name: "Group",
|
|
191
206
|
description: "SCIM core Group",
|
|
192
207
|
attributes: [
|
|
193
|
-
|
|
194
|
-
|
|
208
|
+
A("displayName", { required: !0 }),
|
|
209
|
+
A("members", {
|
|
195
210
|
type: "complex",
|
|
196
211
|
multiValued: !0,
|
|
197
|
-
subAttributes: [
|
|
212
|
+
subAttributes: [A("value")]
|
|
198
213
|
}),
|
|
199
|
-
|
|
214
|
+
A("externalId")
|
|
200
215
|
],
|
|
201
216
|
meta: {
|
|
202
217
|
resourceType: "Schema",
|
|
203
|
-
location: `${e}/${
|
|
218
|
+
location: `${e}/${h}`
|
|
204
219
|
}
|
|
205
|
-
}],
|
|
220
|
+
}], M = (e) => [{
|
|
206
221
|
schemas: ["urn:ietf:params:scim:schemas:core:2.0:ResourceType"],
|
|
207
222
|
id: "User",
|
|
208
223
|
name: "User",
|
|
209
224
|
endpoint: "/Users",
|
|
210
|
-
schema:
|
|
225
|
+
schema: m,
|
|
211
226
|
meta: {
|
|
212
227
|
resourceType: "ResourceType",
|
|
213
228
|
location: `${e}/ResourceTypes/User`
|
|
@@ -217,32 +232,32 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
217
232
|
id: "Group",
|
|
218
233
|
name: "Group",
|
|
219
234
|
endpoint: "/Groups",
|
|
220
|
-
schema:
|
|
235
|
+
schema: h,
|
|
221
236
|
meta: {
|
|
222
237
|
resourceType: "ResourceType",
|
|
223
238
|
location: `${e}/ResourceTypes/Group`
|
|
224
239
|
}
|
|
225
|
-
}],
|
|
226
|
-
id:
|
|
227
|
-
userName:
|
|
228
|
-
givenName:
|
|
229
|
-
familyName:
|
|
230
|
-
email:
|
|
231
|
-
active:
|
|
232
|
-
externalId:
|
|
233
|
-
createdAt:
|
|
234
|
-
updatedAt:
|
|
235
|
-
}).renamedFrom("scim_users").unique("byScimUserName", ["userName"]),
|
|
236
|
-
id:
|
|
237
|
-
displayName:
|
|
238
|
-
externalId:
|
|
239
|
-
members:
|
|
240
|
-
createdAt:
|
|
241
|
-
updatedAt:
|
|
242
|
-
}).renamedFrom("scim_groups"),
|
|
240
|
+
}], N = d("_voltro_scim_users", {
|
|
241
|
+
id: c({ prefix: "scimuser" }),
|
|
242
|
+
userName: f(),
|
|
243
|
+
givenName: f().nullable(),
|
|
244
|
+
familyName: f().nullable(),
|
|
245
|
+
email: f().nullable(),
|
|
246
|
+
active: a().default(!0),
|
|
247
|
+
externalId: f().nullable(),
|
|
248
|
+
createdAt: p().default("now"),
|
|
249
|
+
updatedAt: p().default("now").onUpdate("now")
|
|
250
|
+
}).renamedFrom("scim_users").unique("byScimUserName", ["userName"]), P = d("_voltro_scim_groups", {
|
|
251
|
+
id: c({ prefix: "scimgroup" }),
|
|
252
|
+
displayName: f(),
|
|
253
|
+
externalId: f().nullable(),
|
|
254
|
+
members: l().nullable(),
|
|
255
|
+
createdAt: p().default("now"),
|
|
256
|
+
updatedAt: p().default("now").onUpdate("now")
|
|
257
|
+
}).renamedFrom("scim_groups"), F = (e, t) => t === null || e[t.attribute] === t.value, I = (e = Date.now) => {
|
|
243
258
|
let t = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map(), r = 0, i = (e) => `${e}_${++r}`;
|
|
244
259
|
return {
|
|
245
|
-
listUsers: async (e) => [...t.values()].filter((t) =>
|
|
260
|
+
listUsers: async (e) => [...t.values()].filter((t) => F(t, e)),
|
|
246
261
|
getUser: async (e) => t.get(e) ?? null,
|
|
247
262
|
createUser: async (n) => {
|
|
248
263
|
let r = {
|
|
@@ -263,7 +278,7 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
263
278
|
return t.set(n, i), i;
|
|
264
279
|
},
|
|
265
280
|
deleteUser: async (e) => t.delete(e),
|
|
266
|
-
listGroups: async (e) => [...n.values()].filter((t) =>
|
|
281
|
+
listGroups: async (e) => [...n.values()].filter((t) => F(t, e)),
|
|
267
282
|
getGroup: async (e) => n.get(e) ?? null,
|
|
268
283
|
createGroup: async (t) => {
|
|
269
284
|
let r = {
|
|
@@ -285,11 +300,11 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
285
300
|
},
|
|
286
301
|
deleteGroup: async (e) => n.delete(e)
|
|
287
302
|
};
|
|
288
|
-
},
|
|
303
|
+
}, L = (e) => {
|
|
289
304
|
let t = async (t, n) => {
|
|
290
|
-
let r =
|
|
305
|
+
let r = u(t);
|
|
291
306
|
return e.query((n ? r.where(n) : r).descriptor);
|
|
292
|
-
},
|
|
307
|
+
}, n = (e) => ({
|
|
293
308
|
id: String(e.id),
|
|
294
309
|
userName: String(e.userName),
|
|
295
310
|
givenName: e.givenName ?? null,
|
|
@@ -297,24 +312,24 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
297
312
|
email: e.email ?? null,
|
|
298
313
|
active: !!e.active,
|
|
299
314
|
externalId: e.externalId ?? null,
|
|
300
|
-
createdAt:
|
|
301
|
-
updatedAt:
|
|
315
|
+
createdAt: r(e.createdAt),
|
|
316
|
+
updatedAt: r(e.updatedAt)
|
|
302
317
|
}), i = (e) => ({
|
|
303
318
|
id: String(e.id),
|
|
304
319
|
displayName: String(e.displayName),
|
|
305
320
|
externalId: e.externalId ?? null,
|
|
306
321
|
members: e.members ?? [],
|
|
307
|
-
createdAt:
|
|
308
|
-
updatedAt:
|
|
322
|
+
createdAt: r(e.createdAt),
|
|
323
|
+
updatedAt: r(e.updatedAt)
|
|
309
324
|
});
|
|
310
325
|
return {
|
|
311
|
-
listUsers: async (e) => (await t(
|
|
326
|
+
listUsers: async (e) => (await t(N, e ? o(e.attribute, e.value) : void 0)).map(n),
|
|
312
327
|
getUser: async (e) => {
|
|
313
|
-
let
|
|
314
|
-
return
|
|
328
|
+
let r = await t(N, o("id", e));
|
|
329
|
+
return r[0] ? n(r[0]) : null;
|
|
315
330
|
},
|
|
316
331
|
createUser: async (t) => {
|
|
317
|
-
let n =
|
|
332
|
+
let n = s({
|
|
318
333
|
kind: "typeid",
|
|
319
334
|
prefix: "scimuser"
|
|
320
335
|
}, "_voltro_scim_users"), r = /* @__PURE__ */ new Date(), i = {
|
|
@@ -330,7 +345,7 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
330
345
|
updatedAt: r.getTime()
|
|
331
346
|
};
|
|
332
347
|
},
|
|
333
|
-
putUser: async (n, r) => (await t(
|
|
348
|
+
putUser: async (n, r) => (await t(N, o("id", n)))[0] ? (await e.update("_voltro_scim_users", n, {
|
|
334
349
|
userName: r.userName,
|
|
335
350
|
givenName: r.givenName,
|
|
336
351
|
familyName: r.familyName,
|
|
@@ -343,14 +358,14 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
343
358
|
id: n,
|
|
344
359
|
updatedAt: Date.now()
|
|
345
360
|
}) : null,
|
|
346
|
-
deleteUser: async (n) => (await t(
|
|
347
|
-
listGroups: async (e) => (await t(
|
|
361
|
+
deleteUser: async (n) => (await t(N, o("id", n)))[0] ? (await e.delete("_voltro_scim_users", n), !0) : !1,
|
|
362
|
+
listGroups: async (e) => (await t(P, e ? o(e.attribute, e.value) : void 0)).map(i),
|
|
348
363
|
getGroup: async (e) => {
|
|
349
|
-
let n = await t(
|
|
364
|
+
let n = await t(P, o("id", e));
|
|
350
365
|
return n[0] ? i(n[0]) : null;
|
|
351
366
|
},
|
|
352
367
|
createGroup: async (t) => {
|
|
353
|
-
let n =
|
|
368
|
+
let n = s({
|
|
354
369
|
kind: "typeid",
|
|
355
370
|
prefix: "scimgroup"
|
|
356
371
|
}, "_voltro_scim_groups"), r = /* @__PURE__ */ new Date(), i = {
|
|
@@ -366,7 +381,7 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
366
381
|
updatedAt: r.getTime()
|
|
367
382
|
};
|
|
368
383
|
},
|
|
369
|
-
putGroup: async (n, r) => (await t(
|
|
384
|
+
putGroup: async (n, r) => (await t(P, o("id", n)))[0] ? (await e.update("_voltro_scim_groups", n, {
|
|
370
385
|
displayName: r.displayName,
|
|
371
386
|
externalId: r.externalId,
|
|
372
387
|
members: r.members,
|
|
@@ -376,84 +391,84 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
376
391
|
id: n,
|
|
377
392
|
updatedAt: Date.now()
|
|
378
393
|
}) : null,
|
|
379
|
-
deleteGroup: async (n) => (await t(
|
|
394
|
+
deleteGroup: async (n) => (await t(P, o("id", n)))[0] ? (await e.delete("_voltro_scim_groups", n), !0) : !1
|
|
380
395
|
};
|
|
381
|
-
},
|
|
396
|
+
}, R = (e, t) => ({
|
|
382
397
|
status: e,
|
|
383
398
|
body: JSON.stringify(t),
|
|
384
399
|
contentType: "application/scim+json"
|
|
385
|
-
}),
|
|
400
|
+
}), z = (e) => {
|
|
386
401
|
try {
|
|
387
402
|
return JSON.parse(new TextDecoder().decode(e.rawBody) || "{}");
|
|
388
403
|
} catch {
|
|
389
404
|
return {};
|
|
390
405
|
}
|
|
391
|
-
},
|
|
392
|
-
let { startIndex: n, count: r } =
|
|
406
|
+
}, B = (e, t) => {
|
|
407
|
+
let { startIndex: n, count: r } = D(t), i = n - 1;
|
|
393
408
|
return {
|
|
394
409
|
page: e.slice(i, r === null ? void 0 : i + r),
|
|
395
410
|
total: e.length,
|
|
396
411
|
startIndex: n
|
|
397
412
|
};
|
|
398
|
-
},
|
|
413
|
+
}, V = () => R(400, T(400, "unsupported filter — only `<attribute> eq \"value\"` on the indexed attributes", "invalidFilter")), H = async (e, t, n) => (await e.listUsers({
|
|
399
414
|
attribute: "userName",
|
|
400
415
|
value: t
|
|
401
|
-
})).some((e) => e.id !== n),
|
|
402
|
-
if (!r
|
|
403
|
-
let [, a, o] =
|
|
416
|
+
})).some((e) => e.id !== n), U = (e) => R(409, T(409, `a user with userName '${e}' already exists`, "uniqueness")), W = async (e, t, n, r) => {
|
|
417
|
+
if (!i(r.headers, t)) return R(401, T(401, "invalid or missing bearer token"));
|
|
418
|
+
let [, a, o] = r.path.slice(n.length).replace(/\/+$/, "").split("/"), s = r.method.toUpperCase(), c = new URLSearchParams(r.query), l = `${n}/Users`;
|
|
404
419
|
if (s === "GET" && !o) {
|
|
405
|
-
if (a === "ServiceProviderConfig") return
|
|
406
|
-
if (a === "Schemas") return
|
|
407
|
-
if (a === "ResourceTypes") return
|
|
420
|
+
if (a === "ServiceProviderConfig") return R(200, k(`${n}/ServiceProviderConfig`));
|
|
421
|
+
if (a === "Schemas") return R(200, w(j(`${n}/Schemas`)));
|
|
422
|
+
if (a === "ResourceTypes") return R(200, w(M(n)));
|
|
408
423
|
}
|
|
409
424
|
if (a === "Users") {
|
|
410
425
|
if (s === "GET" && !o) {
|
|
411
|
-
let t =
|
|
412
|
-
if (t.kind === "invalid") return
|
|
413
|
-
let { page: n, total: r, startIndex: i } =
|
|
414
|
-
return
|
|
426
|
+
let t = E(c.get("filter"), ["userName", "externalId"]);
|
|
427
|
+
if (t.kind === "invalid") return V();
|
|
428
|
+
let { page: n, total: r, startIndex: i } = B(await e.listUsers(t.kind === "filter" ? t : null), c);
|
|
429
|
+
return R(200, w(n.map((e) => x(e, l)), {
|
|
415
430
|
totalResults: r,
|
|
416
431
|
startIndex: i
|
|
417
432
|
}));
|
|
418
433
|
}
|
|
419
434
|
if (s === "GET" && o) {
|
|
420
435
|
let t = await e.getUser(o);
|
|
421
|
-
return t ?
|
|
436
|
+
return t ? R(200, x(t, l)) : R(404, T(404, "user not found"));
|
|
422
437
|
}
|
|
423
438
|
if (s === "POST" && !o) {
|
|
424
|
-
let t =
|
|
425
|
-
if (t.userName === "") return
|
|
426
|
-
if (await
|
|
439
|
+
let t = S(z(r));
|
|
440
|
+
if (t.userName === "") return R(400, T(400, "userName is required", "invalidValue"));
|
|
441
|
+
if (await H(e, t.userName)) return U(t.userName);
|
|
427
442
|
try {
|
|
428
|
-
return
|
|
443
|
+
return R(201, x(await e.createUser(t), l));
|
|
429
444
|
} catch (n) {
|
|
430
|
-
if (await
|
|
445
|
+
if (await H(e, t.userName)) return U(t.userName);
|
|
431
446
|
throw n;
|
|
432
447
|
}
|
|
433
448
|
}
|
|
434
449
|
if (s === "PUT" && o) {
|
|
435
450
|
let t = await e.getUser(o);
|
|
436
|
-
if (!t) return
|
|
451
|
+
if (!t) return R(404, T(404, "user not found"));
|
|
437
452
|
let n = {
|
|
438
453
|
...t,
|
|
439
|
-
...
|
|
454
|
+
...S(z(r))
|
|
440
455
|
};
|
|
441
|
-
if (await
|
|
442
|
-
let
|
|
443
|
-
return
|
|
456
|
+
if (await H(e, n.userName, o)) return U(n.userName);
|
|
457
|
+
let i = await e.putUser(o, n);
|
|
458
|
+
return i ? R(200, x(i, l)) : R(404, T(404, "user not found"));
|
|
444
459
|
}
|
|
445
460
|
if (s === "PATCH" && o) {
|
|
446
461
|
let t = await e.getUser(o);
|
|
447
|
-
if (!t) return
|
|
448
|
-
let n =
|
|
449
|
-
if (n.userName !== t.userName && await
|
|
450
|
-
let
|
|
451
|
-
return
|
|
462
|
+
if (!t) return R(404, T(404, "user not found"));
|
|
463
|
+
let n = C(t, z(r));
|
|
464
|
+
if (n.userName !== t.userName && await H(e, n.userName, o)) return U(n.userName);
|
|
465
|
+
let i = await e.putUser(o, n);
|
|
466
|
+
return i ? R(200, x(i, l)) : R(404, T(404, "user not found"));
|
|
452
467
|
}
|
|
453
|
-
if (s === "DELETE" && o) return await e.deleteUser(o) ? { status: 204 } :
|
|
468
|
+
if (s === "DELETE" && o) return await e.deleteUser(o) ? { status: 204 } : R(404, T(404, "user not found"));
|
|
454
469
|
}
|
|
455
470
|
if (a === "Groups") {
|
|
456
|
-
let t = `${n}/Groups`,
|
|
471
|
+
let t = `${n}/Groups`, i = (e) => ({
|
|
457
472
|
schemas: ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
|
458
473
|
id: e.id,
|
|
459
474
|
displayName: e.displayName,
|
|
@@ -465,21 +480,21 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
465
480
|
}
|
|
466
481
|
});
|
|
467
482
|
if (s === "GET" && !o) {
|
|
468
|
-
let t =
|
|
469
|
-
if (t.kind === "invalid") return
|
|
470
|
-
let { page: n, total:
|
|
471
|
-
return
|
|
472
|
-
totalResults:
|
|
483
|
+
let t = E(c.get("filter"), ["displayName", "externalId"]);
|
|
484
|
+
if (t.kind === "invalid") return V();
|
|
485
|
+
let { page: n, total: r, startIndex: a } = B(await e.listGroups(t.kind === "filter" ? t : null), c);
|
|
486
|
+
return R(200, w(n.map(i), {
|
|
487
|
+
totalResults: r,
|
|
473
488
|
startIndex: a
|
|
474
489
|
}));
|
|
475
490
|
}
|
|
476
491
|
if (s === "GET" && o) {
|
|
477
492
|
let t = await e.getGroup(o);
|
|
478
|
-
return t ?
|
|
493
|
+
return t ? R(200, i(t)) : R(404, T(404, "group not found"));
|
|
479
494
|
}
|
|
480
495
|
if (s === "POST" && !o) {
|
|
481
|
-
let t =
|
|
482
|
-
return
|
|
496
|
+
let t = z(r), n = (t.members ?? []).map((e) => String(e.value)).filter(Boolean);
|
|
497
|
+
return R(201, i(await e.createGroup({
|
|
483
498
|
displayName: String(t.displayName ?? ""),
|
|
484
499
|
externalId: t.externalId ?? null,
|
|
485
500
|
members: n
|
|
@@ -487,44 +502,50 @@ var p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:
|
|
|
487
502
|
}
|
|
488
503
|
if (s === "PUT" && o) {
|
|
489
504
|
let t = await e.getGroup(o);
|
|
490
|
-
if (!t) return
|
|
491
|
-
let n =
|
|
505
|
+
if (!t) return R(404, T(404, "group not found"));
|
|
506
|
+
let n = z(r), a = (n.members ?? []).map((e) => String(e.value)).filter(Boolean), s = await e.putGroup(o, {
|
|
492
507
|
...t,
|
|
493
508
|
displayName: String(n.displayName ?? t.displayName),
|
|
494
509
|
externalId: n.externalId ?? t.externalId,
|
|
495
510
|
members: a
|
|
496
511
|
});
|
|
497
|
-
return s ?
|
|
512
|
+
return s ? R(200, i(s)) : R(404, T(404, "group not found"));
|
|
498
513
|
}
|
|
499
514
|
if (s === "PATCH" && o) {
|
|
500
515
|
let t = await e.getGroup(o);
|
|
501
|
-
if (!t) return
|
|
502
|
-
let n = await e.putGroup(o,
|
|
503
|
-
return n ?
|
|
516
|
+
if (!t) return R(404, T(404, "group not found"));
|
|
517
|
+
let n = await e.putGroup(o, O(t, z(r)));
|
|
518
|
+
return n ? R(200, i(n)) : R(404, T(404, "group not found"));
|
|
504
519
|
}
|
|
505
|
-
if (s === "DELETE" && o) return await e.deleteGroup(o) ? { status: 204 } :
|
|
520
|
+
if (s === "DELETE" && o) return await e.deleteGroup(o) ? { status: 204 } : R(404, T(404, "group not found"));
|
|
506
521
|
}
|
|
507
|
-
return
|
|
508
|
-
},
|
|
509
|
-
if (
|
|
510
|
-
let
|
|
522
|
+
return R(404, T(404, "unsupported SCIM endpoint"));
|
|
523
|
+
}, G = "@voltro/plugin-scim", K = (r) => {
|
|
524
|
+
if (r.token.trim() === "") throw Error("scimPlugin({ token }) requires a non-empty bearer token — got an empty string. SCIM exposes the full user + group directory and can deactivate accounts, so it is never mounted ungated. Set the environment variable your IdP presents (e.g. SCIM_TOKEN) and pass it through, or remove scimPlugin() from the plugin list.");
|
|
525
|
+
let i = r.basePath ?? "/scim/v2", a = n({
|
|
526
|
+
base: G,
|
|
527
|
+
alias: r.alias,
|
|
528
|
+
instance: r.name
|
|
529
|
+
}), o;
|
|
511
530
|
return t({
|
|
512
|
-
name:
|
|
531
|
+
name: a,
|
|
532
|
+
baseName: G,
|
|
513
533
|
description: "SCIM 2.0 Users + Groups provisioning endpoints.",
|
|
514
534
|
permissions: ["store:write"],
|
|
515
|
-
extendSchema: { tables: [
|
|
535
|
+
extendSchema: { tables: [N, P] },
|
|
516
536
|
httpRoutes: [{
|
|
517
537
|
method: "*",
|
|
518
|
-
path:
|
|
519
|
-
|
|
538
|
+
path: i,
|
|
539
|
+
originGuard: "exempt",
|
|
540
|
+
handle: async (e) => o ? W(o, r.token, i, e) : R(503, T(503, "scim store not bound"))
|
|
520
541
|
}],
|
|
521
542
|
bindDataStore: (e) => {
|
|
522
|
-
|
|
543
|
+
o = L(e);
|
|
523
544
|
},
|
|
524
545
|
onActivate: (t) => e.sync(() => {
|
|
525
|
-
t.logger.info("scim active", { basePath:
|
|
546
|
+
t.logger.info("scim active", { basePath: i });
|
|
526
547
|
})
|
|
527
548
|
});
|
|
528
549
|
};
|
|
529
550
|
//#endregion
|
|
530
|
-
export {
|
|
551
|
+
export { _ as SCIM_ERROR_SCHEMA, h as SCIM_GROUP_SCHEMA, g as SCIM_LIST_SCHEMA, m as SCIM_USER_SCHEMA, O as applyScimGroupPatch, C as applyScimPatch, L as dataStoreScimStore, S as fromScimUser, W as handleScim, W as handleScimRequest, I as memoryScimStore, D as parsePagination, E as parseScimFilter, M as resourceTypes, T as scimError, w as scimList, K as scimPlugin, j as scimSchemas, k as serviceProviderConfig, x as toScimUser };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-scim",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "SCIM 2.0 provisioning — Users + Groups REST endpoints (/scim/v2) so an enterprise IdP (Okta, Entra, OneLogin) can create/update/deactivate users in your app. Bearer-token gated; the SCIM envelope + filtering handled for you.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"types": "./dist/index.d.ts",
|
|
23
23
|
"import": "./dist/index.js",
|
|
24
24
|
"default": "./dist/index.js"
|
|
25
|
-
}
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
26
27
|
},
|
|
27
28
|
"main": "./dist/index.js",
|
|
28
29
|
"module": "./dist/index.js",
|
|
@@ -32,8 +33,8 @@
|
|
|
32
33
|
"node": ">=24.0.0"
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
35
|
-
"@voltro/database": "0.
|
|
36
|
-
"@voltro/protocol": "0.
|
|
36
|
+
"@voltro/database": "0.34.0",
|
|
37
|
+
"@voltro/protocol": "0.34.0"
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {
|
|
39
40
|
"effect": "^3.22.0"
|