@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
@@ -1 +1 @@
1
- {"version":3,"file":"refresh.js","names":[],"sources":["../../src/server/refresh.ts"],"sourcesContent":["import { Fx } from \"@robelest/fx\";\nimport { GenericId } from \"convex/values\";\n\nimport { authDb } from \"./db\";\nimport { AuthError } from \"./fx\";\nimport { Doc, MutationCtx } from \"./types\";\nimport { ConvexAuthConfig } from \"./types\";\nimport {\n LOG_LEVELS,\n REFRESH_TOKEN_DIVIDER,\n logWithLevel,\n maybeRedact,\n} from \"./utils\";\n\nconst DEFAULT_SESSION_INACTIVE_DURATION_MS = 1000 * 60 * 60 * 24 * 30; // 30 days\nexport const REFRESH_TOKEN_REUSE_WINDOW_MS = 10 * 1000; // 10 seconds\n\n// ---------------------------------------------------------------------------\n// Refresh token CRUD\n// ---------------------------------------------------------------------------\n\n/**\n * Create a new refresh token for the given session.\n */\nexport async function createRefreshToken(\n ctx: MutationCtx,\n config: ConvexAuthConfig,\n sessionId: GenericId<\"Session\">,\n parentRefreshTokenId: GenericId<\"RefreshToken\"> | null,\n): Promise<GenericId<\"RefreshToken\">> {\n const expirationTime =\n Date.now() +\n (config.session?.inactiveDurationMs ??\n (process.env.AUTH_SESSION_INACTIVE_DURATION_MS !== undefined\n ? Number(process.env.AUTH_SESSION_INACTIVE_DURATION_MS)\n : undefined) ??\n DEFAULT_SESSION_INACTIVE_DURATION_MS);\n\n return authDb(ctx, config).refreshTokens.create({\n sessionId,\n expirationTime,\n parentRefreshTokenId: parentRefreshTokenId ?? undefined,\n }) as Promise<GenericId<\"RefreshToken\">>;\n}\n\n/**\n * Parse a compound refresh token string into its constituent IDs.\n */\nexport const parseRefreshToken = (\n refreshToken: string,\n): Fx<\n {\n refreshTokenId: GenericId<\"RefreshToken\">;\n sessionId: GenericId<\"Session\">;\n },\n AuthError\n> => {\n const [refreshTokenId, sessionId] = refreshToken.split(REFRESH_TOKEN_DIVIDER);\n const msg = `Can't parse refresh token: ${maybeRedact(refreshToken)}`;\n const refreshTokenIdFx: Fx<string, AuthError> =\n refreshTokenId != null\n ? Fx.succeed(refreshTokenId)\n : Fx.fail(new AuthError(\"INVALID_REFRESH_TOKEN\", msg));\n\n return refreshTokenIdFx.pipe(\n Fx.chain((rtId) => {\n const sessionIdFx: Fx<string, AuthError> =\n sessionId != null\n ? Fx.succeed(sessionId)\n : Fx.fail(new AuthError(\"INVALID_REFRESH_TOKEN\", msg));\n return sessionIdFx.pipe(\n Fx.map((sId) => ({\n refreshTokenId: rtId as GenericId<\"RefreshToken\">,\n sessionId: sId as GenericId<\"Session\">,\n })),\n );\n }),\n );\n};\n\n/**\n * Mark all refresh tokens descending from the given refresh token as invalid\n * immediately. Used when we detect token reuse — revoke the entire tree.\n */\nexport async function invalidateRefreshTokensInSubtree(\n ctx: MutationCtx,\n refreshToken: Doc<\"RefreshToken\">,\n config: ConvexAuthConfig,\n) {\n const db = authDb(ctx, config);\n const tokensToInvalidate = [refreshToken];\n const visited = new Set<GenericId<\"RefreshToken\">>([refreshToken._id]);\n let frontier: GenericId<\"RefreshToken\">[] = [refreshToken._id];\n while (frontier.length > 0) {\n const nextFrontier: GenericId<\"RefreshToken\">[] = [];\n for (const currentTokenId of frontier) {\n const children = (await db.refreshTokens.getChildren(\n refreshToken.sessionId,\n currentTokenId,\n )) as Doc<\"RefreshToken\">[];\n for (const child of children) {\n if (visited.has(child._id)) continue;\n visited.add(child._id);\n tokensToInvalidate.push(child);\n nextFrontier.push(child._id);\n }\n }\n frontier = nextFrontier;\n }\n await Fx.run(\n Fx.each(tokensToInvalidate, (token) =>\n token.firstUsedTime === undefined ||\n token.firstUsedTime > Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS\n ? Fx.from({\n ok: () =>\n db.refreshTokens.patch(token._id, {\n firstUsedTime: Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS,\n }),\n err: (e) => e as never,\n })\n : Fx.unit,\n ),\n );\n return tokensToInvalidate;\n}\n\n// ---------------------------------------------------------------------------\n// Validation pipeline — the core of refresh token handling\n// ---------------------------------------------------------------------------\n\n/**\n * Validate a refresh token and its associated session.\n *\n * Returns `null` on any validation failure (matching original semantics).\n * Each validation step is a small composable function chained with `Fx.chain`.\n * On failure, the error message is logged and the pipeline folds to `null`.\n */\nexport const refreshTokenIfValid = (\n ctx: MutationCtx,\n refreshTokenId: string,\n tokenSessionId: string,\n config: ConvexAuthConfig,\n): Fx<\n { session: Doc<\"Session\">; refreshTokenDoc: Doc<\"RefreshToken\"> } | null,\n never\n> => {\n const db = authDb(ctx, config);\n\n const fetchDoc = <T>(\n promise: () => Promise<T | null>,\n failMsg: string,\n ): Fx<T | null, never> =>\n Fx.from({ ok: promise, err: () => failMsg }).pipe(\n Fx.recover((msg) => {\n logWithLevel(LOG_LEVELS.ERROR, msg);\n return Fx.succeed(null as T | null);\n }),\n );\n\n // The entire validation is a single pipeline:\n // fetch token → not null → not expired → session matches → fetch session → not null → not expired → combine\n return fetchDoc(\n () =>\n db.refreshTokens.getById(\n refreshTokenId as GenericId<\"RefreshToken\">,\n ) as Promise<Doc<\"RefreshToken\"> | null>,\n \"Invalid refresh token format\",\n )\n .pipe(\n Fx.chain((doc) =>\n doc !== null ? Fx.succeed(doc) : Fx.fail(\"Invalid refresh token\"),\n ),\n Fx.chain((doc) =>\n doc.expirationTime >= Date.now()\n ? Fx.succeed(doc)\n : Fx.fail(\"Expired refresh token\"),\n ),\n Fx.chain((doc) =>\n doc.sessionId === tokenSessionId\n ? Fx.succeed(doc)\n : Fx.fail(\"Invalid refresh token session ID\"),\n ),\n )\n .pipe(\n Fx.chain((doc: Doc<\"RefreshToken\">) =>\n fetchDoc(\n () =>\n db.sessions.getById(\n doc.sessionId,\n ) as Promise<Doc<\"Session\"> | null>,\n \"Invalid refresh token session format\",\n ).pipe(\n Fx.chain((session) =>\n session !== null\n ? Fx.succeed(session)\n : Fx.fail(\"Invalid refresh token session\"),\n ),\n Fx.chain((session) =>\n session.expirationTime >= Date.now()\n ? Fx.succeed(session)\n : Fx.fail(\"Expired refresh token session\"),\n ),\n Fx.map((session) => ({\n session,\n refreshTokenDoc: doc,\n })),\n ),\n ),\n Fx.fold({\n ok: (result) => result,\n err: (msg) => {\n logWithLevel(LOG_LEVELS.ERROR, msg);\n return null;\n },\n }),\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"refresh.js","names":[],"sources":["../../src/server/refresh.ts"],"sourcesContent":["import { Fx } from \"@robelest/fx\";\nimport { GenericId } from \"convex/values\";\n\nimport { authDb } from \"./db\";\nimport { AuthError } from \"./fx\";\nimport { Doc, MutationCtx } from \"./types\";\nimport { ConvexAuthConfig } from \"./types\";\nimport {\n LOG_LEVELS,\n REFRESH_TOKEN_DIVIDER,\n logWithLevel,\n maybeRedact,\n} from \"./utils\";\n\nconst DEFAULT_SESSION_INACTIVE_DURATION_MS = 1000 * 60 * 60 * 24 * 30; // 30 days\n/** @internal */\nexport const REFRESH_TOKEN_REUSE_WINDOW_MS = 10 * 1000; // 10 seconds\n\n// ---------------------------------------------------------------------------\n// Refresh token CRUD\n// ---------------------------------------------------------------------------\n\n/**\n * Create a new refresh token for the given session.\n */\n/** @internal */\nexport async function createRefreshToken(\n ctx: MutationCtx,\n config: ConvexAuthConfig,\n sessionId: GenericId<\"Session\">,\n parentRefreshTokenId: GenericId<\"RefreshToken\"> | null,\n): Promise<GenericId<\"RefreshToken\">> {\n const expirationTime =\n Date.now() +\n (config.session?.inactiveDurationMs ??\n (process.env.AUTH_SESSION_INACTIVE_DURATION_MS !== undefined\n ? Number(process.env.AUTH_SESSION_INACTIVE_DURATION_MS)\n : undefined) ??\n DEFAULT_SESSION_INACTIVE_DURATION_MS);\n\n return authDb(ctx, config).refreshTokens.create({\n sessionId,\n expirationTime,\n parentRefreshTokenId: parentRefreshTokenId ?? undefined,\n }) as Promise<GenericId<\"RefreshToken\">>;\n}\n\n/**\n * Parse a compound refresh token string into its constituent IDs.\n */\n/** @internal */\nexport const parseRefreshToken = (\n refreshToken: string,\n): Fx<\n {\n refreshTokenId: GenericId<\"RefreshToken\">;\n sessionId: GenericId<\"Session\">;\n },\n AuthError\n> => {\n const [refreshTokenId, sessionId] = refreshToken.split(REFRESH_TOKEN_DIVIDER);\n const msg = `Can't parse refresh token: ${maybeRedact(refreshToken)}`;\n const refreshTokenIdFx: Fx<string, AuthError> =\n refreshTokenId != null\n ? Fx.succeed(refreshTokenId)\n : Fx.fail(new AuthError(\"INVALID_REFRESH_TOKEN\", msg));\n\n return refreshTokenIdFx.pipe(\n Fx.chain((rtId) => {\n const sessionIdFx: Fx<string, AuthError> =\n sessionId != null\n ? Fx.succeed(sessionId)\n : Fx.fail(new AuthError(\"INVALID_REFRESH_TOKEN\", msg));\n return sessionIdFx.pipe(\n Fx.map((sId) => ({\n refreshTokenId: rtId as GenericId<\"RefreshToken\">,\n sessionId: sId as GenericId<\"Session\">,\n })),\n );\n }),\n );\n};\n\n/**\n * Mark all refresh tokens descending from the given refresh token as invalid\n * immediately. Used when we detect token reuse — revoke the entire tree.\n */\n/** @internal */\nexport async function invalidateRefreshTokensInSubtree(\n ctx: MutationCtx,\n refreshToken: Doc<\"RefreshToken\">,\n config: ConvexAuthConfig,\n) {\n const db = authDb(ctx, config);\n const tokensToInvalidate = [refreshToken];\n const visited = new Set<GenericId<\"RefreshToken\">>([refreshToken._id]);\n let frontier: GenericId<\"RefreshToken\">[] = [refreshToken._id];\n while (frontier.length > 0) {\n const nextFrontier: GenericId<\"RefreshToken\">[] = [];\n for (const currentTokenId of frontier) {\n const children = (await db.refreshTokens.getChildren(\n refreshToken.sessionId,\n currentTokenId,\n )) as Doc<\"RefreshToken\">[];\n for (const child of children) {\n if (visited.has(child._id)) continue;\n visited.add(child._id);\n tokensToInvalidate.push(child);\n nextFrontier.push(child._id);\n }\n }\n frontier = nextFrontier;\n }\n await Fx.run(\n Fx.each(tokensToInvalidate, (token) =>\n token.firstUsedTime === undefined ||\n token.firstUsedTime > Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS\n ? Fx.from({\n ok: () =>\n db.refreshTokens.patch(token._id, {\n firstUsedTime: Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS,\n }),\n err: (e) => e as never,\n })\n : Fx.unit,\n ),\n );\n return tokensToInvalidate;\n}\n\n// ---------------------------------------------------------------------------\n// Validation pipeline — the core of refresh token handling\n// ---------------------------------------------------------------------------\n\n/**\n * Validate a refresh token and its associated session.\n *\n * Returns `null` on any validation failure (matching original semantics).\n * Each validation step is a small composable function chained with `Fx.chain`.\n * On failure, the error message is logged and the pipeline folds to `null`.\n */\n/** @internal */\nexport const refreshTokenIfValid = (\n ctx: MutationCtx,\n refreshTokenId: string,\n tokenSessionId: string,\n config: ConvexAuthConfig,\n): Fx<\n { session: Doc<\"Session\">; refreshTokenDoc: Doc<\"RefreshToken\"> } | null,\n never\n> => {\n const db = authDb(ctx, config);\n\n const fetchDoc = <T>(\n promise: () => Promise<T | null>,\n failMsg: string,\n ): Fx<T | null, never> =>\n Fx.from({ ok: promise, err: () => failMsg }).pipe(\n Fx.recover((msg) => {\n logWithLevel(LOG_LEVELS.ERROR, msg);\n return Fx.succeed(null as T | null);\n }),\n );\n\n // The entire validation is a single pipeline:\n // fetch token → not null → not expired → session matches → fetch session → not null → not expired → combine\n return fetchDoc(\n () =>\n db.refreshTokens.getById(\n refreshTokenId as GenericId<\"RefreshToken\">,\n ) as Promise<Doc<\"RefreshToken\"> | null>,\n \"Invalid refresh token format\",\n )\n .pipe(\n Fx.chain((doc) =>\n doc !== null ? Fx.succeed(doc) : Fx.fail(\"Invalid refresh token\"),\n ),\n Fx.chain((doc) =>\n doc.expirationTime >= Date.now()\n ? Fx.succeed(doc)\n : Fx.fail(\"Expired refresh token\"),\n ),\n Fx.chain((doc) =>\n doc.sessionId === tokenSessionId\n ? Fx.succeed(doc)\n : Fx.fail(\"Invalid refresh token session ID\"),\n ),\n )\n .pipe(\n Fx.chain((doc: Doc<\"RefreshToken\">) =>\n fetchDoc(\n () =>\n db.sessions.getById(\n doc.sessionId,\n ) as Promise<Doc<\"Session\"> | null>,\n \"Invalid refresh token session format\",\n ).pipe(\n Fx.chain((session) =>\n session !== null\n ? Fx.succeed(session)\n : Fx.fail(\"Invalid refresh token session\"),\n ),\n Fx.chain((session) =>\n session.expirationTime >= Date.now()\n ? Fx.succeed(session)\n : Fx.fail(\"Expired refresh token session\"),\n ),\n Fx.map((session) => ({\n session,\n refreshTokenDoc: doc,\n })),\n ),\n ),\n Fx.fold({\n ok: (result) => result,\n err: (msg) => {\n logWithLevel(LOG_LEVELS.ERROR, msg);\n return null;\n },\n }),\n );\n};\n"],"mappings":"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"}
@@ -1,28 +1 @@
1
- import { ConvexAuthConfig, Doc, MutationCtx, SessionInfo } from "./types.js";
2
- import { GenericId } from "convex/values";
3
- import { Auth } from "convex/server";
4
-
5
- //#region src/server/sessions.d.ts
6
- declare function maybeGenerateTokensForSession(ctx: MutationCtx, config: ConvexAuthConfig, userId: GenericId<"User">, sessionId: GenericId<"Session">, generateTokens: boolean): Promise<SessionInfo>;
7
- declare function createNewAndDeleteExistingSession(ctx: MutationCtx, config: ConvexAuthConfig, userId: GenericId<"User">): Promise<GenericId<"Session">>;
8
- declare function generateTokensForSession(ctx: MutationCtx, config: ConvexAuthConfig, args: {
9
- userId: GenericId<"User">;
10
- sessionId: GenericId<"Session">;
11
- issuedRefreshTokenId: GenericId<"RefreshToken"> | null;
12
- parentRefreshTokenId: GenericId<"RefreshToken"> | null;
13
- }): Promise<{
14
- token: string;
15
- refreshToken: string;
16
- }>;
17
- declare function deleteSession(ctx: MutationCtx, session: Doc<"Session">, config: ConvexAuthConfig): Promise<void>;
18
- /**
19
- * Return the current session ID from the auth identity subject.
20
- *
21
- * Internal helper used by auth runtime internals and `auth.session.current`.
22
- */
23
- declare function getAuthSessionId(ctx: {
24
- auth: Auth;
25
- }): Promise<GenericId<"Session"> | null>;
26
- //#endregion
27
- export { createNewAndDeleteExistingSession, deleteSession, generateTokensForSession, getAuthSessionId, maybeGenerateTokensForSession };
28
- //# sourceMappingURL=sessions.d.ts.map
1
+ export { };
@@ -5,6 +5,7 @@ import { generateToken } from "./tokens.js";
5
5
 
