@estifanos-sh/convex-auth 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/README.md +5 -5
  2. package/dist/_generated/component.d.ts +130 -61
  3. package/dist/_generated/component.d.ts.map +1 -1
  4. package/dist/_generated/dataModel.d.ts +28 -0
  5. package/dist/_generated/dataModel.d.ts.map +1 -0
  6. package/dist/bin.js +1 -2
  7. package/dist/browser/index.d.ts +2 -2
  8. package/dist/browser/index.d.ts.map +1 -1
  9. package/dist/browser/index.js.map +1 -1
  10. package/dist/client/core/types.d.ts +40 -79
  11. package/dist/client/core/types.d.ts.map +1 -1
  12. package/dist/client/core/types.js.map +1 -1
  13. package/dist/client/errors.d.ts +15 -0
  14. package/dist/client/errors.d.ts.map +1 -0
  15. package/dist/client/errors.js +8 -1
  16. package/dist/client/errors.js.map +1 -1
  17. package/dist/client/index.d.ts +6 -3
  18. package/dist/client/index.d.ts.map +1 -1
  19. package/dist/client/index.js +3 -2
  20. package/dist/client/index.js.map +1 -1
  21. package/dist/component/_generated/component.d.ts +130 -61
  22. package/dist/component/_generated/component.d.ts.map +1 -1
  23. package/dist/component/_generated/dataModel.d.ts +1 -1
  24. package/dist/component/_generated/server.d.ts +1 -1
  25. package/dist/component/account.d.ts +43 -37
  26. package/dist/component/account.d.ts.map +1 -1
  27. package/dist/component/account.js +14 -16
  28. package/dist/component/account.js.map +1 -1
  29. package/dist/component/connection/audit.d.ts +29 -29
  30. package/dist/component/connection/audit.d.ts.map +1 -1
  31. package/dist/component/connection/audit.js +2 -1
  32. package/dist/component/connection/audit.js.map +1 -1
  33. package/dist/component/connection/cache.d.ts +6 -6
  34. package/dist/component/connection/domain/verification.d.ts +19 -19
  35. package/dist/component/connection/domain.d.ts +20 -20
  36. package/dist/component/connection/saml/assertion.d.ts +4 -4
  37. package/dist/component/connection/saml/request.d.ts +9 -9
  38. package/dist/component/connection/saml/request.d.ts.map +1 -1
  39. package/dist/component/connection/scim/config.d.ts +13 -13
  40. package/dist/component/connection/scim/identity.d.ts +102 -59
  41. package/dist/component/connection/scim/identity.d.ts.map +1 -1
  42. package/dist/component/connection/scim/identity.js +357 -56
  43. package/dist/component/connection/scim/identity.js.map +1 -1
  44. package/dist/component/connection/secret.d.ts +15 -15
  45. package/dist/component/connection/secret.d.ts.map +1 -1
  46. package/dist/component/connection/webhook/delivery.d.ts +69 -53
  47. package/dist/component/connection/webhook/delivery.d.ts.map +1 -1
  48. package/dist/component/connection/webhook/delivery.js +127 -170
  49. package/dist/component/connection/webhook/delivery.js.map +1 -1
  50. package/dist/component/connection/webhook/endpoint.d.ts +21 -21
  51. package/dist/component/connection/webhook/events.d.ts +26 -0
  52. package/dist/component/connection/webhook/events.d.ts.map +1 -0
  53. package/dist/component/connection/webhook/events.js +39 -0
  54. package/dist/component/connection/webhook/events.js.map +1 -0
  55. package/dist/component/connection.d.ts +38 -38
  56. package/dist/component/connection.d.ts.map +1 -1
  57. package/dist/component/context.d.ts +115 -0
  58. package/dist/component/context.d.ts.map +1 -0
  59. package/dist/component/context.js +50 -0
  60. package/dist/component/context.js.map +1 -0
  61. package/dist/component/convex.config.d.ts +2 -2
  62. package/dist/component/crons.d.ts +2 -2
  63. package/dist/component/event.d.ts +134 -138
  64. package/dist/component/event.d.ts.map +1 -1
  65. package/dist/component/event.js +88 -117
  66. package/dist/component/event.js.map +1 -1
  67. package/dist/component/eventstream.d.ts +30 -0
  68. package/dist/component/eventstream.d.ts.map +1 -0
  69. package/dist/component/eventstream.js +51 -0
  70. package/dist/component/eventstream.js.map +1 -0
  71. package/dist/component/factor/device.d.ts +25 -24
  72. package/dist/component/factor/device.d.ts.map +1 -1
  73. package/dist/component/factor/device.js +5 -2
  74. package/dist/component/factor/device.js.map +1 -1
  75. package/dist/component/factor/passkey.d.ts +84 -75
  76. package/dist/component/factor/passkey.d.ts.map +1 -1
  77. package/dist/component/factor/passkey.js +6 -6
  78. package/dist/component/factor/passkey.js.map +1 -1
  79. package/dist/component/factor/totp.d.ts +54 -49
  80. package/dist/component/factor/totp.d.ts.map +1 -1
  81. package/dist/component/factor/totp.js +3 -3
  82. package/dist/component/factor/totp.js.map +1 -1
  83. package/dist/component/group/active.d.ts +326 -22
  84. package/dist/component/group/active.d.ts.map +1 -1
  85. package/dist/component/group/active.js +21 -11
  86. package/dist/component/group/active.js.map +1 -1
  87. package/dist/component/group/invite.d.ts +28 -28
  88. package/dist/component/group/invite.d.ts.map +1 -1
  89. package/dist/component/group/member.d.ts +34 -34
  90. package/dist/component/group.d.ts +47 -62
  91. package/dist/component/group.d.ts.map +1 -1
  92. package/dist/component/http.d.ts +2 -2
  93. package/dist/component/limits.d.ts +4 -4
  94. package/dist/component/maintenance.d.ts +2 -2
  95. package/dist/component/model.d.ts +1362 -1027
  96. package/dist/component/model.d.ts.map +1 -1
  97. package/dist/component/model.js +23 -6
  98. package/dist/component/model.js.map +1 -1
  99. package/dist/component/modules.d.ts +8 -2
  100. package/dist/component/modules.d.ts.map +1 -1
  101. package/dist/component/modules.js +3 -0
  102. package/dist/component/modules.js.map +1 -1
  103. package/dist/component/oauth/client.d.ts +20 -20
  104. package/dist/component/oauth/code.d.ts +14 -14
  105. package/dist/component/oauth/refresh.d.ts +19 -19
  106. package/dist/component/schema.d.ts +1100 -611
  107. package/dist/component/schema.d.ts.map +1 -1
  108. package/dist/component/schema.js +17 -4
  109. package/dist/component/schema.js.map +1 -1
  110. package/dist/component/session.d.ts +72 -32
  111. package/dist/component/session.d.ts.map +1 -1
  112. package/dist/component/session.js +120 -32
  113. package/dist/component/session.js.map +1 -1
  114. package/dist/component/token/continuation.d.ts +40 -18
  115. package/dist/component/token/continuation.d.ts.map +1 -1
  116. package/dist/component/token/continuation.js +57 -11
  117. package/dist/component/token/continuation.js.map +1 -1
  118. package/dist/component/token/pkce.d.ts +22 -22
  119. package/dist/component/token/pkce.d.ts.map +1 -1
  120. package/dist/component/token/refresh.d.ts +39 -35
  121. package/dist/component/token/refresh.d.ts.map +1 -1
  122. package/dist/component/token/refresh.js +11 -6
  123. package/dist/component/token/refresh.js.map +1 -1
  124. package/dist/component/token/verification.d.ts +36 -32
  125. package/dist/component/token/verification.d.ts.map +1 -1
  126. package/dist/component/token/verification.js +3 -3
  127. package/dist/component/token/verification.js.map +1 -1
  128. package/dist/component/user/email.d.ts +21 -21
  129. package/dist/component/user/key.d.ts +35 -35
  130. package/dist/component/user/key.d.ts.map +1 -1
  131. package/dist/component/user.d.ts +51 -45
  132. package/dist/component/user.d.ts.map +1 -1
  133. package/dist/core/index.d.ts +54 -90
  134. package/dist/core/index.d.ts.map +1 -1
  135. package/dist/core/index.js +1 -4
  136. package/dist/core/index.js.map +1 -1
  137. package/dist/expo/index.d.ts +3 -3
  138. package/dist/expo/index.d.ts.map +1 -1
  139. package/dist/expo/index.js.map +1 -1
  140. package/dist/model.js.map +1 -1
  141. package/dist/providers/anonymous.d.ts +11 -5
  142. package/dist/providers/anonymous.d.ts.map +1 -1
  143. package/dist/providers/anonymous.js +4 -1
  144. package/dist/providers/anonymous.js.map +1 -1
  145. package/dist/providers/apple.d.ts +1 -1
  146. package/dist/providers/credentials.d.ts +21 -13
  147. package/dist/providers/credentials.d.ts.map +1 -1
  148. package/dist/providers/credentials.js +2 -0
  149. package/dist/providers/credentials.js.map +1 -1
  150. package/dist/providers/custom.d.ts +3 -3
  151. package/dist/providers/custom.d.ts.map +1 -1
  152. package/dist/providers/custom.js.map +1 -1
  153. package/dist/providers/email.d.ts +3 -3
  154. package/dist/providers/email.d.ts.map +1 -1
  155. package/dist/providers/email.js.map +1 -1
  156. package/dist/providers/github.d.ts +1 -1
  157. package/dist/providers/google.d.ts +1 -1
  158. package/dist/providers/microsoft.d.ts +1 -1
  159. package/dist/providers/password.d.ts +103 -8
  160. package/dist/providers/password.d.ts.map +1 -1
  161. package/dist/providers/password.js +71 -26
  162. package/dist/providers/password.js.map +1 -1
  163. package/dist/providers/phone.d.ts +4 -3
  164. package/dist/providers/phone.d.ts.map +1 -1
  165. package/dist/providers/phone.js.map +1 -1
  166. package/dist/react/index.d.ts +40 -41
  167. package/dist/react/index.d.ts.map +1 -1
  168. package/dist/react/index.js +27 -52
  169. package/dist/react/index.js.map +1 -1
  170. package/dist/server/auth.d.ts +157 -117
  171. package/dist/server/auth.d.ts.map +1 -1
  172. package/dist/server/auth.js +41 -22
  173. package/dist/server/auth.js.map +1 -1
  174. package/dist/server/component/factor/db.js +4 -4
  175. package/dist/server/component/factor/db.js.map +1 -1
  176. package/dist/server/config.d.ts +2 -1
  177. package/dist/server/connection/domain.d.ts +4 -66
  178. package/dist/server/connection/domain.d.ts.map +1 -1
  179. package/dist/server/connection/domain.js +22 -4
  180. package/dist/server/connection/domain.js.map +1 -1
  181. package/dist/server/connection/group/service.js +6 -4
  182. package/dist/server/connection/group/service.js.map +1 -1
  183. package/dist/server/connection/http.js +94 -261
  184. package/dist/server/connection/http.js.map +1 -1
  185. package/dist/server/connection/oidc.js +8 -17
  186. package/dist/server/connection/oidc.js.map +1 -1
  187. package/dist/server/connection/policy/domain.js +4 -1
  188. package/dist/server/connection/policy/domain.js.map +1 -1
  189. package/dist/server/connection/policy.js +14 -13
  190. package/dist/server/connection/policy.js.map +1 -1
  191. package/dist/server/connection/provision.js +5 -19
  192. package/dist/server/connection/provision.js.map +1 -1
  193. package/dist/server/connection/webhook.js +4 -36
  194. package/dist/server/connection/webhook.js.map +1 -1
  195. package/dist/server/context.d.ts +2 -2
  196. package/dist/server/context.d.ts.map +1 -1
  197. package/dist/server/context.js +35 -24
  198. package/dist/server/context.js.map +1 -1
  199. package/dist/server/contract.d.ts.map +1 -1
  200. package/dist/server/contract.js +6 -14
  201. package/dist/server/contract.js.map +1 -1
  202. package/dist/server/core.js +6 -0
  203. package/dist/server/core.js.map +1 -1
  204. package/dist/server/cors.d.ts +2 -2
  205. package/dist/server/cors.d.ts.map +1 -1
  206. package/dist/server/crypto.js.map +1 -1
  207. package/dist/server/db.js +2 -1
  208. package/dist/server/db.js.map +1 -1
  209. package/dist/server/domains/context.js +34 -0
  210. package/dist/server/domains/context.js.map +1 -0
  211. package/dist/server/domains/factor.d.ts +3 -2
  212. package/dist/server/domains/factor.d.ts.map +1 -1
  213. package/dist/server/domains/factor.js.map +1 -1
  214. package/dist/server/domains/group.js.map +1 -1
  215. package/dist/server/domains/invite.js.map +1 -1
  216. package/dist/server/domains/key.js.map +1 -1
  217. package/dist/server/domains/member.js.map +1 -1
  218. package/dist/server/domains/session.js.map +1 -1
  219. package/dist/server/domains/user.js.map +1 -1
  220. package/dist/server/env.js +2 -22
  221. package/dist/server/env.js.map +1 -1
  222. package/dist/server/events.d.ts +0 -1
  223. package/dist/server/events.d.ts.map +1 -1
  224. package/dist/server/events.js +6 -6
  225. package/dist/server/events.js.map +1 -1
  226. package/dist/server/http.d.ts +1 -0
  227. package/dist/server/http.d.ts.map +1 -1
  228. package/dist/server/http.js +4 -4
  229. package/dist/server/http.js.map +1 -1
  230. package/dist/server/identity/claims.js +1 -1
  231. package/dist/server/identity/convex.d.ts +2 -0
  232. package/dist/server/identity/convex.d.ts.map +1 -1
  233. package/dist/server/index.d.ts +2 -3
  234. package/dist/server/index.js +1 -2
  235. package/dist/server/limits.js +21 -1
  236. package/dist/server/limits.js.map +1 -1
  237. package/dist/server/mutations/credentials/signin.js +6 -3
  238. package/dist/server/mutations/credentials/signin.js.map +1 -1
  239. package/dist/server/mutations/invalidate.js +5 -10
  240. package/dist/server/mutations/invalidate.js.map +1 -1
  241. package/dist/server/mutations/oauth.js +1 -1
  242. package/dist/server/mutations/oauth.js.map +1 -1
  243. package/dist/server/mutations/retrieve.js +4 -1
  244. package/dist/server/mutations/retrieve.js.map +1 -1
  245. package/dist/server/mutations/signin.js +2 -2
  246. package/dist/server/mutations/signin.js.map +1 -1
  247. package/dist/server/mutations/verify.js +4 -3
  248. package/dist/server/mutations/verify.js.map +1 -1
  249. package/dist/server/oauth/factory.js.map +1 -1
  250. package/dist/server/runtime.d.ts +107 -127
  251. package/dist/server/runtime.d.ts.map +1 -1
  252. package/dist/server/runtime.js +43 -28
  253. package/dist/server/runtime.js.map +1 -1
  254. package/dist/server/session/lifecycle.d.ts.map +1 -1
  255. package/dist/server/session/lifecycle.js +18 -3
  256. package/dist/server/session/lifecycle.js.map +1 -1
  257. package/dist/server/signin/flow.js +1 -1
  258. package/dist/server/signin/flow.js.map +1 -1
  259. package/dist/server/tokens.js +1 -0
  260. package/dist/server/tokens.js.map +1 -1
  261. package/dist/server/totp.js +3 -3
  262. package/dist/server/totp.js.map +1 -1
  263. package/dist/server/types.d.ts +63 -43
  264. package/dist/server/types.d.ts.map +1 -1
  265. package/dist/server/user/account.js +0 -1
  266. package/dist/server/user/account.js.map +1 -1
  267. package/dist/server/validators.d.ts +874 -917
  268. package/dist/server/validators.d.ts.map +1 -1
  269. package/dist/server/validators.js +33 -32
  270. package/dist/server/validators.js.map +1 -1
  271. package/dist/server/webauthn.js +4 -4
  272. package/dist/server/webauthn.js.map +1 -1
  273. package/dist/shared/brand.d.ts +3 -7
  274. package/dist/shared/brand.d.ts.map +1 -1
  275. package/dist/shared/params.d.ts +78 -0
  276. package/dist/shared/params.d.ts.map +1 -0
  277. package/dist/shared/results.d.ts +7 -1
  278. package/dist/shared/results.d.ts.map +1 -1
  279. package/dist/svelte/context.svelte.d.ts +23 -12
  280. package/dist/svelte/context.svelte.d.ts.map +1 -1
  281. package/dist/svelte/context.svelte.js +20 -13
  282. package/dist/svelte/index.d.ts +1 -4
  283. package/dist/svelte/index.d.ts.map +1 -1
  284. package/dist/svelte/index.js +1 -4
  285. package/dist/test.d.ts +1105 -611
  286. package/dist/test.d.ts.map +1 -1
  287. package/package.json +2 -1
  288. package/dist/server/env.d.ts +0 -28
  289. package/dist/server/env.d.ts.map +0 -1
  290. package/dist/svelte/AuthLoading.svelte +0 -13
  291. package/dist/svelte/AuthLoading.svelte.d.ts +0 -8
  292. package/dist/svelte/AuthLoading.svelte.d.ts.map +0 -1
  293. package/dist/svelte/SignedIn.svelte +0 -14
  294. package/dist/svelte/SignedIn.svelte.d.ts +0 -8
  295. package/dist/svelte/SignedIn.svelte.d.ts.map +0 -1
  296. package/dist/svelte/SignedOut.svelte +0 -13
  297. package/dist/svelte/SignedOut.svelte.d.ts +0 -8
  298. package/dist/svelte/SignedOut.svelte.d.ts.map +0 -1
