@estifanos-sh/convex-auth 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/README.md +5 -5
  2. package/dist/_generated/component.d.ts +130 -61
  3. package/dist/_generated/component.d.ts.map +1 -1
  4. package/dist/_generated/dataModel.d.ts +28 -0
  5. package/dist/_generated/dataModel.d.ts.map +1 -0
  6. package/dist/bin.js +1 -2
  7. package/dist/browser/index.d.ts +2 -2
  8. package/dist/browser/index.d.ts.map +1 -1
  9. package/dist/browser/index.js.map +1 -1
  10. package/dist/client/core/types.d.ts +40 -79
  11. package/dist/client/core/types.d.ts.map +1 -1
  12. package/dist/client/core/types.js.map +1 -1
  13. package/dist/client/errors.d.ts +15 -0
  14. package/dist/client/errors.d.ts.map +1 -0
  15. package/dist/client/errors.js +8 -1
  16. package/dist/client/errors.js.map +1 -1
  17. package/dist/client/index.d.ts +6 -3
  18. package/dist/client/index.d.ts.map +1 -1
  19. package/dist/client/index.js +3 -2
  20. package/dist/client/index.js.map +1 -1
  21. package/dist/component/_generated/component.d.ts +130 -61
  22. package/dist/component/_generated/component.d.ts.map +1 -1
  23. package/dist/component/_generated/dataModel.d.ts +1 -1
  24. package/dist/component/_generated/server.d.ts +1 -1
  25. package/dist/component/account.d.ts +43 -37
  26. package/dist/component/account.d.ts.map +1 -1
  27. package/dist/component/account.js +14 -16
  28. package/dist/component/account.js.map +1 -1
  29. package/dist/component/connection/audit.d.ts +29 -29
  30. package/dist/component/connection/audit.d.ts.map +1 -1
  31. package/dist/component/connection/audit.js +2 -1
  32. package/dist/component/connection/audit.js.map +1 -1
  33. package/dist/component/connection/cache.d.ts +6 -6
  34. package/dist/component/connection/domain/verification.d.ts +19 -19
  35. package/dist/component/connection/domain.d.ts +20 -20
  36. package/dist/component/connection/saml/assertion.d.ts +4 -4
  37. package/dist/component/connection/saml/request.d.ts +9 -9
  38. package/dist/component/connection/saml/request.d.ts.map +1 -1
  39. package/dist/component/connection/scim/config.d.ts +13 -13
  40. package/dist/component/connection/scim/identity.d.ts +102 -59
  41. package/dist/component/connection/scim/identity.d.ts.map +1 -1
  42. package/dist/component/connection/scim/identity.js +357 -56
  43. package/dist/component/connection/scim/identity.js.map +1 -1
  44. package/dist/component/connection/secret.d.ts +15 -15
  45. package/dist/component/connection/secret.d.ts.map +1 -1
  46. package/dist/component/connection/webhook/delivery.d.ts +69 -53
  47. package/dist/component/connection/webhook/delivery.d.ts.map +1 -1
  48. package/dist/component/connection/webhook/delivery.js +127 -170
  49. package/dist/component/connection/webhook/delivery.js.map +1 -1
  50. package/dist/component/connection/webhook/endpoint.d.ts +21 -21
  51. package/dist/component/connection/webhook/events.d.ts +26 -0
  52. package/dist/component/connection/webhook/events.d.ts.map +1 -0
  53. package/dist/component/connection/webhook/events.js +39 -0
  54. package/dist/component/connection/webhook/events.js.map +1 -0
  55. package/dist/component/connection.d.ts +38 -38
  56. package/dist/component/connection.d.ts.map +1 -1
  57. package/dist/component/context.d.ts +115 -0
  58. package/dist/component/context.d.ts.map +1 -0
  59. package/dist/component/context.js +50 -0
  60. package/dist/component/context.js.map +1 -0
  61. package/dist/component/convex.config.d.ts +2 -2
  62. package/dist/component/crons.d.ts +2 -2
  63. package/dist/component/event.d.ts +134 -138
  64. package/dist/component/event.d.ts.map +1 -1
  65. package/dist/component/event.js +88 -117
  66. package/dist/component/event.js.map +1 -1
  67. package/dist/component/eventstream.d.ts +30 -0
  68. package/dist/component/eventstream.d.ts.map +1 -0
  69. package/dist/component/eventstream.js +51 -0
  70. package/dist/component/eventstream.js.map +1 -0
  71. package/dist/component/factor/device.d.ts +25 -24
  72. package/dist/component/factor/device.d.ts.map +1 -1
  73. package/dist/component/factor/device.js +5 -2
  74. package/dist/component/factor/device.js.map +1 -1
  75. package/dist/component/factor/passkey.d.ts +84 -75
  76. package/dist/component/factor/passkey.d.ts.map +1 -1
  77. package/dist/component/factor/passkey.js +6 -6
  78. package/dist/component/factor/passkey.js.map +1 -1
  79. package/dist/component/factor/totp.d.ts +54 -49
  80. package/dist/component/factor/totp.d.ts.map +1 -1
  81. package/dist/component/factor/totp.js +3 -3
  82. package/dist/component/factor/totp.js.map +1 -1
  83. package/dist/component/group/active.d.ts +326 -22
  84. package/dist/component/group/active.d.ts.map +1 -1
  85. package/dist/component/group/active.js +21 -11
  86. package/dist/component/group/active.js.map +1 -1
  87. package/dist/component/group/invite.d.ts +28 -28
  88. package/dist/component/group/invite.d.ts.map +1 -1
  89. package/dist/component/group/member.d.ts +34 -34
  90. package/dist/component/group.d.ts +47 -62
  91. package/dist/component/group.d.ts.map +1 -1
  92. package/dist/component/http.d.ts +2 -2
  93. package/dist/component/limits.d.ts +4 -4
  94. package/dist/component/maintenance.d.ts +2 -2
  95. package/dist/component/model.d.ts +1362 -1027
  96. package/dist/component/model.d.ts.map +1 -1
  97. package/dist/component/model.js +23 -6
  98. package/dist/component/model.js.map +1 -1
  99. package/dist/component/modules.d.ts +8 -2
  100. package/dist/component/modules.d.ts.map +1 -1
  101. package/dist/component/modules.js +3 -0
  102. package/dist/component/modules.js.map +1 -1
  103. package/dist/component/oauth/client.d.ts +20 -20
  104. package/dist/component/oauth/code.d.ts +14 -14
  105. package/dist/component/oauth/refresh.d.ts +19 -19
  106. package/dist/component/schema.d.ts +1100 -611
  107. package/dist/component/schema.d.ts.map +1 -1
  108. package/dist/component/schema.js +17 -4
  109. package/dist/component/schema.js.map +1 -1
  110. package/dist/component/session.d.ts +72 -32
  111. package/dist/component/session.d.ts.map +1 -1
  112. package/dist/component/session.js +120 -32
  113. package/dist/component/session.js.map +1 -1
  114. package/dist/component/token/continuation.d.ts +40 -18
  115. package/dist/component/token/continuation.d.ts.map +1 -1
  116. package/dist/component/token/continuation.js +57 -11
  117. package/dist/component/token/continuation.js.map +1 -1
  118. package/dist/component/token/pkce.d.ts +22 -22
  119. package/dist/component/token/pkce.d.ts.map +1 -1
  120. package/dist/component/token/refresh.d.ts +39 -35
  121. package/dist/component/token/refresh.d.ts.map +1 -1
  122. package/dist/component/token/refresh.js +11 -6
  123. package/dist/component/token/refresh.js.map +1 -1
  124. package/dist/component/token/verification.d.ts +36 -32
  125. package/dist/component/token/verification.d.ts.map +1 -1
  126. package/dist/component/token/verification.js +3 -3
  127. package/dist/component/token/verification.js.map +1 -1
  128. package/dist/component/user/email.d.ts +21 -21
  129. package/dist/component/user/key.d.ts +35 -35
  130. package/dist/component/user/key.d.ts.map +1 -1
  131. package/dist/component/user.d.ts +51 -45
  132. package/dist/component/user.d.ts.map +1 -1
  133. package/dist/core/index.d.ts +54 -90
  134. package/dist/core/index.d.ts.map +1 -1
  135. package/dist/core/index.js +1 -4
  136. package/dist/core/index.js.map +1 -1
  137. package/dist/expo/index.d.ts +3 -3
  138. package/dist/expo/index.d.ts.map +1 -1
  139. package/dist/expo/index.js.map +1 -1
  140. package/dist/model.js.map +1 -1
  141. package/dist/providers/anonymous.d.ts +11 -5
  142. package/dist/providers/anonymous.d.ts.map +1 -1
  143. package/dist/providers/anonymous.js +4 -1
  144. package/dist/providers/anonymous.js.map +1 -1
  145. package/dist/providers/apple.d.ts +1 -1
  146. package/dist/providers/credentials.d.ts +21 -13
  147. package/dist/providers/credentials.d.ts.map +1 -1
  148. package/dist/providers/credentials.js +2 -0
  149. package/dist/providers/credentials.js.map +1 -1
  150. package/dist/providers/custom.d.ts +3 -3
  151. package/dist/providers/custom.d.ts.map +1 -1
  152. package/dist/providers/custom.js.map +1 -1
  153. package/dist/providers/email.d.ts +3 -3
  154. package/dist/providers/email.d.ts.map +1 -1
  155. package/dist/providers/email.js.map +1 -1
  156. package/dist/providers/github.d.ts +1 -1
  157. package/dist/providers/google.d.ts +1 -1
  158. package/dist/providers/microsoft.d.ts +1 -1
  159. package/dist/providers/password.d.ts +103 -8
  160. package/dist/providers/password.d.ts.map +1 -1
  161. package/dist/providers/password.js +71 -26
  162. package/dist/providers/password.js.map +1 -1
  163. package/dist/providers/phone.d.ts +4 -3
  164. package/dist/providers/phone.d.ts.map +1 -1
  165. package/dist/providers/phone.js.map +1 -1
  166. package/dist/react/index.d.ts +40 -41
  167. package/dist/react/index.d.ts.map +1 -1
  168. package/dist/react/index.js +27 -52
  169. package/dist/react/index.js.map +1 -1
  170. package/dist/server/auth.d.ts +157 -117
  171. package/dist/server/auth.d.ts.map +1 -1
  172. package/dist/server/auth.js +41 -22
  173. package/dist/server/auth.js.map +1 -1
  174. package/dist/server/component/factor/db.js +4 -4
  175. package/dist/server/component/factor/db.js.map +1 -1
  176. package/dist/server/config.d.ts +2 -1
  177. package/dist/server/connection/domain.d.ts +4 -66
  178. package/dist/server/connection/domain.d.ts.map +1 -1
  179. package/dist/server/connection/domain.js +22 -4
  180. package/dist/server/connection/domain.js.map +1 -1
  181. package/dist/server/connection/group/service.js +6 -4
  182. package/dist/server/connection/group/service.js.map +1 -1
  183. package/dist/server/connection/http.js +94 -261
  184. package/dist/server/connection/http.js.map +1 -1
  185. package/dist/server/connection/oidc.js +8 -17
  186. package/dist/server/connection/oidc.js.map +1 -1
  187. package/dist/server/connection/policy/domain.js +4 -1
  188. package/dist/server/connection/policy/domain.js.map +1 -1
  189. package/dist/server/connection/policy.js +14 -13
  190. package/dist/server/connection/policy.js.map +1 -1
  191. package/dist/server/connection/provision.js +5 -19
  192. package/dist/server/connection/provision.js.map +1 -1
  193. package/dist/server/connection/webhook.js +4 -36
  194. package/dist/server/connection/webhook.js.map +1 -1
  195. package/dist/server/context.d.ts +2 -2
  196. package/dist/server/context.d.ts.map +1 -1
  197. package/dist/server/context.js +35 -24
  198. package/dist/server/context.js.map +1 -1
  199. package/dist/server/contract.d.ts.map +1 -1
  200. package/dist/server/contract.js +6 -14
  201. package/dist/server/contract.js.map +1 -1
  202. package/dist/server/core.js +6 -0
  203. package/dist/server/core.js.map +1 -1
  204. package/dist/server/cors.d.ts +2 -2
  205. package/dist/server/cors.d.ts.map +1 -1
  206. package/dist/server/crypto.js.map +1 -1
  207. package/dist/server/db.js +2 -1
  208. package/dist/server/db.js.map +1 -1
  209. package/dist/server/domains/context.js +34 -0
  210. package/dist/server/domains/context.js.map +1 -0
  211. package/dist/server/domains/factor.d.ts +3 -2
  212. package/dist/server/domains/factor.d.ts.map +1 -1
  213. package/dist/server/domains/factor.js.map +1 -1
  214. package/dist/server/domains/group.js.map +1 -1
  215. package/dist/server/domains/invite.js.map +1 -1
  216. package/dist/server/domains/key.js.map +1 -1
  217. package/dist/server/domains/member.js.map +1 -1
  218. package/dist/server/domains/session.js.map +1 -1
  219. package/dist/server/domains/user.js.map +1 -1
  220. package/dist/server/env.js +2 -22
  221. package/dist/server/env.js.map +1 -1
  222. package/dist/server/events.d.ts +0 -1
  223. package/dist/server/events.d.ts.map +1 -1
  224. package/dist/server/events.js +6 -6
  225. package/dist/server/events.js.map +1 -1
  226. package/dist/server/http.d.ts +1 -0
  227. package/dist/server/http.d.ts.map +1 -1
  228. package/dist/server/http.js +4 -4
  229. package/dist/server/http.js.map +1 -1
  230. package/dist/server/identity/claims.js +1 -1
  231. package/dist/server/identity/convex.d.ts +2 -0
  232. package/dist/server/identity/convex.d.ts.map +1 -1
  233. package/dist/server/index.d.ts +2 -3
  234. package/dist/server/index.js +1 -2
  235. package/dist/server/limits.js +21 -1
  236. package/dist/server/limits.js.map +1 -1
  237. package/dist/server/mutations/credentials/signin.js +6 -3
  238. package/dist/server/mutations/credentials/signin.js.map +1 -1
  239. package/dist/server/mutations/invalidate.js +5 -10
  240. package/dist/server/mutations/invalidate.js.map +1 -1
  241. package/dist/server/mutations/oauth.js +1 -1
  242. package/dist/server/mutations/oauth.js.map +1 -1
  243. package/dist/server/mutations/retrieve.js +4 -1
  244. package/dist/server/mutations/retrieve.js.map +1 -1
  245. package/dist/server/mutations/signin.js +2 -2
  246. package/dist/server/mutations/signin.js.map +1 -1
  247. package/dist/server/mutations/verify.js +4 -3
  248. package/dist/server/mutations/verify.js.map +1 -1
  249. package/dist/server/oauth/factory.js.map +1 -1
  250. package/dist/server/runtime.d.ts +107 -127
  251. package/dist/server/runtime.d.ts.map +1 -1
  252. package/dist/server/runtime.js +43 -28
  253. package/dist/server/runtime.js.map +1 -1
  254. package/dist/server/session/lifecycle.d.ts.map +1 -1
  255. package/dist/server/session/lifecycle.js +18 -3
  256. package/dist/server/session/lifecycle.js.map +1 -1
  257. package/dist/server/signin/flow.js +1 -1
  258. package/dist/server/signin/flow.js.map +1 -1
  259. package/dist/server/tokens.js +1 -0
  260. package/dist/server/tokens.js.map +1 -1
  261. package/dist/server/totp.js +3 -3
  262. package/dist/server/totp.js.map +1 -1
  263. package/dist/server/types.d.ts +63 -43
  264. package/dist/server/types.d.ts.map +1 -1
  265. package/dist/server/user/account.js +0 -1
  266. package/dist/server/user/account.js.map +1 -1
  267. package/dist/server/validators.d.ts +874 -917
  268. package/dist/server/validators.d.ts.map +1 -1
  269. package/dist/server/validators.js +33 -32
  270. package/dist/server/validators.js.map +1 -1
  271. package/dist/server/webauthn.js +4 -4
  272. package/dist/server/webauthn.js.map +1 -1
  273. package/dist/shared/brand.d.ts +3 -7
  274. package/dist/shared/brand.d.ts.map +1 -1
  275. package/dist/shared/params.d.ts +78 -0
  276. package/dist/shared/params.d.ts.map +1 -0
  277. package/dist/shared/results.d.ts +7 -1
  278. package/dist/shared/results.d.ts.map +1 -1
  279. package/dist/svelte/context.svelte.d.ts +23 -12
  280. package/dist/svelte/context.svelte.d.ts.map +1 -1
  281. package/dist/svelte/context.svelte.js +20 -13
  282. package/dist/svelte/index.d.ts +1 -4
  283. package/dist/svelte/index.d.ts.map +1 -1
  284. package/dist/svelte/index.js +1 -4
  285. package/dist/test.d.ts +1105 -611
  286. package/dist/test.d.ts.map +1 -1
  287. package/package.json +2 -1
  288. package/dist/server/env.d.ts +0 -28
  289. package/dist/server/env.d.ts.map +0 -1
  290. package/dist/svelte/AuthLoading.svelte +0 -13
  291. package/dist/svelte/AuthLoading.svelte.d.ts +0 -8
  292. package/dist/svelte/AuthLoading.svelte.d.ts.map +0 -1
  293. package/dist/svelte/SignedIn.svelte +0 -14
  294. package/dist/svelte/SignedIn.svelte.d.ts +0 -8
  295. package/dist/svelte/SignedIn.svelte.d.ts.map +0 -1
  296. package/dist/svelte/SignedOut.svelte +0 -13
  297. package/dist/svelte/SignedOut.svelte.d.ts +0 -8
  298. package/dist/svelte/SignedOut.svelte.d.ts.map +0 -1
