@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/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` / name. */
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, tsMs as n } from "@voltro/protocol";
3
- import { checkBearer as r } from "@voltro/protocol/session";
4
- import { boolean as i, eq as a, generateId as o, id as s, json as c, queryFor as l, table as u, text as d, timestamp as f } from "@voltro/database";
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 p = "urn:ietf:params:scim:schemas:core:2.0:User", m = "urn:ietf:params:scim:schemas:core:2.0:Group", h = "urn:ietf:params:scim:api:messages:2.0:ListResponse", g = "urn:ietf:params:scim:api:messages:2.0:Error", _ = (e) => new Date(e).toISOString(), v = (e, t) => ({
7
- schemas: [p],
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: _(e.createdAt),
23
- lastModified: _(e.updatedAt),
34
+ created: v(e.createdAt),
35
+ lastModified: v(e.updatedAt),
24
36
  location: `${t}/${e.id}`
25
37
  }
26
- }), y = (e) => {
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 || !!e.active,
45
+ active: e.active === void 0 || e.active === null || y(e.active, !1),
34
46
  externalId: e.externalId ?? null
35
47
  };
36
- }, b = (e, t) => {
48
+ }, C = (e, t) => {
37
49
  let n = t.Operations ?? t.operations ?? [], r = { ...e };
38
- for (let e of n) if ((e.op ?? "").toLowerCase() === "replace") {
39
- if (e.path === "active") r = {
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: !!e.value
56
+ active: y(e.value, !1)
42
57
  };
43
- else if (e.path === "userName") r = {
58
+ else if (n === "username") r = {
44
59
  ...r,
45
60
  userName: String(e.value)
46
61
  };
47
- else if (e.path === void 0 && e.value && typeof e.value == "object") {
48
- let t = e.value;
49
- "active" in t && (r = {
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: !!t.active
52
- }), "userName" in t && (r = {
66
+ active: y(n, !1)
67
+ } : e === "username" && (r = {
53
68
  ...r,
54
- userName: String(t.userName)
69
+ userName: String(n)
55
70
  });
56
71
  }
57
72
  }
58
73
  return r;
59
- }, x = (e, t) => ({
60
- schemas: [h],
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
- }), S = (e, t, n) => ({
66
- schemas: [g],
80
+ }), T = (e, t, n) => ({
81
+ schemas: [_],
67
82
  status: String(e),
68
83
  ...n ? { scimType: n } : {},
69
84
  detail: t
70
- }), C = (e, t) => {
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
- }, w = (e) => {
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
- }, T = (e, t) => {
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
- }, E = (e) => ({
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
- }), D = (e, t = {}) => ({
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
- }), O = (e) => [{
176
+ }), j = (e) => [{
162
177
  schemas: ["urn:ietf:params:scim:schemas:core:2.0:Schema"],
163
- id: p,
178
+ id: m,
164
179
  name: "User",
165
180
  description: "SCIM core User",
166
181
  attributes: [
167
- D("userName", {
182
+ A("userName", {
168
183
  required: !0,
169
184
  uniqueness: "server"
170
185
  }),
171
- D("name", {
186
+ A("name", {
172
187
  type: "complex",
173
- subAttributes: [D("givenName"), D("familyName")]
188
+ subAttributes: [A("givenName"), A("familyName")]
174
189
  }),
175
- D("emails", {
190
+ A("emails", {
176
191
  type: "complex",
177
192
  multiValued: !0,
178
- subAttributes: [D("value"), D("primary", { type: "boolean" })]
193
+ subAttributes: [A("value"), A("primary", { type: "boolean" })]
179
194
  }),
180
- D("active", { type: "boolean" }),
181
- D("externalId")
195
+ A("active", { type: "boolean" }),
196
+ A("externalId")
182
197
  ],
183
198
  meta: {
184
199
  resourceType: "Schema",
185
- location: `${e}/${p}`
200
+ location: `${e}/${m}`
186
201
  }
187
202
  }, {
188
203
  schemas: ["urn:ietf:params:scim:schemas:core:2.0:Schema"],
189
- id: m,
204
+ id: h,
190
205
  name: "Group",
191
206
  description: "SCIM core Group",
192
207
  attributes: [
193
- D("displayName", { required: !0 }),
194
- D("members", {
208
+ A("displayName", { required: !0 }),
209
+ A("members", {
195
210
  type: "complex",
196
211
  multiValued: !0,
197
- subAttributes: [D("value")]
212
+ subAttributes: [A("value")]
198
213
  }),
199
- D("externalId")
214
+ A("externalId")
200
215
  ],
201
216
  meta: {
202
217
  resourceType: "Schema",
203
- location: `${e}/${m}`
218
+ location: `${e}/${h}`
204
219
  }
205
- }], k = (e) => [{
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: p,
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: m,
235
+ schema: h,
221
236
  meta: {
222
237
  resourceType: "ResourceType",
223
238
  location: `${e}/ResourceTypes/Group`
224
239
  }
225
- }], A = u("_voltro_scim_users", {
226
- id: s({ prefix: "scimuser" }),
227
- userName: d(),
228
- givenName: d().nullable(),
229
- familyName: d().nullable(),
230
- email: d().nullable(),
231
- active: i().default(!0),
232
- externalId: d().nullable(),
233
- createdAt: f().default("now"),
234
- updatedAt: f().default("now").onUpdate("now")
235
- }).renamedFrom("scim_users").unique("byScimUserName", ["userName"]), j = u("_voltro_scim_groups", {
236
- id: s({ prefix: "scimgroup" }),
237
- displayName: d(),
238
- externalId: d().nullable(),
239
- members: c().nullable(),
240
- createdAt: f().default("now"),
241
- updatedAt: f().default("now").onUpdate("now")
242
- }).renamedFrom("scim_groups"), M = (e, t) => t === null || e[t.attribute] === t.value, N = (e = Date.now) => {
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) => M(t, e)),
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) => M(t, e)),
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
- }, P = (e) => {
303
+ }, L = (e) => {
289
304
  let t = async (t, n) => {
290
- let r = l(t);
305
+ let r = u(t);
291
306
  return e.query((n ? r.where(n) : r).descriptor);
292
- }, r = (e) => ({
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: n(e.createdAt),
301
- updatedAt: n(e.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: n(e.createdAt),
308
- updatedAt: n(e.updatedAt)
322
+ createdAt: r(e.createdAt),
323
+ updatedAt: r(e.updatedAt)
309
324
  });
310
325
  return {
311
- listUsers: async (e) => (await t(A, e ? a(e.attribute, e.value) : void 0)).map(r),
326
+ listUsers: async (e) => (await t(N, e ? o(e.attribute, e.value) : void 0)).map(n),
312
327
  getUser: async (e) => {
313
- let n = await t(A, a("id", e));
314
- return n[0] ? r(n[0]) : null;
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 = o({
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(A, a("id", n)))[0] ? (await e.update("_voltro_scim_users", n, {
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(A, a("id", n)))[0] ? (await e.delete("_voltro_scim_users", n), !0) : !1,
347
- listGroups: async (e) => (await t(j, e ? a(e.attribute, e.value) : void 0)).map(i),
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(j, a("id", e));
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 = o({
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(j, a("id", n)))[0] ? (await e.update("_voltro_scim_groups", n, {
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(j, a("id", n)))[0] ? (await e.delete("_voltro_scim_groups", n), !0) : !1
394
+ deleteGroup: async (n) => (await t(P, o("id", n)))[0] ? (await e.delete("_voltro_scim_groups", n), !0) : !1
380
395
  };
381
- }, F = (e, t) => ({
396
+ }, R = (e, t) => ({
382
397
  status: e,
383
398
  body: JSON.stringify(t),
384
399
  contentType: "application/scim+json"
385
- }), I = (e) => {
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
- }, L = (e, t) => {
392
- let { startIndex: n, count: r } = w(t), i = n - 1;
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
- }, R = () => F(400, S(400, "unsupported filter — only `<attribute> eq \"value\"` on the indexed attributes", "invalidFilter")), z = async (e, t, n) => (await e.listUsers({
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), B = (e) => F(409, S(409, `a user with userName '${e}' already exists`, "uniqueness")), V = async (e, t, n, i) => {
402
- if (!r(i.headers, t)) return F(401, S(401, "invalid or missing bearer token"));
403
- let [, a, o] = i.path.slice(n.length).replace(/\/+$/, "").split("/"), s = i.method.toUpperCase(), c = new URLSearchParams(i.query), l = `${n}/Users`;
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 F(200, E(`${n}/ServiceProviderConfig`));
406
- if (a === "Schemas") return F(200, x(O(`${n}/Schemas`)));
407
- if (a === "ResourceTypes") return F(200, x(k(n)));
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 = C(c.get("filter"), ["userName", "externalId"]);
412
- if (t.kind === "invalid") return R();
413
- let { page: n, total: r, startIndex: i } = L(await e.listUsers(t.kind === "filter" ? t : null), c);
414
- return F(200, x(n.map((e) => v(e, l)), {
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 ? F(200, v(t, l)) : F(404, S(404, "user not found"));
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 = y(I(i));
425
- if (t.userName === "") return F(400, S(400, "userName is required", "invalidValue"));
426
- if (await z(e, t.userName)) return B(t.userName);
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 F(201, v(await e.createUser(t), l));
443
+ return R(201, x(await e.createUser(t), l));
429
444
  } catch (n) {
430
- if (await z(e, t.userName)) return B(t.userName);
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 F(404, S(404, "user not found"));
451
+ if (!t) return R(404, T(404, "user not found"));
437
452
  let n = {
438
453
  ...t,
439
- ...y(I(i))
454
+ ...S(z(r))
440
455
  };
441
- if (await z(e, n.userName, o)) return B(n.userName);
442
- let r = await e.putUser(o, n);
443
- return r ? F(200, v(r, l)) : F(404, S(404, "user not found"));
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 F(404, S(404, "user not found"));
448
- let n = b(t, I(i));
449
- if (n.userName !== t.userName && await z(e, n.userName, o)) return B(n.userName);
450
- let r = await e.putUser(o, n);
451
- return r ? F(200, v(r, l)) : F(404, S(404, "user not found"));
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 } : F(404, S(404, "user not found"));
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`, r = (e) => ({
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 = C(c.get("filter"), ["displayName", "externalId"]);
469
- if (t.kind === "invalid") return R();
470
- let { page: n, total: i, startIndex: a } = L(await e.listGroups(t.kind === "filter" ? t : null), c);
471
- return F(200, x(n.map(r), {
472
- totalResults: i,
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 ? F(200, r(t)) : F(404, S(404, "group not found"));
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 = I(i), n = (t.members ?? []).map((e) => String(e.value)).filter(Boolean);
482
- return F(201, r(await e.createGroup({
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 F(404, S(404, "group not found"));
491
- let n = I(i), a = (n.members ?? []).map((e) => String(e.value)).filter(Boolean), s = await e.putGroup(o, {
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 ? F(200, r(s)) : F(404, S(404, "group not found"));
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 F(404, S(404, "group not found"));
502
- let n = await e.putGroup(o, T(t, I(i)));
503
- return n ? F(200, r(n)) : F(404, S(404, "group not found"));
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 } : F(404, S(404, "group not found"));
520
+ if (s === "DELETE" && o) return await e.deleteGroup(o) ? { status: 204 } : R(404, T(404, "group not found"));
506
521
  }
507
- return F(404, S(404, "unsupported SCIM endpoint"));
508
- }, H = (n) => {
509
- if (n.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.");
510
- let r = n.basePath ?? "/scim/v2", i = n.name ? `@voltro/plugin-scim#${n.name}` : "@voltro/plugin-scim", a;
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: i,
531
+ name: a,
532
+ baseName: G,
513
533
  description: "SCIM 2.0 Users + Groups provisioning endpoints.",
514
534
  permissions: ["store:write"],
515
- extendSchema: { tables: [A, j] },
535
+ extendSchema: { tables: [N, P] },
516
536
  httpRoutes: [{
517
537
  method: "*",
518
- path: r,
519
- handle: async (e) => a ? V(a, n.token, r, e) : F(503, S(503, "scim store not bound"))
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
- a = P(e);
543
+ o = L(e);
523
544
  },
524
545
  onActivate: (t) => e.sync(() => {
525
- t.logger.info("scim active", { basePath: r });
546
+ t.logger.info("scim active", { basePath: i });
526
547
  })
527
548
  });
528
549
  };
529
550
  //#endregion
530
- export { g as SCIM_ERROR_SCHEMA, m as SCIM_GROUP_SCHEMA, h as SCIM_LIST_SCHEMA, p as SCIM_USER_SCHEMA, T as applyScimGroupPatch, b as applyScimPatch, P as dataStoreScimStore, y as fromScimUser, V as handleScim, V as handleScimRequest, N as memoryScimStore, w as parsePagination, C as parseScimFilter, k as resourceTypes, S as scimError, x as scimList, H as scimPlugin, O as scimSchemas, E as serviceProviderConfig, v as toScimUser };
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.32.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.32.0",
36
- "@voltro/protocol": "0.32.0"
36
+ "@voltro/database": "0.34.0",
37
+ "@voltro/protocol": "0.34.0"
37
38
  },
38
39
  "peerDependencies": {
39
40
  "effect": "^3.22.0"