@@ -3,6 +3,7 @@ import { vGroupConnectionScimIdentityDoc, vPaginated, vScimResourceType } from "
3
3
  import schema_default from "../../schema.js";
4
4
  import { mutation, query } from "../../_generated/server.js";
5
5
  import "../../functions.js";
6
+ import { revokeSessionState } from "../../session.js";
6
7
  import { ConvexError, v } from "convex/values";
7
8
  import { paginationOptsValidator } from "convex/server";
8
9
  import { paginator } from "convex-helpers/server/pagination";
@@ -18,6 +19,7 @@ import { paginator } from "convex-helpers/server/pagination";
18
19
  * @module
19
20
  */
20
21
  const vScimUserData = v.object(schema_default.tables.User.validator.fields);
22
+ const vProfileUpdate = v.union(v.literal("never"), v.literal("missing"), v.literal("always"));
21
23
  /**
22
24
  * Read SCIM identities, overloaded by the args supplied. With
23
25
  * `{ connectionId, userIds }` it batch-resolves and returns an array aligned to
@@ -62,43 +64,29 @@ const list = query({
62
64
  return await paginator(ctx.db, schema_default).query("GroupConnectionScimIdentity").withIndex("group_connection_id", (idx) => idx.eq("connectionId", connectionId)).paginate(paginationOpts);
63
65
  }
64
66
  });
65
- /** Insert a SCIM identity, or patch it when it already exists (keyed by `(connectionId, resourceType, externalId)`). */
66
- const upsert = mutation({
67
- args: {
68
- connectionId: v.id("GroupConnection"),
69
- groupId: v.id("Group"),
70
- resourceType: vScimResourceType,
71
- externalId: v.string(),
72
- userId: v.optional(v.id("User")),
73
- mappedGroupId: v.optional(v.id("Group")),
74
- lastProvisionedAt: v.optional(v.number()),
75
- active: v.optional(v.boolean()),
76
- raw: v.optional(v.any())
77
- },
78
- returns: v.id("GroupConnectionScimIdentity"),
79
- handler: async (ctx, args) => {
80
- const existing = await ctx.db.query("GroupConnectionScimIdentity").withIndex("group_connection_id_resource_type_external_id", (idx) => idx.eq("connectionId", args.connectionId).eq("resourceType", args.resourceType).eq("externalId", args.externalId)).first();
81
- if (existing) {
82
- await ctx.db.patch("GroupConnectionScimIdentity", existing._id, args);
83
- return existing._id;
84
- }
85
- return await ctx.db.insert("GroupConnectionScimIdentity", args);
86
- }
87
- });
67
+ function providerForConnection(connection) {
68
+ if (connection.protocol === "oidc") return `oidc:${connection._id}`;
69
+ if (connection.protocol === "saml") return `saml:${connection._id}`;
70
+ throw new ConvexError({
71
+ code: ErrorCode.PROVIDER_NOT_CONFIGURED,
72
+ message: "SCIM requires an OIDC or SAML connection."
73
+ });
74
+ }
75
+ function patchProfile(current, next, mode) {
76
+ if (mode === "never") return {};
77
+ return Object.fromEntries(Object.entries(next).filter(([key, value]) => value === void 0 ? false : mode === "always" || current[key] === void 0 || current[key] === null || current[key] === ""));
78
+ }
88
79
  /**
89
- * Atomically provision or resolve a SCIM user by both of its durable natural
90
- * keys: the connection-scoped SCIM external id and the provider Account id.
91
- * A concurrent retry cannot commit a losing User because the identity and
92
- * Account index reads participate in the same OCC transaction as the inserts.
93
- * Existing ownership is preserved; conflicting owners are rejected.
80
+ * Atomically provision a directory-managed user, account, SCIM identity, and
81
+ * membership. Group and provider ownership are derived from the connection.
94
82
  */