@@ -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 normalizeOidcIssuer(value) {
61
- return typeof value === "string" ? value.replace(/\/$/, "") : "";
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
- * the configured/discovered issuer set; exact match when `strictIssuer`), the
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 = normalizeOidcIssuer(discovery.issuer);
177
- const expectedIssuer = typeof discoveryConfig.issuer === "string" ? normalizeOidcIssuer(discoveryConfig.issuer) : discoveredIssuer;
178
- const strictIssuer = security.strictIssuer === true;
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.replace(/\/$/, "") : void 0;
309
- if (!tokenIssuer || !expectedIssuers.includes(tokenIssuer)) throw new Error(`OIDC token issuer mismatch. Received: ${tokenIssuer ?? "<missing>"}. Expected one of: ${expectedIssuers.join(", ")}`);
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 { config, loadGroupPolicyOrThrow, validateGroupConnectionPolicy, 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 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":";;;;;;AAwBA,SAAgB,wBAAwB,MAAkB;CACxD,MAAM,EAAE,QAAQ,wBAAwB,+BAA+B,uBACrE;AAEF,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;AACnE,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"}
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, ["never", "always"], d.provisioning.user.updateProfileOnLogin),
74
- updateProfileFromScim: oneOf(user.updateProfileFromScim, ["never", "missing"], d.provisioning.user.updateProfileFromScim),
75
- authority: oneOf(user.authority, ["connection", "scim"], d.provisioning.user.authority)
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":";;;AAGA,MAAM,kCAAyD;CAC7D,SAAS;CACT,UAAU,EACR,gBAAgB;EACd,MAAM;EACN,MAAM;EACP,EACF;CACD,cAAc;EACZ,MAAM;GACJ,gBAAgB;GAChB,sBAAsB;GACtB,uBAAuB;GACvB,WAAW;GACZ;EACD,KAAK;GACH,MAAM;GACN,gBAAgB,EAAE;GACnB;EACD,aAAa,EACX,MAAM,QACP;EACD,QAAQ;GACN,MAAM;GACN,QAAQ;GACT;EACD,OAAO;GACL,MAAM;GACN,QAAQ;GACT;EACF;CACF;;AAGD,SAAS,MAAwB,OAAgB,SAAc,UAAgB;AAC7E,QAAO,QAAQ,SAAS,MAAW,GAAI,QAAc;;;AAIvD,SAAS,wBAAwB,KAAoD;AACnF,KAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,QAAO;AACpD,QAAO,OAAO,YACZ,OAAO,QAAQ,IAAI,CAChB,QAAQ,CAAC,KAAK,WAAW,OAAO,QAAQ,YAAY,MAAM,QAAQ,MAAM,CAAC,CACzE,KAAK,CAAC,KAAK,WAAW,CACrB,KACA,MAAM,KACJ,IAAI,IACD,MAAoB,QAClB,SAAyB,OAAO,SAAS,YAAY,KAAK,SAAS,EACrE,CACF,CACF,CACF,CAAC,CACL;;AAGH,SAAgB,+BAA+B,QAAwC;CACrF,MAAM,IAAI;CACV,MAAM,QAAQ,SAAS,OAAO,IAAI,EAAE;CAEpC,MAAM,iBAAiB,UADN,SAAS,MAAM,SAAS,IAAI,EAAE,EACN,eAAe,IAAI,EAAE;CAC9D,MAAM,eAAe,SAAS,MAAM,aAAa,IAAI,EAAE;CACvD,MAAM,OAAO,SAAS,aAAa,KAAK,IAAI,EAAE;CAC9C,MAAM,MAAM,SAAS,aAAa,IAAI,IAAI,EAAE;CAC5C,MAAM,cAAc,SAAS,aAAa,YAAY,IAAI,EAAE;CAC5D,MAAM,SAAS,SAAS,aAAa,OAAO,IAAI,EAAE;CAClD,MAAM,QAAQ,SAAS,aAAa,MAAM,IAAI,EAAE;CAChD,MAAM,SAAS,SAAS,MAAM,OAAO,IAAI;CAEzC,MAAM,gBAAgB,wBAAwB,OAAO,QAAQ;CAC7D,MAAM,eAAe,wBAAwB,MAAM,QAAQ;CAE3D,MAAM,aAAoC;EACxC,SAAS;EACT,UAAU,EACR,gBAAgB;GACd,MAAM,MACJ,eAAe,MACf;IAAC;IAAQ;IAAiB;IAAiB,EAC3C,EAAE,SAAS,eAAe,KAC3B;GACD,MAAM,MACJ,eAAe,MACf;IAAC;IAAQ;IAAiB;IAAiB,EAC3C,EAAE,SAAS,eAAe,KAC3B;GACF,EACF;EACD,cAAc;GACZ,MAAM;IACJ,gBACE,OAAO,KAAK,mBAAmB,YAC3B,KAAK,iBACL,EAAE,aAAa,KAAK;IAC1B,sBAAsB,MACpB,KAAK,sBACL;KAAC;KAAS;KAAW;KAAS,EAC9B,EAAE,aAAa,KAAK,qBACrB;IACD,uBAAuB,MACrB,KAAK,uBACL;KAAC;KAAS;KAAW;KAAS,EAC9B,EAAE,aAAa,KAAK,sBACrB;IACD,WAAW,MAAM,KAAK,WAAW,CAAC,OAAO,OAAO,EAAE,EAAE,aAAa,KAAK,UAAU;IACjF;GACD,KAAK;IACH,MAAM,MACJ,IAAI,MACJ;KAAC;KAAO;KAAc;KAA0B,EAChD,EAAE,aAAa,IAAI,KACpB;IACD,gBAAgB,MAAM,QAAQ,IAAI,eAAe,GAC7C,MAAM,KACJ,IAAI,IACF,IAAI,eAAe,QAChB,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,EACzE,CACF,CACF,GACD,OAAO,IAAI,gBAAgB,YAAY,IAAI,YAAY,SAAS,IAC9D,CAAC,IAAI,YAAY,GACjB,EAAE,aAAa,IAAI;IAC1B;GACD,aAAa,EACX,MAAM,MAAM,YAAY,MAAM,CAAC,QAAQ,OAAO,EAAE,EAAE,aAAa,YAAY,KAAK,EACjF;GACD,QAAQ;IACN,MAAM,MAAM,OAAO,MAAM,CAAC,UAAU,OAAO,EAAE,EAAE,aAAa,OAAO,KAAK;IACxE,QAAQ;IACT;GACD,OAAO;IACL,MAAM,MAAM,MAAM,MAAM,CAAC,UAAU,MAAM,EAAE,EAAE,aAAa,MAAM,KAAK;IACrE,QAAQ;IACT;GACF;EACF;AACD,KAAI,cAAe,YAAW,aAAa,OAAO,UAAU;AAC5D,KAAI,aAAc,YAAW,aAAa,MAAM,UAAU;AAC1D,KAAI,OAAQ,YAAW,SAAS;AAChC,QAAO;;AAGT,SAAgB,2BACd,SACA,OACuB;CACvB,MAAM,OAAO,+BAA+B,QAAQ;AACpD,QAAO,+BAA+B;EACpC,GAAG;EACH,GAAG;EACH,UAAU;GACR,GAAG,KAAK;GACR,GAAG,MAAM;GACT,gBAAgB;IACd,GAAG,KAAK,SAAS;IACjB,GAAG,MAAM,UAAU;IACpB;GACF;EACD,cAAc;GACZ,GAAG,KAAK;GACR,GAAG,MAAM;GACT,MAAM;IACJ,GAAG,KAAK,aAAa;IACrB,GAAG,MAAM,cAAc;IACxB;GACD,KAAK;IACH,GAAG,KAAK,aAAa;IACrB,GAAG,MAAM,cAAc;IACxB;GACD,aAAa;IACX,GAAG,KAAK,aAAa;IACrB,GAAG,MAAM,cAAc;IACxB;GACD,QAAQ;IACN,GAAG,KAAK,aAAa;IACrB,GAAG,MAAM,cAAc;IACxB;GACD,OAAO;IACL,GAAG,KAAK,aAAa;IACrB,GAAG,MAAM,cAAc;IACxB;GACF;EACD,QAAQ,MAAM,WAAW,SAAY,KAAK,SAAS;GAAE,GAAG,KAAK;GAAQ,GAAG,MAAM;GAAQ;EACvF,CAAC;;AAGJ,SAAgB,0BAA0B,MAIvC;CACD,MAAM,UAAU,IAAI,IAAY,KAAK,OAAO,aAAa,IAAI,eAAe;AAE5E,KAAI,KAAK,OAAO,aAAa,OAAO,SAAS,QAAQ;EACnD,MAAM,UAAU,KAAK,OAAO,aAAa,OAAO,WAAW,EAAE;AAC7D,OAAK,MAAM,SAAS,KAAK,UAAU,EAAE,CACnC,MAAK,MAAM,UAAU,QAAQ,UAAU,EAAE,CACvC,SAAQ,IAAI,OAAO;;AAKzB,KAAI,KAAK,OAAO,aAAa,MAAM,SAAS,OAAO;EACjD,MAAM,UAAU,KAAK,OAAO,aAAa,MAAM,WAAW,EAAE;AAC5D,OAAK,MAAM,QAAQ,KAAK,SAAS,EAAE,CACjC,MAAK,MAAM,UAAU,QAAQ,SAAS,EAAE,CACtC,SAAQ,IAAI,OAAO;;AAKzB,QAAO,MAAM,KAAK,QAAQ"}
@@ -1,6 +1,6 @@
1
1
  import { ErrorCode } from "../../shared/codes.js";
2
2
  import { convexError } from "../errors.js";
3
- import { getGroupConnection, getScimConfigByConnection, getScimConfigByTokenHash, getScimIdentity, upsertScimConfig, upsertScimIdentity } from "../contract.js";
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: data.status ?? "active",
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, listReadyWebhookDeliveries, listWebhookDeliveries, listWebhookEndpoints, updateWebhookDelivery, updateWebhookEndpoint } from "../contract.js";
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
- list: async (ctx, data) => {
114
- return await listWebhookDeliveries(ctx, config.component.connection, data);
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