@voltro/plugin-scim 0.85.0 → 0.86.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/scim.js ADDED
@@ -0,0 +1,238 @@
1
+ //#region src/scim.ts
2
+ var e = "urn:ietf:params:scim:schemas:core:2.0:User", t = "urn:ietf:params:scim:schemas:core:2.0:Group", n = "urn:ietf:params:scim:api:messages:2.0:ListResponse", r = "urn:ietf:params:scim:api:messages:2.0:Error", i = (e) => new Date(e).toISOString(), a = (e, t) => {
3
+ if (typeof e == "boolean") return e;
4
+ if (typeof e == "number") return Number.isFinite(e) && e !== 0;
5
+ if (typeof e == "string") {
6
+ let t = e.trim().toLowerCase();
7
+ if (t === "true" || t === "1") return !0;
8
+ if (t === "false" || t === "0" || t === "") return !1;
9
+ }
10
+ return t;
11
+ }, o = (e) => {
12
+ let t = (e ?? "").trim();
13
+ return (t.toLowerCase().startsWith("urn:") ? t.slice(t.lastIndexOf(":") + 1) : t).toLowerCase();
14
+ }, s = (t, n) => ({
15
+ schemas: [e],
16
+ id: t.id,
17
+ ...t.externalId ? { externalId: t.externalId } : {},
18
+ userName: t.userName,
19
+ name: {
20
+ givenName: t.givenName ?? "",
21
+ familyName: t.familyName ?? ""
22
+ },
23
+ ...t.email ? { emails: [{
24
+ value: t.email,
25
+ primary: !0
26
+ }] } : {},
27
+ active: t.active,
28
+ meta: {
29
+ resourceType: "User",
30
+ created: i(t.createdAt),
31
+ lastModified: i(t.updatedAt),
32
+ location: `${n}/${t.id}`
33
+ }
34
+ }), c = (e) => {
35
+ let t = e.name ?? {}, n = e.emails ?? [], r = n.find((e) => e.primary)?.value ?? n[0]?.value ?? null;
36
+ return {
37
+ userName: String(e.userName ?? ""),
38
+ givenName: t.givenName ?? null,
39
+ familyName: t.familyName ?? null,
40
+ email: r,
41
+ active: e.active === void 0 || e.active === null || a(e.active, !1),
42
+ externalId: e.externalId ?? null
43
+ };
44
+ }, l = (e, t) => {
45
+ let n = t.Operations ?? t.operations ?? [], r = { ...e };
46
+ for (let e of n) {
47
+ let t = (e.op ?? "").toLowerCase();
48
+ if (t !== "replace" && t !== "add") continue;
49
+ let n = o(e.path);
50
+ if (n === "active") r = {
51
+ ...r,
52
+ active: a(e.value, !1)
53
+ };
54
+ else if (n === "username") r = {
55
+ ...r,
56
+ userName: String(e.value)
57
+ };
58
+ else if (n === "" && e.value && typeof e.value == "object") for (let [t, n] of Object.entries(e.value)) {
59
+ let e = t.toLowerCase();
60
+ e === "active" ? r = {
61
+ ...r,
62
+ active: a(n, !1)
63
+ } : e === "username" && (r = {
64
+ ...r,
65
+ userName: String(n)
66
+ });
67
+ }
68
+ }
69
+ return r;
70
+ }, u = (e, t) => ({
71
+ schemas: [n],
72
+ totalResults: t?.totalResults ?? e.length,
73
+ startIndex: t?.startIndex ?? 1,
74
+ itemsPerPage: e.length,
75
+ Resources: e
76
+ }), d = (e, t, n) => ({
77
+ schemas: [r],
78
+ status: String(e),
79
+ ...n ? { scimType: n } : {},
80
+ detail: t
81
+ }), f = (e, t) => {
82
+ if (!e) return { kind: "none" };
83
+ let n = /^\s*([A-Za-z][A-Za-z0-9]*)\s+eq\s+"([^"]*)"\s*$/i.exec(e);
84
+ if (!n) return { kind: "invalid" };
85
+ let r = t.find((e) => e.toLowerCase() === n[1].toLowerCase());
86
+ return r === void 0 ? { kind: "invalid" } : {
87
+ kind: "filter",
88
+ attribute: r,
89
+ value: n[2]
90
+ };
91
+ }, p = (e) => {
92
+ let t = Number(e.get("startIndex")), n = Number(e.get("count"));
93
+ return {
94
+ startIndex: Number.isSafeInteger(t) && t >= 1 ? t : 1,
95
+ count: e.get("count") !== null && Number.isSafeInteger(n) && n >= 0 ? n : null
96
+ };
97
+ }, m = (e, t) => {
98
+ 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;
99
+ for (let e of n) {
100
+ let t = (e.op ?? "").toLowerCase(), n = e.path ?? "", a = /^members\[value\s+eq\s+"([^"]*)"\]$/i.exec(n);
101
+ if (t === "add" && n.toLowerCase() === "members") {
102
+ let t = r(e.value), n = t.length > 4 ? new Set(i.members) : void 0, a = t.filter((e) => !(n === void 0 ? i.members.includes(e) : n.has(e)));
103
+ i = {
104
+ ...i,
105
+ members: [...i.members, ...a]
106
+ };
107
+ } else if (t === "remove" && a) i = {
108
+ ...i,
109
+ members: i.members.filter((e) => e !== a[1])
110
+ };
111
+ else if (t === "remove" && n.toLowerCase() === "members") {
112
+ let t = new Set(r(e.value));
113
+ i = {
114
+ ...i,
115
+ members: t.size === 0 ? [] : i.members.filter((e) => !t.has(e))
116
+ };
117
+ } else if (t === "replace" && n.toLowerCase() === "members") i = {
118
+ ...i,
119
+ members: [...r(e.value)]
120
+ };
121
+ else if (t === "replace" && n === "displayName") i = {
122
+ ...i,
123
+ displayName: String(e.value)
124
+ };
125
+ else if (t === "replace" && n === "" && e.value && typeof e.value == "object") {
126
+ let t = e.value;
127
+ "displayName" in t && (i = {
128
+ ...i,
129
+ displayName: String(t.displayName)
130
+ }), "members" in t && (i = {
131
+ ...i,
132
+ members: [...r(t.members)]
133
+ });
134
+ }
135
+ }
136
+ return i;
137
+ }, h = (e) => ({
138
+ schemas: ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
139
+ documentationUri: "https://voltro.dev",
140
+ patch: { supported: !0 },
141
+ bulk: {
142
+ supported: !1,
143
+ maxOperations: 0,
144
+ maxPayloadSize: 0
145
+ },
146
+ filter: {
147
+ supported: !0,
148
+ maxResults: 200
149
+ },
150
+ changePassword: { supported: !1 },
151
+ sort: { supported: !1 },
152
+ etag: { supported: !1 },
153
+ authenticationSchemes: [{
154
+ type: "oauthbearertoken",
155
+ name: "Bearer token",
156
+ description: "Static bearer token configured in the IdP SCIM settings"
157
+ }],
158
+ meta: {
159
+ resourceType: "ServiceProviderConfig",
160
+ location: e
161
+ }
162
+ }), g = (e, t = {}) => ({
163
+ name: e,
164
+ type: "string",
165
+ multiValued: !1,
166
+ required: !1,
167
+ caseExact: !1,
168
+ mutability: "readWrite",
169
+ returned: "default",
170
+ uniqueness: "none",
171
+ ...t
172
+ }), _ = (n) => [{
173
+ schemas: ["urn:ietf:params:scim:schemas:core:2.0:Schema"],
174
+ id: e,
175
+ name: "User",
176
+ description: "SCIM core User",
177
+ attributes: [
178
+ g("userName", {
179
+ required: !0,
180
+ uniqueness: "server"
181
+ }),
182
+ g("name", {
183
+ type: "complex",
184
+ subAttributes: [g("givenName"), g("familyName")]
185
+ }),
186
+ g("emails", {
187
+ type: "complex",
188
+ multiValued: !0,
189
+ subAttributes: [g("value"), g("primary", { type: "boolean" })]
190
+ }),
191
+ g("active", { type: "boolean" }),
192
+ g("externalId")
193
+ ],
194
+ meta: {
195
+ resourceType: "Schema",
196
+ location: `${n}/${e}`
197
+ }
198
+ }, {
199
+ schemas: ["urn:ietf:params:scim:schemas:core:2.0:Schema"],
200
+ id: t,
201
+ name: "Group",
202
+ description: "SCIM core Group",
203
+ attributes: [
204
+ g("displayName", { required: !0 }),
205
+ g("members", {
206
+ type: "complex",
207
+ multiValued: !0,
208
+ subAttributes: [g("value")]
209
+ }),
210
+ g("externalId")
211
+ ],
212
+ meta: {
213
+ resourceType: "Schema",
214
+ location: `${n}/${t}`
215
+ }
216
+ }], v = (n) => [{
217
+ schemas: ["urn:ietf:params:scim:schemas:core:2.0:ResourceType"],
218
+ id: "User",
219
+ name: "User",
220
+ endpoint: "/Users",
221
+ schema: e,
222
+ meta: {
223
+ resourceType: "ResourceType",
224
+ location: `${n}/ResourceTypes/User`
225
+ }
226
+ }, {
227
+ schemas: ["urn:ietf:params:scim:schemas:core:2.0:ResourceType"],
228
+ id: "Group",
229
+ name: "Group",
230
+ endpoint: "/Groups",
231
+ schema: t,
232
+ meta: {
233
+ resourceType: "ResourceType",
234
+ location: `${n}/ResourceTypes/Group`
235
+ }
236
+ }];
237
+ //#endregion
238
+ export { r as SCIM_ERROR_SCHEMA, t as SCIM_GROUP_SCHEMA, n as SCIM_LIST_SCHEMA, e as SCIM_USER_SCHEMA, m as applyScimGroupPatch, l as applyScimPatch, c as fromScimUser, p as parsePagination, f as parseScimFilter, v as resourceTypes, d as scimError, u as scimList, _ as scimSchemas, h as serviceProviderConfig, s as toScimUser };
package/dist/store.js ADDED
@@ -0,0 +1,195 @@
1
+ import { tsMs as e } from "@voltro/protocol";
2
+ import { boolean as t, eq as n, generateId as r, id as i, json as a, queryFor as o, table as s, text as c, timestamp as l } from "@voltro/database";
3
+ //#region src/store.ts
4
+ var u = s("_voltro_scim_users", {
5
+ id: i({ prefix: "scimuser" }),
6
+ userName: c(),
7
+ givenName: c().nullable(),
8
+ familyName: c().nullable(),
9
+ email: c().nullable(),
10
+ active: t().default(!0),
11
+ externalId: c().nullable(),
12
+ createdAt: l().default("now"),
13
+ updatedAt: l().default("now").onUpdate("now")
14
+ }).renamedFrom("scim_users").unique("byScimUserName", ["userName"]), d = s("_voltro_scim_groups", {
15
+ id: i({ prefix: "scimgroup" }),
16
+ displayName: c(),
17
+ externalId: c().nullable(),
18
+ members: a().nullable(),
19
+ createdAt: l().default("now"),
20
+ updatedAt: l().default("now").onUpdate("now")
21
+ }).renamedFrom("scim_groups"), f = (e) => {
22
+ if (!Number.isSafeInteger(e.startIndex) || e.startIndex < 1 || !Number.isSafeInteger(e.count) || e.count < 0) throw RangeError("SCIM pages require a positive startIndex and a non-negative count, both safe integers");
23
+ }, p = (e, t) => t === null || e[t.attribute] === t.value, m = (e = Date.now) => {
24
+ let t = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map(), r = 0, i = 0, a = (e) => `${e}_${++r}`, o = (e) => {
25
+ let t = -1, n = [];
26
+ return (r, a) => {
27
+ f(a), t !== i && (n = [...e.keys()].sort(), t = i);
28
+ let o = [], s = 0;
29
+ for (let t of n) {
30
+ let n = e.get(t);
31
+ p(n, r) && (s >= a.startIndex - 1 && o.length < a.count && o.push(n), s++);
32
+ }
33
+ return {
34
+ rows: o,
35
+ totalResults: s
36
+ };
37
+ };
38
+ }, s = o(t), c = o(n);
39
+ return {
40
+ listUsers: async (e) => [...t.values()].filter((t) => p(t, e)),
41
+ pageUsers: async (e, t) => s(e, t),
42
+ getUser: async (e) => t.get(e) ?? null,
43
+ createUser: async (n) => {
44
+ let r = {
45
+ ...n,
46
+ id: a("user"),
47
+ createdAt: e(),
48
+ updatedAt: e()
49
+ };
50
+ return t.set(r.id, r), i++, r;
51
+ },
52
+ putUser: async (n, r) => {
53
+ if (!t.has(n)) return null;
54
+ let i = {
55
+ ...r,
56
+ id: n,
57
+ updatedAt: e()
58
+ };
59
+ return t.set(n, i), i;
60
+ },
61
+ deleteUser: async (e) => {
62
+ let n = t.delete(e);
63
+ return n && i++, n;
64
+ },
65
+ listGroups: async (e) => [...n.values()].filter((t) => p(t, e)),
66
+ pageGroups: async (e, t) => c(e, t),
67
+ getGroup: async (e) => n.get(e) ?? null,
68
+ createGroup: async (t) => {
69
+ let r = {
70
+ ...t,
71
+ id: a("group"),
72
+ createdAt: e(),
73
+ updatedAt: e()
74
+ };
75
+ return n.set(r.id, r), i++, r;
76
+ },
77
+ putGroup: async (t, r) => {
78
+ if (!n.has(t)) return null;
79
+ let i = {
80
+ ...r,
81
+ id: t,
82
+ updatedAt: e()
83
+ };
84
+ return n.set(t, i), i;
85
+ },
86
+ deleteGroup: async (e) => {
87
+ let t = n.delete(e);
88
+ return t && i++, t;
89
+ }
90
+ };
91
+ }, h = (t) => {
92
+ let i = async (e, n) => {
93
+ let r = o(e);
94
+ return t.query((n ? r.where(n) : r).descriptor);
95
+ }, a = (t) => ({
96
+ id: String(t.id),
97
+ userName: String(t.userName),
98
+ givenName: t.givenName ?? null,
99
+ familyName: t.familyName ?? null,
100
+ email: t.email ?? null,
101
+ active: !!t.active,
102
+ externalId: t.externalId ?? null,
103
+ createdAt: e(t.createdAt),
104
+ updatedAt: e(t.updatedAt)
105
+ }), s = (t) => ({
106
+ id: String(t.id),
107
+ displayName: String(t.displayName),
108
+ externalId: t.externalId ?? null,
109
+ members: t.members ?? [],
110
+ createdAt: e(t.createdAt),
111
+ updatedAt: e(t.updatedAt)
112
+ }), c = async (e, r, i, a) => {
113
+ f(i);
114
+ let s = o(e), c = r === null ? s : s.where(n(r.attribute, r.value)), [l, u] = await Promise.all([t.query(c.count().descriptor), i.count === 0 ? Promise.resolve([]) : t.query(c.orderBy("id", "asc").offset(i.startIndex - 1).limit(i.count).descriptor)]);
115
+ return {
116
+ rows: u.map(a),
117
+ totalResults: Number(l[0]?.count ?? 0)
118
+ };
119
+ };
120
+ return {
121
+ listUsers: async (e) => (await i(u, e ? n(e.attribute, e.value) : void 0)).map(a),
122
+ pageUsers: (e, t) => c(u, e, t, a),
123
+ getUser: async (e) => {
124
+ let t = await i(u, n("id", e));
125
+ return t[0] ? a(t[0]) : null;
126
+ },
127
+ createUser: async (e) => {
128
+ let n = r({
129
+ kind: "typeid",
130
+ prefix: "scimuser"
131
+ }, "_voltro_scim_users"), i = /* @__PURE__ */ new Date(), a = {
132
+ id: n,
133
+ ...e,
134
+ createdAt: i,
135
+ updatedAt: i
136
+ };
137
+ return await t.insert("_voltro_scim_users", a), {
138
+ ...e,
139
+ id: n,
140
+ createdAt: i.getTime(),
141
+ updatedAt: i.getTime()
142
+ };
143
+ },
144
+ putUser: async (e, r) => (await i(u, n("id", e)))[0] ? (await t.update("_voltro_scim_users", e, {
145
+ userName: r.userName,
146
+ givenName: r.givenName,
147
+ familyName: r.familyName,
148
+ email: r.email,
149
+ active: r.active,
150
+ externalId: r.externalId,
151
+ updatedAt: /* @__PURE__ */ new Date()
152
+ }), {
153
+ ...r,
154
+ id: e,
155
+ updatedAt: Date.now()
156
+ }) : null,
157
+ deleteUser: async (e) => (await i(u, n("id", e)))[0] ? (await t.delete("_voltro_scim_users", e), !0) : !1,
158
+ listGroups: async (e) => (await i(d, e ? n(e.attribute, e.value) : void 0)).map(s),
159
+ pageGroups: (e, t) => c(d, e, t, s),
160
+ getGroup: async (e) => {
161
+ let t = await i(d, n("id", e));
162
+ return t[0] ? s(t[0]) : null;
163
+ },
164
+ createGroup: async (e) => {
165
+ let n = r({
166
+ kind: "typeid",
167
+ prefix: "scimgroup"
168
+ }, "_voltro_scim_groups"), i = /* @__PURE__ */ new Date(), a = {
169
+ id: n,
170
+ ...e,
171
+ createdAt: i,
172
+ updatedAt: i
173
+ };
174
+ return await t.insert("_voltro_scim_groups", a), {
175
+ ...e,
176
+ id: n,
177
+ createdAt: i.getTime(),
178
+ updatedAt: i.getTime()
179
+ };
180
+ },
181
+ putGroup: async (e, r) => (await i(d, n("id", e)))[0] ? (await t.update("_voltro_scim_groups", e, {
182
+ displayName: r.displayName,
183
+ externalId: r.externalId,
184
+ members: r.members,
185
+ updatedAt: /* @__PURE__ */ new Date()
186
+ }), {
187
+ ...r,
188
+ id: e,
189
+ updatedAt: Date.now()
190
+ }) : null,
191
+ deleteGroup: async (e) => (await i(d, n("id", e)))[0] ? (await t.delete("_voltro_scim_groups", e), !0) : !1
192
+ };
193
+ };
194
+ //#endregion
195
+ export { h as dataStoreScimStore, m as memoryScimStore, d as scimGroupsTable, u as scimUsersTable };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-scim",
3
- "version": "0.85.0",
3
+ "version": "0.86.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",
@@ -33,8 +33,8 @@
33
33
  "node": ">=24.0.0"
34
34
  },
35
35
  "dependencies": {
36
- "@voltro/database": "0.85.0",
37
- "@voltro/protocol": "0.85.0"
36
+ "@voltro/database": "0.86.0",
37
+ "@voltro/protocol": "0.86.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "effect": "4.0.1"