@robelest/convex-auth 0.0.4-preview.13 → 0.0.4-preview.16

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 (328) hide show
  1. package/README.md +140 -9
  2. package/dist/bin.cjs +5957 -5478
  3. package/dist/client/index.d.ts +3 -7
  4. package/dist/client/index.d.ts.map +1 -1
  5. package/dist/client/index.js +27 -26
  6. package/dist/client/index.js.map +1 -1
  7. package/dist/component/_generated/api.d.ts +14 -0
  8. package/dist/component/_generated/api.d.ts.map +1 -1
  9. package/dist/component/_generated/api.js.map +1 -1
  10. package/dist/component/_generated/component.d.ts +1672 -24
  11. package/dist/component/_generated/component.d.ts.map +1 -1
  12. package/dist/component/convex.config.d.ts +2 -2
  13. package/dist/component/convex.config.d.ts.map +1 -1
  14. package/dist/component/index.d.ts +1 -1
  15. package/dist/component/index.js +2 -2
  16. package/dist/component/model.d.ts +153 -0
  17. package/dist/component/model.d.ts.map +1 -0
  18. package/dist/component/model.js +343 -0
  19. package/dist/component/model.js.map +1 -0
  20. package/dist/component/providers/sso.d.ts +1 -1
  21. package/dist/component/public/enterprise.d.ts +54 -0
  22. package/dist/component/public/enterprise.d.ts.map +1 -0
  23. package/dist/component/public/enterprise.js +515 -0
  24. package/dist/component/public/enterprise.js.map +1 -0
  25. package/dist/component/public/factors.d.ts +52 -0
  26. package/dist/component/public/factors.d.ts.map +1 -0
  27. package/dist/component/public/factors.js +285 -0
  28. package/dist/component/public/factors.js.map +1 -0
  29. package/dist/component/public/groups.d.ts +116 -0
  30. package/dist/component/public/groups.d.ts.map +1 -0
  31. package/dist/component/public/groups.js +596 -0
  32. package/dist/component/public/groups.js.map +1 -0
  33. package/dist/component/public/identity.d.ts +93 -0
  34. package/dist/component/public/identity.d.ts.map +1 -0
  35. package/dist/component/public/identity.js +426 -0
  36. package/dist/component/public/identity.js.map +1 -0
  37. package/dist/component/public/keys.d.ts +41 -0
  38. package/dist/component/public/keys.d.ts.map +1 -0
  39. package/dist/component/public/keys.js +157 -0
  40. package/dist/component/public/keys.js.map +1 -0
  41. package/dist/component/public/shared.d.ts +26 -0
  42. package/dist/component/public/shared.d.ts.map +1 -0
  43. package/dist/component/public/shared.js +32 -0
  44. package/dist/component/public/shared.js.map +1 -0
  45. package/dist/component/public.d.ts +9 -321
  46. package/dist/component/public.d.ts.map +1 -1
  47. package/dist/component/public.js +6 -2145
  48. package/dist/component/schema.d.ts +406 -260
  49. package/dist/component/schema.js +37 -32
  50. package/dist/component/schema.js.map +1 -1
  51. package/dist/component/server/auth.d.ts +161 -15
  52. package/dist/component/server/auth.d.ts.map +1 -1
  53. package/dist/component/server/auth.js +100 -7
  54. package/dist/component/server/auth.js.map +1 -1
  55. package/dist/component/server/cookies.js +3 -0
  56. package/dist/component/server/cookies.js.map +1 -1
  57. package/dist/component/server/db.js +1 -0
  58. package/dist/component/server/db.js.map +1 -1
  59. package/dist/component/server/device.js +3 -1
  60. package/dist/component/server/device.js.map +1 -1
  61. package/dist/component/server/domains/core.js +629 -0
  62. package/dist/component/server/domains/core.js.map +1 -0
  63. package/dist/component/server/domains/sso.js +884 -0
  64. package/dist/component/server/domains/sso.js.map +1 -0
  65. package/dist/component/server/factory.d.ts +136 -0
  66. package/dist/component/server/factory.d.ts.map +1 -0
  67. package/dist/component/server/factory.js +1134 -0
  68. package/dist/component/server/factory.js.map +1 -0
  69. package/dist/component/server/fx.js +2 -1
  70. package/dist/component/server/fx.js.map +1 -1
  71. package/dist/component/server/http.js +287 -0
  72. package/dist/component/server/http.js.map +1 -0
  73. package/dist/component/server/identity.js +13 -0
  74. package/dist/component/server/identity.js.map +1 -0
  75. package/dist/component/server/keys.js +4 -0
  76. package/dist/component/server/keys.js.map +1 -1
  77. package/dist/component/server/mutations/account.js +1 -1
  78. package/dist/component/server/mutations/index.js +2 -2
  79. package/dist/component/server/mutations/index.js.map +1 -1
  80. package/dist/component/server/mutations/invalidate.js +1 -1
  81. package/dist/component/server/mutations/oauth.js +10 -7
  82. package/dist/component/server/mutations/oauth.js.map +1 -1
  83. package/dist/component/server/mutations/refresh.js +1 -1
  84. package/dist/component/server/mutations/register.js +1 -1
  85. package/dist/component/server/mutations/retrieve.js +1 -1
  86. package/dist/component/server/mutations/signature.js +1 -1
  87. package/dist/component/server/mutations/store.js +6 -3
  88. package/dist/component/server/mutations/store.js.map +1 -1
  89. package/dist/component/server/mutations/verify.js +1 -1
  90. package/dist/component/server/oauth.js +3 -0
  91. package/dist/component/server/oauth.js.map +1 -1
  92. package/dist/component/server/passkey.js +3 -2
  93. package/dist/component/server/passkey.js.map +1 -1
  94. package/dist/component/server/provider.js +2 -0
  95. package/dist/component/server/provider.js.map +1 -1
  96. package/dist/component/server/providers.js +10 -0
  97. package/dist/component/server/providers.js.map +1 -1
  98. package/dist/component/server/ratelimit.js +3 -0
  99. package/dist/component/server/ratelimit.js.map +1 -1
  100. package/dist/component/server/redirects.js +2 -0
  101. package/dist/component/server/redirects.js.map +1 -1
  102. package/dist/component/server/refresh.js +5 -0
  103. package/dist/component/server/refresh.js.map +1 -1
  104. package/dist/component/server/sessions.js +5 -0
  105. package/dist/component/server/sessions.js.map +1 -1
  106. package/dist/component/server/signin.js +2 -1
  107. package/dist/component/server/signin.js.map +1 -1
  108. package/dist/component/server/sso.js +166 -19
  109. package/dist/component/server/sso.js.map +1 -1
  110. package/dist/component/server/tokens.js +1 -0
  111. package/dist/component/server/tokens.js.map +1 -1
  112. package/dist/component/server/totp.js +4 -2
  113. package/dist/component/server/totp.js.map +1 -1
  114. package/dist/component/server/types.d.ts +106 -38
  115. package/dist/component/server/types.d.ts.map +1 -1
  116. package/dist/component/server/types.js.map +1 -1
  117. package/dist/component/server/users.js +1 -0
  118. package/dist/component/server/users.js.map +1 -1
  119. package/dist/component/server/utils.js +44 -2
  120. package/dist/component/server/utils.js.map +1 -1
  121. package/dist/providers/anonymous.d.ts +1 -1
  122. package/dist/providers/credentials.d.ts +1 -1
  123. package/dist/providers/password.d.ts +1 -1
  124. package/dist/providers/sso.d.ts +1 -1
  125. package/dist/providers/sso.js.map +1 -1
  126. package/dist/server/auth.d.ts +163 -17
  127. package/dist/server/auth.d.ts.map +1 -1
  128. package/dist/server/auth.js +100 -7
  129. package/dist/server/auth.js.map +1 -1
  130. package/dist/server/cookies.d.ts +1 -38
  131. package/dist/server/cookies.js +3 -0
  132. package/dist/server/cookies.js.map +1 -1
  133. package/dist/server/db.d.ts +1 -125
  134. package/dist/server/db.js +1 -0
  135. package/dist/server/db.js.map +1 -1
  136. package/dist/server/device.d.ts +1 -24
  137. package/dist/server/device.js +3 -1
  138. package/dist/server/device.js.map +1 -1
  139. package/dist/server/domains/core.d.ts +434 -0
  140. package/dist/server/domains/core.d.ts.map +1 -0
  141. package/dist/server/domains/core.js +629 -0
  142. package/dist/server/domains/core.js.map +1 -0
  143. package/dist/server/domains/sso.d.ts +409 -0
  144. package/dist/server/domains/sso.d.ts.map +1 -0
  145. package/dist/server/domains/sso.js +884 -0
  146. package/dist/server/domains/sso.js.map +1 -0
  147. package/dist/server/enterpriseValidators.d.ts +1 -0
  148. package/dist/server/enterpriseValidators.js +60 -0
  149. package/dist/server/enterpriseValidators.js.map +1 -0
  150. package/dist/server/factory.d.ts +136 -0
  151. package/dist/server/factory.d.ts.map +1 -0
  152. package/dist/server/factory.js +1134 -0
  153. package/dist/server/factory.js.map +1 -0
  154. package/dist/server/fx.d.ts +1 -16
  155. package/dist/server/fx.d.ts.map +1 -1
  156. package/dist/server/fx.js +1 -0
  157. package/dist/server/fx.js.map +1 -1
  158. package/dist/server/http.d.ts +59 -0
  159. package/dist/server/http.d.ts.map +1 -0
  160. package/dist/server/http.js +287 -0
  161. package/dist/server/http.js.map +1 -0
  162. package/dist/server/identity.d.ts +1 -0
  163. package/dist/server/identity.js +13 -0
  164. package/dist/server/identity.js.map +1 -0
  165. package/dist/server/index.d.ts +468 -1
  166. package/dist/server/index.d.ts.map +1 -1
  167. package/dist/server/index.js +530 -36
  168. package/dist/server/index.js.map +1 -1
  169. package/dist/server/keys.d.ts +1 -57
  170. package/dist/server/keys.js +4 -0
  171. package/dist/server/keys.js.map +1 -1
  172. package/dist/server/mutations/account.d.ts +7 -7
  173. package/dist/server/mutations/account.d.ts.map +1 -1
  174. package/dist/server/mutations/code.d.ts +13 -13
  175. package/dist/server/mutations/code.d.ts.map +1 -1
  176. package/dist/server/mutations/index.d.ts +107 -107
  177. package/dist/server/mutations/index.d.ts.map +1 -1
  178. package/dist/server/mutations/index.js +1 -1
  179. package/dist/server/mutations/index.js.map +1 -1
  180. package/dist/server/mutations/invalidate.d.ts +5 -5
  181. package/dist/server/mutations/invalidate.d.ts.map +1 -1
  182. package/dist/server/mutations/oauth.d.ts +10 -10
  183. package/dist/server/mutations/oauth.d.ts.map +1 -1
  184. package/dist/server/mutations/oauth.js +9 -6
  185. package/dist/server/mutations/oauth.js.map +1 -1
  186. package/dist/server/mutations/refresh.d.ts +4 -4
  187. package/dist/server/mutations/register.d.ts +12 -12
  188. package/dist/server/mutations/register.d.ts.map +1 -1
  189. package/dist/server/mutations/retrieve.d.ts +7 -7
  190. package/dist/server/mutations/signature.d.ts +5 -5
  191. package/dist/server/mutations/signin.d.ts +6 -6
  192. package/dist/server/mutations/signin.d.ts.map +1 -1
  193. package/dist/server/mutations/signout.d.ts +1 -1
  194. package/dist/server/mutations/store.d.ts +3 -2
  195. package/dist/server/mutations/store.d.ts.map +1 -1
  196. package/dist/server/mutations/store.js +6 -3
  197. package/dist/server/mutations/store.js.map +1 -1
  198. package/dist/server/mutations/verifier.d.ts +1 -1
  199. package/dist/server/mutations/verify.d.ts +11 -11
  200. package/dist/server/mutations/verify.d.ts.map +1 -1
  201. package/dist/server/oauth.d.ts +1 -59
  202. package/dist/server/oauth.js +3 -0
  203. package/dist/server/oauth.js.map +1 -1
  204. package/dist/server/passkey.d.ts.map +1 -1
  205. package/dist/server/passkey.js +3 -2
  206. package/dist/server/passkey.js.map +1 -1
  207. package/dist/server/provider.d.ts +1 -14
  208. package/dist/server/provider.d.ts.map +1 -1
  209. package/dist/server/provider.js +2 -0
  210. package/dist/server/provider.js.map +1 -1
  211. package/dist/server/providers.js +10 -0
  212. package/dist/server/providers.js.map +1 -1
  213. package/dist/server/ratelimit.d.ts +1 -22
  214. package/dist/server/ratelimit.js +3 -0
  215. package/dist/server/ratelimit.js.map +1 -1
  216. package/dist/server/redirects.d.ts +1 -10
  217. package/dist/server/redirects.js +2 -0
  218. package/dist/server/redirects.js.map +1 -1
  219. package/dist/server/refresh.d.ts +1 -37
  220. package/dist/server/refresh.js +5 -0
  221. package/dist/server/refresh.js.map +1 -1
  222. package/dist/server/sessions.d.ts +1 -28
  223. package/dist/server/sessions.js +5 -0
  224. package/dist/server/sessions.js.map +1 -1
  225. package/dist/server/signin.d.ts +1 -55
  226. package/dist/server/signin.js +2 -1
  227. package/dist/server/signin.js.map +1 -1
  228. package/dist/server/sso.d.ts +1 -348
  229. package/dist/server/sso.js +165 -18
  230. package/dist/server/sso.js.map +1 -1
  231. package/dist/server/templates.d.ts +1 -21
  232. package/dist/server/templates.js +1 -0
  233. package/dist/server/templates.js.map +1 -1
  234. package/dist/server/tokens.d.ts +1 -11
  235. package/dist/server/tokens.js +1 -0
  236. package/dist/server/tokens.js.map +1 -1
  237. package/dist/server/totp.d.ts +1 -23
  238. package/dist/server/totp.js +4 -2
  239. package/dist/server/totp.js.map +1 -1
  240. package/dist/server/types.d.ts +114 -77
  241. package/dist/server/types.d.ts.map +1 -1
  242. package/dist/server/types.js.map +1 -1
  243. package/dist/server/users.d.ts +1 -31
  244. package/dist/server/users.js +1 -0
  245. package/dist/server/users.js.map +1 -1
  246. package/dist/server/utils.d.ts +1 -27
  247. package/dist/server/utils.js +44 -2
  248. package/dist/server/utils.js.map +1 -1
  249. package/dist/server/version.d.ts +1 -1
  250. package/dist/server/version.js +1 -1
  251. package/dist/server/version.js.map +1 -1
  252. package/package.json +4 -5
  253. package/src/cli/bin.ts +5 -0
  254. package/src/cli/index.ts +22 -9
  255. package/src/cli/keys.ts +3 -0
  256. package/src/client/index.ts +36 -37
  257. package/src/component/_generated/api.ts +14 -0
  258. package/src/component/_generated/component.ts +2106 -9
  259. package/src/component/index.ts +3 -1
  260. package/src/component/model.ts +441 -0
  261. package/src/component/public/enterprise.ts +753 -0
  262. package/src/component/public/factors.ts +332 -0
  263. package/src/component/public/groups.ts +932 -0
  264. package/src/component/public/identity.ts +566 -0
  265. package/src/component/public/keys.ts +209 -0
  266. package/src/component/public/shared.ts +119 -0
  267. package/src/component/public.ts +5 -2965
  268. package/src/component/schema.ts +68 -63
  269. package/src/providers/sso.ts +1 -1
  270. package/src/server/auth.ts +413 -18
  271. package/src/server/cookies.ts +3 -0
  272. package/src/server/db.ts +3 -0
  273. package/src/server/device.ts +3 -1
  274. package/src/server/domains/core.ts +1071 -0
  275. package/src/server/domains/sso.ts +1749 -0
  276. package/src/server/enterpriseValidators.ts +93 -0
  277. package/src/server/factory.ts +2181 -0
  278. package/src/server/fx.ts +1 -0
  279. package/src/server/http.ts +529 -0
  280. package/src/server/identity.ts +18 -0
  281. package/src/server/index.ts +806 -40
  282. package/src/server/keys.ts +4 -0
  283. package/src/server/mutations/index.ts +1 -1
  284. package/src/server/mutations/oauth.ts +36 -8
  285. package/src/server/mutations/store.ts +6 -3
  286. package/src/server/oauth.ts +6 -0
  287. package/src/server/passkey.ts +3 -2
  288. package/src/server/provider.ts +2 -0
  289. package/src/server/providers.ts +20 -0
  290. package/src/server/ratelimit.ts +3 -0
  291. package/src/server/redirects.ts +2 -0
  292. package/src/server/refresh.ts +5 -0
  293. package/src/server/sessions.ts +5 -0
  294. package/src/server/signin.ts +1 -0
  295. package/src/server/sso.ts +259 -17
  296. package/src/server/templates.ts +1 -0
  297. package/src/server/tokens.ts +1 -0
  298. package/src/server/totp.ts +4 -2
  299. package/src/server/types.ts +178 -83
  300. package/src/server/users.ts +1 -0
  301. package/src/server/utils.ts +71 -1
  302. package/src/server/version.ts +1 -1
  303. package/dist/component/public.js.map +0 -1
  304. package/dist/component/server/implementation.d.ts +0 -1264
  305. package/dist/component/server/implementation.d.ts.map +0 -1
  306. package/dist/component/server/implementation.js +0 -2365
  307. package/dist/component/server/implementation.js.map +0 -1
  308. package/dist/server/cookies.d.ts.map +0 -1
  309. package/dist/server/db.d.ts.map +0 -1
  310. package/dist/server/device.d.ts.map +0 -1
  311. package/dist/server/implementation.d.ts +0 -1264
  312. package/dist/server/implementation.d.ts.map +0 -1
  313. package/dist/server/implementation.js +0 -2365
  314. package/dist/server/implementation.js.map +0 -1
  315. package/dist/server/keys.d.ts.map +0 -1
  316. package/dist/server/oauth.d.ts.map +0 -1
  317. package/dist/server/ratelimit.d.ts.map +0 -1
  318. package/dist/server/redirects.d.ts.map +0 -1
  319. package/dist/server/refresh.d.ts.map +0 -1
  320. package/dist/server/sessions.d.ts.map +0 -1
  321. package/dist/server/signin.d.ts.map +0 -1
  322. package/dist/server/sso.d.ts.map +0 -1
  323. package/dist/server/templates.d.ts.map +0 -1
  324. package/dist/server/tokens.d.ts.map +0 -1
  325. package/dist/server/totp.d.ts.map +0 -1
  326. package/dist/server/users.d.ts.map +0 -1
  327. package/dist/server/utils.d.ts.map +0 -1
  328. package/src/server/implementation.ts +0 -5336