95
83
  const provision = mutation({
96
84
  args: {
97
85
  connectionId: v.id("GroupConnection"),
98
- groupId: v.id("Group"),
99
- externalId: v.string(),
100
- provider: v.string(),
86
+ externalId: v.optional(v.string()),
101
87
  userData: vScimUserData,
88
+ profileUpdate: vProfileUpdate,
89
+ roleIds: v.array(v.string()),
102
90
  lastProvisionedAt: v.optional(v.number()),
103
91
  active: v.optional(v.boolean()),
104
92
  raw: v.optional(v.any())
@@ -108,56 +96,369 @@ const provision = mutation({
108
96
  created: v.boolean()
109
97
  }),
110
98
  handler: async (ctx, args) => {
111
- const identity = await ctx.db.query("GroupConnectionScimIdentity").withIndex("group_connection_id_resource_type_external_id", (idx) => idx.eq("connectionId", args.connectionId).eq("resourceType", "user").eq("externalId", args.externalId)).unique();
112
- const account = await ctx.db.query("Account").withIndex("provider_account_id", (idx) => idx.eq("provider", args.provider).eq("providerAccountId", args.externalId)).unique();
99
+ const connection = await activeScimConnection(ctx, args.connectionId);
100
+ const provider = providerForConnection(connection);
101
+ const externalId = args.externalId;
102
+ const identity = externalId === void 0 ? null : await ctx.db.query("GroupConnectionScimIdentity").withIndex("group_connection_id_resource_type_external_id", (idx) => idx.eq("connectionId", args.connectionId).eq("resourceType", "user").eq("externalId", externalId)).unique();
103
+ const account = externalId === void 0 ? null : await ctx.db.query("Account").withIndex("provider_account_id", (idx) => idx.eq("provider", provider).eq("providerAccountId", externalId)).unique();
113
104
  if (identity?.userId !== void 0 && account !== null && identity.userId !== account.userId) throw new ConvexError({
114
105
  code: ErrorCode.ACCOUNT_ALREADY_LINKED,
115
- message: "The SCIM identity and provider account belong to different users."
106
+ message: "SCIM account ownership conflicts."
116
107
  });
117
108
  let userId = identity?.userId ?? account?.userId;
118
109
  let created = false;
119
- if (userId !== void 0) {
120
- if (await ctx.db.get("User", userId) === null) throw new ConvexError({
121
- code: ErrorCode.ACCOUNT_NOT_FOUND,
122
- message: "The SCIM identity references a user that no longer exists."
123
- });
124
- } else {
110
+ if (userId === void 0) {
125
111
  userId = await ctx.db.insert("User", args.userData);
126
112
  created = true;
113
+ } else {
114
+ const user = await ctx.db.get("User", userId);
115
+ if (user === null) throw new ConvexError({
116
+ code: ErrorCode.ACCOUNT_NOT_FOUND,
117
+ message: "SCIM user not found."
118
+ });
119
+ if (await ctx.db.query("GroupConnectionScimIdentity").withIndex("user_id", (idx) => idx.eq("userId", userId)).filter((query$1) => query$1.neq(query$1.field("connectionId"), args.connectionId)).first() !== null) throw new ConvexError({
120
+ code: ErrorCode.ACCOUNT_ALREADY_LINKED,
121
+ message: "User is managed by another SCIM connection."
122
+ });
123
+ const patch = patchProfile(user, args.userData, args.profileUpdate);
124
+ if (Object.keys(patch).length > 0) await ctx.db.patch("User", userId, patch);
127
125
  }
128
- if (account === null) await ctx.db.insert("Account", {
126
+ if (externalId !== void 0 && account === null) await ctx.db.insert("Account", {
129
127
  userId,
130
- provider: args.provider,
131
- providerAccountId: args.externalId
128
+ provider,
129
+ providerAccountId: externalId
132
130
  });
133
131
  const identityData = {
134
132
  connectionId: args.connectionId,
135
- groupId: args.groupId,
133
+ groupId: connection.groupId,
136
134
  resourceType: "user",
137
- externalId: args.externalId,
138
- userId
135
+ ...externalId === void 0 ? {} : { externalId },
136
+ userId,
137
+ active: args.active !== false,
138
+ ...args.lastProvisionedAt === void 0 ? {} : { lastProvisionedAt: args.lastProvisionedAt },
139
+ ...args.raw === void 0 ? {} : { raw: args.raw }
139
140
  };
140
- if (args.lastProvisionedAt !== void 0) identityData.lastProvisionedAt = args.lastProvisionedAt;
141
- if (args.active !== void 0) identityData.active = args.active;
142
- if (args.raw !== void 0) identityData.raw = args.raw;
143
141
  if (identity === null) await ctx.db.insert("GroupConnectionScimIdentity", identityData);
144
- else await ctx.db.patch("GroupConnectionScimIdentity", identity._id, identityData);
142
+ else {
143
+ if (identity.groupId !== connection.groupId) throw new ConvexError({
144
+ code: ErrorCode.INVALID_PARAMETERS,
145
+ message: "SCIM identity group mismatch."
146
+ });
147
+ await ctx.db.patch("GroupConnectionScimIdentity", identity._id, identityData);
148
+ }
149
+ const membership = await ctx.db.query("GroupMember").withIndex("group_id_user_id", (idx) => idx.eq("groupId", connection.groupId).eq("userId", userId)).unique();
150
+ const membershipPatch = {
151
+ roleIds: args.roleIds,
152
+ status: args.active === false ? "inactive" : "active"
153
+ };
154
+ if (membership === null) await ctx.db.insert("GroupMember", {
155
+ groupId: connection.groupId,
156
+ userId,
157
+ ...membershipPatch
158
+ });
159
+ else await ctx.db.patch("GroupMember", membership._id, membershipPatch);
160
+ return {
161
+ userId,
162
+ created
163
+ };
164
+ }
165
+ });
166
+ /** Atomically update a directory-managed user and its connection membership. */
167
+ const update = mutation({
168
+ args: {
169
+ connectionId: v.id("GroupConnection"),
170
+ userId: v.id("User"),
171
+ externalId: v.optional(v.string()),
172
+ userData: vScimUserData,
173
+ profileUpdate: vProfileUpdate,
174
+ roleIds: v.array(v.string()),
175
+ active: v.boolean(),
176
+ lastProvisionedAt: v.optional(v.number()),
177
+ raw: v.optional(v.any())
178
+ },
179
+ returns: v.null(),
180
+ handler: async (ctx, args) => {
181
+ const connection = await activeScimConnection(ctx, args.connectionId);
182
+ const externalId = args.externalId;
183
+ const identity = await ctx.db.query("GroupConnectionScimIdentity").withIndex("group_connection_id_user_id", (idx) => idx.eq("connectionId", args.connectionId).eq("userId", args.userId)).unique();
184
+ if (identity === null || identity.resourceType !== "user" || identity.groupId !== connection.groupId) throw new ConvexError({
185
+ code: ErrorCode.ACCOUNT_NOT_FOUND,
186
+ message: "SCIM user not found."
187
+ });
188
+ if (await ctx.db.query("GroupConnectionScimIdentity").withIndex("user_id", (idx) => idx.eq("userId", args.userId)).filter((query$1) => query$1.neq(query$1.field("connectionId"), args.connectionId)).first() !== null) throw new ConvexError({
189
+ code: ErrorCode.ACCOUNT_ALREADY_LINKED,
190
+ message: "User is managed by another SCIM connection."
191
+ });
192
+ const conflicting = externalId === void 0 ? null : await ctx.db.query("GroupConnectionScimIdentity").withIndex("group_connection_id_resource_type_external_id", (idx) => idx.eq("connectionId", args.connectionId).eq("resourceType", "user").eq("externalId", externalId)).unique();
193
+ if (conflicting !== null && conflicting._id !== identity._id) throw new ConvexError({
194
+ code: ErrorCode.ACCOUNT_ALREADY_LINKED,
195
+ message: "SCIM external id is already linked."
196
+ });
197
+ const user = await ctx.db.get("User", args.userId);
198
+ if (user === null) throw new ConvexError({
199
+ code: ErrorCode.ACCOUNT_NOT_FOUND,
200
+ message: "User not found."
201
+ });
202
+ const provider = providerForConnection(connection);
203
+ const currentExternalId = identity.externalId;
204
+ const [currentAccount, nextAccount] = await Promise.all([currentExternalId === void 0 ? Promise.resolve(null) : ctx.db.query("Account").withIndex("provider_account_id", (idx) => idx.eq("provider", provider).eq("providerAccountId", currentExternalId)).unique(), externalId === void 0 || currentExternalId === externalId ? Promise.resolve(null) : ctx.db.query("Account").withIndex("provider_account_id", (idx) => idx.eq("provider", provider).eq("providerAccountId", externalId)).unique()]);
205
+ if (nextAccount !== null && nextAccount.userId !== args.userId) throw new ConvexError({
206
+ code: ErrorCode.ACCOUNT_ALREADY_LINKED,
207
+ message: "The replacement SCIM external id is already linked."
208
+ });
209
+ if (currentAccount !== null && currentAccount.userId !== args.userId) throw new ConvexError({
210
+ code: ErrorCode.ACCOUNT_ALREADY_LINKED,
211
+ message: "The SCIM account belongs to another user."
212
+ });
213
+ const patch = patchProfile(user, args.userData, args.profileUpdate);
214
+ if (Object.keys(patch).length > 0) await ctx.db.patch("User", args.userId, patch);
215
+ if (currentExternalId === externalId) {
216
+ if (externalId !== void 0 && currentAccount === null) await ctx.db.insert("Account", {
217
+ userId: args.userId,
218
+ provider,
219
+ providerAccountId: externalId
220
+ });
221
+ } else if (currentAccount !== null) if (externalId === void 0) await ctx.db.delete("Account", currentAccount._id);
222
+ else if (nextAccount === null) await ctx.db.patch("Account", currentAccount._id, { providerAccountId: externalId });
223
+ else await ctx.db.delete("Account", currentAccount._id);
224
+ else if (externalId !== void 0 && nextAccount === null) await ctx.db.insert("Account", {
225
+ userId: args.userId,
226
+ provider,
227
+ providerAccountId: externalId
228
+ });
229
+ await ctx.db.patch("GroupConnectionScimIdentity", identity._id, {
230
+ externalId,
231
+ active: args.active,
232
+ lastProvisionedAt: args.lastProvisionedAt,
233
+ raw: args.raw
234
+ });
235
+ const membership = await ctx.db.query("GroupMember").withIndex("group_id_user_id", (idx) => idx.eq("groupId", connection.groupId).eq("userId", args.userId)).unique();
236
+ const membershipPatch = {
237
+ roleIds: args.roleIds,
238
+ status: args.active ? "active" : "inactive"
239
+ };
240
+ if (membership === null) await ctx.db.insert("GroupMember", {
241
+ groupId: connection.groupId,
242
+ userId: args.userId,
243
+ ...membershipPatch
244
+ });
245
+ else await ctx.db.patch("GroupMember", membership._id, membershipPatch);
246
+ return null;
247
+ }
248
+ });
249
+ /** Atomically revoke a directory-managed user's membership, sessions, and identity. */
250
+ const revoke = mutation({
251
+ args: {
252
+ connectionId: v.id("GroupConnection"),
253
+ userId: v.id("User"),
254
+ mode: v.union(v.literal("soft"), v.literal("hard"))
255
+ },
256
+ returns: v.object({
257
+ epoch: v.number(),
258
+ cleanedSessions: v.number(),
259
+ cleanupPending: v.boolean()
260
+ }),
261
+ handler: async (ctx, args) => {
262
+ const connection = await activeScimConnection(ctx, args.connectionId);
263
+ const identity = await ctx.db.query("GroupConnectionScimIdentity").withIndex("group_connection_id_user_id", (idx) => idx.eq("connectionId", args.connectionId).eq("userId", args.userId)).unique();
264
+ if (identity === null || identity.resourceType !== "user" || identity.groupId !== connection.groupId) throw new ConvexError({
265
+ code: ErrorCode.ACCOUNT_NOT_FOUND,
266
+ message: "SCIM user not found."
267
+ });
268
+ if (await ctx.db.query("GroupConnectionScimIdentity").withIndex("user_id", (idx) => idx.eq("userId", args.userId)).filter((query$1) => query$1.neq(query$1.field("connectionId"), args.connectionId)).first() !== null) throw new ConvexError({
269
+ code: ErrorCode.ACCOUNT_ALREADY_LINKED,
270
+ message: "User is managed by another SCIM connection."
271
+ });
272
+ const provider = providerForConnection(connection);
273
+ const externalId = identity.externalId;
274
+ const account = externalId === void 0 ? null : await ctx.db.query("Account").withIndex("provider_account_id", (idx) => idx.eq("provider", provider).eq("providerAccountId", externalId)).unique();
275
+ if (account !== null && account.userId !== args.userId) throw new ConvexError({
276
+ code: ErrorCode.ACCOUNT_ALREADY_LINKED,
277
+ message: "The SCIM account belongs to another user."
278
+ });
279
+ const membership = await ctx.db.query("GroupMember").withIndex("group_id_user_id", (idx) => idx.eq("groupId", connection.groupId).eq("userId", args.userId)).unique();
280
+ if (membership !== null) await ctx.db.delete("GroupMember", membership._id);
281
+ if (account !== null) await ctx.db.delete("Account", account._id);
282
+ const { epoch, cleanedSessions, cleanupPending } = await revokeSessionState(ctx, args.userId);
283
+ if (args.mode === "hard") await ctx.db.delete("GroupConnectionScimIdentity", identity._id);
284
+ else await ctx.db.patch("GroupConnectionScimIdentity", identity._id, {
285
+ active: false,
286
+ lastProvisionedAt: Date.now()
287
+ });
145
288
  return {
289
+ epoch,
290
+ cleanedSessions,
291
+ cleanupPending
292
+ };
293
+ }
294
+ });
295
+ async function activeScimConnection(ctx, connectionId) {
296
+ const connection = await ctx.db.get("GroupConnection", connectionId);
297
+ const scim = connection === null ? null : await ctx.db.query("GroupConnectionScimConfig").withIndex("group_connection_id", (idx) => idx.eq("connectionId", connectionId)).unique();
298
+ if (connection === null || connection.status !== "active" || scim === null || scim.status !== "active" || scim.groupId !== connection.groupId) throw new ConvexError({
299
+ code: ErrorCode.INVALID_PARAMETERS,
300
+ message: "SCIM connection is not active."
301
+ });
302
+ return connection;
303
+ }
304
+ async function assertScimMembers(ctx, connection, userIds) {
305
+ for (const userId of new Set(userIds)) {
306
+ const [user, identity, membership] = await Promise.all([
307
+ ctx.db.get("User", userId),
308
+ ctx.db.query("GroupConnectionScimIdentity").withIndex("group_connection_id_user_id", (idx) => idx.eq("connectionId", connection._id).eq("userId", userId)).first(),
309
+ ctx.db.query("GroupMember").withIndex("group_id_user_id", (idx) => idx.eq("groupId", connection.groupId).eq("userId", userId)).first()
310
+ ]);
311
+ if (user === null || identity === null && membership === null) throw new ConvexError({
312
+ code: ErrorCode.ACCOUNT_NOT_FOUND,
313
+ message: "A SCIM group member is not available through this connection."
314
+ });
315
+ }
316
+ }
317
+ async function replaceScimGroupMembers(ctx, groupId, memberIds, roleIds) {
318
+ const current = await ctx.db.query("GroupMember").withIndex("group_id", (idx) => idx.eq("groupId", groupId)).collect();
319
+ const wanted = new Set(memberIds);
320
+ for (const member of current) if (!wanted.has(member.userId)) await ctx.db.delete("GroupMember", member._id);
321
+ for (const userId of wanted) {
322
+ const member = current.find((candidate) => candidate.userId === userId);
323
+ const patch = {
324
+ roleIds,
325
+ status: "active"
326
+ };
327
+ if (member === void 0) await ctx.db.insert("GroupMember", {
328
+ groupId,
146
329
  userId,
330
+ ...patch
331
+ });
332
+ else await ctx.db.patch("GroupMember", member._id, patch);
333
+ }
334
+ }
335
+ /** Atomically provision a SCIM group, its identity, and its complete membership. */
336
+ const provisionGroup = mutation({
337
+ args: {
338
+ connectionId: v.id("GroupConnection"),
339
+ externalId: v.optional(v.string()),
340
+ name: v.string(),
341
+ memberIds: v.array(v.id("User")),
342
+ roleIds: v.array(v.string()),
343
+ raw: v.optional(v.any())
344
+ },
345
+ returns: v.object({
346
+ groupId: v.id("Group"),
347
+ created: v.boolean()
348
+ }),
349
+ handler: async (ctx, args) => {
350
+ const connection = await activeScimConnection(ctx, args.connectionId);
351
+ const externalId = args.externalId;
352
+ await assertScimMembers(ctx, connection, args.memberIds);
353
+ const identity = externalId === void 0 ? null : await ctx.db.query("GroupConnectionScimIdentity").withIndex("group_connection_id_resource_type_external_id", (idx) => idx.eq("connectionId", args.connectionId).eq("resourceType", "group").eq("externalId", externalId)).unique();
354
+ if (identity !== null && identity.groupId !== connection.groupId) throw new ConvexError({
355
+ code: ErrorCode.INVALID_PARAMETERS,
356
+ message: "SCIM identity group mismatch."
357
+ });
358
+ let groupId = identity?.mappedGroupId;
359
+ let created = false;
360
+ if (groupId === void 0) {
361
+ const parent = await ctx.db.get("Group", connection.groupId);
362
+ if (parent === null) throw new ConvexError({
363
+ code: ErrorCode.INVALID_PARAMETERS,
364
+ message: "Connection group not found."
365
+ });
366
+ groupId = await ctx.db.insert("Group", {
367
+ name: args.name,
368
+ type: "organization",
369
+ parentGroupId: connection.groupId,
370
+ rootGroupId: parent.rootGroupId ?? parent._id,
371
+ isRoot: false
372
+ });
373
+ created = true;
374
+ } else {
375
+ const group = await ctx.db.get("Group", groupId);
376
+ if (group === null || group.parentGroupId !== connection.groupId) throw new ConvexError({
377
+ code: ErrorCode.INVALID_PARAMETERS,
378
+ message: "SCIM group ownership mismatch."
379
+ });
380
+ await ctx.db.patch("Group", groupId, { name: args.name });
381
+ }
382
+ const identityData = {
383
+ connectionId: args.connectionId,
384
+ groupId: connection.groupId,
385
+ resourceType: "group",
386
+ ...externalId === void 0 ? {} : { externalId },
387
+ mappedGroupId: groupId,
388
+ active: true,
389
+ lastProvisionedAt: Date.now(),
390
+ ...args.raw === void 0 ? {} : { raw: args.raw }
391
+ };
392
+ if (identity === null) await ctx.db.insert("GroupConnectionScimIdentity", identityData);
393
+ else await ctx.db.patch("GroupConnectionScimIdentity", identity._id, identityData);
394
+ await replaceScimGroupMembers(ctx, groupId, args.memberIds, args.roleIds);
395
+ return {
396
+ groupId,
147
397
  created
148
398
  };
149
399
  }
150
400
  });
151
- /** Delete a SCIM identity by id. */
152
- const remove = mutation({
153
- args: { id: v.id("GroupConnectionScimIdentity") },
401
+ /** Atomically update a SCIM group and replace its complete membership. */
402
+ const updateGroup = mutation({
403
+ args: {
404
+ connectionId: v.id("GroupConnection"),
405
+ groupId: v.id("Group"),
406
+ name: v.optional(v.string()),
407
+ memberIds: v.array(v.id("User")),
408
+ roleIds: v.array(v.string()),
409
+ raw: v.optional(v.any())
410
+ },
154
411
  returns: v.null(),
155
- handler: async (ctx, { id: identityId }) => {
156
- await ctx.db.delete("GroupConnectionScimIdentity", identityId);
412
+ handler: async (ctx, args) => {
413
+ const connection = await activeScimConnection(ctx, args.connectionId);
414
+ const identity = await ctx.db.query("GroupConnectionScimIdentity").withIndex("mapped_group_id", (idx) => idx.eq("mappedGroupId", args.groupId)).unique();
415
+ if (identity === null || identity.connectionId !== args.connectionId || identity.groupId !== connection.groupId || identity.resourceType !== "group") throw new ConvexError({
416
+ code: ErrorCode.ACCOUNT_NOT_FOUND,
417
+ message: "SCIM group not found."
418
+ });
419
+ const group = await ctx.db.get("Group", args.groupId);
420
+ if (group === null || group.parentGroupId !== connection.groupId) throw new ConvexError({
421
+ code: ErrorCode.INVALID_PARAMETERS,
422
+ message: "SCIM group ownership mismatch."
423
+ });
424
+ await assertScimMembers(ctx, connection, args.memberIds);
425
+ if (args.name !== void 0) await ctx.db.patch("Group", args.groupId, { name: args.name });
426
+ await ctx.db.patch("GroupConnectionScimIdentity", identity._id, {
427
+ active: true,
428
+ lastProvisionedAt: Date.now(),
429
+ ...args.raw === void 0 ? {} : { raw: args.raw }
430
+ });
431
+ await replaceScimGroupMembers(ctx, args.groupId, args.memberIds, args.roleIds);
432
+ return null;
433
+ }
434
+ });
435
+ /** Atomically revoke a SCIM group and its identity after verifying connection ownership. */
436
+ const revokeGroup = mutation({
437
+ args: {
438
+ connectionId: v.id("GroupConnection"),
439
+ groupId: v.id("Group")
440
+ },
441
+ returns: v.null(),
442
+ handler: async (ctx, args) => {
443
+ const connection = await activeScimConnection(ctx, args.connectionId);
444
+ const identity = await ctx.db.query("GroupConnectionScimIdentity").withIndex("mapped_group_id", (idx) => idx.eq("mappedGroupId", args.groupId)).unique();
445
+ if (identity === null || identity.connectionId !== args.connectionId || identity.groupId !== connection.groupId || identity.resourceType !== "group") throw new ConvexError({
446
+ code: ErrorCode.ACCOUNT_NOT_FOUND,
447
+ message: "SCIM group not found."
448
+ });
449
+ const group = await ctx.db.get("Group", args.groupId);
450
+ if (group === null || group.parentGroupId !== identity.groupId) throw new ConvexError({
451
+ code: ErrorCode.INVALID_PARAMETERS,
452
+ message: "SCIM group ownership mismatch."
453
+ });
454
+ const members = await ctx.db.query("GroupMember").withIndex("group_id", (idx) => idx.eq("groupId", args.groupId)).collect();
455
+ for (const member of members) await ctx.db.delete("GroupMember", member._id);
456
+ await ctx.db.delete("GroupConnectionScimIdentity", identity._id);
457
+ await ctx.db.delete("Group", args.groupId);
157
458
  return null;
158
459
  }
159
460
  });
160
461
 
161
462
  //#endregion
162
- export { get, list, provision, remove, upsert };
463
+ export { get, list, provision, provisionGroup, revoke, revokeGroup, update, updateGroup };
163
464
  //# sourceMappingURL=identity.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"identity.js","names":["schema"],"sources":["../../../../src/component/connection/scim/identity.ts"],"sourcesContent":["/**\n * `component.connection.scim.identity.*` — SCIM-provisioned\n * identities for an Connection connection (sub-resource of connection).\n *\n * `get` is overloaded — single lookup or, with `{ connectionId,\n * userIds }`, a batched resolve aligned to input order.\n *\n * @module\n */\n\nimport { paginationOptsValidator } from \"convex/server\";\nimport { ConvexError, type Infer, v } from \"convex/values\";\nimport { paginator } from \"convex-helpers/server/pagination\";\n\nimport { ErrorCode } from \"../../../shared/codes\";\nimport { mutation, query } from \"../../functions\";\nimport { vGroupConnectionScimIdentityDoc, vPaginated, vScimResourceType } from \"../../model\";\nimport schema from \"../../schema\";\n\nconst vScimUserData = v.object(schema.tables.User.validator.fields);\n\ntype ScimUserIdentity = Pick<\n Infer<typeof vGroupConnectionScimIdentityDoc>,\n \"connectionId\" | \"groupId\" | \"externalId\" | \"lastProvisionedAt\" | \"active\" | \"raw\"\n> & {\n resourceType: \"user\";\n userId: NonNullable<Infer<typeof vGroupConnectionScimIdentityDoc>[\"userId\"]>;\n};\n\n/**\n * Read SCIM identities, overloaded by the args supplied. With\n * `{ connectionId, userIds }` it batch-resolves and returns an array aligned to\n * the input order (`null` per missing user). Otherwise it resolves a single\n * identity by `(connectionId, resourceType, externalId)`, `(connectionId,\n * userId)`, `userId`, or `mappedGroupId`. Returns `null` when nothing matches.\n */\nexport const get = query({\n args: {\n connectionId: v.optional(v.id(\"GroupConnection\")),\n resourceType: v.optional(vScimResourceType),\n externalId: v.optional(v.string()),\n userId: v.optional(v.id(\"User\")),\n userIds: v.optional(v.array(v.id(\"User\"))),\n mappedGroupId: v.optional(v.id(\"Group\")),\n },\n returns: v.union(\n vGroupConnectionScimIdentityDoc,\n v.null(),\n v.array(v.union(vGroupConnectionScimIdentityDoc, v.null())),\n ),\n handler: async (ctx, args) => {\n if (args.connectionId !== undefined && args.userIds !== undefined) {\n const userIds = args.userIds;\n if (userIds.length === 0) return [];\n const unique = Array.from(new Set(userIds));\n const docs = await Promise.all(\n unique.map((userId) =>\n ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_user_id\", (idx) =>\n idx.eq(\"connectionId\", args.connectionId!).eq(\"userId\", userId),\n )\n .first(),\n ),\n );\n const byUserId = new Map(unique.map((id, i) => [id, docs[i] ?? null]));\n return userIds.map((userId) => byUserId.get(userId) ?? null);\n }\n if (\n args.connectionId !== undefined &&\n args.resourceType !== undefined &&\n args.externalId !== undefined\n ) {\n return await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_resource_type_external_id\", (idx) =>\n idx\n .eq(\"connectionId\", args.connectionId!)\n .eq(\"resourceType\", args.resourceType!)\n .eq(\"externalId\", args.externalId!),\n )\n .first();\n }\n if (args.connectionId !== undefined && args.userId !== undefined) {\n return await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_user_id\", (idx) =>\n idx.eq(\"connectionId\", args.connectionId!).eq(\"userId\", args.userId!),\n )\n .first();\n }\n if (args.userId !== undefined) {\n return await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"user_id\", (idx) => idx.eq(\"userId\", args.userId!))\n .first();\n }\n if (args.mappedGroupId !== undefined) {\n return await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"mapped_group_id\", (idx) => idx.eq(\"mappedGroupId\", args.mappedGroupId!))\n .first();\n }\n return null;\n },\n});\n\n/** List a connection's SCIM identities, paginated. */\nexport const list = query({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n paginationOpts: paginationOptsValidator,\n },\n returns: vPaginated(vGroupConnectionScimIdentityDoc),\n handler: async (ctx, { connectionId, paginationOpts }) => {\n return await paginator(ctx.db, schema)\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id\", (idx) => idx.eq(\"connectionId\", connectionId))\n .paginate(paginationOpts);\n },\n});\n\n/** Insert a SCIM identity, or patch it when it already exists (keyed by `(connectionId, resourceType, externalId)`). */\nexport const upsert = mutation({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n groupId: v.id(\"Group\"),\n resourceType: vScimResourceType,\n externalId: v.string(),\n userId: v.optional(v.id(\"User\")),\n mappedGroupId: v.optional(v.id(\"Group\")),\n lastProvisionedAt: v.optional(v.number()),\n active: v.optional(v.boolean()),\n raw: v.optional(v.any()),\n },\n returns: v.id(\"GroupConnectionScimIdentity\"),\n handler: async (ctx, args) => {\n const existing = await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_resource_type_external_id\", (idx) =>\n idx\n .eq(\"connectionId\", args.connectionId)\n .eq(\"resourceType\", args.resourceType)\n .eq(\"externalId\", args.externalId),\n )\n .first();\n if (existing) {\n await ctx.db.patch(\"GroupConnectionScimIdentity\", existing._id, args);\n return existing._id;\n }\n return await ctx.db.insert(\"GroupConnectionScimIdentity\", args);\n },\n});\n\n/**\n * Atomically provision or resolve a SCIM user by both of its durable natural\n * keys: the connection-scoped SCIM external id and the provider Account id.\n * A concurrent retry cannot commit a losing User because the identity and\n * Account index reads participate in the same OCC transaction as the inserts.\n * Existing ownership is preserved; conflicting owners are rejected.\n */\nexport const provision = mutation({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n groupId: v.id(\"Group\"),\n externalId: v.string(),\n provider: v.string(),\n userData: vScimUserData,\n lastProvisionedAt: v.optional(v.number()),\n active: v.optional(v.boolean()),\n raw: v.optional(v.any()),\n },\n returns: v.object({\n userId: v.id(\"User\"),\n created: v.boolean(),\n }),\n handler: async (ctx, args) => {\n const identity = await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_resource_type_external_id\", (idx) =>\n idx\n .eq(\"connectionId\", args.connectionId)\n .eq(\"resourceType\", \"user\")\n .eq(\"externalId\", args.externalId),\n )\n .unique();\n const account = await ctx.db\n .query(\"Account\")\n .withIndex(\"provider_account_id\", (idx) =>\n idx.eq(\"provider\", args.provider).eq(\"providerAccountId\", args.externalId),\n )\n .unique();\n\n if (identity?.userId !== undefined && account !== null && identity.userId !== account.userId) {\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_ALREADY_LINKED,\n message: \"The SCIM identity and provider account belong to different users.\",\n });\n }\n\n const existingUserId = identity?.userId ?? account?.userId;\n let userId = existingUserId;\n let created = false;\n if (userId !== undefined) {\n const user = await ctx.db.get(\"User\", userId);\n if (user === null) {\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_NOT_FOUND,\n message: \"The SCIM identity references a user that no longer exists.\",\n });\n }\n } else {\n userId = await ctx.db.insert(\"User\", args.userData);\n created = true;\n }\n\n if (account === null) {\n await ctx.db.insert(\"Account\", {\n userId,\n provider: args.provider,\n providerAccountId: args.externalId,\n });\n }\n\n const identityData: ScimUserIdentity = {\n connectionId: args.connectionId,\n groupId: args.groupId,\n resourceType: \"user\" as const,\n externalId: args.externalId,\n userId,\n };\n if (args.lastProvisionedAt !== undefined) {\n identityData.lastProvisionedAt = args.lastProvisionedAt;\n }\n if (args.active !== undefined) identityData.active = args.active;\n if (args.raw !== undefined) identityData.raw = args.raw;\n if (identity === null) {\n await ctx.db.insert(\"GroupConnectionScimIdentity\", identityData);\n } else {\n await ctx.db.patch(\"GroupConnectionScimIdentity\", identity._id, identityData);\n }\n\n return { userId, created };\n },\n});\n\n/** Delete a SCIM identity by id. */\nconst remove = mutation({\n args: { id: v.id(\"GroupConnectionScimIdentity\") },\n returns: v.null(),\n handler: async (ctx, { id: identityId }) => {\n await ctx.db.delete(\"GroupConnectionScimIdentity\", identityId);\n return null;\n },\n});\n\nexport { remove };\n"],"mappings":"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"}
1
+ {"version":3,"file":"identity.js","names":["schema","query"],"sources":["../../../../src/component/connection/scim/identity.ts"],"sourcesContent":["/**\n * `component.connection.scim.identity.*` — SCIM-provisioned\n * identities for an Connection connection (sub-resource of connection).\n *\n * `get` is overloaded — single lookup or, with `{ connectionId,\n * userIds }`, a batched resolve aligned to input order.\n *\n * @module\n */\n\nimport { paginationOptsValidator } from \"convex/server\";\nimport { ConvexError, type Infer, v } from \"convex/values\";\nimport { paginator } from \"convex-helpers/server/pagination\";\n\nimport { ErrorCode } from \"../../../shared/codes\";\nimport type { Id } from \"../../_generated/dataModel\";\nimport type { MutationCtx } from \"../../_generated/server\";\nimport { mutation, query } from \"../../functions\";\nimport { vGroupConnectionScimIdentityDoc, vPaginated, vScimResourceType } from \"../../model\";\nimport schema from \"../../schema\";\nimport { revokeSessionState } from \"../../session\";\n\nconst vScimUserData = v.object(schema.tables.User.validator.fields);\nconst vProfileUpdate = v.union(v.literal(\"never\"), v.literal(\"missing\"), v.literal(\"always\"));\n\ntype ScimUserData = Infer<typeof vScimUserData>;\ntype ProfileUpdate = Infer<typeof vProfileUpdate>;\n\ntype ScimUserIdentity = Pick<\n Infer<typeof vGroupConnectionScimIdentityDoc>,\n \"connectionId\" | \"groupId\" | \"externalId\" | \"lastProvisionedAt\" | \"active\" | \"raw\"\n> & {\n resourceType: \"user\";\n userId: NonNullable<Infer<typeof vGroupConnectionScimIdentityDoc>[\"userId\"]>;\n};\n\n/**\n * Read SCIM identities, overloaded by the args supplied. With\n * `{ connectionId, userIds }` it batch-resolves and returns an array aligned to\n * the input order (`null` per missing user). Otherwise it resolves a single\n * identity by `(connectionId, resourceType, externalId)`, `(connectionId,\n * userId)`, `userId`, or `mappedGroupId`. Returns `null` when nothing matches.\n */\nexport const get = query({\n args: {\n connectionId: v.optional(v.id(\"GroupConnection\")),\n resourceType: v.optional(vScimResourceType),\n externalId: v.optional(v.string()),\n userId: v.optional(v.id(\"User\")),\n userIds: v.optional(v.array(v.id(\"User\"))),\n mappedGroupId: v.optional(v.id(\"Group\")),\n },\n returns: v.union(\n vGroupConnectionScimIdentityDoc,\n v.null(),\n v.array(v.union(vGroupConnectionScimIdentityDoc, v.null())),\n ),\n handler: async (ctx, args) => {\n if (args.connectionId !== undefined && args.userIds !== undefined) {\n const userIds = args.userIds;\n if (userIds.length === 0) return [];\n const unique = Array.from(new Set(userIds));\n const docs = await Promise.all(\n unique.map((userId) =>\n ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_user_id\", (idx) =>\n idx.eq(\"connectionId\", args.connectionId!).eq(\"userId\", userId),\n )\n .first(),\n ),\n );\n const byUserId = new Map(unique.map((id, i) => [id, docs[i] ?? null]));\n return userIds.map((userId) => byUserId.get(userId) ?? null);\n }\n if (\n args.connectionId !== undefined &&\n args.resourceType !== undefined &&\n args.externalId !== undefined\n ) {\n return await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_resource_type_external_id\", (idx) =>\n idx\n .eq(\"connectionId\", args.connectionId!)\n .eq(\"resourceType\", args.resourceType!)\n .eq(\"externalId\", args.externalId!),\n )\n .first();\n }\n if (args.connectionId !== undefined && args.userId !== undefined) {\n return await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_user_id\", (idx) =>\n idx.eq(\"connectionId\", args.connectionId!).eq(\"userId\", args.userId!),\n )\n .first();\n }\n if (args.userId !== undefined) {\n return await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"user_id\", (idx) => idx.eq(\"userId\", args.userId!))\n .first();\n }\n if (args.mappedGroupId !== undefined) {\n return await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"mapped_group_id\", (idx) => idx.eq(\"mappedGroupId\", args.mappedGroupId!))\n .first();\n }\n return null;\n },\n});\n\n/** List a connection's SCIM identities, paginated. */\nexport const list = query({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n paginationOpts: paginationOptsValidator,\n },\n returns: vPaginated(vGroupConnectionScimIdentityDoc),\n handler: async (ctx, { connectionId, paginationOpts }) => {\n return await paginator(ctx.db, schema)\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id\", (idx) => idx.eq(\"connectionId\", connectionId))\n .paginate(paginationOpts);\n },\n});\n\nfunction providerForConnection(connection: { _id: string; protocol: string }) {\n if (connection.protocol === \"oidc\") return `oidc:${connection._id}`;\n if (connection.protocol === \"saml\") return `saml:${connection._id}`;\n throw new ConvexError({\n code: ErrorCode.PROVIDER_NOT_CONFIGURED,\n message: \"SCIM requires an OIDC or SAML connection.\",\n });\n}\n\nfunction patchProfile(current: Record<string, unknown>, next: ScimUserData, mode: ProfileUpdate) {\n if (mode === \"never\") return {};\n return Object.fromEntries(\n Object.entries(next).filter(([key, value]) =>\n value === undefined\n ? false\n : mode === \"always\" ||\n current[key] === undefined ||\n current[key] === null ||\n current[key] === \"\",\n ),\n );\n}\n\n/**\n * Atomically provision a directory-managed user, account, SCIM identity, and\n * membership. Group and provider ownership are derived from the connection.\n */\nexport const provision = mutation({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n externalId: v.optional(v.string()),\n userData: vScimUserData,\n profileUpdate: vProfileUpdate,\n roleIds: v.array(v.string()),\n lastProvisionedAt: v.optional(v.number()),\n active: v.optional(v.boolean()),\n raw: v.optional(v.any()),\n },\n returns: v.object({ userId: v.id(\"User\"), created: v.boolean() }),\n handler: async (ctx, args) => {\n const connection = await activeScimConnection(ctx, args.connectionId);\n const provider = providerForConnection(connection);\n const externalId = args.externalId;\n const identity =\n externalId === undefined\n ? null\n : await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_resource_type_external_id\", (idx) =>\n idx\n .eq(\"connectionId\", args.connectionId)\n .eq(\"resourceType\", \"user\")\n .eq(\"externalId\", externalId),\n )\n .unique();\n const account =\n externalId === undefined\n ? null\n : await ctx.db\n .query(\"Account\")\n .withIndex(\"provider_account_id\", (idx) =>\n idx.eq(\"provider\", provider).eq(\"providerAccountId\", externalId),\n )\n .unique();\n if (identity?.userId !== undefined && account !== null && identity.userId !== account.userId) {\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_ALREADY_LINKED,\n message: \"SCIM account ownership conflicts.\",\n });\n }\n let userId = identity?.userId ?? account?.userId;\n let created = false;\n if (userId === undefined) {\n userId = await ctx.db.insert(\"User\", args.userData);\n created = true;\n } else {\n const user = await ctx.db.get(\"User\", userId);\n if (user === null)\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_NOT_FOUND,\n message: \"SCIM user not found.\",\n });\n const conflictingIdentity = await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"user_id\", (idx) => idx.eq(\"userId\", userId))\n .filter((query) => query.neq(query.field(\"connectionId\"), args.connectionId))\n .first();\n if (conflictingIdentity !== null) {\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_ALREADY_LINKED,\n message: \"User is managed by another SCIM connection.\",\n });\n }\n const patch = patchProfile(\n user as Record<string, unknown>,\n args.userData,\n args.profileUpdate,\n );\n if (Object.keys(patch).length > 0) await ctx.db.patch(\"User\", userId, patch);\n }\n if (externalId !== undefined && account === null)\n await ctx.db.insert(\"Account\", { userId, provider, providerAccountId: externalId });\n const identityData: ScimUserIdentity = {\n connectionId: args.connectionId,\n groupId: connection.groupId,\n resourceType: \"user\",\n ...(externalId === undefined ? {} : { externalId }),\n userId,\n active: args.active !== false,\n ...(args.lastProvisionedAt === undefined\n ? {}\n : { lastProvisionedAt: args.lastProvisionedAt }),\n ...(args.raw === undefined ? {} : { raw: args.raw }),\n };\n if (identity === null) await ctx.db.insert(\"GroupConnectionScimIdentity\", identityData);\n else {\n if (identity.groupId !== connection.groupId)\n throw new ConvexError({\n code: ErrorCode.INVALID_PARAMETERS,\n message: \"SCIM identity group mismatch.\",\n });\n await ctx.db.patch(\"GroupConnectionScimIdentity\", identity._id, identityData);\n }\n const membership = await ctx.db\n .query(\"GroupMember\")\n .withIndex(\"group_id_user_id\", (idx) =>\n idx.eq(\"groupId\", connection.groupId).eq(\"userId\", userId),\n )\n .unique();\n const membershipPatch = {\n roleIds: args.roleIds,\n status: args.active === false ? \"inactive\" : \"active\",\n };\n if (membership === null)\n await ctx.db.insert(\"GroupMember\", {\n groupId: connection.groupId,\n userId,\n ...membershipPatch,\n });\n else await ctx.db.patch(\"GroupMember\", membership._id, membershipPatch);\n return { userId, created };\n },\n});\n\n/** Atomically update a directory-managed user and its connection membership. */\nexport const update = mutation({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n userId: v.id(\"User\"),\n externalId: v.optional(v.string()),\n userData: vScimUserData,\n profileUpdate: vProfileUpdate,\n roleIds: v.array(v.string()),\n active: v.boolean(),\n lastProvisionedAt: v.optional(v.number()),\n raw: v.optional(v.any()),\n },\n returns: v.null(),\n handler: async (ctx, args) => {\n const connection = await activeScimConnection(ctx, args.connectionId);\n const externalId = args.externalId;\n const identity = await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_user_id\", (idx) =>\n idx.eq(\"connectionId\", args.connectionId).eq(\"userId\", args.userId),\n )\n .unique();\n if (\n identity === null ||\n identity.resourceType !== \"user\" ||\n identity.groupId !== connection.groupId\n )\n throw new ConvexError({ code: ErrorCode.ACCOUNT_NOT_FOUND, message: \"SCIM user not found.\" });\n const conflictingIdentity = await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"user_id\", (idx) => idx.eq(\"userId\", args.userId))\n .filter((query) => query.neq(query.field(\"connectionId\"), args.connectionId))\n .first();\n if (conflictingIdentity !== null) {\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_ALREADY_LINKED,\n message: \"User is managed by another SCIM connection.\",\n });\n }\n const conflicting =\n externalId === undefined\n ? null\n : await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_resource_type_external_id\", (idx) =>\n idx\n .eq(\"connectionId\", args.connectionId)\n .eq(\"resourceType\", \"user\")\n .eq(\"externalId\", externalId),\n )\n .unique();\n if (conflicting !== null && conflicting._id !== identity._id)\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_ALREADY_LINKED,\n message: \"SCIM external id is already linked.\",\n });\n const user = await ctx.db.get(\"User\", args.userId);\n if (user === null)\n throw new ConvexError({ code: ErrorCode.ACCOUNT_NOT_FOUND, message: \"User not found.\" });\n const provider = providerForConnection(connection);\n const currentExternalId = identity.externalId;\n const [currentAccount, nextAccount] = await Promise.all([\n currentExternalId === undefined\n ? Promise.resolve(null)\n : ctx.db\n .query(\"Account\")\n .withIndex(\"provider_account_id\", (idx) =>\n idx.eq(\"provider\", provider).eq(\"providerAccountId\", currentExternalId),\n )\n .unique(),\n externalId === undefined || currentExternalId === externalId\n ? Promise.resolve(null)\n : ctx.db\n .query(\"Account\")\n .withIndex(\"provider_account_id\", (idx) =>\n idx.eq(\"provider\", provider).eq(\"providerAccountId\", externalId),\n )\n .unique(),\n ]);\n if (nextAccount !== null && nextAccount.userId !== args.userId) {\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_ALREADY_LINKED,\n message: \"The replacement SCIM external id is already linked.\",\n });\n }\n if (currentAccount !== null && currentAccount.userId !== args.userId) {\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_ALREADY_LINKED,\n message: \"The SCIM account belongs to another user.\",\n });\n }\n const patch = patchProfile(user as Record<string, unknown>, args.userData, args.profileUpdate);\n if (Object.keys(patch).length > 0) await ctx.db.patch(\"User\", args.userId, patch);\n if (currentExternalId === externalId) {\n if (externalId !== undefined && currentAccount === null) {\n await ctx.db.insert(\"Account\", {\n userId: args.userId,\n provider,\n providerAccountId: externalId,\n });\n }\n } else if (currentAccount !== null) {\n if (externalId === undefined) {\n await ctx.db.delete(\"Account\", currentAccount._id);\n } else if (nextAccount === null) {\n await ctx.db.patch(\"Account\", currentAccount._id, { providerAccountId: externalId });\n } else {\n await ctx.db.delete(\"Account\", currentAccount._id);\n }\n } else if (externalId !== undefined && nextAccount === null) {\n await ctx.db.insert(\"Account\", {\n userId: args.userId,\n provider,\n providerAccountId: externalId,\n });\n }\n await ctx.db.patch(\"GroupConnectionScimIdentity\", identity._id, {\n externalId,\n active: args.active,\n lastProvisionedAt: args.lastProvisionedAt,\n raw: args.raw,\n });\n const membership = await ctx.db\n .query(\"GroupMember\")\n .withIndex(\"group_id_user_id\", (idx) =>\n idx.eq(\"groupId\", connection.groupId).eq(\"userId\", args.userId),\n )\n .unique();\n const membershipPatch = { roleIds: args.roleIds, status: args.active ? \"active\" : \"inactive\" };\n if (membership === null)\n await ctx.db.insert(\"GroupMember\", {\n groupId: connection.groupId,\n userId: args.userId,\n ...membershipPatch,\n });\n else await ctx.db.patch(\"GroupMember\", membership._id, membershipPatch);\n return null;\n },\n});\n\n/** Atomically revoke a directory-managed user's membership, sessions, and identity. */\nexport const revoke = mutation({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n userId: v.id(\"User\"),\n mode: v.union(v.literal(\"soft\"), v.literal(\"hard\")),\n },\n returns: v.object({\n epoch: v.number(),\n cleanedSessions: v.number(),\n cleanupPending: v.boolean(),\n }),\n handler: async (ctx, args) => {\n const connection = await activeScimConnection(ctx, args.connectionId);\n const identity = await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_user_id\", (idx) =>\n idx.eq(\"connectionId\", args.connectionId).eq(\"userId\", args.userId),\n )\n .unique();\n if (\n identity === null ||\n identity.resourceType !== \"user\" ||\n identity.groupId !== connection.groupId\n )\n throw new ConvexError({ code: ErrorCode.ACCOUNT_NOT_FOUND, message: \"SCIM user not found.\" });\n const conflictingIdentity = await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"user_id\", (idx) => idx.eq(\"userId\", args.userId))\n .filter((query) => query.neq(query.field(\"connectionId\"), args.connectionId))\n .first();\n if (conflictingIdentity !== null) {\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_ALREADY_LINKED,\n message: \"User is managed by another SCIM connection.\",\n });\n }\n const provider = providerForConnection(connection);\n const externalId = identity.externalId;\n const account =\n externalId === undefined\n ? null\n : await ctx.db\n .query(\"Account\")\n .withIndex(\"provider_account_id\", (idx) =>\n idx.eq(\"provider\", provider).eq(\"providerAccountId\", externalId),\n )\n .unique();\n if (account !== null && account.userId !== args.userId) {\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_ALREADY_LINKED,\n message: \"The SCIM account belongs to another user.\",\n });\n }\n const membership = await ctx.db\n .query(\"GroupMember\")\n .withIndex(\"group_id_user_id\", (idx) =>\n idx.eq(\"groupId\", connection.groupId).eq(\"userId\", args.userId),\n )\n .unique();\n if (membership !== null) await ctx.db.delete(\"GroupMember\", membership._id);\n if (account !== null) await ctx.db.delete(\"Account\", account._id);\n const { epoch, cleanedSessions, cleanupPending } = await revokeSessionState(ctx, args.userId);\n if (args.mode === \"hard\") await ctx.db.delete(\"GroupConnectionScimIdentity\", identity._id);\n else\n await ctx.db.patch(\"GroupConnectionScimIdentity\", identity._id, {\n active: false,\n lastProvisionedAt: Date.now(),\n });\n return { epoch, cleanedSessions, cleanupPending };\n },\n});\n\nasync function activeScimConnection(ctx: MutationCtx, connectionId: Id<\"GroupConnection\">) {\n const connection = await ctx.db.get(\"GroupConnection\", connectionId);\n const scim =\n connection === null\n ? null\n : await ctx.db\n .query(\"GroupConnectionScimConfig\")\n .withIndex(\"group_connection_id\", (idx) => idx.eq(\"connectionId\", connectionId))\n .unique();\n if (\n connection === null ||\n connection.status !== \"active\" ||\n scim === null ||\n scim.status !== \"active\" ||\n scim.groupId !== connection.groupId\n ) {\n throw new ConvexError({\n code: ErrorCode.INVALID_PARAMETERS,\n message: \"SCIM connection is not active.\",\n });\n }\n return connection;\n}\n\nasync function assertScimMembers(\n ctx: MutationCtx,\n connection: { _id: Id<\"GroupConnection\">; groupId: Id<\"Group\"> },\n userIds: Array<Id<\"User\">>,\n) {\n for (const userId of new Set(userIds)) {\n const [user, identity, membership] = await Promise.all([\n ctx.db.get(\"User\", userId),\n ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_user_id\", (idx) =>\n idx.eq(\"connectionId\", connection._id).eq(\"userId\", userId),\n )\n .first(),\n ctx.db\n .query(\"GroupMember\")\n .withIndex(\"group_id_user_id\", (idx) =>\n idx.eq(\"groupId\", connection.groupId).eq(\"userId\", userId),\n )\n .first(),\n ]);\n if (user === null || (identity === null && membership === null)) {\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_NOT_FOUND,\n message: \"A SCIM group member is not available through this connection.\",\n });\n }\n }\n}\n\nasync function replaceScimGroupMembers(\n ctx: MutationCtx,\n groupId: Id<\"Group\">,\n memberIds: Array<Id<\"User\">>,\n roleIds: Array<string>,\n) {\n const current = await ctx.db\n .query(\"GroupMember\")\n .withIndex(\"group_id\", (idx) => idx.eq(\"groupId\", groupId))\n .collect();\n const wanted = new Set(memberIds);\n for (const member of current) {\n if (!wanted.has(member.userId)) await ctx.db.delete(\"GroupMember\", member._id);\n }\n for (const userId of wanted) {\n const member = current.find((candidate) => candidate.userId === userId);\n const patch = { roleIds, status: \"active\" as const };\n if (member === undefined) await ctx.db.insert(\"GroupMember\", { groupId, userId, ...patch });\n else await ctx.db.patch(\"GroupMember\", member._id, patch);\n }\n}\n\n/** Atomically provision a SCIM group, its identity, and its complete membership. */\nexport const provisionGroup = mutation({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n externalId: v.optional(v.string()),\n name: v.string(),\n memberIds: v.array(v.id(\"User\")),\n roleIds: v.array(v.string()),\n raw: v.optional(v.any()),\n },\n returns: v.object({ groupId: v.id(\"Group\"), created: v.boolean() }),\n handler: async (ctx, args) => {\n const connection = await activeScimConnection(ctx, args.connectionId);\n const externalId = args.externalId;\n await assertScimMembers(ctx, connection, args.memberIds);\n const identity =\n externalId === undefined\n ? null\n : await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"group_connection_id_resource_type_external_id\", (idx) =>\n idx\n .eq(\"connectionId\", args.connectionId)\n .eq(\"resourceType\", \"group\")\n .eq(\"externalId\", externalId),\n )\n .unique();\n if (identity !== null && identity.groupId !== connection.groupId) {\n throw new ConvexError({\n code: ErrorCode.INVALID_PARAMETERS,\n message: \"SCIM identity group mismatch.\",\n });\n }\n let groupId = identity?.mappedGroupId;\n let created = false;\n if (groupId === undefined) {\n const parent = await ctx.db.get(\"Group\", connection.groupId);\n if (parent === null)\n throw new ConvexError({\n code: ErrorCode.INVALID_PARAMETERS,\n message: \"Connection group not found.\",\n });\n groupId = await ctx.db.insert(\"Group\", {\n name: args.name,\n type: \"organization\",\n parentGroupId: connection.groupId,\n rootGroupId: parent.rootGroupId ?? parent._id,\n isRoot: false,\n });\n created = true;\n } else {\n const group = await ctx.db.get(\"Group\", groupId);\n if (group === null || group.parentGroupId !== connection.groupId) {\n throw new ConvexError({\n code: ErrorCode.INVALID_PARAMETERS,\n message: \"SCIM group ownership mismatch.\",\n });\n }\n await ctx.db.patch(\"Group\", groupId, { name: args.name });\n }\n const identityData = {\n connectionId: args.connectionId,\n groupId: connection.groupId,\n resourceType: \"group\" as const,\n ...(externalId === undefined ? {} : { externalId }),\n mappedGroupId: groupId,\n active: true,\n lastProvisionedAt: Date.now(),\n ...(args.raw === undefined ? {} : { raw: args.raw }),\n };\n if (identity === null) await ctx.db.insert(\"GroupConnectionScimIdentity\", identityData);\n else await ctx.db.patch(\"GroupConnectionScimIdentity\", identity._id, identityData);\n await replaceScimGroupMembers(ctx, groupId, args.memberIds, args.roleIds);\n return { groupId, created };\n },\n});\n\n/** Atomically update a SCIM group and replace its complete membership. */\nexport const updateGroup = mutation({\n args: {\n connectionId: v.id(\"GroupConnection\"),\n groupId: v.id(\"Group\"),\n name: v.optional(v.string()),\n memberIds: v.array(v.id(\"User\")),\n roleIds: v.array(v.string()),\n raw: v.optional(v.any()),\n },\n returns: v.null(),\n handler: async (ctx, args) => {\n const connection = await activeScimConnection(ctx, args.connectionId);\n const identity = await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"mapped_group_id\", (idx) => idx.eq(\"mappedGroupId\", args.groupId))\n .unique();\n if (\n identity === null ||\n identity.connectionId !== args.connectionId ||\n identity.groupId !== connection.groupId ||\n identity.resourceType !== \"group\"\n ) {\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_NOT_FOUND,\n message: \"SCIM group not found.\",\n });\n }\n const group = await ctx.db.get(\"Group\", args.groupId);\n if (group === null || group.parentGroupId !== connection.groupId) {\n throw new ConvexError({\n code: ErrorCode.INVALID_PARAMETERS,\n message: \"SCIM group ownership mismatch.\",\n });\n }\n await assertScimMembers(ctx, connection, args.memberIds);\n if (args.name !== undefined) await ctx.db.patch(\"Group\", args.groupId, { name: args.name });\n await ctx.db.patch(\"GroupConnectionScimIdentity\", identity._id, {\n active: true,\n lastProvisionedAt: Date.now(),\n ...(args.raw === undefined ? {} : { raw: args.raw }),\n });\n await replaceScimGroupMembers(ctx, args.groupId, args.memberIds, args.roleIds);\n return null;\n },\n});\n\n/** Atomically revoke a SCIM group and its identity after verifying connection ownership. */\nexport const revokeGroup = mutation({\n args: { connectionId: v.id(\"GroupConnection\"), groupId: v.id(\"Group\") },\n returns: v.null(),\n handler: async (ctx, args) => {\n const connection = await activeScimConnection(ctx, args.connectionId);\n const identity = await ctx.db\n .query(\"GroupConnectionScimIdentity\")\n .withIndex(\"mapped_group_id\", (idx) => idx.eq(\"mappedGroupId\", args.groupId))\n .unique();\n if (\n identity === null ||\n identity.connectionId !== args.connectionId ||\n identity.groupId !== connection.groupId ||\n identity.resourceType !== \"group\"\n ) {\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_NOT_FOUND,\n message: \"SCIM group not found.\",\n });\n }\n const group = await ctx.db.get(\"Group\", args.groupId);\n if (group === null || group.parentGroupId !== identity.groupId) {\n throw new ConvexError({\n code: ErrorCode.INVALID_PARAMETERS,\n message: \"SCIM group ownership mismatch.\",\n });\n }\n const members = await ctx.db\n .query(\"GroupMember\")\n .withIndex(\"group_id\", (idx) => idx.eq(\"groupId\", args.groupId))\n .collect();\n for (const member of members) await ctx.db.delete(\"GroupMember\", member._id);\n await ctx.db.delete(\"GroupConnectionScimIdentity\", identity._id);\n await ctx.db.delete(\"Group\", args.groupId);\n return null;\n },\n});\n"],"mappings":"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"}