@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
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { retryWithBackoff } from "../../shared/retry.js";
|
|
2
|
-
import { log } from "../log.js";
|
|
3
2
|
import { withSpan } from "../utils/span.js";
|
|
4
3
|
import { assertSafeIdpFetchUrl, assertSafeIdpHost } from "../../shared/fetch/guard.js";
|
|
5
4
|
import { normalizeOAuthTokenResponse } from "../oauth/normalize.js";
|
|
@@ -57,8 +56,8 @@ function resolveOidcAuthMethod(client, discoveredMethods) {
|
|
|
57
56
|
if (client.authMethod === "client_secret_basic" || client.authMethod === "client_secret_post") return client.authMethod;
|
|
58
57
|
return discoveredMethods.includes("client_secret_basic") ? "client_secret_basic" : "client_secret_post";
|
|
59
58
|
}
|
|
60
|
-
function
|
|
61
|
-
return typeof value === "string" ? value
|
|
59
|
+
function oidcIssuer(value) {
|
|
60
|
+
return typeof value === "string" ? value : "";
|
|
62
61
|
}
|
|
63
62
|
function getOidcSections(config) {
|
|
64
63
|
const record = asRecord(config) ?? {};
|
|
@@ -163,7 +162,7 @@ async function userInfoProfile(opts) {
|
|
|
163
162
|
* attempts to override reserved OAuth parameters. Token validation requires the
|
|
164
163
|
* nonce, verifies the ID token against the JWKS (or the client secret for
|
|
165
164
|
* advertised HS* algorithms), and enforces the audience, the issuer (against
|
|
166
|
-
*
|
|
165
|
+
* exact configured issuer, the
|
|
167
166
|
* nonce, and `azp` when present. UserInfo results are rejected if their subject
|
|
168
167
|
* disagrees with the ID token's.
|
|
169
168
|
*
|
|
@@ -173,16 +172,9 @@ async function userInfoProfile(opts) {
|
|
|
173
172
|
async function createGroupConnectionOidcProvider(ctx, componentConnection, config, redirectUri) {
|
|
174
173
|
const { discovery: discoveryConfig, client, request, security, profile } = getOidcSections(config);
|
|
175
174
|
const discovery = await discoverOidcConfiguration(ctx, componentConnection, config);
|
|
176
|
-
const discoveredIssuer =
|
|
177
|
-
const expectedIssuer = typeof discoveryConfig.issuer === "string" ?
|
|
178
|
-
|
|
179
|
-
if (typeof discoveryConfig.issuer === "string" && expectedIssuer !== discoveredIssuer) {
|
|
180
|
-
if (strictIssuer) throw new Error(`Configured OIDC issuer mismatch. configured=${expectedIssuer} discovery=${discoveredIssuer}`);
|
|
181
|
-
log("WARN", "Configured OIDC issuer differs from discovery issuer; accepting both for token verification.", {
|
|
182
|
-
configuredIssuer: expectedIssuer,
|
|
183
|
-
discoveryIssuer: discoveredIssuer
|
|
184
|
-
});
|
|
185
|
-
}
|
|
175
|
+
const discoveredIssuer = oidcIssuer(discovery.issuer);
|
|
176
|
+
const expectedIssuer = typeof discoveryConfig.issuer === "string" ? oidcIssuer(discoveryConfig.issuer) : discoveredIssuer;
|
|
177
|
+
if (typeof discoveryConfig.issuer === "string" && expectedIssuer !== discoveredIssuer) throw new Error(`Configured OIDC issuer mismatch. configured=${expectedIssuer} discovery=${discoveredIssuer}`);
|
|
186
178
|
const authorizationEndpoint = discovery.authorization_endpoint;
|
|
187
179
|
const tokenEndpoint = discovery.token_endpoint;
|
|
188
180
|
const jwksUri = typeof discoveryConfig.jwksUri === "string" ? discoveryConfig.jwksUri : typeof discovery.jwks_uri === "string" ? discovery.jwks_uri : "";
|
|
@@ -203,7 +195,6 @@ async function createGroupConnectionOidcProvider(ctx, componentConnection, confi
|
|
|
203
195
|
const expectedAudience = Array.isArray(discoveryConfig.audience) ? discoveryConfig.audience.filter((value) => typeof value === "string") : typeof discoveryConfig.audience === "string" ? discoveryConfig.audience : String(client.id);
|
|
204
196
|
const clockToleranceSeconds = typeof security.clockToleranceSeconds === "number" ? security.clockToleranceSeconds : 10;
|
|
205
197
|
if (clockToleranceSeconds < 0 || clockToleranceSeconds > 300) throw new Error("OIDC clockToleranceSeconds must be between 0 and 300.");
|
|
206
|
-
const expectedIssuers = strictIssuer ? [expectedIssuer] : Array.from(new Set([expectedIssuer, discoveredIssuer]));
|
|
207
198
|
const jwks = getOidcJwks(jwksUri, runtimeOrigin, externalHost, oidcFetch);
|
|
208
199
|
let verifiedClaims = null;
|
|
209
200
|
let verifiedProfile = null;
|
|
@@ -305,8 +296,8 @@ async function createGroupConnectionOidcProvider(ctx, componentConnection, confi
|
|
|
305
296
|
const payload = asRecord(verification.payload);
|
|
306
297
|
if (payload === null) throw new Error("OIDC ID token payload is not an object.");
|
|
307
298
|
const tokenIssuerRaw = typeof payload.iss === "string" ? payload.iss : void 0;
|
|
308
|
-
const tokenIssuer = typeof tokenIssuerRaw === "string" ? tokenIssuerRaw
|
|
309
|
-
if (!tokenIssuer ||
|
|
299
|
+
const tokenIssuer = typeof tokenIssuerRaw === "string" ? tokenIssuerRaw : void 0;
|
|
300
|
+
if (!tokenIssuer || tokenIssuer !== expectedIssuer) throw new Error(`OIDC token issuer mismatch. Received: ${tokenIssuer ?? "<missing>"}. Expected: ${expectedIssuer}`);
|
|
310
301
|
if (payload.nonce !== ctx$1.nonce) throw new Error("OIDC nonce mismatch.");
|
|
311
302
|
if (payload.azp !== void 0 && payload.azp !== String(client.id)) throw new Error("OIDC authorized party does not match client ID.");
|
|
312
303
|
verifiedClaims = payload;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oidc.js","names":["ctx"],"sources":["../../../src/server/connection/oidc.ts"],"sourcesContent":["import { sha256 } from \"@oslojs/crypto/sha2\";\nimport { encodeBase64, encodeBase64urlNoPadding } from \"@oslojs/encoding\";\nimport type { GenericActionCtx, GenericDataModel } from \"convex/server\";\nimport { createRemoteJWKSet, customFetch, decodeProtectedHeader, jwtVerify } from \"jose\";\nimport type { JWTVerifyGetKey, JWTVerifyOptions } from \"jose\";\n\nimport type { AuthComponentApi } from \"../component/api\";\n\nimport { assertSafeIdpFetchUrl, assertSafeIdpHost } from \"../../shared/fetch/guard\";\nimport { log } from \"../log\";\nimport { normalizeOAuthTokenResponse } from \"../oauth/normalize\";\nimport type {\n OIDCClaimMapping,\n OAuthMaterializedConfig,\n OAuthProfile,\n OAuthTokens,\n} from \"../types\";\nimport { createCache } from \"../utils/cache\";\nimport { retryWithBackoff } from \"../utils/retry\";\nimport { withSpan } from \"../utils/span\";\nimport { finalizeNormalizedProfile, normalizeStringArray } from \"./profile\";\nimport { asRecord, groupOidcProviderId, getGroupOidcUrls } from \"./shared\";\n\nconst OIDC_JWKS_CACHE = createCache<string, ReturnType<typeof createRemoteJWKSet>>({\n capacity: 128,\n timeToLiveMs: 60 * 60 * 1000,\n lookup: (cacheKey) => {\n const key = JSON.parse(cacheKey) as {\n url: string;\n runtimeOrigin?: string;\n externalHost?: string;\n };\n const fetchImpl =\n key.runtimeOrigin !== undefined || key.externalHost !== undefined\n ? createGroupConnectionOidcFetchFromParts(key.runtimeOrigin, key.externalHost)\n : undefined;\n return fetchImpl\n ? createRemoteJWKSet(new URL(key.url), { [customFetch]: fetchImpl })\n : createRemoteJWKSet(new URL(key.url));\n },\n});\n\ntype OidcDiscovery = {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n jwks_uri: string;\n userinfo_endpoint?: string;\n token_endpoint_auth_methods_supported?: string[];\n id_token_signing_alg_values_supported?: string[];\n};\n\ntype OidcUserInfo = {\n sub?: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n picture?: string;\n groups?: unknown;\n roles?: unknown;\n};\n\nfunction validateOidcDiscovery(data: unknown): OidcDiscovery {\n if (typeof data !== \"object\" || data === null) {\n throw new Error(\"OIDC discovery response is not an object.\");\n }\n const obj = asRecord(data);\n if (obj === null) {\n throw new Error(\"OIDC discovery response is not an object.\");\n }\n if (typeof obj.issuer !== \"string\") {\n throw new Error(\"OIDC discovery is missing 'issuer'.\");\n }\n if (typeof obj.authorization_endpoint !== \"string\") {\n throw new Error(\"OIDC discovery is missing 'authorization_endpoint'.\");\n }\n if (typeof obj.token_endpoint !== \"string\") {\n throw new Error(\"OIDC discovery is missing 'token_endpoint'.\");\n }\n if (typeof obj.jwks_uri !== \"string\") {\n throw new Error(\"OIDC discovery is missing 'jwks_uri'.\");\n }\n return {\n issuer: obj.issuer,\n authorization_endpoint: obj.authorization_endpoint,\n token_endpoint: obj.token_endpoint,\n jwks_uri: obj.jwks_uri,\n userinfo_endpoint:\n typeof obj.userinfo_endpoint === \"string\" ? obj.userinfo_endpoint : undefined,\n token_endpoint_auth_methods_supported: Array.isArray(obj.token_endpoint_auth_methods_supported)\n ? obj.token_endpoint_auth_methods_supported.filter(\n (v: unknown): v is string => typeof v === \"string\",\n )\n : undefined,\n id_token_signing_alg_values_supported: Array.isArray(obj.id_token_signing_alg_values_supported)\n ? obj.id_token_signing_alg_values_supported.filter(\n (v: unknown): v is string => typeof v === \"string\",\n )\n : undefined,\n };\n}\n\nfunction validateOidcUserInfo(data: unknown): OidcUserInfo {\n if (typeof data !== \"object\" || data === null) {\n return {};\n }\n const obj = asRecord(data);\n if (obj === null) {\n return {};\n }\n return {\n sub: typeof obj.sub === \"string\" ? obj.sub : undefined,\n email: typeof obj.email === \"string\" ? obj.email : undefined,\n email_verified: typeof obj.email_verified === \"boolean\" ? obj.email_verified : undefined,\n name: typeof obj.name === \"string\" ? obj.name : undefined,\n picture: typeof obj.picture === \"string\" ? obj.picture : undefined,\n groups: obj.groups,\n roles: obj.roles,\n };\n}\n\nconst asError = (error: unknown) => (error instanceof Error ? error : new Error(String(error)));\n\nfunction resolveOidcAuthMethod(\n client: Record<string, unknown>,\n discoveredMethods: string[],\n): \"client_secret_basic\" | \"client_secret_post\" {\n if (client.authMethod === \"client_secret_basic\" || client.authMethod === \"client_secret_post\") {\n return client.authMethod;\n }\n return discoveredMethods.includes(\"client_secret_basic\")\n ? \"client_secret_basic\"\n : \"client_secret_post\";\n}\n\nfunction normalizeOidcIssuer(value: unknown): string {\n return typeof value === \"string\" ? value.replace(/\\/$/, \"\") : \"\";\n}\n\n/**\n * Typed shape of the `oidc` section of a group connection's `config` blob —\n * the OIDC counterpart to {@link SamlConfigShape}. The stored `config` is\n * `v.any()`, so {@link getOidcConfig} performs the single boundary cast to\n * this shape and call sites read it without further casts.\n */\nexport type OidcConfigShape = {\n enabled?: boolean;\n discovery?: {\n issuer?: string;\n discoveryUrl?: string;\n jwksUri?: string;\n audience?: string | string[];\n };\n client?: {\n id?: string;\n secret?: string;\n authMethod?: \"client_secret_post\" | \"client_secret_basic\";\n };\n request?: {\n scopes?: string[];\n loginHint?: string;\n authorizationParams?: Record<string, string>;\n };\n security?: {\n clockToleranceSeconds?: number;\n strictIssuer?: boolean;\n };\n profile?: {\n mapping?: OIDCClaimMapping;\n extraFields?: Record<string, string>;\n };\n};\n\nfunction getOidcSections(config: unknown) {\n const record = asRecord(config) ?? {};\n return {\n discovery: asRecord(record.discovery) ?? {},\n client: asRecord(record.client) ?? {},\n request: asRecord(record.request) ?? {},\n security: asRecord(record.security) ?? {},\n profile: asRecord(record.profile) ?? {},\n };\n}\n\nasync function discoverOidcConfiguration(\n ctx: GenericActionCtx<GenericDataModel>,\n componentConnection: AuthComponentApi[\"connection\"],\n config: unknown,\n): Promise<OidcDiscovery> {\n const { discovery } = getOidcSections(config);\n const discoveryUrl =\n typeof discovery.discoveryUrl === \"string\"\n ? discovery.discoveryUrl\n : typeof discovery.issuer === \"string\"\n ? `${discovery.issuer.replace(/\\/$/, \"\")}/.well-known/openid-configuration`\n : null;\n\n if (!discoveryUrl) {\n throw new Error(\"Group connection OIDC requires an issuer or discoveryUrl.\");\n }\n\n const runtimeOrigin =\n typeof discovery.discoveryUrl === \"string\" ? new URL(discovery.discoveryUrl).origin : undefined;\n const externalHost =\n typeof discovery.issuer === \"string\" ? new URL(discovery.issuer).host : undefined;\n\n assertSafeIdpFetchUrl(discoveryUrl);\n return withSpan(\"convex-auth.connection.oidc.discovery\", {}, async () => {\n return retryWithBackoff(\n async () => {\n const json = (await ctx.runAction(componentConnection.cache.oidcDiscovery, {\n url: discoveryUrl,\n runtimeOrigin,\n externalHost,\n })) as unknown;\n return validateOidcDiscovery(json);\n },\n { maxRetries: 2, baseMs: 200 },\n );\n });\n}\n\nfunction createGroupConnectionOidcFetch(\n config: Record<string, unknown>,\n discoveredIssuer?: string,\n) {\n const { discovery } = getOidcSections(config);\n const runtimeOrigin =\n typeof discovery.discoveryUrl === \"string\" ? new URL(discovery.discoveryUrl).origin : undefined;\n const externalHost =\n typeof discovery.issuer === \"string\"\n ? new URL(discovery.issuer).host\n : typeof discoveredIssuer === \"string\"\n ? new URL(discoveredIssuer).host\n : undefined;\n\n return createGroupConnectionOidcFetchFromParts(runtimeOrigin, externalHost);\n}\n\nfunction createGroupConnectionOidcFetchFromParts(runtimeOrigin?: string, externalHost?: string) {\n return async (input: string | URL, init?: RequestInit) => {\n const url = new URL(typeof input === \"string\" ? input : input.toString());\n const rewrittenUrl =\n runtimeOrigin !== undefined && url.origin !== runtimeOrigin\n ? new URL(`${runtimeOrigin}${url.pathname}${url.search}`)\n : url;\n const headers = new Headers(init?.headers);\n if (runtimeOrigin !== undefined && externalHost !== undefined) {\n assertSafeIdpHost(externalHost);\n headers.set(\"host\", externalHost);\n }\n assertSafeIdpFetchUrl(rewrittenUrl.toString());\n return await fetch(rewrittenUrl, { ...init, headers, redirect: \"manual\" });\n };\n}\n\nfunction normalizeOidcProfile(claims: Record<string, unknown>, mapping?: OIDCClaimMapping) {\n const getMapped = (key: string | undefined) =>\n typeof key === \"string\" ? claims[key] : undefined;\n return finalizeNormalizedProfile({\n id:\n (typeof getMapped(mapping?.subject) === \"string\"\n ? (getMapped(mapping?.subject) as string)\n : undefined) ?? (typeof claims.sub === \"string\" ? claims.sub : crypto.randomUUID()),\n email:\n (typeof getMapped(mapping?.email) === \"string\"\n ? (getMapped(mapping?.email) as string)\n : undefined) ?? (typeof claims.email === \"string\" ? claims.email : undefined),\n emailVerified:\n (typeof getMapped(mapping?.emailVerified) === \"boolean\"\n ? (getMapped(mapping?.emailVerified) as boolean)\n : undefined) ??\n (typeof claims.email_verified === \"boolean\" ? claims.email_verified : undefined),\n name:\n (typeof getMapped(mapping?.name) === \"string\"\n ? (getMapped(mapping?.name) as string)\n : undefined) ?? (typeof claims.name === \"string\" ? claims.name : undefined),\n image:\n (typeof getMapped(mapping?.image) === \"string\"\n ? (getMapped(mapping?.image) as string)\n : undefined) ?? (typeof claims.picture === \"string\" ? claims.picture : undefined),\n groups: normalizeStringArray(getMapped(mapping?.groups)),\n roles: normalizeStringArray(getMapped(mapping?.roles)),\n });\n}\n\nfunction getOidcJwks(\n url: string,\n runtimeOrigin?: string,\n externalHost?: string,\n fetchImpl?: ReturnType<typeof createGroupConnectionOidcFetch>,\n) {\n const cacheKey = JSON.stringify({\n url,\n runtimeOrigin: fetchImpl ? runtimeOrigin : undefined,\n externalHost: fetchImpl ? externalHost : undefined,\n });\n return OIDC_JWKS_CACHE.get(cacheKey);\n}\n\nasync function userInfoProfile(opts: {\n endpoint: string;\n accessToken: string;\n verifiedClaims: Record<string, unknown>;\n verifiedProfile: OAuthProfile & { emailVerified?: boolean };\n fetchImpl?: ReturnType<typeof createGroupConnectionOidcFetch>;\n}): Promise<(OAuthProfile & { emailVerified?: boolean }) | null> {\n return withSpan(\"convex-auth.connection.oidc.userinfo\", {}, async () => {\n let userInfo: OidcUserInfo;\n try {\n const response = await (opts.fetchImpl ?? fetch)(opts.endpoint, {\n headers: { Authorization: `Bearer ${opts.accessToken}` },\n });\n if (!response.ok) {\n throw new Error(`OIDC userinfo request failed: ${response.status}`);\n }\n userInfo = validateOidcUserInfo(await response.json());\n } catch {\n return null;\n }\n\n const userInfoSubject = typeof userInfo.sub === \"string\" ? userInfo.sub : undefined;\n const tokenSubject =\n typeof opts.verifiedClaims.sub === \"string\" ? opts.verifiedClaims.sub : undefined;\n if (\n userInfoSubject !== undefined &&\n tokenSubject !== undefined &&\n userInfoSubject !== tokenSubject\n ) {\n throw new Error(\"OIDC userinfo subject does not match ID token subject.\");\n }\n\n return {\n id:\n userInfoSubject ??\n (typeof opts.verifiedClaims.sub === \"string\" ? opts.verifiedClaims.sub : undefined) ??\n crypto.randomUUID(),\n email: typeof userInfo.email === \"string\" ? userInfo.email : opts.verifiedProfile.email,\n emailVerified:\n typeof userInfo.email_verified === \"boolean\"\n ? userInfo.email_verified\n : opts.verifiedProfile.emailVerified,\n name: typeof userInfo.name === \"string\" ? userInfo.name : opts.verifiedProfile.name,\n image: typeof userInfo.picture === \"string\" ? userInfo.picture : opts.verifiedProfile.image,\n groups: normalizeStringArray(userInfo.groups) ?? opts.verifiedProfile.groups,\n roles: normalizeStringArray(userInfo.roles) ?? opts.verifiedProfile.roles,\n } as OAuthProfile & { emailVerified?: boolean };\n });\n}\n\n/**\n * Build the OIDC authorization-code provider and oauth config for a group\n * connection from its discovery document and stored config.\n *\n * The authorization URL always uses PKCE with `S256` and a nonce, and rejects\n * attempts to override reserved OAuth parameters. Token validation requires the\n * nonce, verifies the ID token against the JWKS (or the client secret for\n * advertised HS* algorithms), and enforces the audience, the issuer (against\n * the configured/discovered issuer set; exact match when `strictIssuer`), the\n * nonce, and `azp` when present. UserInfo results are rejected if their subject\n * disagrees with the ID token's.\n *\n * @see https://openid.net/specs/openid-connect-core-1_0.html\n * @internal\n */\nexport async function createGroupConnectionOidcProvider(\n ctx: GenericActionCtx<GenericDataModel>,\n componentConnection: AuthComponentApi[\"connection\"],\n config: Record<string, unknown>,\n redirectUri: string,\n) {\n const {\n discovery: discoveryConfig,\n client,\n request,\n security,\n profile,\n } = getOidcSections(config);\n const discovery: OidcDiscovery = await discoverOidcConfiguration(\n ctx,\n componentConnection,\n config,\n );\n const discoveredIssuer = normalizeOidcIssuer(discovery.issuer);\n const expectedIssuer =\n typeof discoveryConfig.issuer === \"string\"\n ? normalizeOidcIssuer(discoveryConfig.issuer)\n : discoveredIssuer;\n const strictIssuer = security.strictIssuer === true;\n if (typeof discoveryConfig.issuer === \"string\" && expectedIssuer !== discoveredIssuer) {\n if (strictIssuer) {\n throw new Error(\n `Configured OIDC issuer mismatch. configured=${expectedIssuer} discovery=${discoveredIssuer}`,\n );\n }\n log(\n \"WARN\",\n \"Configured OIDC issuer differs from discovery issuer; accepting both for token verification.\",\n {\n configuredIssuer: expectedIssuer,\n discoveryIssuer: discoveredIssuer,\n },\n );\n }\n const authorizationEndpoint = discovery.authorization_endpoint as string;\n const tokenEndpoint = discovery.token_endpoint as string;\n const jwksUri =\n typeof discoveryConfig.jwksUri === \"string\"\n ? discoveryConfig.jwksUri\n : typeof discovery.jwks_uri === \"string\"\n ? discovery.jwks_uri\n : \"\";\n const supportedIdTokenSigningAlgs = Array.isArray(discovery.id_token_signing_alg_values_supported)\n ? discovery.id_token_signing_alg_values_supported.filter(\n (value: unknown): value is string => typeof value === \"string\",\n )\n : [];\n const discoveredTokenEndpointAuthMethods = Array.isArray(\n discovery.token_endpoint_auth_methods_supported,\n )\n ? discovery.token_endpoint_auth_methods_supported.filter(\n (value: unknown): value is string => typeof value === \"string\",\n )\n : [];\n const tokenEndpointAuthMethod = resolveOidcAuthMethod(client, discoveredTokenEndpointAuthMethods);\n if (\n typeof client.authMethod === \"string\" &&\n discoveredTokenEndpointAuthMethods.length > 0 &&\n !discoveredTokenEndpointAuthMethods.includes(tokenEndpointAuthMethod)\n ) {\n throw new Error(\n `OIDC token endpoint auth method ${tokenEndpointAuthMethod} is not advertised by discovery.`,\n );\n }\n const userinfoEndpoint = (discovery.userinfo_endpoint as string | undefined) ?? undefined;\n const claimMapping =\n typeof profile.mapping === \"object\" && profile.mapping !== null\n ? (profile.mapping as OIDCClaimMapping)\n : undefined;\n const oidcFetch = createGroupConnectionOidcFetch(config, discovery.issuer as string);\n const runtimeOrigin =\n typeof discoveryConfig.discoveryUrl === \"string\"\n ? new URL(discoveryConfig.discoveryUrl).origin\n : undefined;\n const externalHost =\n typeof discoveryConfig.issuer === \"string\"\n ? new URL(discoveryConfig.issuer).host\n : typeof discovery.issuer === \"string\"\n ? new URL(discovery.issuer).host\n : undefined;\n const scopes = Array.isArray(request.scopes)\n ? request.scopes.filter((value: unknown): value is string => typeof value === \"string\")\n : [\"openid\", \"profile\", \"email\"];\n const expectedAudience: string | string[] = Array.isArray(discoveryConfig.audience)\n ? discoveryConfig.audience.filter(\n (value: unknown): value is string => typeof value === \"string\",\n )\n : typeof discoveryConfig.audience === \"string\"\n ? discoveryConfig.audience\n : String(client.id);\n const clockToleranceSeconds =\n typeof security.clockToleranceSeconds === \"number\" ? security.clockToleranceSeconds : 10;\n if (clockToleranceSeconds < 0 || clockToleranceSeconds > 300) {\n throw new Error(\"OIDC clockToleranceSeconds must be between 0 and 300.\");\n }\n const expectedIssuers = strictIssuer\n ? [expectedIssuer]\n : Array.from(new Set([expectedIssuer, discoveredIssuer]));\n const jwks = getOidcJwks(jwksUri, runtimeOrigin, externalHost, oidcFetch);\n let verifiedClaims: Record<string, unknown> | null = null;\n let verifiedProfile: (OAuthProfile & { emailVerified?: boolean }) | null = null;\n const normalizeProfile = (claims: Record<string, unknown>) =>\n normalizeOidcProfile(claims, claimMapping);\n\n const provider = {\n pkce: \"required\" as const,\n createAuthorizationURL({\n state,\n codeVerifier,\n scopes: requestedScopes,\n nonce,\n loginHint,\n }: {\n state: string;\n codeVerifier?: string;\n scopes: string[];\n nonce?: string;\n loginHint?: string;\n }) {\n if (!codeVerifier) {\n throw new Error(\"OIDC PKCE requires a code verifier.\");\n }\n const url = new URL(authorizationEndpoint);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", String(client.id));\n url.searchParams.set(\"redirect_uri\", redirectUri);\n url.searchParams.set(\n \"scope\",\n (requestedScopes.length > 0 ? requestedScopes : scopes).join(\" \"),\n );\n url.searchParams.set(\"state\", state);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\n \"code_challenge\",\n encodeBase64urlNoPadding(sha256(new TextEncoder().encode(codeVerifier))),\n );\n if (nonce !== undefined) {\n url.searchParams.set(\"nonce\", nonce);\n }\n if (typeof loginHint === \"string\") {\n url.searchParams.set(\"login_hint\", loginHint);\n }\n const authorizationParams = asRecord(request.authorizationParams) ?? {};\n const reservedAuthorizationParams = new Set([\n \"response_type\",\n \"client_id\",\n \"redirect_uri\",\n \"scope\",\n \"state\",\n \"code_challenge\",\n \"code_challenge_method\",\n \"nonce\",\n \"login_hint\",\n ]);\n for (const [key, value] of Object.entries(authorizationParams)) {\n if (reservedAuthorizationParams.has(key)) {\n throw new Error(`OIDC authorizationParams cannot override reserved parameter: ${key}`);\n }\n if (typeof value === \"string\") {\n url.searchParams.set(key, value);\n }\n }\n return url;\n },\n async validateAuthorizationCode({\n code,\n codeVerifier,\n }: {\n code: string;\n codeVerifier?: string;\n }) {\n const body = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n });\n const headers = new Headers({\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n });\n if (typeof client.secret === \"string\" && tokenEndpointAuthMethod === \"client_secret_basic\") {\n const encodeCredential = (value: string) => encodeURIComponent(value).replace(/%20/g, \"+\");\n const credentials = `${encodeCredential(String(client.id))}:${encodeCredential(client.secret)}`;\n const basicAuth = encodeBase64(new TextEncoder().encode(credentials));\n headers.set(\"Authorization\", `Basic ${basicAuth}`);\n } else {\n body.set(\"client_id\", String(client.id));\n if (typeof client.secret === \"string\") {\n body.set(\"client_secret\", client.secret);\n }\n }\n if (codeVerifier) {\n body.set(\"code_verifier\", codeVerifier);\n }\n const response = await oidcFetch(tokenEndpoint, {\n method: \"POST\",\n headers,\n body,\n });\n if (!response.ok) {\n const detail = await response.text();\n throw new Error(\n `OIDC token exchange failed: ${response.status}${detail ? ` ${detail}` : \"\"}`,\n );\n }\n const data = asRecord(await response.json());\n if (data === null) {\n throw new Error(\"OIDC token response is not an object.\");\n }\n return normalizeOAuthTokenResponse(data);\n },\n };\n\n const oauthConfig = {\n scopes,\n nonce: true,\n validateTokens: async (tokens: OAuthTokens, ctx: { nonce?: string }) => {\n if (ctx.nonce === undefined) {\n throw new Error(\"OIDC nonce is required.\");\n }\n\n const idToken = tokens.idToken;\n if (idToken === undefined) {\n throw new Error(\"OIDC response is missing id_token.\");\n }\n const verifiedIdToken = idToken;\n const protectedHeader = decodeProtectedHeader(verifiedIdToken);\n const tokenAlg = protectedHeader.alg;\n const useSymmetricValidation =\n typeof tokenAlg === \"string\" &&\n (tokenAlg === \"HS256\" || tokenAlg === \"HS384\" || tokenAlg === \"HS512\") &&\n supportedIdTokenSigningAlgs.includes(tokenAlg);\n\n const verificationOptions: JWTVerifyOptions = {\n audience: expectedAudience,\n requiredClaims: [\"iss\", \"sub\", \"aud\", \"exp\", \"iat\"],\n clockTolerance: clockToleranceSeconds,\n };\n\n let verification;\n try {\n verification = await (useSymmetricValidation\n ? jwtVerify(\n verifiedIdToken,\n (() => {\n if (typeof client.secret !== \"string\") {\n throw new Error(\n \"OIDC provider uses symmetric ID token signatures but clientSecret is missing.\",\n );\n }\n return new TextEncoder().encode(client.secret);\n })(),\n verificationOptions,\n )\n : jwtVerify(verifiedIdToken, jwks as JWTVerifyGetKey, verificationOptions));\n } catch (error) {\n throw asError(error);\n }\n\n const payload = asRecord(verification.payload);\n if (payload === null) {\n throw new Error(\"OIDC ID token payload is not an object.\");\n }\n const tokenIssuerRaw = typeof payload.iss === \"string\" ? payload.iss : undefined;\n const tokenIssuer =\n typeof tokenIssuerRaw === \"string\" ? tokenIssuerRaw.replace(/\\/$/, \"\") : undefined;\n\n if (!tokenIssuer || !expectedIssuers.includes(tokenIssuer)) {\n throw new Error(\n `OIDC token issuer mismatch. Received: ${tokenIssuer ?? \"<missing>\"}. Expected one of: ${expectedIssuers.join(\", \")}`,\n );\n }\n\n if (payload.nonce !== ctx.nonce) {\n throw new Error(\"OIDC nonce mismatch.\");\n }\n\n if (payload.azp !== undefined && payload.azp !== String(client.id)) {\n throw new Error(\"OIDC authorized party does not match client ID.\");\n }\n\n verifiedClaims = payload;\n verifiedProfile = normalizeProfile(payload);\n },\n accountLinking: config.accountLinking,\n profile: async (tokens: OAuthTokens): Promise<OAuthProfile> => {\n if (verifiedProfile === null || verifiedClaims === null) {\n throw new Error(\"OIDC profile requested before the id_token was verified.\");\n }\n if (userinfoEndpoint && typeof tokens.accessToken === \"string\") {\n const resolvedProfile = await userInfoProfile({\n endpoint: userinfoEndpoint,\n accessToken: tokens.accessToken,\n verifiedClaims,\n verifiedProfile,\n fetchImpl: oidcFetch,\n });\n if (resolvedProfile !== null) {\n return resolvedProfile;\n }\n }\n return verifiedProfile;\n },\n } as const;\n\n return { provider, oauthConfig };\n}\n\n/**\n * Build a minimal {@link OAuthMaterializedConfig} (no runtime provider) for a\n * group connection, used where only the provider id and account-linking mode\n * are needed.\n * @internal\n */\nexport function createSyntheticOAuthMaterializedConfig(\n providerId: string,\n options?: {\n accountLinking?: OAuthMaterializedConfig[\"accountLinking\"];\n },\n): OAuthMaterializedConfig {\n return {\n id: providerId,\n type: \"oauth\",\n provider: null,\n scopes: [],\n accountLinking: options?.accountLinking ?? \"sameConnection\",\n };\n}\n\n/**\n * Assemble the per-request OIDC runtime for a group connection: its provider\n * id, the configured provider and oauth config, and the group OIDC URLs.\n * @internal\n */\nexport async function createGroupConnectionOidcRuntime(opts: {\n ctx: GenericActionCtx<GenericDataModel>;\n componentConnection: AuthComponentApi[\"connection\"];\n rootUrl: string;\n connectionId: string;\n oidc: Record<string, unknown>;\n sharedRedirectURI?: string;\n}) {\n const providerId = groupOidcProviderId(opts.connectionId);\n const urls = getGroupOidcUrls({\n rootUrl: opts.rootUrl,\n connectionId: opts.connectionId,\n sharedRedirectURI: opts.sharedRedirectURI,\n });\n const { provider, oauthConfig } = await createGroupConnectionOidcProvider(\n opts.ctx,\n opts.componentConnection,\n opts.oidc,\n urls.callbackUrl,\n );\n return {\n oidc: opts.oidc,\n providerId,\n provider,\n oauthConfig,\n ...urls,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"oidc.js","names":["ctx"],"sources":["../../../src/server/connection/oidc.ts"],"sourcesContent":["import { sha256 } from \"@oslojs/crypto/sha2\";\nimport { encodeBase64, encodeBase64urlNoPadding } from \"@oslojs/encoding\";\nimport type { GenericActionCtx, GenericDataModel } from \"convex/server\";\nimport { createRemoteJWKSet, customFetch, decodeProtectedHeader, jwtVerify } from \"jose\";\nimport type { JWTVerifyGetKey, JWTVerifyOptions } from \"jose\";\n\nimport type { AuthComponentApi } from \"../component/api\";\n\nimport { assertSafeIdpFetchUrl, assertSafeIdpHost } from \"../../shared/fetch/guard\";\nimport { normalizeOAuthTokenResponse } from \"../oauth/normalize\";\nimport type {\n OIDCClaimMapping,\n OAuthMaterializedConfig,\n OAuthProfile,\n OAuthTokens,\n} from \"../types\";\nimport { createCache } from \"../utils/cache\";\nimport { retryWithBackoff } from \"../utils/retry\";\nimport { withSpan } from \"../utils/span\";\nimport { finalizeNormalizedProfile, normalizeStringArray } from \"./profile\";\nimport { asRecord, groupOidcProviderId, getGroupOidcUrls } from \"./shared\";\n\nconst OIDC_JWKS_CACHE = createCache<string, ReturnType<typeof createRemoteJWKSet>>({\n capacity: 128,\n timeToLiveMs: 60 * 60 * 1000,\n lookup: (cacheKey) => {\n const key = JSON.parse(cacheKey) as {\n url: string;\n runtimeOrigin?: string;\n externalHost?: string;\n };\n const fetchImpl =\n key.runtimeOrigin !== undefined || key.externalHost !== undefined\n ? createGroupConnectionOidcFetchFromParts(key.runtimeOrigin, key.externalHost)\n : undefined;\n return fetchImpl\n ? createRemoteJWKSet(new URL(key.url), { [customFetch]: fetchImpl })\n : createRemoteJWKSet(new URL(key.url));\n },\n});\n\ntype OidcDiscovery = {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n jwks_uri: string;\n userinfo_endpoint?: string;\n token_endpoint_auth_methods_supported?: string[];\n id_token_signing_alg_values_supported?: string[];\n};\n\ntype OidcUserInfo = {\n sub?: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n picture?: string;\n groups?: unknown;\n roles?: unknown;\n};\n\nfunction validateOidcDiscovery(data: unknown): OidcDiscovery {\n if (typeof data !== \"object\" || data === null) {\n throw new Error(\"OIDC discovery response is not an object.\");\n }\n const obj = asRecord(data);\n if (obj === null) {\n throw new Error(\"OIDC discovery response is not an object.\");\n }\n if (typeof obj.issuer !== \"string\") {\n throw new Error(\"OIDC discovery is missing 'issuer'.\");\n }\n if (typeof obj.authorization_endpoint !== \"string\") {\n throw new Error(\"OIDC discovery is missing 'authorization_endpoint'.\");\n }\n if (typeof obj.token_endpoint !== \"string\") {\n throw new Error(\"OIDC discovery is missing 'token_endpoint'.\");\n }\n if (typeof obj.jwks_uri !== \"string\") {\n throw new Error(\"OIDC discovery is missing 'jwks_uri'.\");\n }\n return {\n issuer: obj.issuer,\n authorization_endpoint: obj.authorization_endpoint,\n token_endpoint: obj.token_endpoint,\n jwks_uri: obj.jwks_uri,\n userinfo_endpoint:\n typeof obj.userinfo_endpoint === \"string\" ? obj.userinfo_endpoint : undefined,\n token_endpoint_auth_methods_supported: Array.isArray(obj.token_endpoint_auth_methods_supported)\n ? obj.token_endpoint_auth_methods_supported.filter(\n (v: unknown): v is string => typeof v === \"string\",\n )\n : undefined,\n id_token_signing_alg_values_supported: Array.isArray(obj.id_token_signing_alg_values_supported)\n ? obj.id_token_signing_alg_values_supported.filter(\n (v: unknown): v is string => typeof v === \"string\",\n )\n : undefined,\n };\n}\n\nfunction validateOidcUserInfo(data: unknown): OidcUserInfo {\n if (typeof data !== \"object\" || data === null) {\n return {};\n }\n const obj = asRecord(data);\n if (obj === null) {\n return {};\n }\n return {\n sub: typeof obj.sub === \"string\" ? obj.sub : undefined,\n email: typeof obj.email === \"string\" ? obj.email : undefined,\n email_verified: typeof obj.email_verified === \"boolean\" ? obj.email_verified : undefined,\n name: typeof obj.name === \"string\" ? obj.name : undefined,\n picture: typeof obj.picture === \"string\" ? obj.picture : undefined,\n groups: obj.groups,\n roles: obj.roles,\n };\n}\n\nconst asError = (error: unknown) => (error instanceof Error ? error : new Error(String(error)));\n\nfunction resolveOidcAuthMethod(\n client: Record<string, unknown>,\n discoveredMethods: string[],\n): \"client_secret_basic\" | \"client_secret_post\" {\n if (client.authMethod === \"client_secret_basic\" || client.authMethod === \"client_secret_post\") {\n return client.authMethod;\n }\n return discoveredMethods.includes(\"client_secret_basic\")\n ? \"client_secret_basic\"\n : \"client_secret_post\";\n}\n\nfunction oidcIssuer(value: unknown): string {\n return typeof value === \"string\" ? value : \"\";\n}\n\n/**\n * Typed shape of the `oidc` section of a group connection's `config` blob —\n * the OIDC counterpart to {@link SamlConfigShape}. The stored `config` is\n * `v.any()`, so {@link getOidcConfig} performs the single boundary cast to\n * this shape and call sites read it without further casts.\n */\nexport type OidcConfigShape = {\n enabled?: boolean;\n discovery?: {\n issuer?: string;\n discoveryUrl?: string;\n jwksUri?: string;\n audience?: string | string[];\n };\n client?: {\n id?: string;\n secret?: string;\n authMethod?: \"client_secret_post\" | \"client_secret_basic\";\n };\n request?: {\n scopes?: string[];\n loginHint?: string;\n authorizationParams?: Record<string, string>;\n };\n security?: {\n clockToleranceSeconds?: number;\n };\n profile?: {\n mapping?: OIDCClaimMapping;\n extraFields?: Record<string, string>;\n };\n};\n\nfunction getOidcSections(config: unknown) {\n const record = asRecord(config) ?? {};\n return {\n discovery: asRecord(record.discovery) ?? {},\n client: asRecord(record.client) ?? {},\n request: asRecord(record.request) ?? {},\n security: asRecord(record.security) ?? {},\n profile: asRecord(record.profile) ?? {},\n };\n}\n\nasync function discoverOidcConfiguration(\n ctx: GenericActionCtx<GenericDataModel>,\n componentConnection: AuthComponentApi[\"connection\"],\n config: unknown,\n): Promise<OidcDiscovery> {\n const { discovery } = getOidcSections(config);\n const discoveryUrl =\n typeof discovery.discoveryUrl === \"string\"\n ? discovery.discoveryUrl\n : typeof discovery.issuer === \"string\"\n ? `${discovery.issuer.replace(/\\/$/, \"\")}/.well-known/openid-configuration`\n : null;\n\n if (!discoveryUrl) {\n throw new Error(\"Group connection OIDC requires an issuer or discoveryUrl.\");\n }\n\n const runtimeOrigin =\n typeof discovery.discoveryUrl === \"string\" ? new URL(discovery.discoveryUrl).origin : undefined;\n const externalHost =\n typeof discovery.issuer === \"string\" ? new URL(discovery.issuer).host : undefined;\n\n assertSafeIdpFetchUrl(discoveryUrl);\n return withSpan(\"convex-auth.connection.oidc.discovery\", {}, async () => {\n return retryWithBackoff(\n async () => {\n const json = (await ctx.runAction(componentConnection.cache.oidcDiscovery, {\n url: discoveryUrl,\n runtimeOrigin,\n externalHost,\n })) as unknown;\n return validateOidcDiscovery(json);\n },\n { maxRetries: 2, baseMs: 200 },\n );\n });\n}\n\nfunction createGroupConnectionOidcFetch(\n config: Record<string, unknown>,\n discoveredIssuer?: string,\n) {\n const { discovery } = getOidcSections(config);\n const runtimeOrigin =\n typeof discovery.discoveryUrl === \"string\" ? new URL(discovery.discoveryUrl).origin : undefined;\n const externalHost =\n typeof discovery.issuer === \"string\"\n ? new URL(discovery.issuer).host\n : typeof discoveredIssuer === \"string\"\n ? new URL(discoveredIssuer).host\n : undefined;\n\n return createGroupConnectionOidcFetchFromParts(runtimeOrigin, externalHost);\n}\n\nfunction createGroupConnectionOidcFetchFromParts(runtimeOrigin?: string, externalHost?: string) {\n return async (input: string | URL, init?: RequestInit) => {\n const url = new URL(typeof input === \"string\" ? input : input.toString());\n const rewrittenUrl =\n runtimeOrigin !== undefined && url.origin !== runtimeOrigin\n ? new URL(`${runtimeOrigin}${url.pathname}${url.search}`)\n : url;\n const headers = new Headers(init?.headers);\n if (runtimeOrigin !== undefined && externalHost !== undefined) {\n assertSafeIdpHost(externalHost);\n headers.set(\"host\", externalHost);\n }\n assertSafeIdpFetchUrl(rewrittenUrl.toString());\n return await fetch(rewrittenUrl, { ...init, headers, redirect: \"manual\" });\n };\n}\n\nfunction normalizeOidcProfile(claims: Record<string, unknown>, mapping?: OIDCClaimMapping) {\n const getMapped = (key: string | undefined) =>\n typeof key === \"string\" ? claims[key] : undefined;\n return finalizeNormalizedProfile({\n id:\n (typeof getMapped(mapping?.subject) === \"string\"\n ? (getMapped(mapping?.subject) as string)\n : undefined) ?? (typeof claims.sub === \"string\" ? claims.sub : crypto.randomUUID()),\n email:\n (typeof getMapped(mapping?.email) === \"string\"\n ? (getMapped(mapping?.email) as string)\n : undefined) ?? (typeof claims.email === \"string\" ? claims.email : undefined),\n emailVerified:\n (typeof getMapped(mapping?.emailVerified) === \"boolean\"\n ? (getMapped(mapping?.emailVerified) as boolean)\n : undefined) ??\n (typeof claims.email_verified === \"boolean\" ? claims.email_verified : undefined),\n name:\n (typeof getMapped(mapping?.name) === \"string\"\n ? (getMapped(mapping?.name) as string)\n : undefined) ?? (typeof claims.name === \"string\" ? claims.name : undefined),\n image:\n (typeof getMapped(mapping?.image) === \"string\"\n ? (getMapped(mapping?.image) as string)\n : undefined) ?? (typeof claims.picture === \"string\" ? claims.picture : undefined),\n groups: normalizeStringArray(getMapped(mapping?.groups)),\n roles: normalizeStringArray(getMapped(mapping?.roles)),\n });\n}\n\nfunction getOidcJwks(\n url: string,\n runtimeOrigin?: string,\n externalHost?: string,\n fetchImpl?: ReturnType<typeof createGroupConnectionOidcFetch>,\n) {\n const cacheKey = JSON.stringify({\n url,\n runtimeOrigin: fetchImpl ? runtimeOrigin : undefined,\n externalHost: fetchImpl ? externalHost : undefined,\n });\n return OIDC_JWKS_CACHE.get(cacheKey);\n}\n\nasync function userInfoProfile(opts: {\n endpoint: string;\n accessToken: string;\n verifiedClaims: Record<string, unknown>;\n verifiedProfile: OAuthProfile & { emailVerified?: boolean };\n fetchImpl?: ReturnType<typeof createGroupConnectionOidcFetch>;\n}): Promise<(OAuthProfile & { emailVerified?: boolean }) | null> {\n return withSpan(\"convex-auth.connection.oidc.userinfo\", {}, async () => {\n let userInfo: OidcUserInfo;\n try {\n const response = await (opts.fetchImpl ?? fetch)(opts.endpoint, {\n headers: { Authorization: `Bearer ${opts.accessToken}` },\n });\n if (!response.ok) {\n throw new Error(`OIDC userinfo request failed: ${response.status}`);\n }\n userInfo = validateOidcUserInfo(await response.json());\n } catch {\n return null;\n }\n\n const userInfoSubject = typeof userInfo.sub === \"string\" ? userInfo.sub : undefined;\n const tokenSubject =\n typeof opts.verifiedClaims.sub === \"string\" ? opts.verifiedClaims.sub : undefined;\n if (\n userInfoSubject !== undefined &&\n tokenSubject !== undefined &&\n userInfoSubject !== tokenSubject\n ) {\n throw new Error(\"OIDC userinfo subject does not match ID token subject.\");\n }\n\n return {\n id:\n userInfoSubject ??\n (typeof opts.verifiedClaims.sub === \"string\" ? opts.verifiedClaims.sub : undefined) ??\n crypto.randomUUID(),\n email: typeof userInfo.email === \"string\" ? userInfo.email : opts.verifiedProfile.email,\n emailVerified:\n typeof userInfo.email_verified === \"boolean\"\n ? userInfo.email_verified\n : opts.verifiedProfile.emailVerified,\n name: typeof userInfo.name === \"string\" ? userInfo.name : opts.verifiedProfile.name,\n image: typeof userInfo.picture === \"string\" ? userInfo.picture : opts.verifiedProfile.image,\n groups: normalizeStringArray(userInfo.groups) ?? opts.verifiedProfile.groups,\n roles: normalizeStringArray(userInfo.roles) ?? opts.verifiedProfile.roles,\n } as OAuthProfile & { emailVerified?: boolean };\n });\n}\n\n/**\n * Build the OIDC authorization-code provider and oauth config for a group\n * connection from its discovery document and stored config.\n *\n * The authorization URL always uses PKCE with `S256` and a nonce, and rejects\n * attempts to override reserved OAuth parameters. Token validation requires the\n * nonce, verifies the ID token against the JWKS (or the client secret for\n * advertised HS* algorithms), and enforces the audience, the issuer (against\n * exact configured issuer, the\n * nonce, and `azp` when present. UserInfo results are rejected if their subject\n * disagrees with the ID token's.\n *\n * @see https://openid.net/specs/openid-connect-core-1_0.html\n * @internal\n */\nexport async function createGroupConnectionOidcProvider(\n ctx: GenericActionCtx<GenericDataModel>,\n componentConnection: AuthComponentApi[\"connection\"],\n config: Record<string, unknown>,\n redirectUri: string,\n) {\n const {\n discovery: discoveryConfig,\n client,\n request,\n security,\n profile,\n } = getOidcSections(config);\n const discovery: OidcDiscovery = await discoverOidcConfiguration(\n ctx,\n componentConnection,\n config,\n );\n const discoveredIssuer = oidcIssuer(discovery.issuer);\n const expectedIssuer =\n typeof discoveryConfig.issuer === \"string\"\n ? oidcIssuer(discoveryConfig.issuer)\n : discoveredIssuer;\n if (typeof discoveryConfig.issuer === \"string\" && expectedIssuer !== discoveredIssuer) {\n throw new Error(\n `Configured OIDC issuer mismatch. configured=${expectedIssuer} discovery=${discoveredIssuer}`,\n );\n }\n const authorizationEndpoint = discovery.authorization_endpoint as string;\n const tokenEndpoint = discovery.token_endpoint as string;\n const jwksUri =\n typeof discoveryConfig.jwksUri === \"string\"\n ? discoveryConfig.jwksUri\n : typeof discovery.jwks_uri === \"string\"\n ? discovery.jwks_uri\n : \"\";\n const supportedIdTokenSigningAlgs = Array.isArray(discovery.id_token_signing_alg_values_supported)\n ? discovery.id_token_signing_alg_values_supported.filter(\n (value: unknown): value is string => typeof value === \"string\",\n )\n : [];\n const discoveredTokenEndpointAuthMethods = Array.isArray(\n discovery.token_endpoint_auth_methods_supported,\n )\n ? discovery.token_endpoint_auth_methods_supported.filter(\n (value: unknown): value is string => typeof value === \"string\",\n )\n : [];\n const tokenEndpointAuthMethod = resolveOidcAuthMethod(client, discoveredTokenEndpointAuthMethods);\n if (\n typeof client.authMethod === \"string\" &&\n discoveredTokenEndpointAuthMethods.length > 0 &&\n !discoveredTokenEndpointAuthMethods.includes(tokenEndpointAuthMethod)\n ) {\n throw new Error(\n `OIDC token endpoint auth method ${tokenEndpointAuthMethod} is not advertised by discovery.`,\n );\n }\n const userinfoEndpoint = (discovery.userinfo_endpoint as string | undefined) ?? undefined;\n const claimMapping =\n typeof profile.mapping === \"object\" && profile.mapping !== null\n ? (profile.mapping as OIDCClaimMapping)\n : undefined;\n const oidcFetch = createGroupConnectionOidcFetch(config, discovery.issuer as string);\n const runtimeOrigin =\n typeof discoveryConfig.discoveryUrl === \"string\"\n ? new URL(discoveryConfig.discoveryUrl).origin\n : undefined;\n const externalHost =\n typeof discoveryConfig.issuer === \"string\"\n ? new URL(discoveryConfig.issuer).host\n : typeof discovery.issuer === \"string\"\n ? new URL(discovery.issuer).host\n : undefined;\n const scopes = Array.isArray(request.scopes)\n ? request.scopes.filter((value: unknown): value is string => typeof value === \"string\")\n : [\"openid\", \"profile\", \"email\"];\n const expectedAudience: string | string[] = Array.isArray(discoveryConfig.audience)\n ? discoveryConfig.audience.filter(\n (value: unknown): value is string => typeof value === \"string\",\n )\n : typeof discoveryConfig.audience === \"string\"\n ? discoveryConfig.audience\n : String(client.id);\n const clockToleranceSeconds =\n typeof security.clockToleranceSeconds === \"number\" ? security.clockToleranceSeconds : 10;\n if (clockToleranceSeconds < 0 || clockToleranceSeconds > 300) {\n throw new Error(\"OIDC clockToleranceSeconds must be between 0 and 300.\");\n }\n const jwks = getOidcJwks(jwksUri, runtimeOrigin, externalHost, oidcFetch);\n let verifiedClaims: Record<string, unknown> | null = null;\n let verifiedProfile: (OAuthProfile & { emailVerified?: boolean }) | null = null;\n const normalizeProfile = (claims: Record<string, unknown>) =>\n normalizeOidcProfile(claims, claimMapping);\n\n const provider = {\n pkce: \"required\" as const,\n createAuthorizationURL({\n state,\n codeVerifier,\n scopes: requestedScopes,\n nonce,\n loginHint,\n }: {\n state: string;\n codeVerifier?: string;\n scopes: string[];\n nonce?: string;\n loginHint?: string;\n }) {\n if (!codeVerifier) {\n throw new Error(\"OIDC PKCE requires a code verifier.\");\n }\n const url = new URL(authorizationEndpoint);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", String(client.id));\n url.searchParams.set(\"redirect_uri\", redirectUri);\n url.searchParams.set(\n \"scope\",\n (requestedScopes.length > 0 ? requestedScopes : scopes).join(\" \"),\n );\n url.searchParams.set(\"state\", state);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\n \"code_challenge\",\n encodeBase64urlNoPadding(sha256(new TextEncoder().encode(codeVerifier))),\n );\n if (nonce !== undefined) {\n url.searchParams.set(\"nonce\", nonce);\n }\n if (typeof loginHint === \"string\") {\n url.searchParams.set(\"login_hint\", loginHint);\n }\n const authorizationParams = asRecord(request.authorizationParams) ?? {};\n const reservedAuthorizationParams = new Set([\n \"response_type\",\n \"client_id\",\n \"redirect_uri\",\n \"scope\",\n \"state\",\n \"code_challenge\",\n \"code_challenge_method\",\n \"nonce\",\n \"login_hint\",\n ]);\n for (const [key, value] of Object.entries(authorizationParams)) {\n if (reservedAuthorizationParams.has(key)) {\n throw new Error(`OIDC authorizationParams cannot override reserved parameter: ${key}`);\n }\n if (typeof value === \"string\") {\n url.searchParams.set(key, value);\n }\n }\n return url;\n },\n async validateAuthorizationCode({\n code,\n codeVerifier,\n }: {\n code: string;\n codeVerifier?: string;\n }) {\n const body = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n });\n const headers = new Headers({\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n });\n if (typeof client.secret === \"string\" && tokenEndpointAuthMethod === \"client_secret_basic\") {\n const encodeCredential = (value: string) => encodeURIComponent(value).replace(/%20/g, \"+\");\n const credentials = `${encodeCredential(String(client.id))}:${encodeCredential(client.secret)}`;\n const basicAuth = encodeBase64(new TextEncoder().encode(credentials));\n headers.set(\"Authorization\", `Basic ${basicAuth}`);\n } else {\n body.set(\"client_id\", String(client.id));\n if (typeof client.secret === \"string\") {\n body.set(\"client_secret\", client.secret);\n }\n }\n if (codeVerifier) {\n body.set(\"code_verifier\", codeVerifier);\n }\n const response = await oidcFetch(tokenEndpoint, {\n method: \"POST\",\n headers,\n body,\n });\n if (!response.ok) {\n const detail = await response.text();\n throw new Error(\n `OIDC token exchange failed: ${response.status}${detail ? ` ${detail}` : \"\"}`,\n );\n }\n const data = asRecord(await response.json());\n if (data === null) {\n throw new Error(\"OIDC token response is not an object.\");\n }\n return normalizeOAuthTokenResponse(data);\n },\n };\n\n const oauthConfig = {\n scopes,\n nonce: true,\n validateTokens: async (tokens: OAuthTokens, ctx: { nonce?: string }) => {\n if (ctx.nonce === undefined) {\n throw new Error(\"OIDC nonce is required.\");\n }\n\n const idToken = tokens.idToken;\n if (idToken === undefined) {\n throw new Error(\"OIDC response is missing id_token.\");\n }\n const verifiedIdToken = idToken;\n const protectedHeader = decodeProtectedHeader(verifiedIdToken);\n const tokenAlg = protectedHeader.alg;\n const useSymmetricValidation =\n typeof tokenAlg === \"string\" &&\n (tokenAlg === \"HS256\" || tokenAlg === \"HS384\" || tokenAlg === \"HS512\") &&\n supportedIdTokenSigningAlgs.includes(tokenAlg);\n\n const verificationOptions: JWTVerifyOptions = {\n audience: expectedAudience,\n requiredClaims: [\"iss\", \"sub\", \"aud\", \"exp\", \"iat\"],\n clockTolerance: clockToleranceSeconds,\n };\n\n let verification;\n try {\n verification = await (useSymmetricValidation\n ? jwtVerify(\n verifiedIdToken,\n (() => {\n if (typeof client.secret !== \"string\") {\n throw new Error(\n \"OIDC provider uses symmetric ID token signatures but clientSecret is missing.\",\n );\n }\n return new TextEncoder().encode(client.secret);\n })(),\n verificationOptions,\n )\n : jwtVerify(verifiedIdToken, jwks as JWTVerifyGetKey, verificationOptions));\n } catch (error) {\n throw asError(error);\n }\n\n const payload = asRecord(verification.payload);\n if (payload === null) {\n throw new Error(\"OIDC ID token payload is not an object.\");\n }\n const tokenIssuerRaw = typeof payload.iss === \"string\" ? payload.iss : undefined;\n const tokenIssuer = typeof tokenIssuerRaw === \"string\" ? tokenIssuerRaw : undefined;\n\n if (!tokenIssuer || tokenIssuer !== expectedIssuer) {\n throw new Error(\n `OIDC token issuer mismatch. Received: ${tokenIssuer ?? \"<missing>\"}. Expected: ${expectedIssuer}`,\n );\n }\n\n if (payload.nonce !== ctx.nonce) {\n throw new Error(\"OIDC nonce mismatch.\");\n }\n\n if (payload.azp !== undefined && payload.azp !== String(client.id)) {\n throw new Error(\"OIDC authorized party does not match client ID.\");\n }\n\n verifiedClaims = payload;\n verifiedProfile = normalizeProfile(payload);\n },\n accountLinking: config.accountLinking,\n profile: async (tokens: OAuthTokens): Promise<OAuthProfile> => {\n if (verifiedProfile === null || verifiedClaims === null) {\n throw new Error(\"OIDC profile requested before the id_token was verified.\");\n }\n if (userinfoEndpoint && typeof tokens.accessToken === \"string\") {\n const resolvedProfile = await userInfoProfile({\n endpoint: userinfoEndpoint,\n accessToken: tokens.accessToken,\n verifiedClaims,\n verifiedProfile,\n fetchImpl: oidcFetch,\n });\n if (resolvedProfile !== null) {\n return resolvedProfile;\n }\n }\n return verifiedProfile;\n },\n } as const;\n\n return { provider, oauthConfig };\n}\n\n/**\n * Build a minimal {@link OAuthMaterializedConfig} (no runtime provider) for a\n * group connection, used where only the provider id and account-linking mode\n * are needed.\n * @internal\n */\nexport function createSyntheticOAuthMaterializedConfig(\n providerId: string,\n options?: {\n accountLinking?: OAuthMaterializedConfig[\"accountLinking\"];\n },\n): OAuthMaterializedConfig {\n return {\n id: providerId,\n type: \"oauth\",\n provider: null,\n scopes: [],\n accountLinking: options?.accountLinking ?? \"sameConnection\",\n };\n}\n\n/**\n * Assemble the per-request OIDC runtime for a group connection: its provider\n * id, the configured provider and oauth config, and the group OIDC URLs.\n * @internal\n */\nexport async function createGroupConnectionOidcRuntime(opts: {\n ctx: GenericActionCtx<GenericDataModel>;\n componentConnection: AuthComponentApi[\"connection\"];\n rootUrl: string;\n connectionId: string;\n oidc: Record<string, unknown>;\n sharedRedirectURI?: string;\n}) {\n const providerId = groupOidcProviderId(opts.connectionId);\n const urls = getGroupOidcUrls({\n rootUrl: opts.rootUrl,\n connectionId: opts.connectionId,\n sharedRedirectURI: opts.sharedRedirectURI,\n });\n const { provider, oauthConfig } = await createGroupConnectionOidcProvider(\n opts.ctx,\n opts.componentConnection,\n opts.oidc,\n urls.callbackUrl,\n );\n return {\n oidc: opts.oidc,\n providerId,\n provider,\n oauthConfig,\n ...urls,\n };\n}\n"],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ import { patchGroupConnectionPolicy } from "../policy.js";
|
|
|
5
5
|
|
|
6
6
|
//#region src/server/connection/policy/domain.ts
|
|
7
7
|
function createGroupPolicyDomain(deps) {
|
|
8
|
-
const { config, loadGroupPolicyOrThrow, validateGroupConnectionPolicy, emitGroupAuthEvent } = deps;
|
|
8
|
+
const { config, loadGroupPolicyOrThrow, validateGroupConnectionPolicy, hasActiveScimConnection, emitGroupAuthEvent } = deps;
|
|
9
9
|
return {
|
|
10
10
|
get: async (ctx, args) => {
|
|
11
11
|
return await loadGroupPolicyOrThrow(ctx, args.groupId);
|
|
@@ -15,6 +15,9 @@ function createGroupPolicyDomain(deps) {
|
|
|
15
15
|
const group = await getGroup(ctx, config.component.group, groupId);
|
|
16
16
|
if (!group) throw convexError(ErrorCode.INVALID_PARAMETERS, "Group not found.");
|
|
17
17
|
const policy = patchGroupConnectionPolicy(group.policy, args.patch);
|
|
18
|
+
const invalidChecks = validateGroupConnectionPolicy(policy).filter((check) => !check.ok);
|
|
19
|
+
if (invalidChecks.length > 0) throw convexError(ErrorCode.INVALID_PARAMETERS, invalidChecks.map((check) => check.message ?? check.name).join(" "));
|
|
20
|
+
if (policy.provisioning.user.authority === "scim" && !await hasActiveScimConnection(ctx, groupId)) throw convexError(ErrorCode.INVALID_PARAMETERS, "SCIM-managed provisioning requires an active SCIM configuration.");
|
|
18
21
|
await ctx.runMutation(config.component.group.update, {
|
|
19
22
|
id: groupId,
|
|
20
23
|
patch: { policy }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.js","names":[],"sources":["../../../../src/server/connection/policy/domain.ts"],"sourcesContent":["import { ErrorCode } from \"../../../shared/codes\";\nimport type { ComponentCtx, ComponentReadCtx } from \"../../component/context\";\nimport { getGroup } from \"../../contract\";\nimport { convexError } from \"../../errors\";\nimport type { EmitGroupAuthEventInput } from \"../group/service\";\nimport type {\n ConvexAuthMaterializedConfig,\n GroupConnectionPolicy,\n GroupConnectionPolicyPatch,\n} from \"../../types\";\nimport { patchGroupConnectionPolicy } from \"../policy\";\n\ntype PolicyDeps = {\n config: ConvexAuthMaterializedConfig;\n loadGroupPolicyOrThrow: (\n ctx: ComponentReadCtx,\n groupId: string,\n ) => Promise<GroupConnectionPolicy>;\n validateGroupConnectionPolicy: (\n policy: GroupConnectionPolicy,\n ) => Array<{ name: string; ok: boolean; message?: string }>;\n emitGroupAuthEvent: (ctx: ComponentCtx, data: EmitGroupAuthEventInput) => Promise<string>;\n};\n\nexport function createGroupPolicyDomain(deps: PolicyDeps) {\n const {
|
|
1
|
+
{"version":3,"file":"domain.js","names":[],"sources":["../../../../src/server/connection/policy/domain.ts"],"sourcesContent":["import { ErrorCode } from \"../../../shared/codes\";\nimport type { ComponentCtx, ComponentReadCtx } from \"../../component/context\";\nimport { getGroup } from \"../../contract\";\nimport { convexError } from \"../../errors\";\nimport type { EmitGroupAuthEventInput } from \"../group/service\";\nimport type {\n ConvexAuthMaterializedConfig,\n GroupConnectionPolicy,\n GroupConnectionPolicyPatch,\n} from \"../../types\";\nimport { patchGroupConnectionPolicy } from \"../policy\";\n\ntype PolicyDeps = {\n config: ConvexAuthMaterializedConfig;\n loadGroupPolicyOrThrow: (\n ctx: ComponentReadCtx,\n groupId: string,\n ) => Promise<GroupConnectionPolicy>;\n validateGroupConnectionPolicy: (\n policy: GroupConnectionPolicy,\n ) => Array<{ name: string; ok: boolean; message?: string }>;\n hasActiveScimConnection: (ctx: ComponentReadCtx, groupId: string) => Promise<boolean>;\n emitGroupAuthEvent: (ctx: ComponentCtx, data: EmitGroupAuthEventInput) => Promise<string>;\n};\n\nexport function createGroupPolicyDomain(deps: PolicyDeps) {\n const {\n config,\n loadGroupPolicyOrThrow,\n validateGroupConnectionPolicy,\n hasActiveScimConnection,\n emitGroupAuthEvent,\n } = deps;\n\n return {\n get: async (ctx: ComponentReadCtx, args: { groupId: string }) => {\n return await loadGroupPolicyOrThrow(ctx, args.groupId);\n },\n update: async (\n ctx: ComponentCtx,\n args: { groupId: string; patch: GroupConnectionPolicyPatch },\n ) => {\n const { groupId } = args;\n const group = await getGroup(ctx, config.component.group, groupId);\n if (!group) {\n throw convexError(ErrorCode.INVALID_PARAMETERS, \"Group not found.\");\n }\n const policy = patchGroupConnectionPolicy(group.policy, args.patch);\n const invalidChecks = validateGroupConnectionPolicy(policy).filter((check) => !check.ok);\n if (invalidChecks.length > 0) {\n throw convexError(\n ErrorCode.INVALID_PARAMETERS,\n invalidChecks.map((check) => check.message ?? check.name).join(\" \"),\n );\n }\n if (\n policy.provisioning.user.authority === \"scim\" &&\n !(await hasActiveScimConnection(ctx, groupId))\n ) {\n throw convexError(\n ErrorCode.INVALID_PARAMETERS,\n \"SCIM-managed provisioning requires an active SCIM configuration.\",\n );\n }\n await ctx.runMutation(config.component.group.update, {\n id: groupId,\n patch: { policy },\n });\n await emitGroupAuthEvent(ctx, {\n groupId,\n kind: \"connection.policy.updated\",\n actor: { type: \"system\" },\n subject: { type: \"group\", id: groupId },\n data: { version: policy.version },\n });\n return policy;\n },\n validate: async (ctx: ComponentReadCtx, args: { groupId: string }) => {\n const { groupId } = args;\n const group = await getGroup(ctx, config.component.group, groupId);\n if (!group) {\n return {\n ok: false,\n groupId,\n checks: [\n {\n name: \"group_exists\",\n ok: false,\n message: \"Group not found.\",\n },\n ],\n };\n }\n const policy = await loadGroupPolicyOrThrow(ctx, groupId);\n const checks = validateGroupConnectionPolicy(policy);\n return {\n ok: checks.every((check: { ok: boolean }) => check.ok),\n groupId,\n policy,\n checks,\n };\n },\n };\n}\n"],"mappings":";;;;;;AAyBA,SAAgB,wBAAwB,MAAkB;CACxD,MAAM,EACJ,QACA,wBACA,+BACA,yBACA,uBACE;AAEJ,QAAO;EACL,KAAK,OAAO,KAAuB,SAA8B;AAC/D,UAAO,MAAM,uBAAuB,KAAK,KAAK,QAAQ;;EAExD,QAAQ,OACN,KACA,SACG;GACH,MAAM,EAAE,YAAY;GACpB,MAAM,QAAQ,MAAM,SAAS,KAAK,OAAO,UAAU,OAAO,QAAQ;AAClE,OAAI,CAAC,MACH,OAAM,YAAY,UAAU,oBAAoB,mBAAmB;GAErE,MAAM,SAAS,2BAA2B,MAAM,QAAQ,KAAK,MAAM;GACnE,MAAM,gBAAgB,8BAA8B,OAAO,CAAC,QAAQ,UAAU,CAAC,MAAM,GAAG;AACxF,OAAI,cAAc,SAAS,EACzB,OAAM,YACJ,UAAU,oBACV,cAAc,KAAK,UAAU,MAAM,WAAW,MAAM,KAAK,CAAC,KAAK,IAAI,CACpE;AAEH,OACE,OAAO,aAAa,KAAK,cAAc,UACvC,CAAE,MAAM,wBAAwB,KAAK,QAAQ,CAE7C,OAAM,YACJ,UAAU,oBACV,mEACD;AAEH,SAAM,IAAI,YAAY,OAAO,UAAU,MAAM,QAAQ;IACnD,IAAI;IACJ,OAAO,EAAE,QAAQ;IAClB,CAAC;AACF,SAAM,mBAAmB,KAAK;IAC5B;IACA,MAAM;IACN,OAAO,EAAE,MAAM,UAAU;IACzB,SAAS;KAAE,MAAM;KAAS,IAAI;KAAS;IACvC,MAAM,EAAE,SAAS,OAAO,SAAS;IAClC,CAAC;AACF,UAAO;;EAET,UAAU,OAAO,KAAuB,SAA8B;GACpE,MAAM,EAAE,YAAY;AAEpB,OAAI,CADU,MAAM,SAAS,KAAK,OAAO,UAAU,OAAO,QAAQ,CAEhE,QAAO;IACL,IAAI;IACJ;IACA,QAAQ,CACN;KACE,MAAM;KACN,IAAI;KACJ,SAAS;KACV,CACF;IACF;GAEH,MAAM,SAAS,MAAM,uBAAuB,KAAK,QAAQ;GACzD,MAAM,SAAS,8BAA8B,OAAO;AACpD,UAAO;IACL,IAAI,OAAO,OAAO,UAA2B,MAAM,GAAG;IACtD;IACA;IACA;IACD;;EAEJ"}
|
|
@@ -14,7 +14,6 @@ const DEFAULT_GROUP_CONNECTION_POLICY = {
|
|
|
14
14
|
updateProfileFromScim: "always",
|
|
15
15
|
authority: "app"
|
|
16
16
|
},
|
|
17
|
-
scimReuse: { user: "externalId" },
|
|
18
17
|
jit: {
|
|
19
18
|
mode: "createUserAndMembership",
|
|
20
19
|
defaultRoleIds: []
|
|
@@ -44,7 +43,6 @@ function normalizeGroupConnectionPolicy(policy) {
|
|
|
44
43
|
const input = asRecord(policy) ?? {};
|
|
45
44
|
const accountLinking = asRecord((asRecord(input.identity) ?? {}).accountLinking) ?? {};
|
|
46
45
|
const provisioning = asRecord(input.provisioning) ?? {};
|
|
47
|
-
const scimReuse = asRecord(provisioning.scimReuse) ?? {};
|
|
48
46
|
const user = asRecord(provisioning.user) ?? {};
|
|
49
47
|
const jit = asRecord(provisioning.jit) ?? {};
|
|
50
48
|
const deprovision = asRecord(provisioning.deprovision) ?? {};
|
|
@@ -70,11 +68,18 @@ function normalizeGroupConnectionPolicy(policy) {
|
|
|
70
68
|
provisioning: {
|
|
71
69
|
user: {
|
|
72
70
|
createOnSignIn: typeof user.createOnSignIn === "boolean" ? user.createOnSignIn : d.provisioning.user.createOnSignIn,
|
|
73
|
-
updateProfileOnLogin: oneOf(user.updateProfileOnLogin, [
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
updateProfileOnLogin: oneOf(user.updateProfileOnLogin, [
|
|
72
|
+
"never",
|
|
73
|
+
"missing",
|
|
74
|
+
"always"
|
|
75
|
+
], d.provisioning.user.updateProfileOnLogin),
|
|
76
|
+
updateProfileFromScim: oneOf(user.updateProfileFromScim, [
|
|
77
|
+
"never",
|
|
78
|
+
"missing",
|
|
79
|
+
"always"
|
|
80
|
+
], d.provisioning.user.updateProfileFromScim),
|
|
81
|
+
authority: oneOf(user.authority, ["app", "scim"], d.provisioning.user.authority)
|
|
76
82
|
},
|
|
77
|
-
scimReuse: { user: oneOf(scimReuse.user, ["none"], d.provisioning.scimReuse.user) },
|
|
78
83
|
jit: {
|
|
79
84
|
mode: oneOf(jit.mode, [
|
|
80
85
|
"off",
|
|
@@ -83,13 +88,13 @@ function normalizeGroupConnectionPolicy(policy) {
|
|
|
83
88
|
], d.provisioning.jit.mode),
|
|
84
89
|
defaultRoleIds: Array.isArray(jit.defaultRoleIds) ? Array.from(new Set(jit.defaultRoleIds.filter((value) => typeof value === "string" && value.length > 0))) : typeof jit.defaultRole === "string" && jit.defaultRole.length > 0 ? [jit.defaultRole] : d.provisioning.jit.defaultRoleIds
|
|
85
90
|
},
|
|
86
|
-
deprovision: { mode: oneOf(deprovision.mode, ["hard"], d.provisioning.deprovision.mode) },
|
|
91
|
+
deprovision: { mode: oneOf(deprovision.mode, ["soft", "hard"], d.provisioning.deprovision.mode) },
|
|
87
92
|
groups: {
|
|
88
|
-
mode: oneOf(groups.mode, ["sync"], d.provisioning.groups.mode),
|
|
93
|
+
mode: oneOf(groups.mode, ["ignore", "sync"], d.provisioning.groups.mode),
|
|
89
94
|
source: "protocol"
|
|
90
95
|
},
|
|
91
96
|
roles: {
|
|
92
|
-
mode: oneOf(roles.mode, ["map"], d.provisioning.roles.mode),
|
|
97
|
+
mode: oneOf(roles.mode, ["ignore", "map"], d.provisioning.roles.mode),
|
|
93
98
|
source: "protocol"
|
|
94
99
|
}
|
|
95
100
|
}
|
|
@@ -115,10 +120,6 @@ function patchGroupConnectionPolicy(current, patch) {
|
|
|
115
120
|
provisioning: {
|
|
116
121
|
...base.provisioning,
|
|
117
122
|
...patch.provisioning,
|
|
118
|
-
scimReuse: {
|
|
119
|
-
...base.provisioning.scimReuse,
|
|
120
|
-
...patch.provisioning?.scimReuse
|
|
121
|
-
},
|
|
122
123
|
user: {
|
|
123
124
|
...base.provisioning.user,
|
|
124
125
|
...patch.provisioning?.user
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy.js","names":[],"sources":["../../../src/server/connection/policy.ts"],"sourcesContent":["import type { GroupConnectionPolicy, GroupConnectionPolicyPatch } from \"../types\";\nimport { asRecord } from \"./shared\";\n\nconst DEFAULT_GROUP_CONNECTION_POLICY: GroupConnectionPolicy = {\n version: 1,\n identity: {\n accountLinking: {\n oidc: \"sameConnection\",\n saml: \"sameConnection\",\n },\n },\n provisioning: {\n user: {\n createOnSignIn: true,\n updateProfileOnLogin: \"missing\",\n updateProfileFromScim: \"always\",\n authority: \"app\",\n },\n scimReuse: {\n user: \"externalId\",\n },\n jit: {\n mode: \"createUserAndMembership\",\n defaultRoleIds: [],\n },\n deprovision: {\n mode: \"soft\",\n },\n groups: {\n mode: \"ignore\",\n source: \"protocol\",\n },\n roles: {\n mode: \"ignore\",\n source: \"protocol\",\n },\n },\n};\n\n/** Accept a value only if it's one of the allowed options, otherwise return the fallback. */\nfunction oneOf<T extends string>(value: unknown, allowed: T[], fallback: T): T {\n return allowed.includes(value as T) ? (value as T) : fallback;\n}\n\n/** Parse an object whose values are string arrays (e.g. group→roleIds mapping). */\nfunction parseStringArrayMapping(raw: unknown): Record<string, string[]> | undefined {\n if (typeof raw !== \"object\" || raw === null) return undefined;\n return Object.fromEntries(\n Object.entries(raw)\n .filter(([key, value]) => typeof key === \"string\" && Array.isArray(value))\n .map(([key, value]) => [\n key,\n Array.from(\n new Set(\n (value as unknown[]).filter(\n (item): item is string => typeof item === \"string\" && item.length > 0,\n ),\n ),\n ),\n ]),\n );\n}\n\nexport function normalizeGroupConnectionPolicy(policy: unknown): GroupConnectionPolicy {\n const d = DEFAULT_GROUP_CONNECTION_POLICY;\n const input = asRecord(policy) ?? {};\n const identity = asRecord(input.identity) ?? {};\n const accountLinking = asRecord(identity.accountLinking) ?? {};\n const provisioning = asRecord(input.provisioning) ?? {};\n const scimReuse = asRecord(provisioning.scimReuse) ?? {};\n const user = asRecord(provisioning.user) ?? {};\n const jit = asRecord(provisioning.jit) ?? {};\n const deprovision = asRecord(provisioning.deprovision) ?? {};\n const groups = asRecord(provisioning.groups) ?? {};\n const roles = asRecord(provisioning.roles) ?? {};\n const extend = asRecord(input.extend) ?? undefined;\n\n const groupsMapping = parseStringArrayMapping(groups.mapping);\n const rolesMapping = parseStringArrayMapping(roles.mapping);\n\n const normalized: GroupConnectionPolicy = {\n version: 1,\n identity: {\n accountLinking: {\n oidc: oneOf(\n accountLinking.oidc,\n [\"none\", \"verifiedEmail\", \"sameConnection\"],\n d.identity.accountLinking.oidc,\n ),\n saml: oneOf(\n accountLinking.saml,\n [\"none\", \"verifiedEmail\", \"sameConnection\"],\n d.identity.accountLinking.saml,\n ),\n },\n },\n provisioning: {\n user: {\n createOnSignIn:\n typeof user.createOnSignIn === \"boolean\"\n ? user.createOnSignIn\n : d.provisioning.user.createOnSignIn,\n updateProfileOnLogin: oneOf(\n user.updateProfileOnLogin,\n [\"never\", \"always\"],\n d.provisioning.user.updateProfileOnLogin,\n ),\n updateProfileFromScim: oneOf(\n user.updateProfileFromScim,\n [\"never\", \"missing\"],\n d.provisioning.user.updateProfileFromScim,\n ),\n authority: oneOf(user.authority, [\"connection\", \"scim\"], d.provisioning.user.authority),\n },\n scimReuse: {\n user: oneOf(scimReuse.user, [\"none\"], d.provisioning.scimReuse.user),\n },\n jit: {\n mode: oneOf(\n jit.mode,\n [\"off\", \"createUser\", \"createUserAndMembership\"],\n d.provisioning.jit.mode,\n ),\n defaultRoleIds: Array.isArray(jit.defaultRoleIds)\n ? Array.from(\n new Set(\n jit.defaultRoleIds.filter(\n (value): value is string => typeof value === \"string\" && value.length > 0,\n ),\n ),\n )\n : typeof jit.defaultRole === \"string\" && jit.defaultRole.length > 0\n ? [jit.defaultRole]\n : d.provisioning.jit.defaultRoleIds,\n },\n deprovision: {\n mode: oneOf(deprovision.mode, [\"hard\"], d.provisioning.deprovision.mode),\n },\n groups: {\n mode: oneOf(groups.mode, [\"sync\"], d.provisioning.groups.mode),\n source: \"protocol\",\n },\n roles: {\n mode: oneOf(roles.mode, [\"map\"], d.provisioning.roles.mode),\n source: \"protocol\",\n },\n },\n };\n if (groupsMapping) normalized.provisioning.groups.mapping = groupsMapping;\n if (rolesMapping) normalized.provisioning.roles.mapping = rolesMapping;\n if (extend) normalized.extend = extend;\n return normalized;\n}\n\nexport function patchGroupConnectionPolicy(\n current: unknown,\n patch: GroupConnectionPolicyPatch,\n): GroupConnectionPolicy {\n const base = normalizeGroupConnectionPolicy(current);\n return normalizeGroupConnectionPolicy({\n ...base,\n ...patch,\n identity: {\n ...base.identity,\n ...patch.identity,\n accountLinking: {\n ...base.identity.accountLinking,\n ...patch.identity?.accountLinking,\n },\n },\n provisioning: {\n ...base.provisioning,\n ...patch.provisioning,\n scimReuse: {\n ...base.provisioning.scimReuse,\n ...patch.provisioning?.scimReuse,\n },\n user: {\n ...base.provisioning.user,\n ...patch.provisioning?.user,\n },\n jit: {\n ...base.provisioning.jit,\n ...patch.provisioning?.jit,\n },\n deprovision: {\n ...base.provisioning.deprovision,\n ...patch.provisioning?.deprovision,\n },\n groups: {\n ...base.provisioning.groups,\n ...patch.provisioning?.groups,\n },\n roles: {\n ...base.provisioning.roles,\n ...patch.provisioning?.roles,\n },\n },\n extend: patch.extend === undefined ? base.extend : { ...base.extend, ...patch.extend },\n });\n}\n\nexport function resolveProvisionedRoleIds(opts: {\n policy: GroupConnectionPolicy;\n groups?: string[];\n roles?: string[];\n}) {\n const roleIds = new Set<string>(opts.policy.provisioning.jit.defaultRoleIds);\n\n if (opts.policy.provisioning.groups.mode === \"sync\") {\n const mapping = opts.policy.provisioning.groups.mapping ?? {};\n for (const group of opts.groups ?? []) {\n for (const roleId of mapping[group] ?? []) {\n roleIds.add(roleId);\n }\n }\n }\n\n if (opts.policy.provisioning.roles.mode === \"map\") {\n const mapping = opts.policy.provisioning.roles.mapping ?? {};\n for (const role of opts.roles ?? []) {\n for (const roleId of mapping[role] ?? []) {\n roleIds.add(roleId);\n }\n }\n }\n\n return Array.from(roleIds);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"policy.js","names":[],"sources":["../../../src/server/connection/policy.ts"],"sourcesContent":["import type { GroupConnectionPolicy, GroupConnectionPolicyPatch } from \"../types\";\nimport { asRecord } from \"./shared\";\n\nconst DEFAULT_GROUP_CONNECTION_POLICY: GroupConnectionPolicy = {\n version: 1,\n identity: {\n accountLinking: {\n oidc: \"sameConnection\",\n saml: \"sameConnection\",\n },\n },\n provisioning: {\n user: {\n createOnSignIn: true,\n updateProfileOnLogin: \"missing\",\n updateProfileFromScim: \"always\",\n authority: \"app\",\n },\n jit: {\n mode: \"createUserAndMembership\",\n defaultRoleIds: [],\n },\n deprovision: {\n mode: \"soft\",\n },\n groups: {\n mode: \"ignore\",\n source: \"protocol\",\n },\n roles: {\n mode: \"ignore\",\n source: \"protocol\",\n },\n },\n};\n\n/** Accept a value only if it's one of the allowed options, otherwise return the fallback. */\nfunction oneOf<T extends string>(value: unknown, allowed: T[], fallback: T): T {\n return allowed.includes(value as T) ? (value as T) : fallback;\n}\n\n/** Parse an object whose values are string arrays (e.g. group→roleIds mapping). */\nfunction parseStringArrayMapping(raw: unknown): Record<string, string[]> | undefined {\n if (typeof raw !== \"object\" || raw === null) return undefined;\n return Object.fromEntries(\n Object.entries(raw)\n .filter(([key, value]) => typeof key === \"string\" && Array.isArray(value))\n .map(([key, value]) => [\n key,\n Array.from(\n new Set(\n (value as unknown[]).filter(\n (item): item is string => typeof item === \"string\" && item.length > 0,\n ),\n ),\n ),\n ]),\n );\n}\n\nexport function normalizeGroupConnectionPolicy(policy: unknown): GroupConnectionPolicy {\n const d = DEFAULT_GROUP_CONNECTION_POLICY;\n const input = asRecord(policy) ?? {};\n const identity = asRecord(input.identity) ?? {};\n const accountLinking = asRecord(identity.accountLinking) ?? {};\n const provisioning = asRecord(input.provisioning) ?? {};\n const user = asRecord(provisioning.user) ?? {};\n const jit = asRecord(provisioning.jit) ?? {};\n const deprovision = asRecord(provisioning.deprovision) ?? {};\n const groups = asRecord(provisioning.groups) ?? {};\n const roles = asRecord(provisioning.roles) ?? {};\n const extend = asRecord(input.extend) ?? undefined;\n\n const groupsMapping = parseStringArrayMapping(groups.mapping);\n const rolesMapping = parseStringArrayMapping(roles.mapping);\n\n const normalized: GroupConnectionPolicy = {\n version: 1,\n identity: {\n accountLinking: {\n oidc: oneOf(\n accountLinking.oidc,\n [\"none\", \"verifiedEmail\", \"sameConnection\"],\n d.identity.accountLinking.oidc,\n ),\n saml: oneOf(\n accountLinking.saml,\n [\"none\", \"verifiedEmail\", \"sameConnection\"],\n d.identity.accountLinking.saml,\n ),\n },\n },\n provisioning: {\n user: {\n createOnSignIn:\n typeof user.createOnSignIn === \"boolean\"\n ? user.createOnSignIn\n : d.provisioning.user.createOnSignIn,\n updateProfileOnLogin: oneOf(\n user.updateProfileOnLogin,\n [\"never\", \"missing\", \"always\"],\n d.provisioning.user.updateProfileOnLogin,\n ),\n updateProfileFromScim: oneOf(\n user.updateProfileFromScim,\n [\"never\", \"missing\", \"always\"],\n d.provisioning.user.updateProfileFromScim,\n ),\n authority: oneOf(user.authority, [\"app\", \"scim\"], d.provisioning.user.authority),\n },\n jit: {\n mode: oneOf(\n jit.mode,\n [\"off\", \"createUser\", \"createUserAndMembership\"],\n d.provisioning.jit.mode,\n ),\n defaultRoleIds: Array.isArray(jit.defaultRoleIds)\n ? Array.from(\n new Set(\n jit.defaultRoleIds.filter(\n (value): value is string => typeof value === \"string\" && value.length > 0,\n ),\n ),\n )\n : typeof jit.defaultRole === \"string\" && jit.defaultRole.length > 0\n ? [jit.defaultRole]\n : d.provisioning.jit.defaultRoleIds,\n },\n deprovision: {\n mode: oneOf(deprovision.mode, [\"soft\", \"hard\"], d.provisioning.deprovision.mode),\n },\n groups: {\n mode: oneOf(groups.mode, [\"ignore\", \"sync\"], d.provisioning.groups.mode),\n source: \"protocol\",\n },\n roles: {\n mode: oneOf(roles.mode, [\"ignore\", \"map\"], d.provisioning.roles.mode),\n source: \"protocol\",\n },\n },\n };\n if (groupsMapping) normalized.provisioning.groups.mapping = groupsMapping;\n if (rolesMapping) normalized.provisioning.roles.mapping = rolesMapping;\n if (extend) normalized.extend = extend;\n return normalized;\n}\n\nexport function patchGroupConnectionPolicy(\n current: unknown,\n patch: GroupConnectionPolicyPatch,\n): GroupConnectionPolicy {\n const base = normalizeGroupConnectionPolicy(current);\n return normalizeGroupConnectionPolicy({\n ...base,\n ...patch,\n identity: {\n ...base.identity,\n ...patch.identity,\n accountLinking: {\n ...base.identity.accountLinking,\n ...patch.identity?.accountLinking,\n },\n },\n provisioning: {\n ...base.provisioning,\n ...patch.provisioning,\n user: {\n ...base.provisioning.user,\n ...patch.provisioning?.user,\n },\n jit: {\n ...base.provisioning.jit,\n ...patch.provisioning?.jit,\n },\n deprovision: {\n ...base.provisioning.deprovision,\n ...patch.provisioning?.deprovision,\n },\n groups: {\n ...base.provisioning.groups,\n ...patch.provisioning?.groups,\n },\n roles: {\n ...base.provisioning.roles,\n ...patch.provisioning?.roles,\n },\n },\n extend: patch.extend === undefined ? base.extend : { ...base.extend, ...patch.extend },\n });\n}\n\nexport function resolveProvisionedRoleIds(opts: {\n policy: GroupConnectionPolicy;\n groups?: string[];\n roles?: string[];\n}) {\n const roleIds = new Set<string>(opts.policy.provisioning.jit.defaultRoleIds);\n\n if (opts.policy.provisioning.groups.mode === \"sync\") {\n const mapping = opts.policy.provisioning.groups.mapping ?? {};\n for (const group of opts.groups ?? []) {\n for (const roleId of mapping[group] ?? []) {\n roleIds.add(roleId);\n }\n }\n }\n\n if (opts.policy.provisioning.roles.mode === \"map\") {\n const mapping = opts.policy.provisioning.roles.mapping ?? {};\n for (const role of opts.roles ?? []) {\n for (const roleId of mapping[role] ?? []) {\n roleIds.add(roleId);\n }\n }\n }\n\n return Array.from(roleIds);\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ErrorCode } from "../../shared/codes.js";
|
|
2
2
|
import { convexError } from "../errors.js";
|
|
3
|
-
import { getGroupConnection, getScimConfigByConnection, getScimConfigByTokenHash,
|
|
3
|
+
import { getGroupConnection, getScimConfigByConnection, getScimConfigByTokenHash, upsertScimConfig } from "../contract.js";
|
|
4
4
|
|
|
5
5
|
//#region src/server/connection/provision.ts
|
|
6
6
|
function getScimConfigShape(scimConfig) {
|
|
@@ -55,12 +55,6 @@ function createGroupScimDomain(deps) {
|
|
|
55
55
|
ok: hasBasePath,
|
|
56
56
|
message: hasBasePath ? void 0 : "SCIM basePath does not match the derived route."
|
|
57
57
|
});
|
|
58
|
-
const supportsIdempotentExternalId = policy.provisioning.scimReuse.user === "externalId";
|
|
59
|
-
checks.push({
|
|
60
|
-
name: "user_external_id_reuse_enabled",
|
|
61
|
-
ok: supportsIdempotentExternalId,
|
|
62
|
-
message: supportsIdempotentExternalId ? void 0 : "SCIM user retry-safe provisioning works best with scimReuse.user = externalId."
|
|
63
|
-
});
|
|
64
58
|
checks.push({
|
|
65
59
|
name: "filter_subset_supported",
|
|
66
60
|
ok: true,
|
|
@@ -97,13 +91,16 @@ function createGroupScimDomain(deps) {
|
|
|
97
91
|
upsert: async (ctx, data) => {
|
|
98
92
|
const connection = await getGroupConnection(ctx, config.component.connection, data.connectionId);
|
|
99
93
|
if (connection === null) throw convexError(ErrorCode.INVALID_PARAMETERS, "Connection not found.");
|
|
94
|
+
const policy = await loadGroupPolicyOrThrow(ctx, connection.groupId);
|
|
95
|
+
const status = data.status ?? "active";
|
|
96
|
+
if (policy.provisioning.user.authority === "scim" && status !== "active") throw convexError(ErrorCode.INVALID_PARAMETERS, "SCIM-managed provisioning requires an active SCIM configuration.");
|
|
100
97
|
const rawToken = generateRandomString(48, INVITE_TOKEN_ALPHABET);
|
|
101
98
|
const tokenHash = await sha256(rawToken);
|
|
102
99
|
const basePath = getScimBasePath(connection._id);
|
|
103
100
|
const configId = await upsertScimConfig(ctx, config.component.connection, {
|
|
104
101
|
connectionId: connection._id,
|
|
105
102
|
groupId: connection.groupId,
|
|
106
|
-
status
|
|
103
|
+
status,
|
|
107
104
|
basePath,
|
|
108
105
|
tokenHash,
|
|
109
106
|
lastRotatedAt: Date.now(),
|
|
@@ -162,17 +159,6 @@ function createGroupScimDomain(deps) {
|
|
|
162
159
|
},
|
|
163
160
|
validate: async (ctx, args) => {
|
|
164
161
|
return await validateScim(ctx, args.connectionId);
|
|
165
|
-
},
|
|
166
|
-
identity: {
|
|
167
|
-
get: async (ctx, data) => {
|
|
168
|
-
return await getScimIdentity(ctx, config.component.connection, data);
|
|
169
|
-
},
|
|
170
|
-
upsert: async (ctx, data) => {
|
|
171
|
-
return await upsertScimIdentity(ctx, config.component.connection, {
|
|
172
|
-
...data,
|
|
173
|
-
lastProvisionedAt: Date.now()
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
162
|
}
|
|
177
163
|
};
|
|
178
164
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provision.js","names":[],"sources":["../../../src/server/connection/provision.ts"],"sourcesContent":["import { ErrorCode } from \"../../shared/codes\";\nimport type { ComponentCtx, ComponentReadCtx } from \"../component/context\";\nimport {\n getGroupConnection,\n getScimConfigByConnection,\n getScimConfigByTokenHash,\n getScimIdentity,\n upsertScimConfig,\n upsertScimIdentity,\n} from \"../contract\";\nimport { convexError } from \"../errors\";\nimport type { EmitGroupAuthEventInput } from \"./group/service\";\nimport type { GroupConnectionPolicy, ConvexAuthMaterializedConfig } from \"../types\";\n\ntype DomainScimConfig = {\n security?: {\n maxRequestSize?: number;\n };\n profile?: {\n mapping?: {\n subject?: string;\n externalId?: string;\n email?: string;\n firstName?: string;\n lastName?: string;\n name?: string;\n phone?: string;\n active?: string;\n groups?: string;\n roles?: string;\n };\n extraFields?: Record<string, string>;\n };\n};\n\nfunction getScimConfigShape(scimConfig: { extend?: unknown } | null | undefined): DomainScimConfig {\n return typeof scimConfig?.extend === \"object\" && scimConfig.extend !== null\n ? (scimConfig.extend as DomainScimConfig)\n : {};\n}\n\n/**\n * SCIM config doc with the bearer `tokenHash` stripped, for public/admin read\n * APIs. The raw token is returned once at `set`; later reads expose only\n * non-sensitive metadata plus a `hasToken` flag, mirroring the secret-redaction\n * discipline of `getPublicSamlConfig`/`getPublicConnectionConfig`.\n */\nfunction getPublicScimConfig<T extends { tokenHash?: string }>(\n scimConfig: T,\n): Omit<T, \"tokenHash\"> & { hasToken: boolean };\nfunction getPublicScimConfig<T extends { tokenHash?: string }>(\n scimConfig: T | null | undefined,\n): (Omit<T, \"tokenHash\"> & { hasToken: boolean }) | null;\nfunction getPublicScimConfig<T extends { tokenHash?: string }>(\n scimConfig: T | null | undefined,\n): (Omit<T, \"tokenHash\"> & { hasToken: boolean }) | null {\n if (!scimConfig) {\n return null;\n }\n const { tokenHash, ...rest } = scimConfig;\n return { ...rest, hasToken: typeof tokenHash === \"string\" && tokenHash.length > 0 };\n}\n\ntype ScimDeps = {\n config: ConvexAuthMaterializedConfig;\n requireEnv: (name: string) => string;\n generateRandomString: (length: number, alphabet: string) => string;\n INVITE_TOKEN_ALPHABET: string;\n sha256: (input: string) => Promise<string>;\n loadGroupPolicyOrThrow: (\n ctx: ComponentReadCtx,\n groupId: string,\n ) => Promise<GroupConnectionPolicy>;\n emitGroupAuthEvent: (ctx: ComponentCtx, data: EmitGroupAuthEventInput) => Promise<string>;\n};\n\nexport function createGroupScimDomain(deps: ScimDeps) {\n const {\n config,\n requireEnv,\n generateRandomString,\n INVITE_TOKEN_ALPHABET,\n sha256,\n loadGroupPolicyOrThrow,\n emitGroupAuthEvent,\n } = deps;\n\n const getScimBasePath = (connectionId: string) =>\n `${requireEnv(\"CONVEX_SITE_URL\")}/connections/${connectionId}/scim/v2`;\n\n const validateScim = async (ctx: ComponentReadCtx, connectionId: string) => {\n const checks: Array<{\n name: string;\n ok: boolean;\n message?: string;\n }> = [];\n\n const connection = await getGroupConnection(ctx, config.component.connection, connectionId);\n\n if (!connection) {\n return {\n ok: false,\n connectionId,\n checks: [\n {\n name: \"group_connection_exists\",\n ok: false,\n message: \"Connection not found.\",\n },\n ],\n };\n }\n\n const policy = await loadGroupPolicyOrThrow(ctx, connection.groupId);\n const scimConfig = await getScimConfigByConnection(\n ctx,\n config.component.connection,\n connectionId,\n );\n\n const hasConfig = scimConfig !== null && scimConfig !== undefined;\n checks.push({\n name: \"scim_config_exists\",\n ok: hasConfig,\n message: hasConfig ? undefined : \"SCIM has not been configured.\",\n });\n\n const isActive = scimConfig?.status === \"active\";\n checks.push({\n name: \"scim_config_active\",\n ok: isActive,\n message: isActive\n ? undefined\n : `SCIM config status is ${hasConfig ? scimConfig?.status : \"unknown\"}.`,\n });\n\n const hasToken = typeof scimConfig?.tokenHash === \"string\" && scimConfig.tokenHash.length > 0;\n checks.push({\n name: \"token_hash_set\",\n ok: hasToken,\n message: hasToken ? undefined : \"SCIM bearer token has not been set.\",\n });\n\n const hasBasePath =\n typeof scimConfig?.basePath === \"string\" &&\n scimConfig.basePath === getScimBasePath(connection._id);\n checks.push({\n name: \"base_path_matches_route\",\n ok: hasBasePath,\n message: hasBasePath ? undefined : \"SCIM basePath does not match the derived route.\",\n });\n\n const supportsIdempotentExternalId = policy.provisioning.scimReuse.user === \"externalId\";\n checks.push({\n name: \"user_external_id_reuse_enabled\",\n ok: supportsIdempotentExternalId,\n message: supportsIdempotentExternalId\n ? undefined\n : \"SCIM user retry-safe provisioning works best with scimReuse.user = externalId.\",\n });\n\n checks.push({\n name: \"filter_subset_supported\",\n ok: true,\n message: \"Supported filters: eq, co, sw, ew, pr on common user/group fields.\",\n });\n\n checks.push({\n name: \"protocol_capabilities_declared\",\n ok: true,\n });\n\n return {\n ok: checks.every((c) => c.ok),\n connectionId: connection._id,\n basePath: getScimBasePath(connection._id),\n deprovisionMode: policy.provisioning.deprovision.mode,\n capabilities: {\n users: true,\n groups: true,\n patch: true,\n put: true,\n filters: [\"eq\", \"co\", \"sw\", \"ew\", \"pr\"],\n bulk: false,\n etag: false,\n },\n checks,\n };\n };\n\n return {\n upsert: async (\n ctx: ComponentCtx,\n data: {\n connectionId: string;\n status?: \"draft\" | \"active\" | \"disabled\";\n security?: {\n maxRequestSize?: number;\n };\n profile?: {\n mapping?: {\n subject?: string;\n externalId?: string;\n email?: string;\n firstName?: string;\n lastName?: string;\n name?: string;\n phone?: string;\n active?: string;\n groups?: string;\n roles?: string;\n };\n extraFields?: Record<string, string>;\n };\n },\n ) => {\n const connection = await getGroupConnection(\n ctx,\n config.component.connection,\n data.connectionId,\n );\n if (connection === null) {\n throw convexError(ErrorCode.INVALID_PARAMETERS, \"Connection not found.\");\n }\n const rawToken = generateRandomString(48, INVITE_TOKEN_ALPHABET);\n const tokenHash = await sha256(rawToken);\n const basePath = getScimBasePath(connection._id);\n const configId = await upsertScimConfig(ctx, config.component.connection, {\n connectionId: connection._id,\n groupId: connection.groupId,\n status: data.status ?? \"active\",\n basePath,\n tokenHash,\n lastRotatedAt: Date.now(),\n extend: {\n security: data.security,\n profile: data.profile,\n },\n });\n await emitGroupAuthEvent(ctx, {\n connectionId: connection._id,\n groupId: connection.groupId,\n kind: \"connection.scim.set\",\n actor: { type: \"system\" },\n subject: { type: \"connection\", id: connection._id },\n data: { scimConfigId: configId },\n webhook: { payload: { connectionId: connection._id, scimConfigId: configId } },\n });\n return {\n connectionId: connection._id,\n configId,\n basePath,\n token: rawToken,\n };\n },\n get: async (ctx: ComponentReadCtx, args: { connectionId: string }) => {\n const scimConfig = await getScimConfigByConnection(\n ctx,\n config.component.connection,\n args.connectionId,\n );\n if (!scimConfig) {\n return null;\n }\n const shape = getScimConfigShape(scimConfig);\n return {\n ...getPublicScimConfig(scimConfig),\n security: shape.security,\n profile: shape.profile,\n };\n },\n status: async (ctx: ComponentReadCtx, args: { connectionId: string }) => {\n const { connectionId } = args;\n const currentConfig = await getScimConfigByConnection(\n ctx,\n config.component.connection,\n connectionId,\n );\n const result = await validateScim(ctx, connectionId);\n return {\n connectionId,\n configured: currentConfig?.status !== undefined,\n ready: result.ok,\n config: getPublicScimConfig(currentConfig),\n checks: result.checks,\n capabilities: \"capabilities\" in result ? result.capabilities : undefined,\n };\n },\n getConfigByToken: async (ctx: ComponentReadCtx, args: { token: string }) => {\n return await getScimConfigByTokenHash(\n ctx,\n config.component.connection,\n await sha256(args.token),\n );\n },\n validate: async (ctx: ComponentReadCtx, args: { connectionId: string }) => {\n return await validateScim(ctx, args.connectionId);\n },\n identity: {\n get: async (\n ctx: ComponentReadCtx,\n data: {\n connectionId: string;\n resourceType: \"user\" | \"group\";\n externalId: string;\n },\n ) => {\n return await getScimIdentity(ctx, config.component.connection, data);\n },\n upsert: async (\n ctx: ComponentCtx,\n data: {\n connectionId: string;\n groupId: string;\n resourceType: \"user\" | \"group\";\n externalId: string;\n userId?: string;\n mappedGroupId?: string;\n active?: boolean;\n raw?: Record<string, unknown>;\n },\n ) => {\n return await upsertScimIdentity(ctx, config.component.connection, {\n ...data,\n lastProvisionedAt: Date.now(),\n });\n },\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"provision.js","names":[],"sources":["../../../src/server/connection/provision.ts"],"sourcesContent":["import { ErrorCode } from \"../../shared/codes\";\nimport type { ComponentCtx, ComponentReadCtx } from \"../component/context\";\nimport {\n getGroupConnection,\n getScimConfigByConnection,\n getScimConfigByTokenHash,\n upsertScimConfig,\n} from \"../contract\";\nimport { convexError } from \"../errors\";\nimport type { EmitGroupAuthEventInput } from \"./group/service\";\nimport type { GroupConnectionPolicy, ConvexAuthMaterializedConfig } from \"../types\";\n\ntype DomainScimConfig = {\n security?: {\n maxRequestSize?: number;\n };\n profile?: {\n mapping?: {\n subject?: string;\n externalId?: string;\n email?: string;\n firstName?: string;\n lastName?: string;\n name?: string;\n phone?: string;\n active?: string;\n groups?: string;\n roles?: string;\n };\n extraFields?: Record<string, string>;\n };\n};\n\nfunction getScimConfigShape(scimConfig: { extend?: unknown } | null | undefined): DomainScimConfig {\n return typeof scimConfig?.extend === \"object\" && scimConfig.extend !== null\n ? (scimConfig.extend as DomainScimConfig)\n : {};\n}\n\n/**\n * SCIM config doc with the bearer `tokenHash` stripped, for public/admin read\n * APIs. The raw token is returned once at `set`; later reads expose only\n * non-sensitive metadata plus a `hasToken` flag, mirroring the secret-redaction\n * discipline of `getPublicSamlConfig`/`getPublicConnectionConfig`.\n */\nfunction getPublicScimConfig<T extends { tokenHash?: string }>(\n scimConfig: T,\n): Omit<T, \"tokenHash\"> & { hasToken: boolean };\nfunction getPublicScimConfig<T extends { tokenHash?: string }>(\n scimConfig: T | null | undefined,\n): (Omit<T, \"tokenHash\"> & { hasToken: boolean }) | null;\nfunction getPublicScimConfig<T extends { tokenHash?: string }>(\n scimConfig: T | null | undefined,\n): (Omit<T, \"tokenHash\"> & { hasToken: boolean }) | null {\n if (!scimConfig) {\n return null;\n }\n const { tokenHash, ...rest } = scimConfig;\n return { ...rest, hasToken: typeof tokenHash === \"string\" && tokenHash.length > 0 };\n}\n\ntype ScimDeps = {\n config: ConvexAuthMaterializedConfig;\n requireEnv: (name: string) => string;\n generateRandomString: (length: number, alphabet: string) => string;\n INVITE_TOKEN_ALPHABET: string;\n sha256: (input: string) => Promise<string>;\n loadGroupPolicyOrThrow: (\n ctx: ComponentReadCtx,\n groupId: string,\n ) => Promise<GroupConnectionPolicy>;\n emitGroupAuthEvent: (ctx: ComponentCtx, data: EmitGroupAuthEventInput) => Promise<string>;\n};\n\nexport function createGroupScimDomain(deps: ScimDeps) {\n const {\n config,\n requireEnv,\n generateRandomString,\n INVITE_TOKEN_ALPHABET,\n sha256,\n loadGroupPolicyOrThrow,\n emitGroupAuthEvent,\n } = deps;\n\n const getScimBasePath = (connectionId: string) =>\n `${requireEnv(\"CONVEX_SITE_URL\")}/connections/${connectionId}/scim/v2`;\n\n const validateScim = async (ctx: ComponentReadCtx, connectionId: string) => {\n const checks: Array<{\n name: string;\n ok: boolean;\n message?: string;\n }> = [];\n\n const connection = await getGroupConnection(ctx, config.component.connection, connectionId);\n\n if (!connection) {\n return {\n ok: false,\n connectionId,\n checks: [\n {\n name: \"group_connection_exists\",\n ok: false,\n message: \"Connection not found.\",\n },\n ],\n };\n }\n\n const policy = await loadGroupPolicyOrThrow(ctx, connection.groupId);\n const scimConfig = await getScimConfigByConnection(\n ctx,\n config.component.connection,\n connectionId,\n );\n\n const hasConfig = scimConfig !== null && scimConfig !== undefined;\n checks.push({\n name: \"scim_config_exists\",\n ok: hasConfig,\n message: hasConfig ? undefined : \"SCIM has not been configured.\",\n });\n\n const isActive = scimConfig?.status === \"active\";\n checks.push({\n name: \"scim_config_active\",\n ok: isActive,\n message: isActive\n ? undefined\n : `SCIM config status is ${hasConfig ? scimConfig?.status : \"unknown\"}.`,\n });\n\n const hasToken = typeof scimConfig?.tokenHash === \"string\" && scimConfig.tokenHash.length > 0;\n checks.push({\n name: \"token_hash_set\",\n ok: hasToken,\n message: hasToken ? undefined : \"SCIM bearer token has not been set.\",\n });\n\n const hasBasePath =\n typeof scimConfig?.basePath === \"string\" &&\n scimConfig.basePath === getScimBasePath(connection._id);\n checks.push({\n name: \"base_path_matches_route\",\n ok: hasBasePath,\n message: hasBasePath ? undefined : \"SCIM basePath does not match the derived route.\",\n });\n\n checks.push({\n name: \"filter_subset_supported\",\n ok: true,\n message: \"Supported filters: eq, co, sw, ew, pr on common user/group fields.\",\n });\n\n checks.push({\n name: \"protocol_capabilities_declared\",\n ok: true,\n });\n\n return {\n ok: checks.every((c) => c.ok),\n connectionId: connection._id,\n basePath: getScimBasePath(connection._id),\n deprovisionMode: policy.provisioning.deprovision.mode,\n capabilities: {\n users: true,\n groups: true,\n patch: true,\n put: true,\n filters: [\"eq\", \"co\", \"sw\", \"ew\", \"pr\"],\n bulk: false,\n etag: false,\n },\n checks,\n };\n };\n\n return {\n upsert: async (\n ctx: ComponentCtx,\n data: {\n connectionId: string;\n status?: \"draft\" | \"active\" | \"disabled\";\n security?: {\n maxRequestSize?: number;\n };\n profile?: {\n mapping?: {\n subject?: string;\n externalId?: string;\n email?: string;\n firstName?: string;\n lastName?: string;\n name?: string;\n phone?: string;\n active?: string;\n groups?: string;\n roles?: string;\n };\n extraFields?: Record<string, string>;\n };\n },\n ) => {\n const connection = await getGroupConnection(\n ctx,\n config.component.connection,\n data.connectionId,\n );\n if (connection === null) {\n throw convexError(ErrorCode.INVALID_PARAMETERS, \"Connection not found.\");\n }\n const policy = await loadGroupPolicyOrThrow(ctx, connection.groupId);\n const status = data.status ?? \"active\";\n if (policy.provisioning.user.authority === \"scim\" && status !== \"active\") {\n throw convexError(\n ErrorCode.INVALID_PARAMETERS,\n \"SCIM-managed provisioning requires an active SCIM configuration.\",\n );\n }\n const rawToken = generateRandomString(48, INVITE_TOKEN_ALPHABET);\n const tokenHash = await sha256(rawToken);\n const basePath = getScimBasePath(connection._id);\n const configId = await upsertScimConfig(ctx, config.component.connection, {\n connectionId: connection._id,\n groupId: connection.groupId,\n status,\n basePath,\n tokenHash,\n lastRotatedAt: Date.now(),\n extend: {\n security: data.security,\n profile: data.profile,\n },\n });\n await emitGroupAuthEvent(ctx, {\n connectionId: connection._id,\n groupId: connection.groupId,\n kind: \"connection.scim.set\",\n actor: { type: \"system\" },\n subject: { type: \"connection\", id: connection._id },\n data: { scimConfigId: configId },\n webhook: { payload: { connectionId: connection._id, scimConfigId: configId } },\n });\n return {\n connectionId: connection._id,\n configId,\n basePath,\n token: rawToken,\n };\n },\n get: async (ctx: ComponentReadCtx, args: { connectionId: string }) => {\n const scimConfig = await getScimConfigByConnection(\n ctx,\n config.component.connection,\n args.connectionId,\n );\n if (!scimConfig) {\n return null;\n }\n const shape = getScimConfigShape(scimConfig);\n return {\n ...getPublicScimConfig(scimConfig),\n security: shape.security,\n profile: shape.profile,\n };\n },\n status: async (ctx: ComponentReadCtx, args: { connectionId: string }) => {\n const { connectionId } = args;\n const currentConfig = await getScimConfigByConnection(\n ctx,\n config.component.connection,\n connectionId,\n );\n const result = await validateScim(ctx, connectionId);\n return {\n connectionId,\n configured: currentConfig?.status !== undefined,\n ready: result.ok,\n config: getPublicScimConfig(currentConfig),\n checks: result.checks,\n capabilities: \"capabilities\" in result ? result.capabilities : undefined,\n };\n },\n getConfigByToken: async (ctx: ComponentReadCtx, args: { token: string }) => {\n return await getScimConfigByTokenHash(\n ctx,\n config.component.connection,\n await sha256(args.token),\n );\n },\n validate: async (ctx: ComponentReadCtx, args: { connectionId: string }) => {\n return await validateScim(ctx, args.connectionId);\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ErrorCode } from "../../shared/codes.js";
|
|
2
2
|
import { convexError } from "../errors.js";
|
|
3
|
-
import { createWebhookEndpoint, getWebhookEndpoint,
|
|
3
|
+
import { createWebhookEndpoint, getWebhookEndpoint, listWebhookDeliveries, listWebhookEndpoints, updateWebhookEndpoint } from "../contract.js";
|
|
4
4
|
import { unsafeFetchUrlReason } from "../../shared/fetch/guard.js";
|
|
5
5
|
|
|
6
6
|
//#region src/server/connection/webhook.ts
|
|
@@ -109,41 +109,9 @@ function createGroupWebhookDomain(deps) {
|
|
|
109
109
|
return { endpointId: args.id };
|
|
110
110
|
}
|
|
111
111
|
},
|
|
112
|
-
delivery: {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
},
|
|
116
|
-
listReady: async (ctx, args = {}) => {
|
|
117
|
-
return await listReadyWebhookDeliveries(ctx, config.component.connection, {
|
|
118
|
-
now: Date.now(),
|
|
119
|
-
limit: args.limit
|
|
120
|
-
});
|
|
121
|
-
},
|
|
122
|
-
markDelivered: async (ctx, args) => {
|
|
123
|
-
await updateWebhookDelivery(ctx, config.component.connection, {
|
|
124
|
-
deliveryId: args.id,
|
|
125
|
-
patch: {
|
|
126
|
-
status: "delivered",
|
|
127
|
-
attemptCount: 1,
|
|
128
|
-
lastAttemptAt: Date.now(),
|
|
129
|
-
lastResponseStatus: args.responseStatus
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
},
|
|
133
|
-
markFailed: async (ctx, args) => {
|
|
134
|
-
await updateWebhookDelivery(ctx, config.component.connection, {
|
|
135
|
-
deliveryId: args.id,
|
|
136
|
-
patch: {
|
|
137
|
-
status: args.data.retryAt ? "pending" : "failed",
|
|
138
|
-
attemptCount: args.data.attemptCount,
|
|
139
|
-
lastAttemptAt: Date.now(),
|
|
140
|
-
lastResponseStatus: args.data.responseStatus,
|
|
141
|
-
lastError: args.data.error,
|
|
142
|
-
nextAttemptAt: args.data.retryAt ?? Date.now()
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
112
|
+
delivery: { list: async (ctx, data) => {
|
|
113
|
+
return await listWebhookDeliveries(ctx, config.component.connection, data);
|
|
114
|
+
} }
|
|
147
115
|
};
|
|
148
116
|
}
|
|
149
117
|
|