6
6
  //#region src/server/sessions.ts
7
7
  const DEFAULT_SESSION_TOTAL_DURATION_MS = 1e3 * 60 * 60 * 24 * 30;
8
+ /** @internal */
8
9
  async function maybeGenerateTokensForSession(ctx, config, userId, sessionId, generateTokens) {
9
10
  return {
10
11
  userId,
@@ -17,6 +18,7 @@ async function maybeGenerateTokensForSession(ctx, config, userId, sessionId, gen
17
18
  }) : null
18
19
  };
19
20
  }
21
+ /** @internal */
20
22
  async function createNewAndDeleteExistingSession(ctx, config, userId) {
21
23
  const db = authDb(ctx, config);
22
24
  const existingSessionId = await getAuthSessionId(ctx);
@@ -26,6 +28,7 @@ async function createNewAndDeleteExistingSession(ctx, config, userId) {
26
28
  }
27
29
  return await createSession(ctx, userId, config);
28
30
  }
31
+ /** @internal */
29
32
  async function generateTokensForSession(ctx, config, args) {
30
33
  const ids = {
31
34
  userId: args.userId,
@@ -44,6 +47,7 @@ async function createSession(ctx, userId, config) {
44
47
  const expirationTime = Date.now() + (config.session?.totalDurationMs ?? (process.env.AUTH_SESSION_TOTAL_DURATION_MS !== void 0 ? Number(process.env.AUTH_SESSION_TOTAL_DURATION_MS) : void 0) ?? DEFAULT_SESSION_TOTAL_DURATION_MS);
45
48
  return await db.sessions.create(userId, expirationTime);
46
49
  }
50
+ /** @internal */
47
51
  async function deleteSession(ctx, session, config) {
48
52
  const db = authDb(ctx, config);
49
53
  await db.sessions.delete(session._id);
@@ -54,6 +58,7 @@ async function deleteSession(ctx, session, config) {
54
58
  *
55
59
  * Internal helper used by auth runtime internals and `auth.session.current`.
56
60
  */
61
+ /** @internal */
57
62
  async function getAuthSessionId(ctx) {
58
63
  const identity = await ctx.auth.getUserIdentity();
59
64
  if (identity === null) return null;
@@ -1 +1 @@
1
- {"version":3,"file":"sessions.js","names":[],"sources":["../../src/server/sessions.ts"],"sourcesContent":["import { Auth } from \"convex/server\";\nimport { GenericId } from \"convex/values\";\n\nimport { authDb } from \"./db\";\nimport { createRefreshToken } from \"./refresh\";\nimport { generateToken } from \"./tokens\";\nimport { Doc, MutationCtx, SessionInfo } from \"./types\";\nimport { ConvexAuthConfig } from \"./types\";\nimport {\n LOG_LEVELS,\n TOKEN_SUB_CLAIM_DIVIDER,\n REFRESH_TOKEN_DIVIDER,\n logWithLevel,\n maybeRedact,\n} from \"./utils\";\n\nconst DEFAULT_SESSION_TOTAL_DURATION_MS = 1000 * 60 * 60 * 24 * 30; // 30 days\n\nexport async function maybeGenerateTokensForSession(\n ctx: MutationCtx,\n config: ConvexAuthConfig,\n userId: GenericId<\"User\">,\n sessionId: GenericId<\"Session\">,\n generateTokens: boolean,\n): Promise<SessionInfo> {\n return {\n userId,\n sessionId,\n tokens: generateTokens\n ? await generateTokensForSession(ctx, config, {\n userId,\n sessionId,\n issuedRefreshTokenId: null,\n parentRefreshTokenId: null,\n })\n : null,\n };\n}\n\nexport async function createNewAndDeleteExistingSession(\n ctx: MutationCtx,\n config: ConvexAuthConfig,\n userId: GenericId<\"User\">,\n) {\n const db = authDb(ctx, config);\n const existingSessionId = await getAuthSessionId(ctx);\n if (existingSessionId !== null) {\n const existingSession = await db.sessions.getById(existingSessionId);\n if (existingSession !== null) {\n await deleteSession(ctx, existingSession, config);\n }\n }\n return await createSession(ctx, userId, config);\n}\n\nexport async function generateTokensForSession(\n ctx: MutationCtx,\n config: ConvexAuthConfig,\n args: {\n userId: GenericId<\"User\">;\n sessionId: GenericId<\"Session\">;\n issuedRefreshTokenId: GenericId<\"RefreshToken\"> | null;\n parentRefreshTokenId: GenericId<\"RefreshToken\"> | null;\n },\n) {\n const ids = { userId: args.userId, sessionId: args.sessionId };\n const refreshTokenId =\n args.issuedRefreshTokenId ??\n (await createRefreshToken(\n ctx,\n config,\n args.sessionId,\n args.parentRefreshTokenId,\n ));\n const result = {\n token: await generateToken(ids, config),\n refreshToken: `${refreshTokenId}${REFRESH_TOKEN_DIVIDER}${args.sessionId}`,\n };\n logWithLevel(\n LOG_LEVELS.DEBUG,\n `Generated token ${maybeRedact(result.token)} and refresh token ${maybeRedact(refreshTokenId)} for session ${maybeRedact(args.sessionId)}`,\n );\n return result;\n}\n\nasync function createSession(\n ctx: MutationCtx,\n userId: GenericId<\"User\">,\n config: ConvexAuthConfig,\n) {\n const db = authDb(ctx, config);\n const expirationTime =\n Date.now() +\n (config.session?.totalDurationMs ??\n (process.env.AUTH_SESSION_TOTAL_DURATION_MS !== undefined\n ? Number(process.env.AUTH_SESSION_TOTAL_DURATION_MS)\n : undefined) ??\n DEFAULT_SESSION_TOTAL_DURATION_MS);\n return (await db.sessions.create(\n userId,\n expirationTime,\n )) as GenericId<\"Session\">;\n}\n\nexport async function deleteSession(\n ctx: MutationCtx,\n session: Doc<\"Session\">,\n config: ConvexAuthConfig,\n) {\n const db = authDb(ctx, config);\n await db.sessions.delete(session._id);\n await db.refreshTokens.deleteAll(session._id);\n}\n\n/**\n * Return the current session ID from the auth identity subject.\n *\n * Internal helper used by auth runtime internals and `auth.session.current`.\n */\nexport async function getAuthSessionId(ctx: { auth: Auth }) {\n const identity = await ctx.auth.getUserIdentity();\n if (identity === null) {\n return null;\n }\n const [, sessionId] = identity.subject.split(TOKEN_SUB_CLAIM_DIVIDER);\n return sessionId as GenericId<\"Session\">;\n}\n"],"mappings":";;;;;;AAgBA,MAAM,oCAAoC,MAAO,KAAK,KAAK,KAAK;AAEhE,eAAsB,8BACpB,KACA,QACA,QACA,WACA,gBACsB;AACtB,QAAO;EACL;EACA;EACA,QAAQ,iBACJ,MAAM,yBAAyB,KAAK,QAAQ;GAC1C;GACA;GACA,sBAAsB;GACtB,sBAAsB;GACvB,CAAC,GACF;EACL;;AAGH,eAAsB,kCACpB,KACA,QACA,QACA;CACA,MAAM,KAAK,OAAO,KAAK,OAAO;CAC9B,MAAM,oBAAoB,MAAM,iBAAiB,IAAI;AACrD,KAAI,sBAAsB,MAAM;EAC9B,MAAM,kBAAkB,MAAM,GAAG,SAAS,QAAQ,kBAAkB;AACpE,MAAI,oBAAoB,KACtB,OAAM,cAAc,KAAK,iBAAiB,OAAO;;AAGrD,QAAO,MAAM,cAAc,KAAK,QAAQ,OAAO;;AAGjD,eAAsB,yBACpB,KACA,QACA,MAMA;CACA,MAAM,MAAM;EAAE,QAAQ,KAAK;EAAQ,WAAW,KAAK;EAAW;CAC9D,MAAM,iBACJ,KAAK,wBACJ,MAAM,mBACL,KACA,QACA,KAAK,WACL,KAAK,qBACN;CACH,MAAM,SAAS;EACb,OAAO,MAAM,cAAc,KAAK,OAAO;EACvC,cAAc,GAAG,iBAAiB,wBAAwB,KAAK;EAChE;AACD,cACE,WAAW,OACX,mBAAmB,YAAY,OAAO,MAAM,CAAC,qBAAqB,YAAY,eAAe,CAAC,eAAe,YAAY,KAAK,UAAU,GACzI;AACD,QAAO;;AAGT,eAAe,cACb,KACA,QACA,QACA;CACA,MAAM,KAAK,OAAO,KAAK,OAAO;CAC9B,MAAM,iBACJ,KAAK,KAAK,IACT,OAAO,SAAS,oBACd,QAAQ,IAAI,mCAAmC,SAC5C,OAAO,QAAQ,IAAI,+BAA+B,GAClD,WACJ;AACJ,QAAQ,MAAM,GAAG,SAAS,OACxB,QACA,eACD;;AAGH,eAAsB,cACpB,KACA,SACA,QACA;CACA,MAAM,KAAK,OAAO,KAAK,OAAO;AAC9B,OAAM,GAAG,SAAS,OAAO,QAAQ,IAAI;AACrC,OAAM,GAAG,cAAc,UAAU,QAAQ,IAAI;;;;;;;AAQ/C,eAAsB,iBAAiB,KAAqB;CAC1D,MAAM,WAAW,MAAM,IAAI,KAAK,iBAAiB;AACjD,KAAI,aAAa,KACf,QAAO;CAET,MAAM,GAAG,aAAa,SAAS,QAAQ,MAAM,wBAAwB;AACrE,QAAO"}
1
+ {"version":3,"file":"sessions.js","names":[],"sources":["../../src/server/sessions.ts"],"sourcesContent":["import { Auth } from \"convex/server\";\nimport { GenericId } from \"convex/values\";\n\nimport { authDb } from \"./db\";\nimport { createRefreshToken } from \"./refresh\";\nimport { generateToken } from \"./tokens\";\nimport { Doc, MutationCtx, SessionInfo } from \"./types\";\nimport { ConvexAuthConfig } from \"./types\";\nimport {\n LOG_LEVELS,\n TOKEN_SUB_CLAIM_DIVIDER,\n REFRESH_TOKEN_DIVIDER,\n logWithLevel,\n maybeRedact,\n} from \"./utils\";\n\nconst DEFAULT_SESSION_TOTAL_DURATION_MS = 1000 * 60 * 60 * 24 * 30; // 30 days\n\n/** @internal */\nexport async function maybeGenerateTokensForSession(\n ctx: MutationCtx,\n config: ConvexAuthConfig,\n userId: GenericId<\"User\">,\n sessionId: GenericId<\"Session\">,\n generateTokens: boolean,\n): Promise<SessionInfo> {\n return {\n userId,\n sessionId,\n tokens: generateTokens\n ? await generateTokensForSession(ctx, config, {\n userId,\n sessionId,\n issuedRefreshTokenId: null,\n parentRefreshTokenId: null,\n })\n : null,\n };\n}\n\n/** @internal */\nexport async function createNewAndDeleteExistingSession(\n ctx: MutationCtx,\n config: ConvexAuthConfig,\n userId: GenericId<\"User\">,\n) {\n const db = authDb(ctx, config);\n const existingSessionId = await getAuthSessionId(ctx);\n if (existingSessionId !== null) {\n const existingSession = await db.sessions.getById(existingSessionId);\n if (existingSession !== null) {\n await deleteSession(ctx, existingSession, config);\n }\n }\n return await createSession(ctx, userId, config);\n}\n\n/** @internal */\nexport async function generateTokensForSession(\n ctx: MutationCtx,\n config: ConvexAuthConfig,\n args: {\n userId: GenericId<\"User\">;\n sessionId: GenericId<\"Session\">;\n issuedRefreshTokenId: GenericId<\"RefreshToken\"> | null;\n parentRefreshTokenId: GenericId<\"RefreshToken\"> | null;\n },\n) {\n const ids = { userId: args.userId, sessionId: args.sessionId };\n const refreshTokenId =\n args.issuedRefreshTokenId ??\n (await createRefreshToken(\n ctx,\n config,\n args.sessionId,\n args.parentRefreshTokenId,\n ));\n const result = {\n token: await generateToken(ids, config),\n refreshToken: `${refreshTokenId}${REFRESH_TOKEN_DIVIDER}${args.sessionId}`,\n };\n logWithLevel(\n LOG_LEVELS.DEBUG,\n `Generated token ${maybeRedact(result.token)} and refresh token ${maybeRedact(refreshTokenId)} for session ${maybeRedact(args.sessionId)}`,\n );\n return result;\n}\n\nasync function createSession(\n ctx: MutationCtx,\n userId: GenericId<\"User\">,\n config: ConvexAuthConfig,\n) {\n const db = authDb(ctx, config);\n const expirationTime =\n Date.now() +\n (config.session?.totalDurationMs ??\n (process.env.AUTH_SESSION_TOTAL_DURATION_MS !== undefined\n ? Number(process.env.AUTH_SESSION_TOTAL_DURATION_MS)\n : undefined) ??\n DEFAULT_SESSION_TOTAL_DURATION_MS);\n return (await db.sessions.create(\n userId,\n expirationTime,\n )) as GenericId<\"Session\">;\n}\n\n/** @internal */\nexport async function deleteSession(\n ctx: MutationCtx,\n session: Doc<\"Session\">,\n config: ConvexAuthConfig,\n) {\n const db = authDb(ctx, config);\n await db.sessions.delete(session._id);\n await db.refreshTokens.deleteAll(session._id);\n}\n\n/**\n * Return the current session ID from the auth identity subject.\n *\n * Internal helper used by auth runtime internals and `auth.session.current`.\n */\n/** @internal */\nexport async function getAuthSessionId(ctx: { auth: Auth }) {\n const identity = await ctx.auth.getUserIdentity();\n if (identity === null) {\n return null;\n }\n const [, sessionId] = identity.subject.split(TOKEN_SUB_CLAIM_DIVIDER);\n return sessionId as GenericId<\"Session\">;\n}\n"],"mappings":";;;;;;AAgBA,MAAM,oCAAoC,MAAO,KAAK,KAAK,KAAK;;AAGhE,eAAsB,8BACpB,KACA,QACA,QACA,WACA,gBACsB;AACtB,QAAO;EACL;EACA;EACA,QAAQ,iBACJ,MAAM,yBAAyB,KAAK,QAAQ;GAC1C;GACA;GACA,sBAAsB;GACtB,sBAAsB;GACvB,CAAC,GACF;EACL;;;AAIH,eAAsB,kCACpB,KACA,QACA,QACA;CACA,MAAM,KAAK,OAAO,KAAK,OAAO;CAC9B,MAAM,oBAAoB,MAAM,iBAAiB,IAAI;AACrD,KAAI,sBAAsB,MAAM;EAC9B,MAAM,kBAAkB,MAAM,GAAG,SAAS,QAAQ,kBAAkB;AACpE,MAAI,oBAAoB,KACtB,OAAM,cAAc,KAAK,iBAAiB,OAAO;;AAGrD,QAAO,MAAM,cAAc,KAAK,QAAQ,OAAO;;;AAIjD,eAAsB,yBACpB,KACA,QACA,MAMA;CACA,MAAM,MAAM;EAAE,QAAQ,KAAK;EAAQ,WAAW,KAAK;EAAW;CAC9D,MAAM,iBACJ,KAAK,wBACJ,MAAM,mBACL,KACA,QACA,KAAK,WACL,KAAK,qBACN;CACH,MAAM,SAAS;EACb,OAAO,MAAM,cAAc,KAAK,OAAO;EACvC,cAAc,GAAG,iBAAiB,wBAAwB,KAAK;EAChE;AACD,cACE,WAAW,OACX,mBAAmB,YAAY,OAAO,MAAM,CAAC,qBAAqB,YAAY,eAAe,CAAC,eAAe,YAAY,KAAK,UAAU,GACzI;AACD,QAAO;;AAGT,eAAe,cACb,KACA,QACA,QACA;CACA,MAAM,KAAK,OAAO,KAAK,OAAO;CAC9B,MAAM,iBACJ,KAAK,KAAK,IACT,OAAO,SAAS,oBACd,QAAQ,IAAI,mCAAmC,SAC5C,OAAO,QAAQ,IAAI,+BAA+B,GAClD,WACJ;AACJ,QAAQ,MAAM,GAAG,SAAS,OACxB,QACA,eACD;;;AAIH,eAAsB,cACpB,KACA,SACA,QACA;CACA,MAAM,KAAK,OAAO,KAAK,OAAO;AAC9B,OAAM,GAAG,SAAS,OAAO,QAAQ,IAAI;AACrC,OAAM,GAAG,cAAc,UAAU,QAAQ,IAAI;;;;;;;;AAS/C,eAAsB,iBAAiB,KAAqB;CAC1D,MAAM,WAAW,MAAM,IAAI,KAAK,iBAAiB;AACjD,KAAI,aAAa,KACf,QAAO;CAET,MAAM,GAAG,aAAa,SAAS,QAAQ,MAAM,wBAAwB;AACrE,QAAO"}
@@ -1,55 +1 @@
1
- import { AuthDataModel, AuthProviderMaterializedConfig, GenericActionCtxWithAuthConfig, SessionInfo, Tokens } from "./types.js";
2
- import { GenericId } from "convex/values";
3
-
4
- //#region src/server/signin.d.ts
5
- type EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;
6
- type SignInResult = {
7
- kind: "signedIn";
8
- signedIn: SessionInfo | null;
9
- } | {
10
- kind: "refreshTokens";
11
- signedIn: {
12
- tokens: Tokens;
13
- };
14
- } | {
15
- kind: "started";
16
- started: true;
17
- } | {
18
- kind: "redirect";
19
- redirect: string;
20
- verifier: string;
21
- } | {
22
- kind: "passkeyOptions";
23
- options: Record<string, any>;
24
- verifier: string;
25
- } | {
26
- kind: "totpRequired";
27
- verifier: string;
28
- } | {
29
- kind: "totpSetup";
30
- uri: string;
31
- secret: string;
32
- verifier: string;
33
- totpId: string;
34
- } | {
35
- kind: "deviceCode";
36
- deviceCode: string;
37
- userCode: string;
38
- verificationUri: string;
39
- verificationUriComplete: string;
40
- expiresIn: number;
41
- interval: number;
42
- };
43
- declare function signInImpl(ctx: EnrichedActionCtx, provider: AuthProviderMaterializedConfig | null, args: {
44
- accountId?: GenericId<"Account">;
45
- params?: Record<string, any>;
46
- verifier?: string;
47
- refreshToken?: string;
48
- calledBy?: string;
49
- }, options: {
50
- generateTokens: boolean;
51
- allowExtraProviders: boolean;
52
- }): Promise<SignInResult>;
53
- //#endregion
54
- export { signInImpl };
55
- //# sourceMappingURL=signin.d.ts.map
1
+ export { };
@@ -6,14 +6,15 @@ import { callVerifierSignature } from "./mutations/signature.js";
6
6
  import { callSignIn } from "./mutations/signin.js";
7
7
  import { callVerifier } from "./mutations/verifier.js";
8
8
  import { callVerifyCodeAndSignIn } from "./mutations/verify.js";
9
- import { redirectAbsoluteUrl, setURLSearchParam } from "./redirects.js";
10
9
  import { queryTotpVerifiedByUserId } from "./types.js";
11
10
  import { handleDevice } from "./device.js";
12
11
  import { handlePasskey as handlePasskeyFx } from "./passkey.js";
12
+ import { redirectAbsoluteUrl, setURLSearchParam } from "./redirects.js";
13
13
  import { handleTotp } from "./totp.js";
14
14
 
15
15
  //#region src/server/signin.ts
16
16
  const DEFAULT_EMAIL_VERIFICATION_CODE_DURATION_S = 3600 * 24;
17
+ /** @internal */
17
18
  async function signInImpl(ctx, provider, args, options) {
18
19
  const fx = signInFx(ctx, provider, args, options);
19
20
  return Fx.run(fx.pipe(Fx.recover((e) => Fx.fatal(e.toConvexError()))));
@@ -1 +1 @@
1
- {"version":3,"file":"signin.js","names":[],"sources":["../../src/server/signin.ts"],"sourcesContent":["import type { Fx as FxType } from \"@robelest/fx\";\nimport { GenericId } from \"convex/values\";\n\nimport { handleDevice } from \"./device\";\nimport { AuthError, Fx } from \"./fx\";\nimport {\n callCreateVerificationCode,\n callRefreshSession,\n callSignIn,\n callVerifier,\n callVerifierSignature,\n callVerifyCodeAndSignIn,\n} from \"./mutations/index\";\nimport { handlePasskeyFx } from \"./passkey\";\nimport { redirectAbsoluteUrl, setURLSearchParam } from \"./redirects\";\nimport { handleTotp } from \"./totp\";\nimport {\n AuthProviderMaterializedConfig,\n ConvexCredentialsConfig,\n EmailConfig,\n GenericActionCtxWithAuthConfig,\n PhoneConfig,\n} from \"./types\";\nimport {\n AuthDataModel,\n SessionInfo,\n SessionInfoWithTokens,\n Tokens,\n queryTotpVerifiedByUserId,\n} from \"./types\";\nimport type { OAuthMaterializedConfig } from \"./types\";\nimport { generateRandomString } from \"./utils\";\nimport { requireEnv } from \"./utils\";\n\nconst DEFAULT_EMAIL_VERIFICATION_CODE_DURATION_S = 60 * 60 * 24; // 24 hours\n\ntype EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;\n\ntype SignInResult =\n | { kind: \"signedIn\"; signedIn: SessionInfo | null }\n | { kind: \"refreshTokens\"; signedIn: { tokens: Tokens } }\n | { kind: \"started\"; started: true }\n | { kind: \"redirect\"; redirect: string; verifier: string }\n | { kind: \"passkeyOptions\"; options: Record<string, any>; verifier: string }\n | { kind: \"totpRequired\"; verifier: string }\n | {\n kind: \"totpSetup\";\n uri: string;\n secret: string;\n verifier: string;\n totpId: string;\n }\n | {\n kind: \"deviceCode\";\n deviceCode: string;\n userCode: string;\n verificationUri: string;\n verificationUriComplete: string;\n expiresIn: number;\n interval: number;\n };\n\nexport async function signInImpl(\n ctx: EnrichedActionCtx,\n provider: AuthProviderMaterializedConfig | null,\n args: {\n accountId?: GenericId<\"Account\">;\n params?: Record<string, any>;\n verifier?: string;\n refreshToken?: string;\n calledBy?: string;\n },\n options: {\n generateTokens: boolean;\n allowExtraProviders: boolean;\n },\n): Promise<SignInResult> {\n const fx = signInFx(ctx, provider, args, options);\n return Fx.run(\n fx.pipe(Fx.recover((e) => Fx.fatal((e as AuthError).toConvexError()))),\n );\n}\n\n/**\n * Core sign-in pipeline as an Fx generator.\n *\n * Handles: refresh tokens, verification codes, then dispatches by\n * provider type using a dispatch map (no if-chain).\n */\nfunction signInFx(\n ctx: EnrichedActionCtx,\n provider: AuthProviderMaterializedConfig | null,\n args: {\n accountId?: GenericId<\"Account\">;\n params?: Record<string, any>;\n verifier?: string;\n refreshToken?: string;\n calledBy?: string;\n },\n options: {\n generateTokens: boolean;\n allowExtraProviders: boolean;\n },\n): FxType<SignInResult, AuthError> {\n return Fx.gen(function* () {\n // --- Refresh token (no provider) ---\n if (provider === null && args.refreshToken) {\n const tokens = yield* Fx.promise(() =>\n callRefreshSession(ctx, { refreshToken: args.refreshToken! }),\n );\n if (tokens === null) {\n return { kind: \"signedIn\" as const, signedIn: null };\n }\n return { kind: \"refreshTokens\" as const, signedIn: { tokens } };\n }\n\n // --- Verify code (no provider, code present) ---\n if (provider === null && args.params?.code !== undefined) {\n const result = yield* Fx.promise(() =>\n callVerifyCodeAndSignIn(ctx, {\n params: args.params,\n verifier: args.verifier,\n generateTokens: true,\n allowExtraProviders: options.allowExtraProviders,\n }),\n );\n return { kind: \"signedIn\" as const, signedIn: result };\n }\n\n // --- Provider is required past this point ---\n const resolvedProvider = yield* provider != null\n ? Fx.succeed(provider)\n : Fx.fail(new AuthError(\"SIGN_IN_MISSING_PARAMS\"));\n\n // --- Dispatch by provider type ---\n return yield* Fx.match(resolvedProvider).on(\"type\", {\n email: (p) => handleEmailAndPhoneProviderFx(ctx, p, args, options),\n phone: (p) => handleEmailAndPhoneProviderFx(ctx, p, args, options),\n credentials: (p) => handleCredentialsFx(ctx, p, args, options),\n oauth: (p) => handleOAuthProviderFx(ctx, p, args, options),\n passkey: (p) => handlePasskeyFx(ctx, p, args),\n totp: (p) => handleTotp(ctx, p, args),\n device: (p) => handleDevice(ctx, p, args),\n });\n });\n}\n\n// ============================================================================\n// Email / Phone\n// ============================================================================\n\nfunction handleEmailAndPhoneProviderFx(\n ctx: EnrichedActionCtx,\n provider: EmailConfig | PhoneConfig,\n args: {\n params?: Record<string, any>;\n accountId?: GenericId<\"Account\">;\n },\n options: {\n generateTokens: boolean;\n allowExtraProviders: boolean;\n },\n): FxType<\n | { kind: \"started\"; started: true }\n | { kind: \"signedIn\"; signedIn: SessionInfoWithTokens },\n AuthError\n> {\n return Fx.gen(function* () {\n // --- Code verification path ---\n if (args.params?.code !== undefined) {\n const result = yield* Fx.promise(() =>\n callVerifyCodeAndSignIn(ctx, {\n params: args.params,\n provider: provider.id,\n generateTokens: options.generateTokens,\n allowExtraProviders: options.allowExtraProviders,\n }),\n );\n const verified = yield* result != null\n ? Fx.succeed(result)\n : Fx.fail(new AuthError(\"INVALID_VERIFICATION_CODE\"));\n return {\n kind: \"signedIn\" as const,\n signedIn: verified as SessionInfoWithTokens,\n };\n }\n\n // --- Send verification code path ---\n const alphabet =\n \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n const code = provider.generateVerificationToken\n ? yield* Fx.from({\n ok: async () => provider.generateVerificationToken!(),\n err: () =>\n new AuthError(\n \"INTERNAL_ERROR\",\n \"Failed to generate verification token\",\n ),\n })\n : generateRandomString(32, alphabet);\n const expirationTime =\n Date.now() +\n (provider.maxAge ?? DEFAULT_EMAIL_VERIFICATION_CODE_DURATION_S) * 1000;\n\n const identifier = yield* Fx.promise(() =>\n callCreateVerificationCode(ctx, {\n provider: provider.id,\n accountId: args.accountId,\n email: args.params?.email,\n phone: args.params?.phone,\n code,\n expirationTime,\n allowExtraProviders: options.allowExtraProviders,\n }),\n );\n const destination = yield* Fx.promise(() =>\n redirectAbsoluteUrl(\n ctx.auth.config,\n (args.params ?? {}) as { redirectTo: unknown },\n ),\n );\n const verificationArgs = {\n identifier,\n url: setURLSearchParam(destination, \"code\", code),\n token: code,\n expires: new Date(expirationTime),\n };\n yield* Fx.match(provider).on(\"type\", {\n email: (p) =>\n Fx.from({\n ok: async () =>\n p.sendVerificationRequest(\n {\n ...verificationArgs,\n provider: p,\n request: new Request(\"http://localhost\"),\n },\n ctx,\n ),\n err: () =>\n new AuthError(\"INTERNAL_ERROR\", \"Failed to send email code\"),\n }),\n phone: (p) =>\n Fx.from({\n ok: async () =>\n p.sendVerificationRequest(\n { ...verificationArgs, provider: p },\n ctx,\n ),\n err: () =>\n new AuthError(\"INTERNAL_ERROR\", \"Failed to send phone code\"),\n }),\n });\n return { kind: \"started\" as const, started: true as const };\n });\n}\n\n// ============================================================================\n// Credentials\n// ============================================================================\n\nfunction handleCredentialsFx(\n ctx: EnrichedActionCtx,\n provider: ConvexCredentialsConfig,\n args: {\n params?: Record<string, any>;\n },\n options: {\n generateTokens: boolean;\n },\n): FxType<\n | { kind: \"signedIn\"; signedIn: SessionInfo | null }\n | { kind: \"totpRequired\"; verifier: string },\n AuthError\n> {\n return Fx.gen(function* () {\n const result = yield* Fx.promise(() =>\n provider.authorize(args.params ?? {}, ctx),\n );\n if (result === null) {\n return { kind: \"signedIn\" as const, signedIn: null };\n }\n\n // Check if user has TOTP 2FA enrolled before issuing tokens\n const hasTotpEnrolled = yield* Fx.promise(async () => {\n const totpDoc = await queryTotpVerifiedByUserId(ctx, result.userId);\n return totpDoc !== null;\n });\n if (hasTotpEnrolled) {\n // Create session but withhold tokens — TOTP verification needed\n yield* Fx.promise(() =>\n callSignIn(ctx, {\n userId: result.userId,\n sessionId: result.sessionId,\n generateTokens: false,\n }),\n );\n // Store userId in verifier so the TOTP verify flow can complete sign-in\n const verifier = yield* Fx.promise(() => callVerifier(ctx));\n yield* Fx.promise(() =>\n callVerifierSignature(ctx, {\n verifier,\n signature: JSON.stringify({ userId: result.userId }),\n }),\n );\n return { kind: \"totpRequired\" as const, verifier };\n }\n\n const idsAndTokens = yield* Fx.promise(() =>\n callSignIn(ctx, {\n userId: result.userId,\n sessionId: result.sessionId,\n generateTokens: options.generateTokens,\n }),\n );\n return { kind: \"signedIn\" as const, signedIn: idsAndTokens };\n });\n}\n\n// ============================================================================\n// OAuth\n// ============================================================================\n\nfunction handleOAuthProviderFx(\n ctx: EnrichedActionCtx,\n provider: OAuthMaterializedConfig,\n args: {\n params?: Record<string, any>;\n verifier?: string;\n },\n options: {\n allowExtraProviders: boolean;\n },\n): FxType<\n | { kind: \"signedIn\"; signedIn: SessionInfoWithTokens | null }\n | { kind: \"redirect\"; redirect: string; verifier: string },\n AuthError\n> {\n return Fx.gen(function* () {\n // --- Code verification path ---\n if (args.params?.code !== undefined) {\n const result = yield* Fx.promise(() =>\n callVerifyCodeAndSignIn(ctx, {\n params: args.params,\n verifier: args.verifier,\n generateTokens: true,\n allowExtraProviders: options.allowExtraProviders,\n }),\n );\n return {\n kind: \"signedIn\" as const,\n signedIn: result as SessionInfoWithTokens | null,\n };\n }\n\n // --- Build redirect URL ---\n const redirect = new URL(\n (process.env.CUSTOM_AUTH_SITE_URL ?? requireEnv(\"CONVEX_SITE_URL\")) +\n `/api/auth/signin/${provider.id}`,\n );\n const verifier = yield* Fx.promise(() => callVerifier(ctx));\n redirect.searchParams.set(\"code\", verifier);\n\n if (args.params?.redirectTo !== undefined) {\n yield* Fx.guard(\n typeof args.params.redirectTo !== \"string\",\n Fx.fail(\n new AuthError(\n \"INVALID_REDIRECT\",\n `Expected \\`redirectTo\\` to be a string, got ${args.params.redirectTo}`,\n ),\n ),\n );\n redirect.searchParams.set(\"redirectTo\", args.params.redirectTo);\n }\n\n return {\n kind: \"redirect\" as const,\n redirect: redirect.toString(),\n verifier,\n };\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkCA,MAAM,6CAA6C,OAAU;AA4B7D,eAAsB,WACpB,KACA,UACA,MAOA,SAIuB;CACvB,MAAM,KAAK,SAAS,KAAK,UAAU,MAAM,QAAQ;AACjD,QAAO,GAAG,IACR,GAAG,KAAK,GAAG,SAAS,MAAM,GAAG,MAAO,EAAgB,eAAe,CAAC,CAAC,CAAC,CACvE;;;;;;;;AASH,SAAS,SACP,KACA,UACA,MAOA,SAIiC;AACjC,QAAO,GAAG,IAAI,aAAa;AAEzB,MAAI,aAAa,QAAQ,KAAK,cAAc;GAC1C,MAAM,SAAS,OAAO,GAAG,cACvB,mBAAmB,KAAK,EAAE,cAAc,KAAK,cAAe,CAAC,CAC9D;AACD,OAAI,WAAW,KACb,QAAO;IAAE,MAAM;IAAqB,UAAU;IAAM;AAEtD,UAAO;IAAE,MAAM;IAA0B,UAAU,EAAE,QAAQ;IAAE;;AAIjE,MAAI,aAAa,QAAQ,KAAK,QAAQ,SAAS,OAS7C,QAAO;GAAE,MAAM;GAAqB,UARrB,OAAO,GAAG,cACvB,wBAAwB,KAAK;IAC3B,QAAQ,KAAK;IACb,UAAU,KAAK;IACf,gBAAgB;IAChB,qBAAqB,QAAQ;IAC9B,CAAC,CACH;GACqD;EAIxD,MAAM,mBAAmB,OAAO,YAAY,OACxC,GAAG,QAAQ,SAAS,GACpB,GAAG,KAAK,IAAI,UAAU,yBAAyB,CAAC;AAGpD,SAAO,OAAO,GAAG,MAAM,iBAAiB,CAAC,GAAG,QAAQ;GAClD,QAAQ,MAAM,8BAA8B,KAAK,GAAG,MAAM,QAAQ;GAClE,QAAQ,MAAM,8BAA8B,KAAK,GAAG,MAAM,QAAQ;GAClE,cAAc,MAAM,oBAAoB,KAAK,GAAG,MAAM,QAAQ;GAC9D,QAAQ,MAAM,sBAAsB,KAAK,GAAG,MAAM,QAAQ;GAC1D,UAAU,MAAM,gBAAgB,KAAK,GAAG,KAAK;GAC7C,OAAO,MAAM,WAAW,KAAK,GAAG,KAAK;GACrC,SAAS,MAAM,aAAa,KAAK,GAAG,KAAK;GAC1C,CAAC;GACF;;AAOJ,SAAS,8BACP,KACA,UACA,MAIA,SAQA;AACA,QAAO,GAAG,IAAI,aAAa;AAEzB,MAAI,KAAK,QAAQ,SAAS,QAAW;GACnC,MAAM,SAAS,OAAO,GAAG,cACvB,wBAAwB,KAAK;IAC3B,QAAQ,KAAK;IACb,UAAU,SAAS;IACnB,gBAAgB,QAAQ;IACxB,qBAAqB,QAAQ;IAC9B,CAAC,CACH;AAID,UAAO;IACL,MAAM;IACN,UALe,OAAO,UAAU,OAC9B,GAAG,QAAQ,OAAO,GAClB,GAAG,KAAK,IAAI,UAAU,4BAA4B,CAAC;IAItD;;EAMH,MAAM,OAAO,SAAS,4BAClB,OAAO,GAAG,KAAK;GACb,IAAI,YAAY,SAAS,2BAA4B;GACrD,WACE,IAAI,UACF,kBACA,wCACD;GACJ,CAAC,GACF,qBAAqB,IAVvB,iEAUoC;EACtC,MAAM,iBACJ,KAAK,KAAK,IACT,SAAS,UAAU,8CAA8C;EAmBpE,MAAM,mBAAmB;GACvB,YAlBiB,OAAO,GAAG,cAC3B,2BAA2B,KAAK;IAC9B,UAAU,SAAS;IACnB,WAAW,KAAK;IAChB,OAAO,KAAK,QAAQ;IACpB,OAAO,KAAK,QAAQ;IACpB;IACA;IACA,qBAAqB,QAAQ;IAC9B,CAAC,CACH;GASC,KAAK,kBARa,OAAO,GAAG,cAC5B,oBACE,IAAI,KAAK,QACR,KAAK,UAAU,EAAE,CACnB,CACF,EAGqC,QAAQ,KAAK;GACjD,OAAO;GACP,SAAS,IAAI,KAAK,eAAe;GAClC;AACD,SAAO,GAAG,MAAM,SAAS,CAAC,GAAG,QAAQ;GACnC,QAAQ,MACN,GAAG,KAAK;IACN,IAAI,YACF,EAAE,wBACA;KACE,GAAG;KACH,UAAU;KACV,SAAS,IAAI,QAAQ,mBAAmB;KACzC,EACD,IACD;IACH,WACE,IAAI,UAAU,kBAAkB,4BAA4B;IAC/D,CAAC;GACJ,QAAQ,MACN,GAAG,KAAK;IACN,IAAI,YACF,EAAE,wBACA;KAAE,GAAG;KAAkB,UAAU;KAAG,EACpC,IACD;IACH,WACE,IAAI,UAAU,kBAAkB,4BAA4B;IAC/D,CAAC;GACL,CAAC;AACF,SAAO;GAAE,MAAM;GAAoB,SAAS;GAAe;GAC3D;;AAOJ,SAAS,oBACP,KACA,UACA,MAGA,SAOA;AACA,QAAO,GAAG,IAAI,aAAa;EACzB,MAAM,SAAS,OAAO,GAAG,cACvB,SAAS,UAAU,KAAK,UAAU,EAAE,EAAE,IAAI,CAC3C;AACD,MAAI,WAAW,KACb,QAAO;GAAE,MAAM;GAAqB,UAAU;GAAM;AAQtD,MAJwB,OAAO,GAAG,QAAQ,YAAY;AAEpD,UADgB,MAAM,0BAA0B,KAAK,OAAO,OAAO,KAChD;IACnB,EACmB;AAEnB,UAAO,GAAG,cACR,WAAW,KAAK;IACd,QAAQ,OAAO;IACf,WAAW,OAAO;IAClB,gBAAgB;IACjB,CAAC,CACH;GAED,MAAM,WAAW,OAAO,GAAG,cAAc,aAAa,IAAI,CAAC;AAC3D,UAAO,GAAG,cACR,sBAAsB,KAAK;IACzB;IACA,WAAW,KAAK,UAAU,EAAE,QAAQ,OAAO,QAAQ,CAAC;IACrD,CAAC,CACH;AACD,UAAO;IAAE,MAAM;IAAyB;IAAU;;AAUpD,SAAO;GAAE,MAAM;GAAqB,UAPf,OAAO,GAAG,cAC7B,WAAW,KAAK;IACd,QAAQ,OAAO;IACf,WAAW,OAAO;IAClB,gBAAgB,QAAQ;IACzB,CAAC,CACH;GAC2D;GAC5D;;AAOJ,SAAS,sBACP,KACA,UACA,MAIA,SAOA;AACA,QAAO,GAAG,IAAI,aAAa;AAEzB,MAAI,KAAK,QAAQ,SAAS,OASxB,QAAO;GACL,MAAM;GACN,UAVa,OAAO,GAAG,cACvB,wBAAwB,KAAK;IAC3B,QAAQ,KAAK;IACb,UAAU,KAAK;IACf,gBAAgB;IAChB,qBAAqB,QAAQ;IAC9B,CAAC,CACH;GAIA;EAIH,MAAM,WAAW,IAAI,KAClB,QAAQ,IAAI,wBAAwB,WAAW,kBAAkB,IAChE,oBAAoB,SAAS,KAChC;EACD,MAAM,WAAW,OAAO,GAAG,cAAc,aAAa,IAAI,CAAC;AAC3D,WAAS,aAAa,IAAI,QAAQ,SAAS;AAE3C,MAAI,KAAK,QAAQ,eAAe,QAAW;AACzC,UAAO,GAAG,MACR,OAAO,KAAK,OAAO,eAAe,UAClC,GAAG,KACD,IAAI,UACF,oBACA,+CAA+C,KAAK,OAAO,aAC5D,CACF,CACF;AACD,YAAS,aAAa,IAAI,cAAc,KAAK,OAAO,WAAW;;AAGjE,SAAO;GACL,MAAM;GACN,UAAU,SAAS,UAAU;GAC7B;GACD;GACD"}
1
+ {"version":3,"file":"signin.js","names":[],"sources":["../../src/server/signin.ts"],"sourcesContent":["import type { Fx as FxType } from \"@robelest/fx\";\nimport { GenericId } from \"convex/values\";\n\nimport { handleDevice } from \"./device\";\nimport { AuthError, Fx } from \"./fx\";\nimport {\n callCreateVerificationCode,\n callRefreshSession,\n callSignIn,\n callVerifier,\n callVerifierSignature,\n callVerifyCodeAndSignIn,\n} from \"./mutations/index\";\nimport { handlePasskeyFx } from \"./passkey\";\nimport { redirectAbsoluteUrl, setURLSearchParam } from \"./redirects\";\nimport { handleTotp } from \"./totp\";\nimport {\n AuthProviderMaterializedConfig,\n ConvexCredentialsConfig,\n EmailConfig,\n GenericActionCtxWithAuthConfig,\n PhoneConfig,\n} from \"./types\";\nimport {\n AuthDataModel,\n SessionInfo,\n SessionInfoWithTokens,\n Tokens,\n queryTotpVerifiedByUserId,\n} from \"./types\";\nimport type { OAuthMaterializedConfig } from \"./types\";\nimport { generateRandomString } from \"./utils\";\nimport { requireEnv } from \"./utils\";\n\nconst DEFAULT_EMAIL_VERIFICATION_CODE_DURATION_S = 60 * 60 * 24; // 24 hours\n\ntype EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;\n\ntype SignInResult =\n | { kind: \"signedIn\"; signedIn: SessionInfo | null }\n | { kind: \"refreshTokens\"; signedIn: { tokens: Tokens } }\n | { kind: \"started\"; started: true }\n | { kind: \"redirect\"; redirect: string; verifier: string }\n | { kind: \"passkeyOptions\"; options: Record<string, any>; verifier: string }\n | { kind: \"totpRequired\"; verifier: string }\n | {\n kind: \"totpSetup\";\n uri: string;\n secret: string;\n verifier: string;\n totpId: string;\n }\n | {\n kind: \"deviceCode\";\n deviceCode: string;\n userCode: string;\n verificationUri: string;\n verificationUriComplete: string;\n expiresIn: number;\n interval: number;\n };\n\n/** @internal */\nexport async function signInImpl(\n ctx: EnrichedActionCtx,\n provider: AuthProviderMaterializedConfig | null,\n args: {\n accountId?: GenericId<\"Account\">;\n params?: Record<string, any>;\n verifier?: string;\n refreshToken?: string;\n calledBy?: string;\n },\n options: {\n generateTokens: boolean;\n allowExtraProviders: boolean;\n },\n): Promise<SignInResult> {\n const fx = signInFx(ctx, provider, args, options);\n return Fx.run(\n fx.pipe(Fx.recover((e) => Fx.fatal((e as AuthError).toConvexError()))),\n );\n}\n\n/**\n * Core sign-in pipeline as an Fx generator.\n *\n * Handles: refresh tokens, verification codes, then dispatches by\n * provider type using a dispatch map (no if-chain).\n */\nfunction signInFx(\n ctx: EnrichedActionCtx,\n provider: AuthProviderMaterializedConfig | null,\n args: {\n accountId?: GenericId<\"Account\">;\n params?: Record<string, any>;\n verifier?: string;\n refreshToken?: string;\n calledBy?: string;\n },\n options: {\n generateTokens: boolean;\n allowExtraProviders: boolean;\n },\n): FxType<SignInResult, AuthError> {\n return Fx.gen(function* () {\n // --- Refresh token (no provider) ---\n if (provider === null && args.refreshToken) {\n const tokens = yield* Fx.promise(() =>\n callRefreshSession(ctx, { refreshToken: args.refreshToken! }),\n );\n if (tokens === null) {\n return { kind: \"signedIn\" as const, signedIn: null };\n }\n return { kind: \"refreshTokens\" as const, signedIn: { tokens } };\n }\n\n // --- Verify code (no provider, code present) ---\n if (provider === null && args.params?.code !== undefined) {\n const result = yield* Fx.promise(() =>\n callVerifyCodeAndSignIn(ctx, {\n params: args.params,\n verifier: args.verifier,\n generateTokens: true,\n allowExtraProviders: options.allowExtraProviders,\n }),\n );\n return { kind: \"signedIn\" as const, signedIn: result };\n }\n\n // --- Provider is required past this point ---\n const resolvedProvider = yield* provider != null\n ? Fx.succeed(provider)\n : Fx.fail(new AuthError(\"SIGN_IN_MISSING_PARAMS\"));\n\n // --- Dispatch by provider type ---\n return yield* Fx.match(resolvedProvider).on(\"type\", {\n email: (p) => handleEmailAndPhoneProviderFx(ctx, p, args, options),\n phone: (p) => handleEmailAndPhoneProviderFx(ctx, p, args, options),\n credentials: (p) => handleCredentialsFx(ctx, p, args, options),\n oauth: (p) => handleOAuthProviderFx(ctx, p, args, options),\n passkey: (p) => handlePasskeyFx(ctx, p, args),\n totp: (p) => handleTotp(ctx, p, args),\n device: (p) => handleDevice(ctx, p, args),\n });\n });\n}\n\n// ============================================================================\n// Email / Phone\n// ============================================================================\n\nfunction handleEmailAndPhoneProviderFx(\n ctx: EnrichedActionCtx,\n provider: EmailConfig | PhoneConfig,\n args: {\n params?: Record<string, any>;\n accountId?: GenericId<\"Account\">;\n },\n options: {\n generateTokens: boolean;\n allowExtraProviders: boolean;\n },\n): FxType<\n | { kind: \"started\"; started: true }\n | { kind: \"signedIn\"; signedIn: SessionInfoWithTokens },\n AuthError\n> {\n return Fx.gen(function* () {\n // --- Code verification path ---\n if (args.params?.code !== undefined) {\n const result = yield* Fx.promise(() =>\n callVerifyCodeAndSignIn(ctx, {\n params: args.params,\n provider: provider.id,\n generateTokens: options.generateTokens,\n allowExtraProviders: options.allowExtraProviders,\n }),\n );\n const verified = yield* result != null\n ? Fx.succeed(result)\n : Fx.fail(new AuthError(\"INVALID_VERIFICATION_CODE\"));\n return {\n kind: \"signedIn\" as const,\n signedIn: verified as SessionInfoWithTokens,\n };\n }\n\n // --- Send verification code path ---\n const alphabet =\n \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n const code = provider.generateVerificationToken\n ? yield* Fx.from({\n ok: async () => provider.generateVerificationToken!(),\n err: () =>\n new AuthError(\n \"INTERNAL_ERROR\",\n \"Failed to generate verification token\",\n ),\n })\n : generateRandomString(32, alphabet);\n const expirationTime =\n Date.now() +\n (provider.maxAge ?? DEFAULT_EMAIL_VERIFICATION_CODE_DURATION_S) * 1000;\n\n const identifier = yield* Fx.promise(() =>\n callCreateVerificationCode(ctx, {\n provider: provider.id,\n accountId: args.accountId,\n email: args.params?.email,\n phone: args.params?.phone,\n code,\n expirationTime,\n allowExtraProviders: options.allowExtraProviders,\n }),\n );\n const destination = yield* Fx.promise(() =>\n redirectAbsoluteUrl(\n ctx.auth.config,\n (args.params ?? {}) as { redirectTo: unknown },\n ),\n );\n const verificationArgs = {\n identifier,\n url: setURLSearchParam(destination, \"code\", code),\n token: code,\n expires: new Date(expirationTime),\n };\n yield* Fx.match(provider).on(\"type\", {\n email: (p) =>\n Fx.from({\n ok: async () =>\n p.sendVerificationRequest(\n {\n ...verificationArgs,\n provider: p,\n request: new Request(\"http://localhost\"),\n },\n ctx,\n ),\n err: () =>\n new AuthError(\"INTERNAL_ERROR\", \"Failed to send email code\"),\n }),\n phone: (p) =>\n Fx.from({\n ok: async () =>\n p.sendVerificationRequest(\n { ...verificationArgs, provider: p },\n ctx,\n ),\n err: () =>\n new AuthError(\"INTERNAL_ERROR\", \"Failed to send phone code\"),\n }),\n });\n return { kind: \"started\" as const, started: true as const };\n });\n}\n\n// ============================================================================\n// Credentials\n// ============================================================================\n\nfunction handleCredentialsFx(\n ctx: EnrichedActionCtx,\n provider: ConvexCredentialsConfig,\n args: {\n params?: Record<string, any>;\n },\n options: {\n generateTokens: boolean;\n },\n): FxType<\n | { kind: \"signedIn\"; signedIn: SessionInfo | null }\n | { kind: \"totpRequired\"; verifier: string },\n AuthError\n> {\n return Fx.gen(function* () {\n const result = yield* Fx.promise(() =>\n provider.authorize(args.params ?? {}, ctx),\n );\n if (result === null) {\n return { kind: \"signedIn\" as const, signedIn: null };\n }\n\n // Check if user has TOTP 2FA enrolled before issuing tokens\n const hasTotpEnrolled = yield* Fx.promise(async () => {\n const totpDoc = await queryTotpVerifiedByUserId(ctx, result.userId);\n return totpDoc !== null;\n });\n if (hasTotpEnrolled) {\n // Create session but withhold tokens — TOTP verification needed\n yield* Fx.promise(() =>\n callSignIn(ctx, {\n userId: result.userId,\n sessionId: result.sessionId,\n generateTokens: false,\n }),\n );\n // Store userId in verifier so the TOTP verify flow can complete sign-in\n const verifier = yield* Fx.promise(() => callVerifier(ctx));\n yield* Fx.promise(() =>\n callVerifierSignature(ctx, {\n verifier,\n signature: JSON.stringify({ userId: result.userId }),\n }),\n );\n return { kind: \"totpRequired\" as const, verifier };\n }\n\n const idsAndTokens = yield* Fx.promise(() =>\n callSignIn(ctx, {\n userId: result.userId,\n sessionId: result.sessionId,\n generateTokens: options.generateTokens,\n }),\n );\n return { kind: \"signedIn\" as const, signedIn: idsAndTokens };\n });\n}\n\n// ============================================================================\n// OAuth\n// ============================================================================\n\nfunction handleOAuthProviderFx(\n ctx: EnrichedActionCtx,\n provider: OAuthMaterializedConfig,\n args: {\n params?: Record<string, any>;\n verifier?: string;\n },\n options: {\n allowExtraProviders: boolean;\n },\n): FxType<\n | { kind: \"signedIn\"; signedIn: SessionInfoWithTokens | null }\n | { kind: \"redirect\"; redirect: string; verifier: string },\n AuthError\n> {\n return Fx.gen(function* () {\n // --- Code verification path ---\n if (args.params?.code !== undefined) {\n const result = yield* Fx.promise(() =>\n callVerifyCodeAndSignIn(ctx, {\n params: args.params,\n verifier: args.verifier,\n generateTokens: true,\n allowExtraProviders: options.allowExtraProviders,\n }),\n );\n return {\n kind: \"signedIn\" as const,\n signedIn: result as SessionInfoWithTokens | null,\n };\n }\n\n // --- Build redirect URL ---\n const redirect = new URL(\n (process.env.CUSTOM_AUTH_SITE_URL ?? requireEnv(\"CONVEX_SITE_URL\")) +\n `/api/auth/signin/${provider.id}`,\n );\n const verifier = yield* Fx.promise(() => callVerifier(ctx));\n redirect.searchParams.set(\"code\", verifier);\n\n if (args.params?.redirectTo !== undefined) {\n yield* Fx.guard(\n typeof args.params.redirectTo !== \"string\",\n Fx.fail(\n new AuthError(\n \"INVALID_REDIRECT\",\n `Expected \\`redirectTo\\` to be a string, got ${args.params.redirectTo}`,\n ),\n ),\n );\n redirect.searchParams.set(\"redirectTo\", args.params.redirectTo);\n }\n\n return {\n kind: \"redirect\" as const,\n redirect: redirect.toString(),\n verifier,\n };\n });\n}\n"],"mappings":"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"}
@@ -1,348 +1 @@
1
- import { OAuthMaterializedConfig, OAuthProfile, SAMLAttributeMapping } from "./types.js";
2
-
3
- //#region src/server/sso.d.ts
4
- type ParsedSamlMetadata = {
5
- issuer: string;
6
- sso: {
7
- redirect?: string;
8
- post?: string;
9
- };
10
- slo: {
11
- redirect?: string;
12
- post?: string;
13
- };
14
- signingCert: string | string[] | null;
15
- encryptionCert: string | string[] | null;
16
- nameIdFormats: string[];
17
- wantsSignedAuthnRequests: boolean;
18
- };
19
- type EnterpriseSamlSource = {
20
- kind: "enterprise";
21
- id: string;
22
- };
23
- type EnterpriseSamlRelayState = {
24
- source: EnterpriseSamlSource;
25
- signature: string;
26
- requestId: string;
27
- state: string;
28
- redirectTo?: string;
29
- };
30
- type EnterpriseSamlUrls = {
31
- metadataUrl: string;
32
- acsUrl: string;
33
- sloUrl?: string;
34
- };
35
- type EnterpriseSamlLoadedSource = {
36
- source: EnterpriseSamlSource;
37
- config: unknown;
38
- status?: string;
39
- };
40
- type EnterpriseSamlHttpRequest = {
41
- url: URL;
42
- body: Record<string, string>;
43
- query: Record<string, string>;
44
- binding: "redirect" | "post";
45
- relayState?: string;
46
- hasSamlRequest: boolean;
47
- hasSamlResponse: boolean;
48
- };
49
- type ScimListRequest = {
50
- startIndex: number;
51
- count: number;
52
- filter?: {
53
- attribute: string;
54
- value: string;
55
- };
56
- };
57
- declare const SCIM_USER_SCHEMA_ID = "urn:ietf:params:scim:schemas:core:2.0:User";
58
- declare const SCIM_GROUP_SCHEMA_ID = "urn:ietf:params:scim:schemas:core:2.0:Group";
59
- declare const ENTERPRISE_OIDC_PROVIDER_PREFIX = "enterprise:oidc:";
60
- declare const ENTERPRISE_SAML_PROVIDER_PREFIX = "enterprise:saml:";
61
- declare function normalizeDomain(domain: string): string;
62
- declare function enterpriseOidcProviderId(enterpriseId: string): string;
63
- declare function enterpriseSamlProviderId(enterpriseId: string): string;
64
- declare function getEnterpriseSamlUrls(opts: {
65
- rootUrl: string;
66
- source: EnterpriseSamlSource;
67
- }): EnterpriseSamlUrls;
68
- declare function getEnterpriseOidcUrls(opts: {
69
- rootUrl: string;
70
- enterpriseId: string;
71
- }): {
72
- signInUrl: string;
73
- callbackUrl: string;
74
- };
75
- declare function isEnterpriseSamlSourceActive(source: EnterpriseSamlLoadedSource): boolean;
76
- declare function isEnterpriseProviderId(providerId: string): boolean;
77
- declare function getOidcConfig(config: unknown): Record<string, any>;
78
- declare function getSamlConfig(config: unknown): Record<string, any>;
79
- declare function upsertProtocolConfig(config: unknown, protocol: "oidc" | "saml", protocolConfig: Record<string, unknown>): {
80
- protocols: Record<string, any>;
81
- };
82
- declare function createSamlPostBindingResponse(opts: {
83
- endpoint: string;
84
- parameter: "SAMLRequest" | "SAMLResponse";
85
- value: string;
86
- relayState?: string;
87
- }): Response;
88
- declare function decodeRelayState(value: string | null): Record<string, unknown>;
89
- declare function encodeEnterpriseSamlRelayState(value: EnterpriseSamlRelayState): string;
90
- declare function decodeEnterpriseSamlRelayStateOrThrow(value: string | null): EnterpriseSamlRelayState;
91
- declare function readRequestBody(request: Request): Promise<Record<string, string>>;
92
- declare function readEnterpriseSamlHttpRequest(request: Request): Promise<EnterpriseSamlHttpRequest>;
93
- declare function createEnterpriseOidcProvider(config: Record<string, any>, redirectUri: string): Promise<{
94
- provider: {
95
- createAuthorizationURL(state: string, codeVerifier: string, requestedScopes: string[]): URL;
96
- validateAuthorizationCode(code: string, codeVerifier?: string): Promise<{
97
- data: Record<string, any>;
98
- idToken(): string;
99
- accessToken(): string;
100
- }>;
101
- };
102
- oauthConfig: {
103
- readonly scopes: string[];
104
- readonly nonce: true;
105
- readonly validateTokens: (tokens: any, ctx: {
106
- nonce?: string;
107
- }) => Promise<void>;
108
- readonly accountLinking: any;
109
- readonly profile: (tokens: any) => Promise<OAuthProfile>;
110
- };
111
- }>;
112
- declare function createSyntheticOAuthMaterializedConfig(providerId: string): OAuthMaterializedConfig;
113
- declare function parseSamlIdpMetadata(metadata: string): ParsedSamlMetadata;
114
- declare function createServiceProviderMetadata(opts: {
115
- entityId: string;
116
- acsUrl: string;
117
- sloUrl?: string;
118
- authnRequestsSigned?: boolean;
119
- signingCert?: string | string[];
120
- encryptCert?: string | string[];
121
- privateKey?: string;
122
- privateKeyPass?: string;
123
- encPrivateKey?: string;
124
- encPrivateKeyPass?: string;
125
- }): any;
126
- declare function createEnterpriseSamlMetadataXml(opts: {
127
- rootUrl: string;
128
- source: EnterpriseSamlSource;
129
- config: unknown;
130
- }): any;
131
- declare function getSamlServiceProviderOptions(opts: {
132
- rootUrl: string;
133
- source: EnterpriseSamlSource;
134
- config: unknown;
135
- overrides?: {
136
- entityId?: string;
137
- acsUrl?: string;
138
- sloUrl?: string;
139
- };
140
- relayState?: string;
141
- }): {
142
- entityId: any;
143
- acsUrl: any;
144
- sloUrl: any;
145
- relayState: string | undefined;
146
- authnRequestsSigned: any;
147
- signingCert: any;
148
- encryptCert: any;
149
- privateKey: any;
150
- privateKeyPass: any;
151
- encPrivateKey: any;
152
- encPrivateKeyPass: any;
153
- };
154
- declare function createSamlServiceProvider(opts: {
155
- entityId: string;
156
- acsUrl: string;
157
- sloUrl?: string;
158
- relayState?: string;
159
- authnRequestsSigned?: boolean;
160
- signingCert?: string | string[];
161
- encryptCert?: string | string[];
162
- privateKey?: string;
163
- privateKeyPass?: string;
164
- encPrivateKey?: string;
165
- encPrivateKeyPass?: string;
166
- }): any;
167
- declare function createEnterpriseSamlRuntime(opts: {
168
- rootUrl: string;
169
- source: EnterpriseSamlSource;
170
- config: unknown;
171
- relayState?: string;
172
- overrides?: {
173
- entityId?: string;
174
- acsUrl?: string;
175
- sloUrl?: string;
176
- };
177
- }): {
178
- saml: Record<string, any>;
179
- sp: any;
180
- idp: any;
181
- urls: EnterpriseSamlUrls;
182
- };
183
- declare function createEnterpriseSamlSignInRequest(opts: {
184
- rootUrl: string;
185
- source: EnterpriseSamlSource;
186
- config: unknown;
187
- state: string;
188
- signature: string;
189
- redirectTo?: string;
190
- }): {
191
- requestId: string;
192
- binding: string;
193
- relayState: string;
194
- redirectUrl: string | undefined;
195
- post: {
196
- endpoint: string;
197
- value: string;
198
- } | undefined;
199
- };
200
- declare function parseEnterpriseSamlLoginResponse(opts: {
201
- request: Request;
202
- rootUrl: string;
203
- source: EnterpriseSamlSource;
204
- config: unknown;
205
- }): Promise<{
206
- runtime: {
207
- saml: Record<string, any>;
208
- sp: any;
209
- idp: any;
210
- urls: EnterpriseSamlUrls;
211
- };
212
- parsed: any;
213
- relayState: EnterpriseSamlRelayState;
214
- url: URL;
215
- body: Record<string, string>;
216
- query: Record<string, string>;
217
- binding: "redirect" | "post";
218
- hasSamlRequest: boolean;
219
- hasSamlResponse: boolean;
220
- }>;
221
- declare function validateEnterpriseSamlLoginRelayState(opts: {
222
- relayState: EnterpriseSamlRelayState;
223
- source: EnterpriseSamlSource;
224
- inResponseTo?: string;
225
- }): void;
226
- declare function parseEnterpriseSamlLogoutMessage(opts: {
227
- request: Request;
228
- rootUrl: string;
229
- source: EnterpriseSamlSource;
230
- config: unknown;
231
- }): Promise<{
232
- runtime: {
233
- saml: Record<string, any>;
234
- sp: any;
235
- idp: any;
236
- urls: EnterpriseSamlUrls;
237
- };
238
- parsedRequest: any;
239
- url: URL;
240
- body: Record<string, string>;
241
- query: Record<string, string>;
242
- binding: "redirect" | "post";
243
- relayState?: string;
244
- hasSamlRequest: boolean;
245
- hasSamlResponse: boolean;
246
- }>;
247
- declare function createEnterpriseOidcRuntime(opts: {
248
- rootUrl: string;
249
- enterpriseId: string;
250
- config: unknown;
251
- }): Promise<{
252
- signInUrl: string;
253
- callbackUrl: string;
254
- oidc: Record<string, any>;
255
- providerId: string;
256
- provider: {
257
- createAuthorizationURL(state: string, codeVerifier: string, requestedScopes: string[]): URL;
258
- validateAuthorizationCode(code: string, codeVerifier?: string): Promise<{
259
- data: Record<string, any>;
260
- idToken(): string;
261
- accessToken(): string;
262
- }>;
263
- };
264
- oauthConfig: {
265
- readonly scopes: string[];
266
- readonly nonce: true;
267
- readonly validateTokens: (tokens: any, ctx: {
268
- nonce?: string;
269
- }) => Promise<void>;
270
- readonly accountLinking: any;
271
- readonly profile: (tokens: any) => Promise<OAuthProfile>;
272
- };
273
- }>;
274
- declare function profileFromSamlExtract(extract: any, mapping?: SAMLAttributeMapping): {
275
- id: string;
276
- email: string | undefined;
277
- emailVerified: boolean | undefined;
278
- name: string | undefined;
279
- samlAttributes: Record<string, unknown>;
280
- samlSessionIndex: string | undefined;
281
- };
282
- declare function parseScimPath(pathname: string): {
283
- enterpriseId: string;
284
- resource: string;
285
- resourceId: undefined;
286
- } | {
287
- enterpriseId: string;
288
- resource: string;
289
- resourceId: string;
290
- };
291
- declare function parseScimListRequest(url: URL): ScimListRequest;
292
- declare function scimJson(data: unknown, status?: number, headers?: HeadersInit): Response;
293
- declare function scimError(status: number, scimType: string, detail: string): Response;
294
- declare function serializeScimUser(args: {
295
- id: string;
296
- user: Record<string, any>;
297
- externalId?: string;
298
- active?: boolean;
299
- location?: string;
300
- }): {
301
- schemas: string[];
302
- id: string;
303
- externalId: string | undefined;
304
- meta: {
305
- resourceType: string;
306
- location: string | undefined;
307
- };
308
- userName: any;
309
- active: boolean;
310
- name: {
311
- formatted: any;
312
- } | undefined;
313
- emails: {
314
- value: string;
315
- primary: boolean;
316
- }[] | undefined;
317
- phoneNumbers: {
318
- value: string;
319
- primary: boolean;
320
- }[] | undefined;
321
- displayName: any;
322
- };
323
- declare function serializeScimGroup(args: {
324
- id: string;
325
- group: Record<string, any>;
326
- externalId?: string;
327
- members?: Array<{
328
- value: string;
329
- display?: string;
330
- }>;
331
- location?: string;
332
- }): {
333
- schemas: string[];
334
- id: string;
335
- externalId: string | undefined;
336
- meta: {
337
- resourceType: string;
338
- location: string | undefined;
339
- };
340
- displayName: any;
341
- members: {
342
- value: string;
343
- display?: string;
344
- }[];
345
- };
346
- //#endregion
347
- export { ENTERPRISE_OIDC_PROVIDER_PREFIX, ENTERPRISE_SAML_PROVIDER_PREFIX, EnterpriseSamlHttpRequest, EnterpriseSamlLoadedSource, EnterpriseSamlRelayState, EnterpriseSamlSource, EnterpriseSamlUrls, ParsedSamlMetadata, SCIM_GROUP_SCHEMA_ID, SCIM_USER_SCHEMA_ID, ScimListRequest, createEnterpriseOidcProvider, createEnterpriseOidcRuntime, createEnterpriseSamlMetadataXml, createEnterpriseSamlRuntime, createEnterpriseSamlSignInRequest, createSamlPostBindingResponse, createSamlServiceProvider, createServiceProviderMetadata, createSyntheticOAuthMaterializedConfig, decodeEnterpriseSamlRelayStateOrThrow, decodeRelayState, encodeEnterpriseSamlRelayState, enterpriseOidcProviderId, enterpriseSamlProviderId, getEnterpriseOidcUrls, getEnterpriseSamlUrls, getOidcConfig, getSamlConfig, getSamlServiceProviderOptions, isEnterpriseProviderId, isEnterpriseSamlSourceActive, normalizeDomain, parseEnterpriseSamlLoginResponse, parseEnterpriseSamlLogoutMessage, parseSamlIdpMetadata, parseScimListRequest, parseScimPath, profileFromSamlExtract, readEnterpriseSamlHttpRequest, readRequestBody, scimError, scimJson, serializeScimGroup, serializeScimUser, upsertProtocolConfig, validateEnterpriseSamlLoginRelayState };
348
- //# sourceMappingURL=sso.d.ts.map
1
+ export { };