@@ -0,0 +1,1134 @@
1
+ import { isAuthError } from "./errors.js";
2
+ import { AuthError, Fx } from "./fx.js";
3
+ import { LOG_LEVELS, decryptSecret, encryptSecret, generateRandomString, logError, logWithLevel, requireEnv, sha256 } from "./utils.js";
4
+ import { redirectToParamCookie, useRedirectToParam } from "./cookies.js";
5
+ import { configDefaults, listAvailableProviders } from "./providers.js";
6
+ import { callModifyAccount } from "./mutations/account.js";
7
+ import { callInvalidateSessions } from "./mutations/invalidate.js";
8
+ import { SCIM_GROUP_SCHEMA_ID, SCIM_USER_SCHEMA_ID, createEnterpriseOidcRuntime, createEnterpriseSamlMetadataXml, createEnterpriseSamlSignInRequest, createSamlPostBindingResponse, createServiceProviderMetadata, encodeEnterpriseSamlRelayState, enterpriseOidcProviderId, enterpriseSamlProviderId, getEnterpriseOidcUrls, getOidcConfig, getPublicOidcConfig, getSamlConfig, getSamlServiceProviderOptions, isEnterpriseSamlSourceActive, normalizeDomain, normalizeEnterprisePolicy, parseEnterpriseSamlLoginResponse, parseEnterpriseSamlLogoutMessage, parseSamlIdpMetadata, parseScimListRequest, parseScimPath, patchEnterprisePolicy, profileFromSamlExtract, scimError, scimJson, serializeScimGroup, serializeScimUser, upsertProtocolConfig, validateEnterpriseSamlLoginRelayState, withOidcSecretState } from "./sso.js";
9
+ import { callUserOAuth } from "./mutations/oauth.js";
10
+ import { callCreateAccountFromCredentials } from "./mutations/register.js";
11
+ import { callRetrieveAccountWithCredentials } from "./mutations/retrieve.js";
12
+ import { callVerifierSignature } from "./mutations/signature.js";
13
+ import { callSignOut } from "./mutations/signout.js";
14
+ import { storeArgs, storeImpl } from "./mutations/index.js";
15
+ import { redirectAbsoluteUrl, setURLSearchParam } from "./redirects.js";
16
+ import { signInImpl } from "./signin.js";
17
+ import { createCoreDomains } from "./domains/core.js";
18
+ import { createSsoDomain } from "./domains/sso.js";
19
+ import { addAuthRoutes, addOpenIdRoutes, addSSORoutes, convertErrorsToResponse, createHttpAction, createHttpRoute, getCookies } from "./http.js";
20
+ import { createOAuthAuthorizationURL, handleOAuthCallback } from "./oauth.js";
21
+ import { actionGeneric, internalMutationGeneric } from "convex/server";
22
+ import { v } from "convex/values";
23
+ import { serialize } from "cookie";
24
+
25
+ //#region src/server/factory.ts
26
+ const ENTERPRISE_OIDC_CLIENT_SECRET_KIND = "oidc_client_secret";
27
+ /**
28
+ * Configure the Convex Auth library. Returns an object with
29
+ * functions and `auth` helper. You must export the functions
30
+ * from `convex/auth.ts` to make them callable:
31
+ *
32
+ * ```ts filename="convex/auth.ts"
33
+ * import { createAuth } from "@robelest/convex-auth/component";
34
+ * import { components } from "./_generated/api";
35
+ *
36
+ * export const auth = createAuth(components.auth, {
37
+ * providers: [],
38
+ * });
39
+ * export const { signIn, signOut, store } = auth;
40
+ * ```
41
+ *
42
+ * @returns An object with fields you should reexport from your
43
+ * `convex/auth.ts` file.
44
+ */
45
+ function Auth(config_) {
46
+ const config = configDefaults(config_);
47
+ const hasOAuth = config.providers.some((provider) => provider.type === "oauth");
48
+ const hasSSO = config.providers.some((provider) => provider.type === "sso");
49
+ const getProviderOrThrow = (id, allowExtraProviders = false) => {
50
+ const provider = config.providers.find((configuredProvider) => configuredProvider.id === id) ?? (allowExtraProviders ? config.extraProviders.find((configuredProvider) => configuredProvider.id === id) : void 0);
51
+ if (provider === void 0) {
52
+ const detail = `Provider \`${id}\` is not configured, available providers are ${listAvailableProviders(config, allowExtraProviders)}.`;
53
+ logWithLevel(LOG_LEVELS.ERROR, detail);
54
+ throw new AuthError("PROVIDER_NOT_CONFIGURED", detail, { provider: id }).toConvexError();
55
+ }
56
+ return provider;
57
+ };
58
+ const getEnterpriseSecret = async (ctx, enterpriseId, kind) => {
59
+ return await ctx.runQuery(config.component.public.enterpriseSecretGet, {
60
+ enterpriseId,
61
+ kind
62
+ });
63
+ };
64
+ const getEnterpriseOidcConfigWithSecret = async (ctx, enterprise) => {
65
+ const oidc = getOidcConfig(enterprise.config);
66
+ const secret = await getEnterpriseSecret(ctx, enterprise._id, ENTERPRISE_OIDC_CLIENT_SECRET_KIND);
67
+ return {
68
+ ...oidc,
69
+ ...secret ? { clientSecret: await decryptSecret(secret.ciphertext) } : {}
70
+ };
71
+ };
72
+ const INVITE_TOKEN_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
73
+ const INVITE_TOKEN_LENGTH = 48;
74
+ const enterpriseNotFoundError = "Enterprise not found.";
75
+ const ENTERPRISE_CONTROL_ROUTE_BASE = "/api/auth/sso";
76
+ const getPolicyFromEnterprise = (enterprise) => normalizeEnterprisePolicy(enterprise.policy);
77
+ const loadEnterpriseOrThrow = async (ctx, enterpriseId) => {
78
+ const enterprise = await ctx.runQuery(config.component.public.enterpriseGet, { enterpriseId });
79
+ if (!enterprise) throw new AuthError("INVALID_PARAMETERS", enterpriseNotFoundError).toConvexError();
80
+ return enterprise;
81
+ };
82
+ const loadActiveEnterpriseOrThrow = async (ctx, enterpriseId) => {
83
+ const enterprise = await loadEnterpriseOrThrow(ctx, enterpriseId);
84
+ if (enterprise.status !== "active") throw new AuthError("INVALID_PARAMETERS", "Enterprise connection is not active.").toConvexError();
85
+ return enterprise;
86
+ };
87
+ const loadActiveEnterpriseSamlOrThrow = async (ctx, enterpriseId) => {
88
+ const enterprise = await loadEnterpriseOrThrow(ctx, enterpriseId);
89
+ const loaded = {
90
+ source: {
91
+ kind: "enterprise",
92
+ id: enterpriseId
93
+ },
94
+ config: enterprise.config,
95
+ status: enterprise.status,
96
+ enterprise
97
+ };
98
+ if (!isEnterpriseSamlSourceActive(loaded)) throw new AuthError("INVALID_PARAMETERS", "Enterprise connection is not active.").toConvexError();
99
+ const saml = getSamlConfig(loaded.config);
100
+ if (!saml.idp?.metadataXml) throw new AuthError("PROVIDER_NOT_CONFIGURED", "SAML is not configured for this enterprise.").toConvexError();
101
+ return {
102
+ loaded,
103
+ enterprise,
104
+ saml
105
+ };
106
+ };
107
+ const loadEnterpriseOidcOrThrow = async (ctx, enterpriseId) => {
108
+ const enterprise = await loadActiveEnterpriseOrThrow(ctx, enterpriseId);
109
+ const oidc = await getEnterpriseOidcConfigWithSecret(ctx, enterprise);
110
+ if (oidc.enabled !== true) throw new AuthError("PROVIDER_NOT_CONFIGURED", "OIDC is not configured for this enterprise.").toConvexError();
111
+ return {
112
+ enterprise,
113
+ oidc
114
+ };
115
+ };
116
+ const validateEnterprisePolicy = (policy) => {
117
+ const checks = [];
118
+ checks.push({
119
+ name: "policy_version",
120
+ ok: policy.version === 1
121
+ });
122
+ checks.push({
123
+ name: "jit_default_role_ids_present",
124
+ ok: policy.provisioning.jit.mode !== "createUserAndMembership" || policy.provisioning.jit.defaultRoleIds.length > 0,
125
+ message: policy.provisioning.jit.mode === "createUserAndMembership" && policy.provisioning.jit.defaultRoleIds.length === 0 ? "At least one default roleId is required when JIT membership provisioning is enabled." : void 0
126
+ });
127
+ checks.push({
128
+ name: "jit_default_role_ids_known",
129
+ ok: policy.provisioning.jit.defaultRoleIds.every((roleId) => config.authorization.roles[roleId] !== void 0),
130
+ message: policy.provisioning.jit.defaultRoleIds.every((roleId) => config.authorization.roles[roleId] !== void 0) ? void 0 : "JIT defaultRoleIds contains unknown roleIds."
131
+ });
132
+ checks.push({
133
+ name: "scim_reuse_supported",
134
+ ok: policy.provisioning.scimReuse.user === "externalId" || policy.provisioning.scimReuse.user === "none"
135
+ });
136
+ return checks;
137
+ };
138
+ const recordEnterpriseAuditEvent = async (ctx, data) => {
139
+ const { ok, ...rest } = data;
140
+ return await ctx.runMutation(config.component.public.enterpriseAuditEventCreate, {
141
+ ...rest,
142
+ status: ok ? "success" : "failure",
143
+ occurredAt: Date.now()
144
+ });
145
+ };
146
+ const emitEnterpriseWebhookDeliveries = async (ctx, data) => {
147
+ const endpoints = await ctx.runQuery(config.component.public.enterpriseWebhookEndpointList, { enterpriseId: data.enterpriseId });
148
+ for (const endpoint of endpoints) {
149
+ if (endpoint.status !== "active" || !endpoint.subscriptions.includes(data.eventType)) continue;
150
+ await ctx.runMutation(config.component.public.enterpriseWebhookDeliveryEnqueue, {
151
+ enterpriseId: data.enterpriseId,
152
+ endpointId: endpoint._id,
153
+ auditEventId: data.auditEventId,
154
+ eventType: data.eventType,
155
+ payload: data.payload,
156
+ nextAttemptAt: Date.now()
157
+ });
158
+ }
159
+ };
160
+ const getEnterpriseScimContext = async (ctx, request) => {
161
+ const authHeader = request.headers.get("Authorization");
162
+ if (!authHeader?.startsWith("Bearer ")) throw new AuthError("MISSING_BEARER_TOKEN").toConvexError();
163
+ const token = authHeader.slice(7);
164
+ const scimConfig = await ctx.runQuery(config.component.public.enterpriseScimConfigGetByTokenHash, { tokenHash: await sha256(token) });
165
+ if (!scimConfig || scimConfig.status !== "active") throw new AuthError("INVALID_API_KEY", "Invalid SCIM token.").toConvexError();
166
+ const parsedPath = parseScimPath(new URL(request.url).pathname);
167
+ if (parsedPath.enterpriseId !== scimConfig.enterpriseId) throw new AuthError("INVALID_API_KEY", "SCIM token/tenant mismatch.").toConvexError();
168
+ const enterprise = await ctx.runQuery(config.component.public.enterpriseGet, { enterpriseId: scimConfig.enterpriseId });
169
+ if (enterprise === null) throw new AuthError("INVALID_PARAMETERS", "Enterprise not found.").toConvexError();
170
+ return {
171
+ scimConfig,
172
+ enterprise,
173
+ parsedPath
174
+ };
175
+ };
176
+ const SCIM_SCHEMAS = [{
177
+ id: SCIM_USER_SCHEMA_ID,
178
+ name: "User",
179
+ description: "User Account",
180
+ attributes: [
181
+ {
182
+ name: "userName",
183
+ type: "string",
184
+ required: true
185
+ },
186
+ {
187
+ name: "displayName",
188
+ type: "string"
189
+ },
190
+ {
191
+ name: "active",
192
+ type: "boolean"
193
+ },
194
+ {
195
+ name: "emails",
196
+ type: "complex",
197
+ multiValued: true
198
+ }
199
+ ]
200
+ }, {
201
+ id: SCIM_GROUP_SCHEMA_ID,
202
+ name: "Group",
203
+ description: "Group",
204
+ attributes: [{
205
+ name: "displayName",
206
+ type: "string",
207
+ required: true
208
+ }, {
209
+ name: "members",
210
+ type: "complex",
211
+ multiValued: true
212
+ }]
213
+ }];
214
+ const SCIM_RESOURCE_TYPES = [{
215
+ id: "User",
216
+ name: "User",
217
+ endpoint: "/Users",
218
+ schema: SCIM_USER_SCHEMA_ID
219
+ }, {
220
+ id: "Group",
221
+ name: "Group",
222
+ endpoint: "/Groups",
223
+ schema: SCIM_GROUP_SCHEMA_ID
224
+ }];
225
+ const handleStaticScimCollection = (items, resourceId, opts) => {
226
+ if (resourceId !== void 0) {
227
+ const item = items.find((entry) => entry[opts.by] === decodeURIComponent(resourceId));
228
+ return item ? scimJson(item) : scimError(404, "notFound", opts.notFound);
229
+ }
230
+ return scimJson({
231
+ schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
232
+ Resources: items,
233
+ totalResults: items.length,
234
+ startIndex: 1,
235
+ itemsPerPage: items.length
236
+ });
237
+ };
238
+ const filterScimCollection = (items, filter, filters) => {
239
+ if (!filter) return items;
240
+ const predicate = filters[filter.attribute];
241
+ if (!predicate) throw new Error("Unsupported SCIM filter.");
242
+ return items.filter((item) => predicate(item, filter.value));
243
+ };
244
+ const paginateScimCollection = (items, listRequest) => {
245
+ const start = listRequest.startIndex - 1;
246
+ return items.slice(start, start + listRequest.count);
247
+ };
248
+ const requireScimResourceId = (resourceId, label) => {
249
+ if (!resourceId) return scimError(400, "invalidPath", `${label} resource ID is required.`);
250
+ return null;
251
+ };
252
+ const readScimJson = async (request) => await request.json();
253
+ let auth;
254
+ auth = {
255
+ ...createCoreDomains({
256
+ config,
257
+ getAuth: () => auth,
258
+ callInvalidateSessions,
259
+ callCreateAccountFromCredentials,
260
+ callRetrieveAccountWithCredentials,
261
+ callModifyAccount,
262
+ getEnrichCtx: () => enrichCtx,
263
+ inviteTokenAlphabet: INVITE_TOKEN_ALPHABET,
264
+ inviteTokenLength: INVITE_TOKEN_LENGTH
265
+ }),
266
+ sso: createSsoDomain({
267
+ config,
268
+ getAuth: () => auth,
269
+ normalizeEnterprisePolicy,
270
+ normalizeDomain,
271
+ getEnterpriseSecret,
272
+ loadEnterpriseOrThrow,
273
+ validateEnterprisePolicy,
274
+ recordEnterpriseAuditEvent,
275
+ emitEnterpriseWebhookDeliveries,
276
+ enterpriseNotFoundError,
277
+ ENTERPRISE_OIDC_CLIENT_SECRET_KIND,
278
+ requireEnv,
279
+ generateRandomString,
280
+ INVITE_TOKEN_ALPHABET,
281
+ sha256,
282
+ encryptSecret,
283
+ upsertProtocolConfig,
284
+ parseSamlIdpMetadata,
285
+ createServiceProviderMetadata,
286
+ getSamlServiceProviderOptions,
287
+ getPublicOidcConfig,
288
+ withOidcSecretState,
289
+ getOidcConfig,
290
+ getEnterpriseOidcUrls,
291
+ enterpriseOidcProviderId,
292
+ getPolicyFromEnterprise,
293
+ patchEnterprisePolicy
294
+ }),
295
+ http: {
296
+ add: (http) => {
297
+ addOpenIdRoutes(http, {
298
+ getIssuer: () => requireEnv("CONVEX_SITE_URL"),
299
+ getJwks: () => requireEnv("JWKS")
300
+ });
301
+ if (hasSSO) {
302
+ const handleSamlAcs = async (ctx, request, runtimeRoute) => Fx.run(Fx.gen(function* () {
303
+ yield* Fx.guard(runtimeRoute.protocol !== "saml" || runtimeRoute.rest.length !== 1 || runtimeRoute.rest[0] !== "acs", Fx.fail(new AuthError("INVALID_PARAMETERS", "Invalid enterprise runtime path.").toConvexError()));
304
+ const enterpriseId = runtimeRoute.enterpriseId;
305
+ const { loaded, enterprise, saml } = yield* Fx.from({
306
+ ok: () => loadActiveEnterpriseSamlOrThrow(ctx, enterpriseId),
307
+ err: (e) => e
308
+ });
309
+ const parsedResponse = yield* Fx.from({
310
+ ok: () => parseEnterpriseSamlLoginResponse({
311
+ request,
312
+ rootUrl: requireEnv("CONVEX_SITE_URL"),
313
+ source: {
314
+ kind: "enterprise",
315
+ id: enterprise._id
316
+ },
317
+ config: loaded.config
318
+ }),
319
+ err: (e) => new AuthError("OAUTH_PROVIDER_ERROR", `SAML response parse failed: ${e instanceof Error ? e.message : String(e)}`).toConvexError()
320
+ });
321
+ yield* Fx.from({
322
+ ok: () => {
323
+ validateEnterpriseSamlLoginRelayState({
324
+ relayState: parsedResponse.relayState,
325
+ source: {
326
+ kind: "enterprise",
327
+ id: enterprise._id
328
+ },
329
+ inResponseTo: parsedResponse.parsed.extract?.response?.inResponseTo
330
+ });
331
+ return Promise.resolve();
332
+ },
333
+ err: () => new AuthError("OAUTH_INVALID_STATE", "SAML RelayState did not match the pending login request.").toConvexError()
334
+ });
335
+ const { samlAttributes, samlSessionIndex, ...userProfile } = profileFromSamlExtract(parsedResponse.parsed.extract, saml.attributeMapping);
336
+ const profile = userProfile;
337
+ const maybeRedirectTo = useRedirectToParam(enterpriseSamlProviderId(enterprise._id), getCookies(request));
338
+ const verificationCode = yield* Fx.from({
339
+ ok: () => callUserOAuth(ctx, {
340
+ provider: enterpriseSamlProviderId(enterprise._id),
341
+ providerAccountId: profile.id,
342
+ profile,
343
+ signature: parsedResponse.relayState.signature,
344
+ accountExtend: {
345
+ identity: {
346
+ protocol: "saml",
347
+ enterpriseId: enterprise._id,
348
+ subject: profile.id,
349
+ entityId: typeof saml.entityId === "string" ? saml.entityId : void 0
350
+ },
351
+ saml: {
352
+ attributes: samlAttributes,
353
+ sessionIndex: samlSessionIndex
354
+ }
355
+ }
356
+ }),
357
+ err: (e) => e
358
+ });
359
+ const vurl = setURLSearchParam(yield* Fx.from({
360
+ ok: () => redirectAbsoluteUrl(config, { redirectTo: maybeRedirectTo?.redirectTo ?? (typeof parsedResponse.relayState.redirectTo === "string" ? parsedResponse.relayState.redirectTo : void 0) }),
361
+ err: (e) => e
362
+ }), "code", verificationCode);
363
+ const vheaders = new Headers({ Location: vurl });
364
+ vheaders.set("Cache-Control", "must-revalidate");
365
+ for (const { name, value, options } of maybeRedirectTo !== null ? [maybeRedirectTo.updatedCookie] : []) vheaders.append("Set-Cookie", serialize(name, value, options));
366
+ return new Response(null, {
367
+ status: 302,
368
+ headers: vheaders
369
+ });
370
+ }).pipe(Fx.recover((e) => Fx.fatal(e))));
371
+ const handleSamlSlo = async (ctx, request, runtimeRoute) => {
372
+ if (runtimeRoute.protocol !== "saml" || runtimeRoute.rest.length !== 1 || runtimeRoute.rest[0] !== "slo") throw new AuthError("INVALID_PARAMETERS", "Invalid enterprise runtime path.").toConvexError();
373
+ const { loaded, enterprise } = await loadActiveEnterpriseSamlOrThrow(ctx, runtimeRoute.enterpriseId);
374
+ const parsedMessage = await parseEnterpriseSamlLogoutMessage({
375
+ request,
376
+ rootUrl: requireEnv("CONVEX_SITE_URL"),
377
+ source: {
378
+ kind: "enterprise",
379
+ id: enterprise._id
380
+ },
381
+ config: loaded.config
382
+ });
383
+ if (parsedMessage.hasSamlRequest && parsedMessage.parsedRequest) {
384
+ const responseContext = parsedMessage.runtime.sp.createLogoutResponse(parsedMessage.runtime.idp, parsedMessage.parsedRequest.extract, parsedMessage.binding, parsedMessage.relayState ?? "");
385
+ if (parsedMessage.binding === "redirect") return new Response(null, {
386
+ status: 302,
387
+ headers: { Location: responseContext.context }
388
+ });
389
+ return createSamlPostBindingResponse({
390
+ endpoint: responseContext.entityEndpoint,
391
+ parameter: "SAMLResponse",
392
+ value: responseContext.context,
393
+ relayState: parsedMessage.relayState
394
+ });
395
+ }
396
+ if (parsedMessage.hasSamlResponse) return new Response(null, { status: 204 });
397
+ throw new AuthError("INVALID_PARAMETERS", "Missing SAML logout payload.").toConvexError();
398
+ };
399
+ const handleScimRequest = async (ctx, request) => {
400
+ try {
401
+ const { scimConfig, enterprise, parsedPath } = await getEnterpriseScimContext(ctx, request);
402
+ const state = {
403
+ ctx,
404
+ request,
405
+ url: new URL(request.url),
406
+ parsedPath,
407
+ enterprise,
408
+ scimConfig,
409
+ policy: getPolicyFromEnterprise(enterprise),
410
+ recordScimEvent: async (eventType, ok, subjectType, subjectId, metadata) => {
411
+ const auditEventId = await recordEnterpriseAuditEvent(ctx, {
412
+ enterpriseId: enterprise._id,
413
+ groupId: enterprise.groupId,
414
+ eventType,
415
+ actorType: "scim",
416
+ subjectType,
417
+ subjectId,
418
+ ok,
419
+ metadata
420
+ });
421
+ await emitEnterpriseWebhookDeliveries(ctx, {
422
+ enterpriseId: enterprise._id,
423
+ eventType,
424
+ auditEventId,
425
+ payload: {
426
+ enterpriseId: enterprise._id,
427
+ subjectId,
428
+ metadata
429
+ }
430
+ });
431
+ }
432
+ };
433
+ const handleUsersGet = async (state$1) => {
434
+ const members = await auth.member.list(state$1.ctx, {
435
+ where: { groupId: state$1.enterprise.groupId },
436
+ limit: 100
437
+ });
438
+ const identities = await state$1.ctx.runQuery(config.component.public.enterpriseScimIdentityListByEnterprise, { enterpriseId: state$1.enterprise._id });
439
+ const identityByUserId = new Map(identities.filter((identity) => identity.userId !== void 0).map((identity) => [identity.userId, identity]));
440
+ const users = (await Promise.all(members.items.map(async (member) => {
441
+ const user = await auth.user.get(state$1.ctx, member.userId);
442
+ return user ? {
443
+ user,
444
+ member,
445
+ identity: identityByUserId.get(user._id)
446
+ } : null;
447
+ }))).filter(Boolean);
448
+ const listRequest = parseScimListRequest(state$1.url);
449
+ const filtered = filterScimCollection(users, listRequest.filter, {
450
+ id: (item, value) => item.user._id === value,
451
+ externalId: (item, value) => item.identity?.externalId === value,
452
+ userName: (item, value) => item.user.email === value,
453
+ "emails.value": (item, value) => item.user.email === value,
454
+ active: (item, value) => String(item.identity?.active ?? item.member.status === "active") === value
455
+ });
456
+ if (state$1.parsedPath.resourceId) {
457
+ const resource = filtered.find(({ user }) => user._id === state$1.parsedPath.resourceId);
458
+ return resource ? scimJson(serializeScimUser({
459
+ id: resource.user._id,
460
+ user: resource.user,
461
+ externalId: resource.identity?.externalId,
462
+ location: `${state$1.url.origin}${state$1.url.pathname.replace(/\/[^/]+$/, "")}/${resource.user._id}`,
463
+ active: resource.identity?.active ?? resource.member.status === "active"
464
+ }), 200, { Location: `${state$1.url.origin}${state$1.url.pathname.replace(/\/[^/]+$/, "")}/${resource.user._id}` }) : scimError(404, "notFound", "User not found.");
465
+ }
466
+ const paged = paginateScimCollection(filtered, listRequest);
467
+ await state$1.recordScimEvent("enterprise.scim.read", true, "enterprise_scim", state$1.scimConfig._id);
468
+ return scimJson({
469
+ schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
470
+ Resources: paged.map(({ user, identity, member }) => serializeScimUser({
471
+ id: user._id,
472
+ user,
473
+ externalId: identity?.externalId,
474
+ location: `${state$1.url.origin}${state$1.url.pathname}/${user._id}`,
475
+ active: identity?.active ?? member.status === "active"
476
+ })),
477
+ totalResults: filtered.length,
478
+ startIndex: listRequest.startIndex,
479
+ itemsPerPage: paged.length
480
+ });
481
+ };
482
+ const handleUsersPost = async (state$1) => {
483
+ const body = await readScimJson(state$1.request);
484
+ const primaryEmail = Array.isArray(body.emails) ? body.emails.find((entry) => entry.primary === true)?.value ?? body.emails[0]?.value : void 0;
485
+ const phone = Array.isArray(body.phoneNumbers) ? body.phoneNumbers[0]?.value : void 0;
486
+ const userId = await state$1.ctx.runMutation(config.component.public.userInsert, { data: {
487
+ name: body.displayName ?? body.name?.formatted,
488
+ email: primaryEmail ?? body.userName,
489
+ ...typeof (primaryEmail ?? body.userName) === "string" ? { emailVerificationTime: Date.now() } : {},
490
+ phone,
491
+ ...typeof phone === "string" ? { phoneVerificationTime: Date.now() } : {}
492
+ } });
493
+ try {
494
+ await auth.member.create(state$1.ctx, {
495
+ groupId: state$1.enterprise.groupId,
496
+ userId,
497
+ roleIds: state$1.policy.provisioning.jit.defaultRoleIds,
498
+ status: body.active === false ? "inactive" : "active"
499
+ });
500
+ } catch {}
501
+ if (typeof body.externalId === "string") await state$1.ctx.runMutation(config.component.public.enterpriseScimIdentityUpsert, {
502
+ enterpriseId: state$1.enterprise._id,
503
+ groupId: state$1.enterprise.groupId,
504
+ resourceType: "user",
505
+ externalId: body.externalId,
506
+ userId,
507
+ active: body.active !== false,
508
+ raw: body,
509
+ lastProvisionedAt: Date.now()
510
+ });
511
+ await state$1.recordScimEvent("enterprise.scim.user.created", true, "user", userId);
512
+ const createdUser = await auth.user.get(state$1.ctx, userId);
513
+ const location = `${state$1.url.origin}${state$1.url.pathname}/${userId}`;
514
+ return scimJson(serializeScimUser({
515
+ id: userId,
516
+ user: createdUser ?? {},
517
+ externalId: body.externalId,
518
+ location,
519
+ active: body.active !== false
520
+ }), 201, { Location: location });
521
+ };
522
+ const handleUsersUpsert = async (state$1) => {
523
+ const missing = requireScimResourceId(state$1.parsedPath.resourceId, "User");
524
+ if (missing) return missing;
525
+ const userId = state$1.parsedPath.resourceId;
526
+ const existingUser = await auth.user.get(state$1.ctx, userId);
527
+ if (!existingUser) return scimError(404, "notFound", "User not found.");
528
+ const body = await readScimJson(state$1.request);
529
+ const patchData = {};
530
+ let nextActive;
531
+ if (state$1.request.method === "PUT") {
532
+ patchData.name = body.displayName ?? body.name?.formatted;
533
+ patchData.email = body.userName ?? (Array.isArray(body.emails) ? body.emails[0]?.value : void 0);
534
+ patchData.phone = Array.isArray(body.phoneNumbers) ? body.phoneNumbers[0]?.value : void 0;
535
+ if (typeof patchData.email === "string") patchData.emailVerificationTime = Date.now();
536
+ if (typeof patchData.phone === "string") patchData.phoneVerificationTime = Date.now();
537
+ } else for (const operation of Array.isArray(body.Operations) ? body.Operations : []) {
538
+ if (operation.path === "active") nextActive = operation.value;
539
+ if (operation.path === "displayName" || operation.path === "name.formatted") patchData.name = operation.value;
540
+ if (operation.path === "userName" || operation.path === "emails.value") {
541
+ patchData.email = operation.value;
542
+ if (typeof operation.value === "string") patchData.emailVerificationTime = Date.now();
543
+ }
544
+ if (operation.path === "phoneNumbers.value") {
545
+ patchData.phone = operation.value;
546
+ if (typeof operation.value === "string") patchData.phoneVerificationTime = Date.now();
547
+ }
548
+ }
549
+ await state$1.ctx.runMutation(config.component.public.userPatch, {
550
+ userId,
551
+ data: patchData
552
+ });
553
+ const membership = await auth.member.getByUserAndGroup(state$1.ctx, {
554
+ groupId: state$1.enterprise.groupId,
555
+ userId
556
+ });
557
+ if (membership) await auth.member.update(state$1.ctx, membership._id, { status: body.active === false || nextActive === false ? "inactive" : "active" });
558
+ await state$1.ctx.runMutation(config.component.public.enterpriseScimIdentityUpsert, {
559
+ enterpriseId: state$1.enterprise._id,
560
+ groupId: state$1.enterprise.groupId,
561
+ resourceType: "user",
562
+ externalId: typeof body.externalId === "string" ? body.externalId : (await state$1.ctx.runQuery(config.component.public.enterpriseScimIdentityGetByEnterpriseAndUser, {
563
+ enterpriseId: state$1.enterprise._id,
564
+ userId
565
+ }))?.externalId ?? userId,
566
+ userId,
567
+ active: body.active !== false && nextActive !== false,
568
+ raw: body,
569
+ lastProvisionedAt: Date.now()
570
+ });
571
+ await state$1.recordScimEvent("enterprise.scim.user.updated", true, "user", userId);
572
+ const updatedUser = await auth.user.get(state$1.ctx, userId);
573
+ const location = `${state$1.url.origin}${state$1.url.pathname}`;
574
+ return scimJson(serializeScimUser({
575
+ id: userId,
576
+ user: updatedUser ?? existingUser,
577
+ externalId: typeof body.externalId === "string" ? body.externalId : void 0,
578
+ location,
579
+ active: body.active !== false && nextActive !== false
580
+ }), 200, { Location: location });
581
+ };
582
+ const handleUsersDelete = async (state$1) => {
583
+ const missing = requireScimResourceId(state$1.parsedPath.resourceId, "User");
584
+ if (missing) return missing;
585
+ const userId = state$1.parsedPath.resourceId;
586
+ const membership = await auth.member.getByUserAndGroup(state$1.ctx, {
587
+ groupId: state$1.enterprise.groupId,
588
+ userId
589
+ });
590
+ if (membership) await auth.member.delete(state$1.ctx, membership._id);
591
+ const identity = await state$1.ctx.runQuery(config.component.public.enterpriseScimIdentityGetByEnterpriseAndUser, {
592
+ enterpriseId: state$1.enterprise._id,
593
+ userId
594
+ });
595
+ if (identity) if (state$1.policy.provisioning.deprovision.mode === "hard") await state$1.ctx.runMutation(config.component.public.enterpriseScimIdentityDelete, { identityId: identity._id });
596
+ else await state$1.ctx.runMutation(config.component.public.enterpriseScimIdentityUpsert, {
597
+ enterpriseId: identity.enterpriseId,
598
+ groupId: identity.groupId,
599
+ resourceType: identity.resourceType,
600
+ externalId: identity.externalId,
601
+ userId: identity.userId,
602
+ mappedGroupId: identity.mappedGroupId,
603
+ active: false,
604
+ raw: identity.raw,
605
+ lastProvisionedAt: Date.now()
606
+ });
607
+ await state$1.recordScimEvent("enterprise.scim.user.deleted", true, "user", userId);
608
+ return new Response(null, { status: 204 });
609
+ };
610
+ const handleGroupsGet = async (state$1) => {
611
+ const groupsList = await auth.group.list(state$1.ctx, {
612
+ where: { parentGroupId: state$1.enterprise.groupId },
613
+ limit: 100
614
+ });
615
+ const identities = await state$1.ctx.runQuery(config.component.public.enterpriseScimIdentityListByEnterprise, { enterpriseId: state$1.enterprise._id });
616
+ const identityByGroupId = new Map(identities.filter((identity) => identity.mappedGroupId !== void 0).map((identity) => [identity.mappedGroupId, identity]));
617
+ const groups = groupsList.items.map((group) => ({
618
+ group,
619
+ identity: identityByGroupId.get(group._id)
620
+ }));
621
+ const listRequest = parseScimListRequest(state$1.url);
622
+ const filtered = filterScimCollection(groups, listRequest.filter, {
623
+ id: (item, value) => item.group._id === value,
624
+ externalId: (item, value) => item.identity?.externalId === value,
625
+ displayName: (item, value) => item.group.name === value
626
+ });
627
+ if (state$1.parsedPath.resourceId) {
628
+ const resource = filtered.find(({ group }) => group._id === state$1.parsedPath.resourceId);
629
+ if (!resource) return scimError(404, "notFound", "Group not found.");
630
+ const members = (await auth.member.list(state$1.ctx, {
631
+ where: {
632
+ groupId: resource.group._id,
633
+ status: "active"
634
+ },
635
+ limit: 100
636
+ })).items.map((member) => ({ value: member.userId }));
637
+ const location = `${state$1.url.origin}${state$1.url.pathname.replace(/\/[^/]+$/, "")}/${resource.group._id}`;
638
+ return scimJson(serializeScimGroup({
639
+ id: resource.group._id,
640
+ group: resource.group,
641
+ externalId: resource.identity?.externalId,
642
+ location,
643
+ members
644
+ }), 200, { Location: location });
645
+ }
646
+ const paged = paginateScimCollection(filtered, listRequest);
647
+ return scimJson({
648
+ schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
649
+ Resources: paged.map(({ group, identity }) => serializeScimGroup({
650
+ id: group._id,
651
+ group,
652
+ externalId: identity?.externalId,
653
+ location: `${state$1.url.origin}${state$1.url.pathname}/${group._id}`
654
+ })),
655
+ totalResults: filtered.length,
656
+ startIndex: listRequest.startIndex,
657
+ itemsPerPage: paged.length
658
+ });
659
+ };
660
+ const handleGroupsPost = async (state$1) => {
661
+ const body = await readScimJson(state$1.request);
662
+ const { groupId } = await auth.group.create(state$1.ctx, {
663
+ name: String(body.displayName ?? "Group"),
664
+ parentGroupId: state$1.enterprise.groupId,
665
+ type: "organization"
666
+ });
667
+ await state$1.ctx.runMutation(config.component.public.enterpriseScimIdentityUpsert, {
668
+ enterpriseId: state$1.enterprise._id,
669
+ groupId: state$1.enterprise.groupId,
670
+ resourceType: "group",
671
+ externalId: body.externalId ?? groupId,
672
+ mappedGroupId: groupId,
673
+ active: true,
674
+ raw: body,
675
+ lastProvisionedAt: Date.now()
676
+ });
677
+ for (const member of Array.isArray(body.members) ? body.members : []) try {
678
+ await auth.member.create(state$1.ctx, {
679
+ groupId,
680
+ userId: String(member.value),
681
+ roleIds: state$1.policy.provisioning.jit.defaultRoleIds,
682
+ status: "active"
683
+ });
684
+ } catch {}
685
+ await state$1.recordScimEvent("enterprise.scim.group.created", true, "group", groupId);
686
+ const group = await auth.group.get(state$1.ctx, groupId);
687
+ const location = `${state$1.url.origin}${state$1.url.pathname}/${groupId}`;
688
+ return scimJson(serializeScimGroup({
689
+ id: groupId,
690
+ group: group ?? {},
691
+ externalId: body.externalId,
692
+ location,
693
+ members: (await auth.member.list(state$1.ctx, {
694
+ where: {
695
+ groupId,
696
+ status: "active"
697
+ },
698
+ limit: 100
699
+ })).items.map((member) => ({ value: member.userId }))
700
+ }), 201, { Location: location });
701
+ };
702
+ const handleGroupsPatch = async (state$1) => {
703
+ const missing = requireScimResourceId(state$1.parsedPath.resourceId, "Group");
704
+ if (missing) return missing;
705
+ const groupId = state$1.parsedPath.resourceId;
706
+ const body = await readScimJson(state$1.request);
707
+ for (const operation of Array.isArray(body.Operations) ? body.Operations : []) {
708
+ if (operation.path === "displayName") await auth.group.update(state$1.ctx, groupId, { name: operation.value });
709
+ if (operation.path === "members" && operation.op === "add") for (const member of Array.isArray(operation.value) ? operation.value : []) try {
710
+ await auth.member.create(state$1.ctx, {
711
+ groupId,
712
+ userId: String(member.value),
713
+ roleIds: state$1.policy.provisioning.jit.defaultRoleIds,
714
+ status: "active"
715
+ });
716
+ } catch {}
717
+ if (operation.path === "members" && operation.op === "replace") {
718
+ const currentMembers = (await auth.member.list(state$1.ctx, {
719
+ where: {
720
+ groupId,
721
+ status: "active"
722
+ },
723
+ limit: 100
724
+ })).items;
725
+ const currentUserIds = new Set(currentMembers.map((member) => member.userId));
726
+ const nextUserIds = new Set((Array.isArray(operation.value) ? operation.value : []).map((member) => String(member.value)));
727
+ for (const member of currentMembers) if (!nextUserIds.has(member.userId)) await auth.member.delete(state$1.ctx, member._id);
728
+ for (const userId of nextUserIds.values()) if (!currentUserIds.has(userId)) try {
729
+ await auth.member.create(state$1.ctx, {
730
+ groupId,
731
+ userId,
732
+ roleIds: state$1.policy.provisioning.jit.defaultRoleIds,
733
+ status: "active"
734
+ });
735
+ } catch {}
736
+ }
737
+ if (typeof operation.path === "string" && operation.op === "remove" && operation.path.startsWith("members[")) {
738
+ const userId = operation.path.match(/^members\[value eq "([^"]+)"\]$/)?.[1];
739
+ if (userId) {
740
+ const membership = await auth.member.getByUserAndGroup(state$1.ctx, {
741
+ groupId,
742
+ userId
743
+ });
744
+ if (membership) await auth.member.delete(state$1.ctx, membership._id);
745
+ }
746
+ }
747
+ }
748
+ await state$1.recordScimEvent("enterprise.scim.group.updated", true, "group", groupId);
749
+ const group = await auth.group.get(state$1.ctx, groupId);
750
+ const location = `${state$1.url.origin}${state$1.url.pathname}`;
751
+ const members = (await auth.member.list(state$1.ctx, {
752
+ where: {
753
+ groupId,
754
+ status: "active"
755
+ },
756
+ limit: 100
757
+ })).items;
758
+ return scimJson(serializeScimGroup({
759
+ id: groupId,
760
+ group: group ?? {},
761
+ location,
762
+ members: members.map((member) => ({ value: member.userId }))
763
+ }), 200, { Location: location });
764
+ };
765
+ const handleGroupsDelete = async (state$1) => {
766
+ const missing = requireScimResourceId(state$1.parsedPath.resourceId, "Group");
767
+ if (missing) return missing;
768
+ const groupId = state$1.parsedPath.resourceId;
769
+ await auth.group.delete(state$1.ctx, groupId);
770
+ const identity = await state$1.ctx.runQuery(config.component.public.enterpriseScimIdentityGetByMappedGroup, { mappedGroupId: groupId });
771
+ if (identity) await state$1.ctx.runMutation(config.component.public.enterpriseScimIdentityDelete, { identityId: identity._id });
772
+ await state$1.recordScimEvent("enterprise.scim.group.deleted", true, "group", groupId);
773
+ return new Response(null, { status: 204 });
774
+ };
775
+ const handler = {
776
+ ServiceProviderConfig: { GET: async () => scimJson({
777
+ schemas: ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
778
+ patch: { supported: true },
779
+ bulk: {
780
+ supported: false,
781
+ maxOperations: 0,
782
+ maxPayloadSize: 0
783
+ },
784
+ filter: {
785
+ supported: true,
786
+ maxResults: 100
787
+ },
788
+ changePassword: { supported: false },
789
+ sort: { supported: false },
790
+ etag: { supported: false },
791
+ authenticationSchemes: [{
792
+ type: "oauthbearertoken",
793
+ name: "Bearer Token",
794
+ description: "Use the SCIM token generated by Convex Auth enterprise."
795
+ }]
796
+ }) },
797
+ Schemas: { GET: async (state$1) => handleStaticScimCollection(SCIM_SCHEMAS, state$1.parsedPath.resourceId, {
798
+ by: "id",
799
+ notFound: "Schema not found."
800
+ }) },
801
+ ResourceTypes: { GET: async (state$1) => handleStaticScimCollection(SCIM_RESOURCE_TYPES, state$1.parsedPath.resourceId, {
802
+ by: "name",
803
+ notFound: "Resource type not found."
804
+ }) },
805
+ Users: {
806
+ GET: handleUsersGet,
807
+ POST: handleUsersPost,
808
+ PATCH: handleUsersUpsert,
809
+ PUT: handleUsersUpsert,
810
+ DELETE: handleUsersDelete
811
+ },
812
+ Groups: {
813
+ GET: handleGroupsGet,
814
+ POST: handleGroupsPost,
815
+ PATCH: handleGroupsPatch,
816
+ DELETE: handleGroupsDelete
817
+ }
818
+ }[state.parsedPath.resource]?.[state.request.method];
819
+ return handler ? await handler(state) : scimError(404, "notFound", "SCIM resource not found.");
820
+ } catch (error) {
821
+ if (error instanceof Error && error.message === "Unsupported SCIM filter.") return scimError(400, "invalidFilter", error.message);
822
+ if (isAuthError(error)) {
823
+ const code = error.data.code;
824
+ return scimError(code === "MISSING_BEARER_TOKEN" || code === "INVALID_API_KEY" ? 401 : 400, code, error.data.message);
825
+ }
826
+ throw error;
827
+ }
828
+ };
829
+ addSSORoutes(http, {
830
+ routeBase: ENTERPRISE_CONTROL_ROUTE_BASE,
831
+ convertErrorsToResponse,
832
+ handleSamlMetadata: async (ctx, _request, runtimeRoute) => {
833
+ const { loaded } = await loadActiveEnterpriseSamlOrThrow(ctx, runtimeRoute.enterpriseId);
834
+ return new Response(createEnterpriseSamlMetadataXml({
835
+ rootUrl: requireEnv("CONVEX_SITE_URL"),
836
+ source: loaded.source,
837
+ config: loaded.config
838
+ }), {
839
+ status: 200,
840
+ headers: { "Content-Type": "application/xml" }
841
+ });
842
+ },
843
+ handleSamlSignIn: async (ctx, request, runtimeRoute) => {
844
+ const url = new URL(request.url);
845
+ const verifier = url.searchParams.get("code");
846
+ if (!verifier) throw new AuthError("OAUTH_MISSING_VERIFIER").toConvexError();
847
+ const { loaded, enterprise } = await loadActiveEnterpriseSamlOrThrow(ctx, runtimeRoute.enterpriseId);
848
+ const state = generateRandomString(24, INVITE_TOKEN_ALPHABET);
849
+ const signInRequest = createEnterpriseSamlSignInRequest({
850
+ rootUrl: requireEnv("CONVEX_SITE_URL"),
851
+ source: {
852
+ kind: "enterprise",
853
+ id: enterprise._id
854
+ },
855
+ config: loaded.config,
856
+ state,
857
+ signature: `saml ${enterprise._id} pending ${state}`,
858
+ redirectTo: url.searchParams.get("redirectTo") ?? void 0
859
+ });
860
+ const signature = `saml ${enterprise._id} ${signInRequest.requestId} ${state}`;
861
+ await callVerifierSignature(ctx, {
862
+ verifier,
863
+ signature
864
+ });
865
+ const redirectTo = url.searchParams.get("redirectTo");
866
+ const redirectCookies = redirectTo !== null ? [redirectToParamCookie(enterpriseSamlProviderId(enterprise._id), redirectTo)] : [];
867
+ const relayState = encodeEnterpriseSamlRelayState({
868
+ source: {
869
+ kind: "enterprise",
870
+ id: enterprise._id
871
+ },
872
+ signature,
873
+ requestId: signInRequest.requestId,
874
+ state,
875
+ redirectTo: url.searchParams.get("redirectTo") ?? void 0
876
+ });
877
+ if (signInRequest.binding === "redirect" && signInRequest.redirectUrl) {
878
+ const redirectUrl = new URL(signInRequest.redirectUrl);
879
+ redirectUrl.searchParams.set("RelayState", relayState);
880
+ const headers = new Headers({ Location: redirectUrl.toString() });
881
+ for (const { name, value, options } of redirectCookies) headers.append("Set-Cookie", serialize(name, value, options));
882
+ return new Response(null, {
883
+ status: 302,
884
+ headers
885
+ });
886
+ }
887
+ const response = createSamlPostBindingResponse({
888
+ endpoint: signInRequest.post.endpoint,
889
+ parameter: "SAMLRequest",
890
+ value: signInRequest.post.value,
891
+ relayState
892
+ });
893
+ for (const { name, value, options } of redirectCookies) response.headers.append("Set-Cookie", serialize(name, value, options));
894
+ return response;
895
+ },
896
+ handleOidcSignIn: async (ctx, request, runtimeRoute) => {
897
+ const url = new URL(request.url);
898
+ const verifier = url.searchParams.get("code");
899
+ if (!verifier) throw new AuthError("OAUTH_MISSING_VERIFIER").toConvexError();
900
+ const { enterprise, oidc } = await loadEnterpriseOidcOrThrow(ctx, runtimeRoute.enterpriseId);
901
+ const { providerId, provider, oauthConfig } = await createEnterpriseOidcRuntime({
902
+ rootUrl: requireEnv("CONVEX_SITE_URL"),
903
+ enterpriseId: enterprise._id,
904
+ oidc
905
+ });
906
+ const { redirect, cookies, signature } = await createOAuthAuthorizationURL(providerId, provider, oauthConfig);
907
+ await callVerifierSignature(ctx, {
908
+ verifier,
909
+ signature
910
+ });
911
+ const redirectTo = url.searchParams.get("redirectTo");
912
+ const headers_ = new Headers({ Location: redirect });
913
+ for (const { name, value, options } of [...cookies, ...redirectTo !== null ? [redirectToParamCookie(providerId, redirectTo)] : []]) headers_.append("Set-Cookie", serialize(name, value, options));
914
+ return new Response(null, {
915
+ status: 302,
916
+ headers: headers_
917
+ });
918
+ },
919
+ handleOidcCallback: async (ctx, request, runtimeRoute) => {
920
+ const url = new URL(request.url);
921
+ const { enterprise, oidc } = await loadEnterpriseOidcOrThrow(ctx, runtimeRoute.enterpriseId);
922
+ const { providerId, provider, oauthConfig } = await createEnterpriseOidcRuntime({
923
+ rootUrl: requireEnv("CONVEX_SITE_URL"),
924
+ enterpriseId: enterprise._id,
925
+ oidc
926
+ });
927
+ const cookies = getCookies(request);
928
+ const maybeRedirectTo = useRedirectToParam(providerId, cookies);
929
+ const destinationUrl = await redirectAbsoluteUrl(config, { redirectTo: maybeRedirectTo?.redirectTo });
930
+ const params = url.searchParams;
931
+ const result = await Fx.run(handleOAuthCallback(providerId, provider, oauthConfig, Object.fromEntries(params.entries()), cookies));
932
+ const extraFields = oidc.extraFields;
933
+ let profile = result.profile;
934
+ if (extraFields && typeof profile === "object" && profile) {
935
+ const extend = {};
936
+ for (const [claimName, fieldName] of Object.entries(extraFields)) if (claimName in profile) extend[fieldName] = profile[claimName];
937
+ if (Object.keys(extend).length > 0) profile = {
938
+ ...profile,
939
+ extend
940
+ };
941
+ }
942
+ const verificationCode = await callUserOAuth(ctx, {
943
+ provider: providerId,
944
+ providerAccountId: result.providerAccountId,
945
+ profile,
946
+ signature: result.signature,
947
+ accountExtend: { identity: {
948
+ protocol: "oidc",
949
+ enterpriseId: enterprise._id,
950
+ subject: result.providerAccountId,
951
+ issuer: typeof oidc.issuer === "string" ? oidc.issuer : void 0,
952
+ discoveryUrl: typeof oidc.discoveryUrl === "string" ? oidc.discoveryUrl : void 0
953
+ } }
954
+ });
955
+ const headers = new Headers({ Location: setURLSearchParam(destinationUrl, "code", verificationCode) });
956
+ for (const { name, value, options } of result.cookies) headers.append("Set-Cookie", serialize(name, value, options));
957
+ if (maybeRedirectTo) headers.append("Set-Cookie", serialize(maybeRedirectTo.updatedCookie.name, maybeRedirectTo.updatedCookie.value, maybeRedirectTo.updatedCookie.options));
958
+ return new Response(null, {
959
+ status: 302,
960
+ headers
961
+ });
962
+ },
963
+ handleSamlAcs,
964
+ handleSamlSlo,
965
+ handleScimRequest,
966
+ scimError
967
+ });
968
+ }
969
+ if (hasOAuth) addAuthRoutes(http, {
970
+ handleSignIn: convertErrorsToResponse(400, async (ctx, request) => {
971
+ const url = new URL(request.url);
972
+ const providerId = url.pathname.split("/").at(-1);
973
+ if (providerId === null) throw new AuthError("OAUTH_MISSING_PROVIDER").toConvexError();
974
+ const verifier = url.searchParams.get("code");
975
+ if (verifier === null) throw new AuthError("OAUTH_MISSING_VERIFIER").toConvexError();
976
+ const oauthConfig = getProviderOrThrow(providerId);
977
+ const { redirect, cookies, signature } = await createOAuthAuthorizationURL(providerId, oauthConfig.provider, oauthConfig);
978
+ await callVerifierSignature(ctx, {
979
+ verifier,
980
+ signature
981
+ });
982
+ const redirectTo = url.searchParams.get("redirectTo");
983
+ if (redirectTo !== null) cookies.push(redirectToParamCookie(providerId, redirectTo));
984
+ const headers = new Headers({ Location: redirect });
985
+ for (const { name, value, options } of cookies) headers.append("Set-Cookie", serialize(name, value, options));
986
+ return new Response(null, {
987
+ status: 302,
988
+ headers
989
+ });
990
+ }),
991
+ handleCallback: async (ctx, request) => {
992
+ const url = new URL(request.url);
993
+ const providerId = new URL(request.url).pathname.split("/").at(-1);
994
+ if (!providerId) throw new AuthError("OAUTH_MISSING_PROVIDER").toConvexError();
995
+ logWithLevel(LOG_LEVELS.DEBUG, "Handling OAuth callback for provider:", providerId);
996
+ const provider = getProviderOrThrow(providerId);
997
+ const cookies = getCookies(request);
998
+ const maybeRedirectTo = useRedirectToParam(provider.id, cookies);
999
+ const destinationUrl = await redirectAbsoluteUrl(config, { redirectTo: maybeRedirectTo?.redirectTo });
1000
+ const params = url.searchParams;
1001
+ if (request.headers.get("Content-Type") === "application/x-www-form-urlencoded") (await request.formData()).forEach((value, key) => {
1002
+ if (typeof value === "string") params.append(key, value);
1003
+ });
1004
+ return Fx.run(Fx.from({
1005
+ ok: async () => {
1006
+ const oauthConfig = provider;
1007
+ const result = await Fx.run(handleOAuthCallback(providerId, oauthConfig.provider, oauthConfig, Object.fromEntries(params.entries()), cookies));
1008
+ const oauthCookies = result.cookies;
1009
+ const { id: profileId, ...profileData } = result.profile;
1010
+ const { signature } = result;
1011
+ const redirUrl = setURLSearchParam(destinationUrl, "code", await callUserOAuth(ctx, {
1012
+ provider: providerId,
1013
+ providerAccountId: profileId,
1014
+ profile: profileData,
1015
+ signature
1016
+ }));
1017
+ const redirHeaders = new Headers({ Location: redirUrl });
1018
+ redirHeaders.set("Cache-Control", "must-revalidate");
1019
+ for (const { name, value, options } of [...oauthCookies, ...maybeRedirectTo !== null ? [maybeRedirectTo.updatedCookie] : []]) redirHeaders.append("Set-Cookie", serialize(name, value, options));
1020
+ return new Response(null, {
1021
+ status: 302,
1022
+ headers: redirHeaders
1023
+ });
1024
+ },
1025
+ err: (error) => error
1026
+ }).pipe(Fx.recover((error) => {
1027
+ logError(error);
1028
+ const respHeaders = new Headers({ Location: destinationUrl });
1029
+ for (const { name, value, options } of maybeRedirectTo !== null ? [maybeRedirectTo.updatedCookie] : []) respHeaders.append("Set-Cookie", serialize(name, value, options));
1030
+ return Fx.succeed(new Response(null, {
1031
+ status: 302,
1032
+ headers: respHeaders
1033
+ }));
1034
+ })));
1035
+ }
1036
+ });
1037
+ },
1038
+ action: createHttpAction(auth),
1039
+ route: createHttpRoute(createHttpAction(auth))
1040
+ }
1041
+ };
1042
+ const enrichCtx = (ctx) => ({
1043
+ ...ctx,
1044
+ auth: {
1045
+ ...ctx.auth,
1046
+ config,
1047
+ account: auth.account,
1048
+ session: auth.session,
1049
+ access: auth.access,
1050
+ provider: auth.provider
1051
+ }
1052
+ });
1053
+ return {
1054
+ auth,
1055
+ signIn: actionGeneric({
1056
+ args: {
1057
+ provider: v.optional(v.string()),
1058
+ params: v.optional(v.any()),
1059
+ verifier: v.optional(v.string()),
1060
+ refreshToken: v.optional(v.string()),
1061
+ calledBy: v.optional(v.string())
1062
+ },
1063
+ handler: async (ctx, args) => {
1064
+ if (args.calledBy !== void 0) logWithLevel("INFO", `\`auth:signIn\` called by ${args.calledBy}`);
1065
+ const provider = args.provider !== void 0 ? getProviderOrThrow(args.provider) : null;
1066
+ const result = await signInImpl(enrichCtx(ctx), provider, args, {
1067
+ generateTokens: true,
1068
+ allowExtraProviders: false
1069
+ });
1070
+ return Fx.run(Fx.match(result, result.kind, {
1071
+ redirect: (r) => Fx.succeed({
1072
+ kind: "redirect",
1073
+ redirect: r.redirect,
1074
+ verifier: r.verifier
1075
+ }),
1076
+ signedIn: (r) => Fx.succeed({
1077
+ kind: "signedIn",
1078
+ tokens: r.signedIn?.tokens ?? null
1079
+ }),
1080
+ refreshTokens: (r) => Fx.succeed({
1081
+ kind: "signedIn",
1082
+ tokens: r.signedIn?.tokens ?? null
1083
+ }),
1084
+ started: () => Fx.succeed({ kind: "started" }),
1085
+ passkeyOptions: (r) => Fx.succeed({
1086
+ kind: "passkeyOptions",
1087
+ options: r.options,
1088
+ verifier: r.verifier
1089
+ }),
1090
+ totpRequired: (r) => Fx.succeed({
1091
+ kind: "totpRequired",
1092
+ verifier: r.verifier
1093
+ }),
1094
+ totpSetup: (r) => Fx.succeed({
1095
+ kind: "totpSetup",
1096
+ totpSetup: {
1097
+ uri: r.uri,
1098
+ secret: r.secret,
1099
+ totpId: r.totpId
1100
+ },
1101
+ verifier: r.verifier
1102
+ }),
1103
+ deviceCode: (r) => Fx.succeed({
1104
+ kind: "deviceCode",
1105
+ deviceCode: {
1106
+ deviceCode: r.deviceCode,
1107
+ userCode: r.userCode,
1108
+ verificationUri: r.verificationUri,
1109
+ verificationUriComplete: r.verificationUriComplete,
1110
+ expiresIn: r.expiresIn,
1111
+ interval: r.interval
1112
+ }
1113
+ })
1114
+ }));
1115
+ }
1116
+ }),
1117
+ signOut: actionGeneric({
1118
+ args: {},
1119
+ handler: async (ctx) => {
1120
+ await callSignOut(ctx);
1121
+ }
1122
+ }),
1123
+ store: internalMutationGeneric({
1124
+ args: storeArgs,
1125
+ handler: async (ctx, args) => {
1126
+ return storeImpl(ctx, args, getProviderOrThrow, config);
1127
+ }
1128
+ })
1129
+ };
1130
+ }
1131
+
1132
+ //#endregion
1133
+ export { Auth };
1134
+ //# sourceMappingURL=factory.js.map