@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.
- package/README.md +5 -5
- package/dist/_generated/component.d.ts +130 -61
- package/dist/_generated/component.d.ts.map +1 -1
- package/dist/_generated/dataModel.d.ts +28 -0
- package/dist/_generated/dataModel.d.ts.map +1 -0
- package/dist/bin.js +1 -2
- package/dist/browser/index.d.ts +2 -2
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js.map +1 -1
- package/dist/client/core/types.d.ts +40 -79
- package/dist/client/core/types.d.ts.map +1 -1
- package/dist/client/core/types.js.map +1 -1
- package/dist/client/errors.d.ts +15 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +8 -1
- package/dist/client/errors.js.map +1 -1
- package/dist/client/index.d.ts +6 -3
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +3 -2
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +130 -61
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/_generated/dataModel.d.ts +1 -1
- package/dist/component/_generated/server.d.ts +1 -1
- package/dist/component/account.d.ts +43 -37
- package/dist/component/account.d.ts.map +1 -1
- package/dist/component/account.js +14 -16
- package/dist/component/account.js.map +1 -1
- package/dist/component/connection/audit.d.ts +29 -29
- package/dist/component/connection/audit.d.ts.map +1 -1
- package/dist/component/connection/audit.js +2 -1
- package/dist/component/connection/audit.js.map +1 -1
- package/dist/component/connection/cache.d.ts +6 -6
- package/dist/component/connection/domain/verification.d.ts +19 -19
- package/dist/component/connection/domain.d.ts +20 -20
- package/dist/component/connection/saml/assertion.d.ts +4 -4
- package/dist/component/connection/saml/request.d.ts +9 -9
- package/dist/component/connection/saml/request.d.ts.map +1 -1
- package/dist/component/connection/scim/config.d.ts +13 -13
- package/dist/component/connection/scim/identity.d.ts +102 -59
- package/dist/component/connection/scim/identity.d.ts.map +1 -1
- package/dist/component/connection/scim/identity.js +357 -56
- package/dist/component/connection/scim/identity.js.map +1 -1
- package/dist/component/connection/secret.d.ts +15 -15
- package/dist/component/connection/secret.d.ts.map +1 -1
- package/dist/component/connection/webhook/delivery.d.ts +69 -53
- package/dist/component/connection/webhook/delivery.d.ts.map +1 -1
- package/dist/component/connection/webhook/delivery.js +127 -170
- package/dist/component/connection/webhook/delivery.js.map +1 -1
- package/dist/component/connection/webhook/endpoint.d.ts +21 -21
- package/dist/component/connection/webhook/events.d.ts +26 -0
- package/dist/component/connection/webhook/events.d.ts.map +1 -0
- package/dist/component/connection/webhook/events.js +39 -0
- package/dist/component/connection/webhook/events.js.map +1 -0
- package/dist/component/connection.d.ts +38 -38
- package/dist/component/connection.d.ts.map +1 -1
- package/dist/component/context.d.ts +115 -0
- package/dist/component/context.d.ts.map +1 -0
- package/dist/component/context.js +50 -0
- package/dist/component/context.js.map +1 -0
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/crons.d.ts +2 -2
- package/dist/component/event.d.ts +134 -138
- package/dist/component/event.d.ts.map +1 -1
- package/dist/component/event.js +88 -117
- package/dist/component/event.js.map +1 -1
- package/dist/component/eventstream.d.ts +30 -0
- package/dist/component/eventstream.d.ts.map +1 -0
- package/dist/component/eventstream.js +51 -0
- package/dist/component/eventstream.js.map +1 -0
- package/dist/component/factor/device.d.ts +25 -24
- package/dist/component/factor/device.d.ts.map +1 -1
- package/dist/component/factor/device.js +5 -2
- package/dist/component/factor/device.js.map +1 -1
- package/dist/component/factor/passkey.d.ts +84 -75
- package/dist/component/factor/passkey.d.ts.map +1 -1
- package/dist/component/factor/passkey.js +6 -6
- package/dist/component/factor/passkey.js.map +1 -1
- package/dist/component/factor/totp.d.ts +54 -49
- package/dist/component/factor/totp.d.ts.map +1 -1
- package/dist/component/factor/totp.js +3 -3
- package/dist/component/factor/totp.js.map +1 -1
- package/dist/component/group/active.d.ts +326 -22
- package/dist/component/group/active.d.ts.map +1 -1
- package/dist/component/group/active.js +21 -11
- package/dist/component/group/active.js.map +1 -1
- package/dist/component/group/invite.d.ts +28 -28
- package/dist/component/group/invite.d.ts.map +1 -1
- package/dist/component/group/member.d.ts +34 -34
- package/dist/component/group.d.ts +47 -62
- package/dist/component/group.d.ts.map +1 -1
- package/dist/component/http.d.ts +2 -2
- package/dist/component/limits.d.ts +4 -4
- package/dist/component/maintenance.d.ts +2 -2
- package/dist/component/model.d.ts +1362 -1027
- package/dist/component/model.d.ts.map +1 -1
- package/dist/component/model.js +23 -6
- package/dist/component/model.js.map +1 -1
- package/dist/component/modules.d.ts +8 -2
- package/dist/component/modules.d.ts.map +1 -1
- package/dist/component/modules.js +3 -0
- package/dist/component/modules.js.map +1 -1
- package/dist/component/oauth/client.d.ts +20 -20
- package/dist/component/oauth/code.d.ts +14 -14
- package/dist/component/oauth/refresh.d.ts +19 -19
- package/dist/component/schema.d.ts +1100 -611
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +17 -4
- package/dist/component/schema.js.map +1 -1
- package/dist/component/session.d.ts +72 -32
- package/dist/component/session.d.ts.map +1 -1
- package/dist/component/session.js +120 -32
- package/dist/component/session.js.map +1 -1
- package/dist/component/token/continuation.d.ts +40 -18
- package/dist/component/token/continuation.d.ts.map +1 -1
- package/dist/component/token/continuation.js +57 -11
- package/dist/component/token/continuation.js.map +1 -1
- package/dist/component/token/pkce.d.ts +22 -22
- package/dist/component/token/pkce.d.ts.map +1 -1
- package/dist/component/token/refresh.d.ts +39 -35
- package/dist/component/token/refresh.d.ts.map +1 -1
- package/dist/component/token/refresh.js +11 -6
- package/dist/component/token/refresh.js.map +1 -1
- package/dist/component/token/verification.d.ts +36 -32
- package/dist/component/token/verification.d.ts.map +1 -1
- package/dist/component/token/verification.js +3 -3
- package/dist/component/token/verification.js.map +1 -1
- package/dist/component/user/email.d.ts +21 -21
- package/dist/component/user/key.d.ts +35 -35
- package/dist/component/user/key.d.ts.map +1 -1
- package/dist/component/user.d.ts +51 -45
- package/dist/component/user.d.ts.map +1 -1
- package/dist/core/index.d.ts +54 -90
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -4
- package/dist/core/index.js.map +1 -1
- package/dist/expo/index.d.ts +3 -3
- package/dist/expo/index.d.ts.map +1 -1
- package/dist/expo/index.js.map +1 -1
- package/dist/model.js.map +1 -1
- package/dist/providers/anonymous.d.ts +11 -5
- package/dist/providers/anonymous.d.ts.map +1 -1
- package/dist/providers/anonymous.js +4 -1
- package/dist/providers/anonymous.js.map +1 -1
- package/dist/providers/apple.d.ts +1 -1
- package/dist/providers/credentials.d.ts +21 -13
- package/dist/providers/credentials.d.ts.map +1 -1
- package/dist/providers/credentials.js +2 -0
- package/dist/providers/credentials.js.map +1 -1
- package/dist/providers/custom.d.ts +3 -3
- package/dist/providers/custom.d.ts.map +1 -1
- package/dist/providers/custom.js.map +1 -1
- package/dist/providers/email.d.ts +3 -3
- package/dist/providers/email.d.ts.map +1 -1
- package/dist/providers/email.js.map +1 -1
- package/dist/providers/github.d.ts +1 -1
- package/dist/providers/google.d.ts +1 -1
- package/dist/providers/microsoft.d.ts +1 -1
- package/dist/providers/password.d.ts +103 -8
- package/dist/providers/password.d.ts.map +1 -1
- package/dist/providers/password.js +71 -26
- package/dist/providers/password.js.map +1 -1
- package/dist/providers/phone.d.ts +4 -3
- package/dist/providers/phone.d.ts.map +1 -1
- package/dist/providers/phone.js.map +1 -1
- package/dist/react/index.d.ts +40 -41
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +27 -52
- package/dist/react/index.js.map +1 -1
- package/dist/server/auth.d.ts +157 -117
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +41 -22
- package/dist/server/auth.js.map +1 -1
- package/dist/server/component/factor/db.js +4 -4
- package/dist/server/component/factor/db.js.map +1 -1
- package/dist/server/config.d.ts +2 -1
- package/dist/server/connection/domain.d.ts +4 -66
- package/dist/server/connection/domain.d.ts.map +1 -1
- package/dist/server/connection/domain.js +22 -4
- package/dist/server/connection/domain.js.map +1 -1
- package/dist/server/connection/group/service.js +6 -4
- package/dist/server/connection/group/service.js.map +1 -1
- package/dist/server/connection/http.js +94 -261
- package/dist/server/connection/http.js.map +1 -1
- package/dist/server/connection/oidc.js +8 -17
- package/dist/server/connection/oidc.js.map +1 -1
- package/dist/server/connection/policy/domain.js +4 -1
- package/dist/server/connection/policy/domain.js.map +1 -1
- package/dist/server/connection/policy.js +14 -13
- package/dist/server/connection/policy.js.map +1 -1
- package/dist/server/connection/provision.js +5 -19
- package/dist/server/connection/provision.js.map +1 -1
- package/dist/server/connection/webhook.js +4 -36
- package/dist/server/connection/webhook.js.map +1 -1
- package/dist/server/context.d.ts +2 -2
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js +35 -24
- package/dist/server/context.js.map +1 -1
- package/dist/server/contract.d.ts.map +1 -1
- package/dist/server/contract.js +6 -14
- package/dist/server/contract.js.map +1 -1
- package/dist/server/core.js +6 -0
- package/dist/server/core.js.map +1 -1
- package/dist/server/cors.d.ts +2 -2
- package/dist/server/cors.d.ts.map +1 -1
- package/dist/server/crypto.js.map +1 -1
- package/dist/server/db.js +2 -1
- package/dist/server/db.js.map +1 -1
- package/dist/server/domains/context.js +34 -0
- package/dist/server/domains/context.js.map +1 -0
- package/dist/server/domains/factor.d.ts +3 -2
- package/dist/server/domains/factor.d.ts.map +1 -1
- package/dist/server/domains/factor.js.map +1 -1
- package/dist/server/domains/group.js.map +1 -1
- package/dist/server/domains/invite.js.map +1 -1
- package/dist/server/domains/key.js.map +1 -1
- package/dist/server/domains/member.js.map +1 -1
- package/dist/server/domains/session.js.map +1 -1
- package/dist/server/domains/user.js.map +1 -1
- package/dist/server/env.js +2 -22
- package/dist/server/env.js.map +1 -1
- package/dist/server/events.d.ts +0 -1
- package/dist/server/events.d.ts.map +1 -1
- package/dist/server/events.js +6 -6
- package/dist/server/events.js.map +1 -1
- package/dist/server/http.d.ts +1 -0
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +4 -4
- package/dist/server/http.js.map +1 -1
- package/dist/server/identity/claims.js +1 -1
- package/dist/server/identity/convex.d.ts +2 -0
- package/dist/server/identity/convex.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -3
- package/dist/server/index.js +1 -2
- package/dist/server/limits.js +21 -1
- package/dist/server/limits.js.map +1 -1
- package/dist/server/mutations/credentials/signin.js +6 -3
- package/dist/server/mutations/credentials/signin.js.map +1 -1
- package/dist/server/mutations/invalidate.js +5 -10
- package/dist/server/mutations/invalidate.js.map +1 -1
- package/dist/server/mutations/oauth.js +1 -1
- package/dist/server/mutations/oauth.js.map +1 -1
- package/dist/server/mutations/retrieve.js +4 -1
- package/dist/server/mutations/retrieve.js.map +1 -1
- package/dist/server/mutations/signin.js +2 -2
- package/dist/server/mutations/signin.js.map +1 -1
- package/dist/server/mutations/verify.js +4 -3
- package/dist/server/mutations/verify.js.map +1 -1
- package/dist/server/oauth/factory.js.map +1 -1
- package/dist/server/runtime.d.ts +107 -127
- package/dist/server/runtime.d.ts.map +1 -1
- package/dist/server/runtime.js +43 -28
- package/dist/server/runtime.js.map +1 -1
- package/dist/server/session/lifecycle.d.ts.map +1 -1
- package/dist/server/session/lifecycle.js +18 -3
- package/dist/server/session/lifecycle.js.map +1 -1
- package/dist/server/signin/flow.js +1 -1
- package/dist/server/signin/flow.js.map +1 -1
- package/dist/server/tokens.js +1 -0
- package/dist/server/tokens.js.map +1 -1
- package/dist/server/totp.js +3 -3
- package/dist/server/totp.js.map +1 -1
- package/dist/server/types.d.ts +63 -43
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/user/account.js +0 -1
- package/dist/server/user/account.js.map +1 -1
- package/dist/server/validators.d.ts +874 -917
- package/dist/server/validators.d.ts.map +1 -1
- package/dist/server/validators.js +33 -32
- package/dist/server/validators.js.map +1 -1
- package/dist/server/webauthn.js +4 -4
- package/dist/server/webauthn.js.map +1 -1
- package/dist/shared/brand.d.ts +3 -7
- package/dist/shared/brand.d.ts.map +1 -1
- package/dist/shared/params.d.ts +78 -0
- package/dist/shared/params.d.ts.map +1 -0
- package/dist/shared/results.d.ts +7 -1
- package/dist/shared/results.d.ts.map +1 -1
- package/dist/svelte/context.svelte.d.ts +23 -12
- package/dist/svelte/context.svelte.d.ts.map +1 -1
- package/dist/svelte/context.svelte.js +20 -13
- package/dist/svelte/index.d.ts +1 -4
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -4
- package/dist/test.d.ts +1105 -611
- package/dist/test.d.ts.map +1 -1
- package/package.json +2 -1
- package/dist/server/env.d.ts +0 -28
- package/dist/server/env.d.ts.map +0 -1
- package/dist/svelte/AuthLoading.svelte +0 -13
- package/dist/svelte/AuthLoading.svelte.d.ts +0 -8
- package/dist/svelte/AuthLoading.svelte.d.ts.map +0 -1
- package/dist/svelte/SignedIn.svelte +0 -14
- package/dist/svelte/SignedIn.svelte.d.ts +0 -8
- package/dist/svelte/SignedIn.svelte.d.ts.map +0 -1
- package/dist/svelte/SignedOut.svelte +0 -13
- package/dist/svelte/SignedOut.svelte.d.ts +0 -8
- 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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
90
|
-
*
|
|
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
|
-
|
|
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
|
|
112
|
-
const
|
|
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: "
|
|
106
|
+
message: "SCIM account ownership conflicts."
|
|
116
107
|
});
|
|
117
108
|
let userId = identity?.userId ?? account?.userId;
|
|
118
109
|
let created = false;
|
|
119
|
-
if (userId
|
|
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
|
|
131
|
-
providerAccountId:
|
|
128
|
+
provider,
|
|
129
|
+
providerAccountId: externalId
|
|
132
130
|
});
|
|
133
131
|
const identityData = {
|
|
134
132
|
connectionId: args.connectionId,
|
|
135
|
-
groupId:
|
|
133
|
+
groupId: connection.groupId,
|
|
136
134
|
resourceType: "user",
|
|
137
|
-
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
|
|
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
|
-
/**
|
|
152
|
-
const
|
|
153
|
-
args: {
|
|
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,
|
|
156
|
-
await ctx
|
|
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,
|
|
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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"}
|