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

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