@robelest/convex-auth 0.0.4-preview.21 → 0.0.4-preview.23

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 (310) hide show
  1. package/dist/authorization/index.d.ts +1 -1
  2. package/dist/authorization/index.js +1 -1
  3. package/dist/authorization/index.js.map +1 -1
  4. package/dist/client/index.d.ts +1 -2
  5. package/dist/client/index.d.ts.map +1 -1
  6. package/dist/client/index.js +36 -39
  7. package/dist/client/index.js.map +1 -1
  8. package/dist/component/client/index.d.ts +1 -2
  9. package/dist/component/convex.config.d.ts +2 -2
  10. package/dist/component/convex.config.d.ts.map +1 -1
  11. package/dist/component/model.d.ts +5 -5
  12. package/dist/component/model.d.ts.map +1 -1
  13. package/dist/component/public/enterprise/audit.d.ts.map +1 -1
  14. package/dist/component/public/enterprise/audit.js.map +1 -1
  15. package/dist/component/public/enterprise/core.d.ts.map +1 -1
  16. package/dist/component/public/enterprise/core.js.map +1 -1
  17. package/dist/component/public/enterprise/domains.d.ts.map +1 -1
  18. package/dist/component/public/enterprise/domains.js.map +1 -1
  19. package/dist/component/public/enterprise/scim.d.ts.map +1 -1
  20. package/dist/component/public/enterprise/scim.js.map +1 -1
  21. package/dist/component/public/enterprise/secrets.d.ts.map +1 -1
  22. package/dist/component/public/enterprise/secrets.js.map +1 -1
  23. package/dist/component/public/enterprise/webhooks.d.ts.map +1 -1
  24. package/dist/component/public/enterprise/webhooks.js.map +1 -1
  25. package/dist/component/public/factors/devices.d.ts.map +1 -1
  26. package/dist/component/public/factors/devices.js.map +1 -1
  27. package/dist/component/public/factors/passkeys.d.ts.map +1 -1
  28. package/dist/component/public/factors/passkeys.js.map +1 -1
  29. package/dist/component/public/factors/totp.d.ts.map +1 -1
  30. package/dist/component/public/factors/totp.js.map +1 -1
  31. package/dist/component/public/groups/core.js.map +1 -1
  32. package/dist/component/public/groups/invites.d.ts.map +1 -1
  33. package/dist/component/public/groups/invites.js.map +1 -1
  34. package/dist/component/public/groups/members.d.ts.map +1 -1
  35. package/dist/component/public/groups/members.js.map +1 -1
  36. package/dist/component/public/identity/accounts.d.ts.map +1 -1
  37. package/dist/component/public/identity/accounts.js.map +1 -1
  38. package/dist/component/public/identity/codes.d.ts.map +1 -1
  39. package/dist/component/public/identity/codes.js.map +1 -1
  40. package/dist/component/public/identity/sessions.d.ts.map +1 -1
  41. package/dist/component/public/identity/sessions.js.map +1 -1
  42. package/dist/component/public/identity/tokens.d.ts.map +1 -1
  43. package/dist/component/public/identity/tokens.js.map +1 -1
  44. package/dist/component/public/identity/users.d.ts.map +1 -1
  45. package/dist/component/public/identity/users.js.map +1 -1
  46. package/dist/component/public/identity/verifiers.d.ts.map +1 -1
  47. package/dist/component/public/identity/verifiers.js.map +1 -1
  48. package/dist/component/public/security/keys.d.ts.map +1 -1
  49. package/dist/component/public/security/keys.js.map +1 -1
  50. package/dist/component/public/security/limits.d.ts.map +1 -1
  51. package/dist/component/public/security/limits.js.map +1 -1
  52. package/dist/component/schema.d.ts +39 -39
  53. package/dist/component/server/auth.d.ts +95 -52
  54. package/dist/component/server/auth.d.ts.map +1 -1
  55. package/dist/component/server/auth.js +63 -43
  56. package/dist/component/server/auth.js.map +1 -1
  57. package/dist/component/server/core.js +116 -235
  58. package/dist/component/server/core.js.map +1 -1
  59. package/dist/component/server/crypto.js +25 -7
  60. package/dist/component/server/crypto.js.map +1 -1
  61. package/dist/component/server/device.js +58 -15
  62. package/dist/component/server/device.js.map +1 -1
  63. package/dist/component/server/enterprise/domain.js +148 -59
  64. package/dist/component/server/enterprise/domain.js.map +1 -1
  65. package/dist/component/server/enterprise/http.js +36 -15
  66. package/dist/component/server/enterprise/http.js.map +1 -1
  67. package/dist/component/server/enterprise/oidc.js +1 -1
  68. package/dist/component/server/http.js +26 -21
  69. package/dist/component/server/http.js.map +1 -1
  70. package/dist/component/server/identity.js +5 -2
  71. package/dist/component/server/identity.js.map +1 -1
  72. package/dist/component/server/limits.js +21 -30
  73. package/dist/component/server/limits.js.map +1 -1
  74. package/dist/component/server/mutations/account.js +12 -10
  75. package/dist/component/server/mutations/account.js.map +1 -1
  76. package/dist/component/server/mutations/code.js +5 -2
  77. package/dist/component/server/mutations/code.js.map +1 -1
  78. package/dist/component/server/mutations/invalidate.js +1 -1
  79. package/dist/component/server/mutations/invalidate.js.map +1 -1
  80. package/dist/component/server/mutations/oauth.js +10 -4
  81. package/dist/component/server/mutations/oauth.js.map +1 -1
  82. package/dist/component/server/mutations/refresh.js +2 -2
  83. package/dist/component/server/mutations/refresh.js.map +1 -1
  84. package/dist/component/server/mutations/register.js +46 -42
  85. package/dist/component/server/mutations/register.js.map +1 -1
  86. package/dist/component/server/mutations/retrieve.js +21 -25
  87. package/dist/component/server/mutations/retrieve.js.map +1 -1
  88. package/dist/component/server/mutations/signature.js +10 -4
  89. package/dist/component/server/mutations/signature.js.map +1 -1
  90. package/dist/component/server/mutations/signout.js.map +1 -1
  91. package/dist/component/server/mutations/store.js +9 -24
  92. package/dist/component/server/mutations/store.js.map +1 -1
  93. package/dist/component/server/mutations/verifier.js.map +1 -1
  94. package/dist/component/server/mutations/verify.js +1 -1
  95. package/dist/component/server/mutations/verify.js.map +1 -1
  96. package/dist/component/server/oauth.js +53 -16
  97. package/dist/component/server/oauth.js.map +1 -1
  98. package/dist/component/server/passkey.js +115 -31
  99. package/dist/component/server/passkey.js.map +1 -1
  100. package/dist/component/server/redirects.js +9 -3
  101. package/dist/component/server/redirects.js.map +1 -1
  102. package/dist/component/server/refresh.js +10 -7
  103. package/dist/component/server/refresh.js.map +1 -1
  104. package/dist/component/server/runtime.d.ts +3 -3
  105. package/dist/component/server/runtime.d.ts.map +1 -1
  106. package/dist/component/server/runtime.js +62 -20
  107. package/dist/component/server/runtime.js.map +1 -1
  108. package/dist/component/server/signin.js +34 -10
  109. package/dist/component/server/signin.js.map +1 -1
  110. package/dist/component/server/totp.js +79 -19
  111. package/dist/component/server/totp.js.map +1 -1
  112. package/dist/component/server/types.d.ts +12 -20
  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 +6 -3
  116. package/dist/component/server/users.js.map +1 -1
  117. package/dist/component/server/utils.js +10 -4
  118. package/dist/component/server/utils.js.map +1 -1
  119. package/dist/core/types.d.ts +14 -22
  120. package/dist/core/types.d.ts.map +1 -1
  121. package/dist/factors/device.js +8 -9
  122. package/dist/factors/device.js.map +1 -1
  123. package/dist/factors/passkey.js +18 -21
  124. package/dist/factors/passkey.js.map +1 -1
  125. package/dist/providers/password.js +66 -81
  126. package/dist/providers/password.js.map +1 -1
  127. package/dist/runtime/invite.js +2 -8
  128. package/dist/runtime/invite.js.map +1 -1
  129. package/dist/server/auth.d.ts +95 -52
  130. package/dist/server/auth.d.ts.map +1 -1
  131. package/dist/server/auth.js +63 -43
  132. package/dist/server/auth.js.map +1 -1
  133. package/dist/server/core.d.ts +71 -159
  134. package/dist/server/core.d.ts.map +1 -1
  135. package/dist/server/core.js +116 -235
  136. package/dist/server/core.js.map +1 -1
  137. package/dist/server/crypto.d.ts.map +1 -1
  138. package/dist/server/crypto.js +25 -7
  139. package/dist/server/crypto.js.map +1 -1
  140. package/dist/server/device.js +58 -15
  141. package/dist/server/device.js.map +1 -1
  142. package/dist/server/enterprise/domain.d.ts +0 -8
  143. package/dist/server/enterprise/domain.d.ts.map +1 -1
  144. package/dist/server/enterprise/domain.js +148 -59
  145. package/dist/server/enterprise/domain.js.map +1 -1
  146. package/dist/server/enterprise/http.d.ts.map +1 -1
  147. package/dist/server/enterprise/http.js +35 -14
  148. package/dist/server/enterprise/http.js.map +1 -1
  149. package/dist/server/http.d.ts +2 -2
  150. package/dist/server/http.d.ts.map +1 -1
  151. package/dist/server/http.js +25 -20
  152. package/dist/server/http.js.map +1 -1
  153. package/dist/server/identity.js +5 -2
  154. package/dist/server/identity.js.map +1 -1
  155. package/dist/server/index.d.ts +2 -2
  156. package/dist/server/limits.js +21 -30
  157. package/dist/server/limits.js.map +1 -1
  158. package/dist/server/mounts.d.ts +26 -64
  159. package/dist/server/mounts.d.ts.map +1 -1
  160. package/dist/server/mounts.js +45 -106
  161. package/dist/server/mounts.js.map +1 -1
  162. package/dist/server/mutations/account.d.ts +8 -9
  163. package/dist/server/mutations/account.d.ts.map +1 -1
  164. package/dist/server/mutations/account.js +11 -9
  165. package/dist/server/mutations/account.js.map +1 -1
  166. package/dist/server/mutations/code.d.ts +13 -13
  167. package/dist/server/mutations/code.d.ts.map +1 -1
  168. package/dist/server/mutations/code.js +5 -2
  169. package/dist/server/mutations/code.js.map +1 -1
  170. package/dist/server/mutations/invalidate.d.ts +4 -4
  171. package/dist/server/mutations/invalidate.d.ts.map +1 -1
  172. package/dist/server/mutations/invalidate.js.map +1 -1
  173. package/dist/server/mutations/oauth.d.ts +12 -10
  174. package/dist/server/mutations/oauth.d.ts.map +1 -1
  175. package/dist/server/mutations/oauth.js +9 -3
  176. package/dist/server/mutations/oauth.js.map +1 -1
  177. package/dist/server/mutations/refresh.d.ts +3 -3
  178. package/dist/server/mutations/refresh.d.ts.map +1 -1
  179. package/dist/server/mutations/refresh.js +1 -1
  180. package/dist/server/mutations/refresh.js.map +1 -1
  181. package/dist/server/mutations/register.d.ts +11 -11
  182. package/dist/server/mutations/register.d.ts.map +1 -1
  183. package/dist/server/mutations/register.js +45 -41
  184. package/dist/server/mutations/register.js.map +1 -1
  185. package/dist/server/mutations/retrieve.d.ts +6 -6
  186. package/dist/server/mutations/retrieve.d.ts.map +1 -1
  187. package/dist/server/mutations/retrieve.js +20 -24
  188. package/dist/server/mutations/retrieve.js.map +1 -1
  189. package/dist/server/mutations/signature.d.ts +6 -7
  190. package/dist/server/mutations/signature.d.ts.map +1 -1
  191. package/dist/server/mutations/signature.js +9 -3
  192. package/dist/server/mutations/signature.js.map +1 -1
  193. package/dist/server/mutations/signin.d.ts +5 -5
  194. package/dist/server/mutations/signin.d.ts.map +1 -1
  195. package/dist/server/mutations/signout.js.map +1 -1
  196. package/dist/server/mutations/store.d.ts +97 -97
  197. package/dist/server/mutations/store.d.ts.map +1 -1
  198. package/dist/server/mutations/store.js +8 -23
  199. package/dist/server/mutations/store.js.map +1 -1
  200. package/dist/server/mutations/verifier.js.map +1 -1
  201. package/dist/server/mutations/verify.d.ts +10 -10
  202. package/dist/server/mutations/verify.d.ts.map +1 -1
  203. package/dist/server/mutations/verify.js.map +1 -1
  204. package/dist/server/oauth.js +53 -16
  205. package/dist/server/oauth.js.map +1 -1
  206. package/dist/server/passkey.d.ts +2 -2
  207. package/dist/server/passkey.d.ts.map +1 -1
  208. package/dist/server/passkey.js +114 -30
  209. package/dist/server/passkey.js.map +1 -1
  210. package/dist/server/redirects.js +9 -3
  211. package/dist/server/redirects.js.map +1 -1
  212. package/dist/server/refresh.js +10 -7
  213. package/dist/server/refresh.js.map +1 -1
  214. package/dist/server/runtime.d.ts +14 -14
  215. package/dist/server/runtime.d.ts.map +1 -1
  216. package/dist/server/runtime.js +61 -19
  217. package/dist/server/runtime.js.map +1 -1
  218. package/dist/server/signin.js +34 -10
  219. package/dist/server/signin.js.map +1 -1
  220. package/dist/server/ssr.d.ts.map +1 -1
  221. package/dist/server/ssr.js +175 -184
  222. package/dist/server/ssr.js.map +1 -1
  223. package/dist/server/totp.js +78 -18
  224. package/dist/server/totp.js.map +1 -1
  225. package/dist/server/types.d.ts +13 -21
  226. package/dist/server/types.d.ts.map +1 -1
  227. package/dist/server/types.js.map +1 -1
  228. package/dist/server/users.js +6 -3
  229. package/dist/server/users.js.map +1 -1
  230. package/dist/server/utils.js +10 -4
  231. package/dist/server/utils.js.map +1 -1
  232. package/package.json +2 -6
  233. package/src/authorization/index.ts +1 -1
  234. package/src/cli/index.ts +1 -1
  235. package/src/client/core/types.ts +14 -14
  236. package/src/client/factors/device.ts +10 -12
  237. package/src/client/factors/passkey.ts +23 -26
  238. package/src/client/index.ts +54 -64
  239. package/src/client/runtime/invite.ts +5 -7
  240. package/src/component/index.ts +1 -0
  241. package/src/component/public/enterprise/audit.ts +6 -1
  242. package/src/component/public/enterprise/core.ts +1 -0
  243. package/src/component/public/enterprise/domains.ts +5 -1
  244. package/src/component/public/enterprise/scim.ts +1 -0
  245. package/src/component/public/enterprise/secrets.ts +1 -0
  246. package/src/component/public/enterprise/webhooks.ts +1 -0
  247. package/src/component/public/factors/devices.ts +1 -0
  248. package/src/component/public/factors/passkeys.ts +1 -0
  249. package/src/component/public/factors/totp.ts +1 -0
  250. package/src/component/public/groups/core.ts +1 -1
  251. package/src/component/public/groups/invites.ts +7 -1
  252. package/src/component/public/groups/members.ts +1 -0
  253. package/src/component/public/identity/accounts.ts +1 -0
  254. package/src/component/public/identity/codes.ts +1 -0
  255. package/src/component/public/identity/sessions.ts +1 -0
  256. package/src/component/public/identity/tokens.ts +1 -0
  257. package/src/component/public/identity/users.ts +1 -0
  258. package/src/component/public/identity/verifiers.ts +1 -0
  259. package/src/component/public/security/keys.ts +1 -0
  260. package/src/component/public/security/limits.ts +1 -0
  261. package/src/providers/password.ts +89 -110
  262. package/src/server/auth.ts +177 -111
  263. package/src/server/core.ts +197 -233
  264. package/src/server/crypto.ts +31 -29
  265. package/src/server/device.ts +65 -32
  266. package/src/server/enterprise/domain.ts +158 -170
  267. package/src/server/enterprise/http.ts +46 -39
  268. package/src/server/http.ts +36 -30
  269. package/src/server/identity.ts +5 -5
  270. package/src/server/index.ts +2 -0
  271. package/src/server/limits.ts +53 -80
  272. package/src/server/mounts.ts +47 -74
  273. package/src/server/mutations/account.ts +22 -36
  274. package/src/server/mutations/code.ts +6 -6
  275. package/src/server/mutations/invalidate.ts +1 -1
  276. package/src/server/mutations/oauth.ts +14 -8
  277. package/src/server/mutations/refresh.ts +5 -4
  278. package/src/server/mutations/register.ts +87 -132
  279. package/src/server/mutations/retrieve.ts +44 -44
  280. package/src/server/mutations/signature.ts +13 -6
  281. package/src/server/mutations/signout.ts +1 -1
  282. package/src/server/mutations/store.ts +16 -31
  283. package/src/server/mutations/verifier.ts +1 -1
  284. package/src/server/mutations/verify.ts +3 -5
  285. package/src/server/oauth.ts +60 -69
  286. package/src/server/passkey.ts +567 -517
  287. package/src/server/redirects.ts +10 -6
  288. package/src/server/refresh.ts +14 -18
  289. package/src/server/runtime.ts +70 -55
  290. package/src/server/signin.ts +44 -37
  291. package/src/server/ssr.ts +390 -407
  292. package/src/server/totp.ts +85 -35
  293. package/src/server/types.ts +19 -22
  294. package/src/server/users.ts +7 -6
  295. package/src/server/utils.ts +10 -12
  296. package/dist/component/server/authError.js +0 -34
  297. package/dist/component/server/authError.js.map +0 -1
  298. package/dist/component/server/errors.d.ts +0 -1
  299. package/dist/component/server/errors.js +0 -137
  300. package/dist/component/server/errors.js.map +0 -1
  301. package/dist/server/authError.d.ts +0 -46
  302. package/dist/server/authError.d.ts.map +0 -1
  303. package/dist/server/authError.js +0 -34
  304. package/dist/server/authError.js.map +0 -1
  305. package/dist/server/errors.d.ts +0 -177
  306. package/dist/server/errors.d.ts.map +0 -1
  307. package/dist/server/errors.js +0 -212
  308. package/dist/server/errors.js.map +0 -1
  309. package/src/server/authError.ts +0 -44
  310. package/src/server/errors.ts +0 -290
@@ -1 +1 @@
1
- {"version":3,"file":"passkey.js","names":["hasSiteUrl","hasRpId","siteUrl"],"sources":["../../src/server/passkey.ts"],"sourcesContent":["/**\n * Server-side WebAuthn ceremony logic for passkey authentication.\n *\n * Handles the four phases of the WebAuthn flow:\n * 1. registerOptions — generate PublicKeyCredentialCreationOptions\n * 2. registerVerify — verify attestation and store credential\n * 3. authOptions — generate PublicKeyCredentialRequestOptions\n * 4. authVerify — verify assertion signature and sign in\n *\n * Uses `@oslojs/webauthn` for attestation/assertion parsing and\n * `@oslojs/crypto` for signature verification.\n *\n * All functions return `Fx<A, AuthError>` composed via `Fx.chain` pipelines.\n *\n * @module\n */\n\nimport {\n p256,\n verifyECDSASignature,\n decodeSEC1PublicKey,\n decodePKIXECDSASignature,\n} from \"@oslojs/crypto/ecdsa\";\nimport {\n RSAPublicKey,\n decodePKCS1RSAPublicKey,\n sha256ObjectIdentifier,\n verifyRSASSAPKCS1v15Signature,\n} from \"@oslojs/crypto/rsa\";\nimport { sha256 } from \"@oslojs/crypto/sha2\";\nimport {\n encodeBase64urlNoPadding,\n decodeBase64urlIgnorePadding,\n} from \"@oslojs/encoding\";\nimport {\n parseAttestationObject,\n parseClientDataJSON,\n parseAuthenticatorData,\n createAssertionSignatureMessage,\n ClientDataType,\n coseAlgorithmES256,\n coseAlgorithmRS256,\n COSEKeyType,\n} from \"@oslojs/webauthn\";\nimport type { Fx as FxType } from \"@robelest/fx\";\n\nimport { authDb } from \"./db\";\nimport { Fx } from \"@robelest/fx\";\n\nimport { AuthError } from \"./authError\";\nimport { userIdFromIdentitySubject } from \"./identity\";\nimport { callSignIn, callVerifier } from \"./mutations/index\";\nimport { callVerifierSignature } from \"./mutations/signature\";\nimport { PasskeyProviderConfig, GenericActionCtxWithAuthConfig } from \"./types\";\nimport {\n AuthDataModel,\n SessionInfo,\n queryUserById,\n queryUserByVerifiedEmail,\n queryPasskeysByUserId,\n queryPasskeyByCredentialId,\n queryVerifierById,\n mutatePasskeyInsert,\n mutatePasskeyUpdateCounter,\n mutateVerifierDelete,\n} from \"./types\";\n\ntype EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;\n\n// ============================================================================\n// Resolve RP options — Fx pipeline with validation\n// ============================================================================\n\n/** Resolved relying party configuration. */\ninterface RpOptions {\n rpName: string;\n rpId: string;\n origin: string | string[];\n attestation: string;\n userVerification: string;\n residentKey: string;\n authenticatorAttachment?: string;\n algorithms: number[];\n challengeExpirationMs: number;\n}\n\n/**\n * Resolve passkey relying party options from provider config and environment.\n *\n * Returns `Fx<RpOptions, AuthError>` — fails if neither SITE_URL nor rpId\n * is configured.\n */\nconst resolveRpOptionsFx = (\n provider: PasskeyProviderConfig,\n): FxType<RpOptions, AuthError> => {\n const siteUrl = process.env.SITE_URL;\n const hasSiteUrl = siteUrl !== undefined && siteUrl !== \"\";\n const hasRpId = provider.options.rpId !== undefined;\n\n return Fx.succeed({ siteUrl, hasSiteUrl, hasRpId }).pipe(\n Fx.chain(({ siteUrl, hasSiteUrl, hasRpId }) =>\n !hasSiteUrl && !hasRpId\n ? Fx.fail(\n new AuthError(\n \"PASSKEY_MISSING_CONFIG\",\n \"Passkey provider requires SITE_URL env var (your frontend URL) \" +\n \"or explicit rpId / origin in the provider config. \" +\n \"CONVEX_SITE_URL cannot be used because WebAuthn RP ID must match the frontend domain.\",\n ),\n )\n : Fx.succeed(siteUrl),\n ),\n Fx.map((siteUrl) => {\n const siteHostname = siteUrl ? new URL(siteUrl).hostname : undefined;\n return {\n rpName: provider.options.rpName ?? siteHostname ?? \"localhost\",\n rpId: provider.options.rpId ?? siteHostname ?? \"localhost\",\n origin: provider.options.origin ?? siteUrl ?? \"http://localhost\",\n attestation: provider.options.attestation ?? \"none\",\n userVerification: provider.options.userVerification ?? \"required\",\n residentKey: provider.options.residentKey ?? \"preferred\",\n authenticatorAttachment: provider.options.authenticatorAttachment,\n algorithms: provider.options.algorithms ?? [\n coseAlgorithmES256,\n coseAlgorithmRS256,\n ],\n challengeExpirationMs:\n provider.options.challengeExpirationMs ?? 300_000,\n };\n }),\n );\n};\n\n// ============================================================================\n// Composable validators — small functions (A) => Fx<B, AuthError>\n// ============================================================================\n\n/** Verify client data type matches expected WebAuthn ceremony type. */\nconst verifyClientDataType =\n <T extends { type: ClientDataType }>(\n expectedType: ClientDataType,\n label: string,\n ) =>\n (clientData: T): FxType<T, AuthError> =>\n clientData.type === expectedType\n ? Fx.succeed(clientData)\n : Fx.fail(\n new AuthError(\n \"PASSKEY_INVALID_CLIENT_DATA\",\n `Invalid client data type: expected ${label}`,\n ),\n );\n\n/** Verify origin is in the allowed list. */\nconst verifyOrigin =\n (rp: RpOptions) =>\n <T extends { origin: string }>(clientData: T): FxType<T, AuthError> => {\n const allowed = Array.isArray(rp.origin) ? rp.origin : [rp.origin];\n return allowed.includes(clientData.origin)\n ? Fx.succeed(clientData)\n : Fx.fail(\n new AuthError(\n \"PASSKEY_INVALID_ORIGIN\",\n `Invalid origin: ${clientData.origin}, expected one of: ${allowed.join(\", \")}`,\n ),\n );\n };\n\n/** Verify the challenge hash matches the stored verifier, then delete verifier. */\nconst verifyAndConsumeChallenge =\n (ctx: EnrichedActionCtx, verifierValue: string) =>\n <T extends { challenge: Uint8Array }>(\n clientData: T,\n ): FxType<T, AuthError> => {\n const challengeHash = encodeBase64urlNoPadding(\n new Uint8Array(sha256(clientData.challenge)),\n );\n return Fx.from({\n ok: () => queryVerifierById(ctx, verifierValue),\n err: () => new AuthError(\"PASSKEY_INVALID_CHALLENGE\"),\n }).pipe(\n Fx.chain((doc) =>\n !doc || doc.signature !== challengeHash\n ? Fx.fail(new AuthError(\"PASSKEY_INVALID_CHALLENGE\"))\n : Fx.succeed(doc),\n ),\n Fx.chain(() =>\n Fx.from({\n ok: () => mutateVerifierDelete(ctx, verifierValue),\n err: () => new AuthError(\"PASSKEY_INVALID_CHALLENGE\"),\n }),\n ),\n Fx.map(() => clientData),\n );\n };\n\n/** Verify RP ID hash matches. */\nconst verifyRpId =\n (rpId: string) =>\n <T extends { verifyRelyingPartyIdHash: (id: string) => boolean }>(\n authData: T,\n ): FxType<T, AuthError> =>\n authData.verifyRelyingPartyIdHash(rpId)\n ? Fx.succeed(authData)\n : Fx.fail(new AuthError(\"PASSKEY_RP_MISMATCH\"));\n\n/** Verify user presence and (optionally) user verification flags. */\nconst verifyUserFlags =\n (rp: RpOptions) =>\n <T extends { userPresent: boolean; userVerified: boolean }>(\n authData: T,\n ): FxType<T, AuthError> =>\n !authData.userPresent\n ? Fx.fail(new AuthError(\"PASSKEY_USER_PRESENCE\"))\n : rp.userVerification === \"required\" && !authData.userVerified\n ? Fx.fail(new AuthError(\"PASSKEY_USER_VERIFICATION\"))\n : Fx.succeed(authData);\n\n// ============================================================================\n// Registration flow\n// ============================================================================\n\n// ============================================================================\n// Authentication flow\n// ============================================================================\n\n// ============================================================================\n// Main dispatch\n// ============================================================================\n\n/** Result type for all passkey flows. */\ntype PasskeyResult =\n | { kind: \"signedIn\"; signedIn: SessionInfo | null }\n | { kind: \"passkeyOptions\"; options: Record<string, any>; verifier: string };\n\nconst PASSKEY_FLOW = {\n registerOptions: \"registerOptions\",\n registerVerify: \"registerVerify\",\n authOptions: \"authOptions\",\n authVerify: \"authVerify\",\n} as const;\n\nconst PASSKEY_FLOWS = [\n PASSKEY_FLOW.registerOptions,\n PASSKEY_FLOW.registerVerify,\n PASSKEY_FLOW.authOptions,\n PASSKEY_FLOW.authVerify,\n] as const;\n\ntype PasskeyDispatch =\n | { flow: typeof PASSKEY_FLOW.registerOptions }\n | { flow: typeof PASSKEY_FLOW.registerVerify }\n | { flow: typeof PASSKEY_FLOW.authOptions }\n | { flow: typeof PASSKEY_FLOW.authVerify };\n\nconst resolvePasskeyDispatchFx = (\n params: Record<string, unknown>,\n): FxType<PasskeyDispatch, AuthError> => {\n const flow = params.flow;\n return typeof flow === \"string\" && PASSKEY_FLOWS.includes(flow as never)\n ? Fx.succeed({ flow: flow as (typeof PASSKEY_FLOWS)[number] })\n : Fx.fail(\n new AuthError(\n \"PASSKEY_MISSING_FLOW\",\n \"Missing `flow` parameter. Expected one of: registerOptions, registerVerify, authOptions, authVerify\",\n ),\n );\n};\n\nconst requirePasskeyVerifierFx = (\n verifier: string | undefined,\n): FxType<string, AuthError> =>\n verifier != null\n ? Fx.succeed(verifier)\n : Fx.fail(new AuthError(\"PASSKEY_MISSING_VERIFIER\"));\n\n/**\n * Main passkey handler dispatched from signIn.ts.\n *\n * Routes to the appropriate phase based on `params.flow` via `dispatchFx`.\n */\nexport function handlePasskeyFx(\n ctx: EnrichedActionCtx,\n provider: PasskeyProviderConfig,\n args: {\n params?: Record<string, any>;\n verifier?: string;\n },\n): FxType<PasskeyResult, AuthError> {\n const params = (args.params ?? {}) as Record<string, any>;\n\n return resolvePasskeyDispatchFx(params).pipe(\n Fx.chain((dispatch) => {\n const flowFx: FxType<PasskeyResult, AuthError> = Fx.match(dispatch).on(\n \"flow\",\n {\n registerOptions: (_) =>\n Fx.zip(\n Fx.from({\n ok: () => ctx.auth.getUserIdentity(),\n err: () => new AuthError(\"PASSKEY_AUTH_REQUIRED\"),\n }).pipe(\n Fx.chain((id) =>\n id === null\n ? Fx.fail(new AuthError(\"PASSKEY_AUTH_REQUIRED\"))\n : Fx.succeed(userIdFromIdentitySubject(id.subject)),\n ),\n ),\n resolveRpOptionsFx(provider),\n ).pipe(\n Fx.chain(([userId, rp]) => {\n const challenge = new Uint8Array(32);\n crypto.getRandomValues(challenge);\n const challengeHash = encodeBase64urlNoPadding(\n new Uint8Array(sha256(challenge)),\n );\n\n return Fx.from({\n ok: async () => {\n const verifier = await callVerifier(ctx);\n await callVerifierSignature(ctx, {\n verifier,\n signature: challengeHash,\n });\n\n const user = await queryUserById(ctx, userId);\n const userName = params.userName ?? user?.email ?? \"user\";\n const userDisplayName =\n params.userDisplayName ?? user?.name ?? userName;\n\n const existing = await queryPasskeysByUserId(ctx, userId);\n const excludeCredentials = existing.map((pk) => ({\n id: pk.credentialId,\n transports: pk.transports,\n }));\n\n const userHandle = encodeBase64urlNoPadding(\n new TextEncoder().encode(userId),\n );\n\n const options = {\n rp: { name: rp.rpName, id: rp.rpId },\n user: {\n id: userHandle,\n name: userName,\n displayName: userDisplayName,\n },\n challenge: encodeBase64urlNoPadding(challenge),\n pubKeyCredParams: rp.algorithms.map((alg) => ({\n type: \"public-key\" as const,\n alg,\n })),\n timeout: rp.challengeExpirationMs,\n attestation: rp.attestation,\n authenticatorSelection: {\n residentKey: rp.residentKey,\n requireResidentKey: rp.residentKey === \"required\",\n userVerification: rp.userVerification,\n ...(rp.authenticatorAttachment\n ? {\n authenticatorAttachment:\n rp.authenticatorAttachment,\n }\n : {}),\n },\n excludeCredentials,\n };\n\n return {\n kind: \"passkeyOptions\" as const,\n options,\n verifier,\n };\n },\n err: () => new AuthError(\"INTERNAL_ERROR\"),\n });\n }),\n ),\n registerVerify: (_) =>\n Fx.zip(\n Fx.from({\n ok: () => ctx.auth.getUserIdentity(),\n err: () => new AuthError(\"PASSKEY_AUTH_REQUIRED\"),\n }).pipe(\n Fx.chain((id) =>\n id === null\n ? Fx.fail(new AuthError(\"PASSKEY_AUTH_REQUIRED\"))\n : Fx.succeed(userIdFromIdentitySubject(id.subject)),\n ),\n ),\n resolveRpOptionsFx(provider),\n ).pipe(\n Fx.chain(([userId, rp]) =>\n requirePasskeyVerifierFx(args.verifier).pipe(\n Fx.chain((verifier) => {\n const clientDataJSON = decodeBase64urlIgnorePadding(\n params.clientDataJSON,\n );\n const clientData = parseClientDataJSON(clientDataJSON);\n\n const verifiedClientDataFx = Fx.succeed(clientData).pipe(\n Fx.chain(\n verifyClientDataType(\n ClientDataType.Create,\n \"webauthn.create\",\n ),\n ),\n Fx.chain(verifyOrigin(rp)),\n Fx.chain(verifyAndConsumeChallenge(ctx, verifier)),\n Fx.map(() => {\n const attestationObjectBytes =\n decodeBase64urlIgnorePadding(\n params.attestationObject,\n );\n const attestation = parseAttestationObject(\n attestationObjectBytes,\n );\n return attestation.authenticatorData;\n }),\n );\n\n return verifiedClientDataFx.pipe(\n Fx.chain(verifyRpId(rp.rpId)),\n Fx.chain(verifyUserFlags(rp)),\n Fx.chain((authData) => {\n if (authData.credential == null) {\n return Fx.fail(\n new AuthError(\"PASSKEY_NO_CREDENTIAL\"),\n );\n }\n return Fx.succeed({\n authData,\n credential: authData.credential,\n });\n }),\n Fx.chain(({ authData, credential }) => {\n const credentialId = encodeBase64urlNoPadding(\n credential.id,\n );\n const publicKey = credential.publicKey;\n\n let algorithm: number;\n if (publicKey.isAlgorithmDefined()) {\n algorithm = publicKey.algorithm();\n } else {\n const keyType = publicKey.type();\n algorithm =\n keyType === COSEKeyType.EC2\n ? coseAlgorithmES256\n : keyType === COSEKeyType.RSA\n ? coseAlgorithmRS256\n : coseAlgorithmES256;\n }\n\n const handlers: Record<\n number,\n (() => FxType<Uint8Array, AuthError>) | undefined\n > = {\n [coseAlgorithmES256]: () => {\n const ec2 = publicKey.ec2();\n const xBytes = new Uint8Array(32);\n let vx = ec2.x;\n for (let i = 31; i >= 0; i--) {\n xBytes[i] = Number(vx & 0xffn);\n vx >>= 8n;\n }\n const yBytes = new Uint8Array(32);\n let vy = ec2.y;\n for (let i = 31; i >= 0; i--) {\n yBytes[i] = Number(vy & 0xffn);\n vy >>= 8n;\n }\n const bytes = new Uint8Array(65);\n bytes[0] = 0x04;\n bytes.set(xBytes, 1);\n bytes.set(yBytes, 33);\n return Fx.succeed(bytes);\n },\n [coseAlgorithmRS256]: () => {\n const rsa = publicKey.rsa();\n const rsaPubKey = new RSAPublicKey(rsa.n, rsa.e);\n return Fx.succeed(rsaPubKey.encodePKCS1());\n },\n };\n\n const handler = handlers[algorithm];\n return (\n handler\n ? handler()\n : Fx.fail(\n new AuthError(\n \"PASSKEY_UNSUPPORTED_ALGORITHM\",\n `Unsupported algorithm: ${algorithm}`,\n ),\n )\n ).pipe(\n Fx.chain((publicKeyBytes) =>\n Fx.from({\n ok: async () => {\n const deviceType =\n params.deviceType ?? \"single-device\";\n const backedUp = params.backedUp ?? false;\n\n const db = authDb(ctx, ctx.auth.config);\n await db.accounts.create({\n userId,\n provider: provider.id,\n providerAccountId: credentialId,\n });\n\n await mutatePasskeyInsert(ctx, {\n userId,\n credentialId,\n publicKey: publicKeyBytes.buffer.slice(\n publicKeyBytes.byteOffset,\n publicKeyBytes.byteOffset +\n publicKeyBytes.byteLength,\n ),\n algorithm,\n counter: authData.signatureCounter,\n transports: params.transports,\n deviceType,\n backedUp,\n name: params.passkeyName,\n createdAt: Date.now(),\n });\n\n const signInResult = await callSignIn(ctx, {\n userId,\n generateTokens: true,\n });\n\n return {\n kind: \"signedIn\" as const,\n signedIn: signInResult,\n };\n },\n err: () => new AuthError(\"INTERNAL_ERROR\"),\n }),\n ),\n );\n }),\n );\n }),\n ),\n ),\n ),\n authOptions: (_) =>\n resolveRpOptionsFx(provider).pipe(\n Fx.chain((rp) => {\n const challenge = new Uint8Array(32);\n crypto.getRandomValues(challenge);\n const challengeHash = encodeBase64urlNoPadding(\n new Uint8Array(sha256(challenge)),\n );\n\n return Fx.from({\n ok: async () => {\n const verifier = await callVerifier(ctx);\n await callVerifierSignature(ctx, {\n verifier,\n signature: challengeHash,\n });\n\n let allowCredentials:\n | Array<{\n type: string;\n id: string;\n transports?: string[];\n }>\n | undefined;\n if (params.email) {\n const user = await queryUserByVerifiedEmail(\n ctx,\n params.email,\n );\n if (user) {\n const passkeys = await queryPasskeysByUserId(\n ctx,\n user._id,\n );\n if (passkeys.length > 0) {\n allowCredentials = passkeys.map((pk) => ({\n type: \"public-key\",\n id: pk.credentialId,\n transports: pk.transports,\n }));\n }\n }\n }\n\n const options: Record<string, any> = {\n challenge: encodeBase64urlNoPadding(challenge),\n timeout: rp.challengeExpirationMs,\n rpId: rp.rpId,\n userVerification: rp.userVerification,\n };\n\n if (allowCredentials) {\n options.allowCredentials = allowCredentials;\n }\n\n return {\n kind: \"passkeyOptions\" as const,\n options,\n verifier,\n };\n },\n err: () => new AuthError(\"INTERNAL_ERROR\"),\n });\n }),\n ),\n authVerify: (_) =>\n Fx.zip(\n resolveRpOptionsFx(provider),\n requirePasskeyVerifierFx(args.verifier),\n ).pipe(\n Fx.chain(([rp, verifier]) => {\n const clientDataJSON = decodeBase64urlIgnorePadding(\n params.clientDataJSON,\n );\n const clientData = parseClientDataJSON(clientDataJSON);\n\n const verifiedClientDataFx = Fx.succeed(clientData).pipe(\n Fx.chain(\n verifyClientDataType(ClientDataType.Get, \"webauthn.get\"),\n ),\n Fx.chain(verifyOrigin(rp)),\n Fx.chain(verifyAndConsumeChallenge(ctx, verifier)),\n Fx.chain(() =>\n params.credentialId != null\n ? Fx.succeed(params.credentialId as string)\n : Fx.fail(\n new AuthError(\n \"PASSKEY_UNKNOWN_CREDENTIAL\",\n \"Missing credential ID\",\n ),\n ),\n ),\n );\n\n return verifiedClientDataFx.pipe(\n Fx.chain((credentialId) =>\n Fx.from({\n ok: () => queryPasskeyByCredentialId(ctx, credentialId),\n err: () => new AuthError(\"PASSKEY_UNKNOWN_CREDENTIAL\"),\n }).pipe(\n Fx.chain((passkey) =>\n passkey\n ? Fx.succeed(passkey)\n : Fx.fail(\n new AuthError(\n \"PASSKEY_UNKNOWN_CREDENTIAL\",\n \"Unknown credential\",\n ),\n ),\n ),\n ),\n ),\n Fx.chain((passkey) => {\n const authenticatorDataBytes = decodeBase64urlIgnorePadding(\n params.authenticatorData,\n );\n const authenticatorData = parseAuthenticatorData(\n authenticatorDataBytes,\n );\n\n const signature = decodeBase64urlIgnorePadding(\n params.signature,\n );\n const signatureMessage = createAssertionSignatureMessage(\n authenticatorDataBytes,\n clientDataJSON,\n );\n const messageHash = sha256(signatureMessage);\n\n const checkedAuthenticatorFx = Fx.succeed(\n authenticatorData,\n ).pipe(\n Fx.chain(verifyRpId(rp.rpId)),\n Fx.chain(verifyUserFlags(rp)),\n );\n\n const signatureVerifiedFx = checkedAuthenticatorFx.pipe(\n Fx.chain(() => {\n const storedPublicKeyBytes = new Uint8Array(\n passkey.publicKey,\n );\n const algorithmHandlers: Record<\n number,\n (() => FxType<void, AuthError>) | undefined\n > = {\n [coseAlgorithmES256]: () => {\n const ecPublicKey = decodeSEC1PublicKey(\n p256,\n storedPublicKeyBytes,\n );\n const ecdsaSignature =\n decodePKIXECDSASignature(signature);\n const valid = verifyECDSASignature(\n ecPublicKey,\n messageHash,\n ecdsaSignature,\n );\n return valid\n ? Fx.succeed(undefined as void)\n : Fx.fail(\n new AuthError(\"PASSKEY_INVALID_SIGNATURE\"),\n );\n },\n [coseAlgorithmRS256]: () => {\n const rsaPublicKey =\n decodePKCS1RSAPublicKey(storedPublicKeyBytes);\n const valid = verifyRSASSAPKCS1v15Signature(\n rsaPublicKey,\n sha256ObjectIdentifier,\n messageHash,\n signature,\n );\n return valid\n ? Fx.succeed(undefined as void)\n : Fx.fail(\n new AuthError(\"PASSKEY_INVALID_SIGNATURE\"),\n );\n },\n };\n\n const handler = algorithmHandlers[passkey.algorithm];\n return handler\n ? handler()\n : Fx.fail(\n new AuthError(\n \"PASSKEY_UNSUPPORTED_ALGORITHM\",\n `Unsupported algorithm: ${passkey.algorithm}`,\n ),\n );\n }),\n );\n\n const counterValidatedFx = signatureVerifiedFx.pipe(\n Fx.chain(() =>\n passkey.counter !== 0 &&\n authenticatorData.signatureCounter !== 0 &&\n authenticatorData.signatureCounter <= passkey.counter\n ? Fx.fail(new AuthError(\"PASSKEY_COUNTER_ERROR\"))\n : Fx.succeed(authenticatorData),\n ),\n );\n\n return counterValidatedFx.pipe(\n Fx.chain(() =>\n Fx.from({\n ok: async () => {\n await mutatePasskeyUpdateCounter(\n ctx,\n passkey._id,\n authenticatorData.signatureCounter,\n Date.now(),\n );\n\n const signInResult = await callSignIn(ctx, {\n userId: passkey.userId,\n generateTokens: true,\n });\n\n return {\n kind: \"signedIn\" as const,\n signedIn: signInResult,\n };\n },\n err: () => new AuthError(\"INTERNAL_ERROR\"),\n }),\n ),\n );\n }),\n );\n }),\n ),\n },\n );\n return flowFx;\n }),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"passkey.js","names":["hasSiteUrl","hasRpId","siteUrl"],"sources":["../../src/server/passkey.ts"],"sourcesContent":["/**\n * Server-side WebAuthn ceremony logic for passkey authentication.\n *\n * Handles the four phases of the WebAuthn flow:\n * 1. registerOptions — generate PublicKeyCredentialCreationOptions\n * 2. registerVerify — verify attestation and store credential\n * 3. authOptions — generate PublicKeyCredentialRequestOptions\n * 4. authVerify — verify assertion signature and sign in\n *\n * Uses `@oslojs/webauthn` for attestation/assertion parsing and\n * `@oslojs/crypto` for signature verification.\n *\n * All functions return `Fx<A, ConvexError<any>>` composed via `Fx.chain` pipelines.\n *\n * @module\n */\n\nimport {\n p256,\n verifyECDSASignature,\n decodeSEC1PublicKey,\n decodePKIXECDSASignature,\n} from \"@oslojs/crypto/ecdsa\";\nimport {\n RSAPublicKey,\n decodePKCS1RSAPublicKey,\n sha256ObjectIdentifier,\n verifyRSASSAPKCS1v15Signature,\n} from \"@oslojs/crypto/rsa\";\nimport { sha256 } from \"@oslojs/crypto/sha2\";\nimport {\n encodeBase64urlNoPadding,\n decodeBase64urlIgnorePadding,\n} from \"@oslojs/encoding\";\nimport {\n parseAttestationObject,\n parseClientDataJSON,\n parseAuthenticatorData,\n createAssertionSignatureMessage,\n ClientDataType,\n coseAlgorithmES256,\n coseAlgorithmRS256,\n COSEKeyType,\n} from \"@oslojs/webauthn\";\nimport type { Fx as FxType } from \"@robelest/fx\";\nimport { Fx } from \"@robelest/fx\";\nimport { Cv } from \"@robelest/fx/convex\";\nimport type { ConvexError } from \"convex/values\";\n\nimport { authDb } from \"./db\";\nimport { userIdFromIdentitySubject } from \"./identity\";\nimport { callSignIn, callVerifier } from \"./mutations/index\";\nimport { callVerifierSignature } from \"./mutations/signature\";\nimport { PasskeyProviderConfig, GenericActionCtxWithAuthConfig } from \"./types\";\nimport {\n AuthDataModel,\n SessionInfo,\n queryUserById,\n queryUserByVerifiedEmail,\n queryPasskeysByUserId,\n queryPasskeyByCredentialId,\n queryVerifierById,\n mutatePasskeyInsert,\n mutatePasskeyUpdateCounter,\n mutateVerifierDelete,\n} from \"./types\";\n\ntype EnrichedActionCtx = GenericActionCtxWithAuthConfig<AuthDataModel>;\n\n// ============================================================================\n// Resolve RP options — Fx pipeline with validation\n// ============================================================================\n\n/** Resolved relying party configuration. */\ninterface RpOptions {\n rpName: string;\n rpId: string;\n origin: string | string[];\n attestation: string;\n userVerification: string;\n residentKey: string;\n authenticatorAttachment?: string;\n algorithms: number[];\n challengeExpirationMs: number;\n}\n\n/**\n * Resolve passkey relying party options from provider config and environment.\n *\n * Returns `Fx<RpOptions, ConvexError<any>>` — fails if neither SITE_URL nor rpId\n * is configured.\n */\nconst resolveRpOptionsFx = (\n provider: PasskeyProviderConfig,\n): FxType<RpOptions, ConvexError<any>> => {\n const siteUrl = process.env.SITE_URL;\n const hasSiteUrl = siteUrl !== undefined && siteUrl !== \"\";\n const hasRpId = provider.options.rpId !== undefined;\n\n return Fx.succeed({ siteUrl, hasSiteUrl, hasRpId }).pipe(\n Fx.chain(({ siteUrl, hasSiteUrl, hasRpId }) =>\n !hasSiteUrl && !hasRpId\n ? Cv.fail({\n code: \"PASSKEY_MISSING_CONFIG\",\n message:\n \"Passkey provider requires SITE_URL env var (your frontend URL) \" +\n \"or explicit rpId / origin in the provider config. \" +\n \"CONVEX_SITE_URL cannot be used because WebAuthn RP ID must match the frontend domain.\",\n })\n : Fx.succeed(siteUrl),\n ),\n Fx.map((siteUrl) => {\n const siteHostname = siteUrl ? new URL(siteUrl).hostname : undefined;\n return {\n rpName: provider.options.rpName ?? siteHostname ?? \"localhost\",\n rpId: provider.options.rpId ?? siteHostname ?? \"localhost\",\n origin: provider.options.origin ?? siteUrl ?? \"http://localhost\",\n attestation: provider.options.attestation ?? \"none\",\n userVerification: provider.options.userVerification ?? \"required\",\n residentKey: provider.options.residentKey ?? \"preferred\",\n authenticatorAttachment: provider.options.authenticatorAttachment,\n algorithms: provider.options.algorithms ?? [\n coseAlgorithmES256,\n coseAlgorithmRS256,\n ],\n challengeExpirationMs:\n provider.options.challengeExpirationMs ?? 300_000,\n };\n }),\n );\n};\n\n// ============================================================================\n// Composable validators — small functions (A) => Fx<B, ConvexError<any>>\n// ============================================================================\n\n/** Verify client data type matches expected WebAuthn ceremony type. */\nconst verifyClientDataType =\n <T extends { type: ClientDataType }>(\n expectedType: ClientDataType,\n label: string,\n ) =>\n (clientData: T): FxType<T, ConvexError<any>> =>\n clientData.type === expectedType\n ? Fx.succeed(clientData)\n : Cv.fail({\n code: \"PASSKEY_INVALID_CLIENT_DATA\",\n message: `Invalid client data type: expected ${label}`,\n });\n\n/** Verify origin is in the allowed list. */\nconst verifyOrigin =\n (rp: RpOptions) =>\n <T extends { origin: string }>(\n clientData: T,\n ): FxType<T, ConvexError<any>> => {\n const allowed = Array.isArray(rp.origin) ? rp.origin : [rp.origin];\n return allowed.includes(clientData.origin)\n ? Fx.succeed(clientData)\n : Cv.fail({\n code: \"PASSKEY_INVALID_ORIGIN\",\n message: `Invalid origin: ${clientData.origin}, expected one of: ${allowed.join(\", \")}`,\n });\n };\n\n/** Verify the challenge hash matches the stored verifier, then delete verifier. */\nconst verifyAndConsumeChallenge =\n (ctx: EnrichedActionCtx, verifierValue: string) =>\n <T extends { challenge: Uint8Array }>(\n clientData: T,\n ): FxType<T, ConvexError<any>> => {\n const challengeHash = encodeBase64urlNoPadding(\n new Uint8Array(sha256(clientData.challenge)),\n );\n return Fx.from({\n ok: () => queryVerifierById(ctx, verifierValue),\n err: () =>\n Cv.error({\n code: \"PASSKEY_INVALID_CHALLENGE\",\n message: \"Invalid or expired passkey challenge.\",\n }),\n }).pipe(\n Fx.chain((doc) =>\n !doc || doc.signature !== challengeHash\n ? Cv.fail({\n code: \"PASSKEY_INVALID_CHALLENGE\",\n message: \"Invalid or expired passkey challenge.\",\n })\n : Fx.succeed(doc),\n ),\n Fx.chain(() =>\n Fx.from({\n ok: () => mutateVerifierDelete(ctx, verifierValue),\n err: () =>\n Cv.error({\n code: \"PASSKEY_INVALID_CHALLENGE\",\n message: \"Invalid or expired passkey challenge.\",\n }),\n }),\n ),\n Fx.map(() => clientData),\n );\n };\n\n/** Verify RP ID hash matches. */\nconst verifyRpId =\n (rpId: string) =>\n <T extends { verifyRelyingPartyIdHash: (id: string) => boolean }>(\n authData: T,\n ): FxType<T, ConvexError<any>> =>\n authData.verifyRelyingPartyIdHash(rpId)\n ? Fx.succeed(authData)\n : Cv.fail({\n code: \"PASSKEY_RP_MISMATCH\",\n message: \"Relying party ID mismatch.\",\n });\n\n/** Verify user presence and (optionally) user verification flags. */\nconst verifyUserFlags =\n (rp: RpOptions) =>\n <T extends { userPresent: boolean; userVerified: boolean }>(\n authData: T,\n ): FxType<T, ConvexError<any>> =>\n !authData.userPresent\n ? Cv.fail({\n code: \"PASSKEY_USER_PRESENCE\",\n message: \"User presence flag not set.\",\n })\n : rp.userVerification === \"required\" && !authData.userVerified\n ? Cv.fail({\n code: \"PASSKEY_USER_VERIFICATION\",\n message: \"User verification required but not performed.\",\n })\n : Fx.succeed(authData);\n\n// ============================================================================\n// Registration flow\n// ============================================================================\n\n// ============================================================================\n// Authentication flow\n// ============================================================================\n\n// ============================================================================\n// Main dispatch\n// ============================================================================\n\n/** Result type for all passkey flows. */\ntype PasskeyResult =\n | { kind: \"signedIn\"; signedIn: SessionInfo | null }\n | { kind: \"passkeyOptions\"; options: Record<string, any>; verifier: string };\n\nconst PASSKEY_FLOW = {\n registerOptions: \"registerOptions\",\n registerVerify: \"registerVerify\",\n authOptions: \"authOptions\",\n authVerify: \"authVerify\",\n} as const;\n\nconst PASSKEY_FLOWS = [\n PASSKEY_FLOW.registerOptions,\n PASSKEY_FLOW.registerVerify,\n PASSKEY_FLOW.authOptions,\n PASSKEY_FLOW.authVerify,\n] as const;\n\ntype PasskeyDispatch =\n | { flow: typeof PASSKEY_FLOW.registerOptions }\n | { flow: typeof PASSKEY_FLOW.registerVerify }\n | { flow: typeof PASSKEY_FLOW.authOptions }\n | { flow: typeof PASSKEY_FLOW.authVerify };\n\nconst resolvePasskeyDispatchFx = (\n params: Record<string, unknown>,\n): FxType<PasskeyDispatch, ConvexError<any>> => {\n const flow = params.flow;\n return typeof flow === \"string\" && PASSKEY_FLOWS.includes(flow as never)\n ? Fx.succeed({ flow: flow as (typeof PASSKEY_FLOWS)[number] })\n : Cv.fail({\n code: \"PASSKEY_MISSING_FLOW\",\n message:\n \"Missing `flow` parameter. Expected one of: registerOptions, registerVerify, authOptions, authVerify\",\n });\n};\n\nconst requirePasskeyVerifierFx = (\n verifier: string | undefined,\n): FxType<string, ConvexError<any>> =>\n verifier != null\n ? Fx.succeed(verifier)\n : Cv.fail({\n code: \"PASSKEY_MISSING_VERIFIER\",\n message: \"Missing verifier for passkey operation.\",\n });\n\n/**\n * Main passkey handler dispatched from signIn.ts.\n *\n * Routes to the appropriate phase based on `params.flow` via `dispatchFx`.\n */\nexport function handlePasskeyFx(\n ctx: EnrichedActionCtx,\n provider: PasskeyProviderConfig,\n args: {\n params?: Record<string, any>;\n verifier?: string;\n },\n): FxType<PasskeyResult, ConvexError<any>> {\n const params = (args.params ?? {}) as Record<string, any>;\n\n return resolvePasskeyDispatchFx(params).pipe(\n Fx.chain((dispatch) => {\n const flowFx: FxType<PasskeyResult, ConvexError<any>> = Fx.match(\n dispatch,\n ).on(\"flow\", {\n registerOptions: (_) =>\n Fx.zip(\n Fx.from({\n ok: () => ctx.auth.getUserIdentity(),\n err: () =>\n Cv.error({\n code: \"PASSKEY_AUTH_REQUIRED\",\n message: \"Sign in first, then add a passkey to your account.\",\n }),\n }).pipe(\n Fx.chain((id) =>\n id === null\n ? Cv.fail({\n code: \"PASSKEY_AUTH_REQUIRED\",\n message:\n \"Sign in first, then add a passkey to your account.\",\n })\n : Fx.succeed(userIdFromIdentitySubject(id.subject)),\n ),\n ),\n resolveRpOptionsFx(provider),\n ).pipe(\n Fx.chain(([userId, rp]) => {\n const challenge = new Uint8Array(32);\n crypto.getRandomValues(challenge);\n const challengeHash = encodeBase64urlNoPadding(\n new Uint8Array(sha256(challenge)),\n );\n\n return Fx.from({\n ok: async () => {\n const verifier = await callVerifier(ctx);\n await callVerifierSignature(ctx, {\n verifier,\n signature: challengeHash,\n });\n\n const user = await queryUserById(ctx, userId);\n const userName = params.userName ?? user?.email ?? \"user\";\n const userDisplayName =\n params.userDisplayName ?? user?.name ?? userName;\n\n const existing = await queryPasskeysByUserId(ctx, userId);\n const excludeCredentials = existing.map((pk) => ({\n id: pk.credentialId,\n transports: pk.transports,\n }));\n\n const userHandle = encodeBase64urlNoPadding(\n new TextEncoder().encode(userId),\n );\n\n const options = {\n rp: { name: rp.rpName, id: rp.rpId },\n user: {\n id: userHandle,\n name: userName,\n displayName: userDisplayName,\n },\n challenge: encodeBase64urlNoPadding(challenge),\n pubKeyCredParams: rp.algorithms.map((alg) => ({\n type: \"public-key\" as const,\n alg,\n })),\n timeout: rp.challengeExpirationMs,\n attestation: rp.attestation,\n authenticatorSelection: {\n residentKey: rp.residentKey,\n requireResidentKey: rp.residentKey === \"required\",\n userVerification: rp.userVerification,\n ...(rp.authenticatorAttachment\n ? {\n authenticatorAttachment: rp.authenticatorAttachment,\n }\n : {}),\n },\n excludeCredentials,\n };\n\n return {\n kind: \"passkeyOptions\" as const,\n options,\n verifier,\n };\n },\n err: () =>\n Cv.error({\n code: \"INTERNAL_ERROR\",\n message: \"An unexpected error occurred.\",\n }),\n });\n }),\n ),\n registerVerify: (_) =>\n Fx.zip(\n Fx.from({\n ok: () => ctx.auth.getUserIdentity(),\n err: () =>\n Cv.error({\n code: \"PASSKEY_AUTH_REQUIRED\",\n message: \"Sign in first, then add a passkey to your account.\",\n }),\n }).pipe(\n Fx.chain((id) =>\n id === null\n ? Cv.fail({\n code: \"PASSKEY_AUTH_REQUIRED\",\n message:\n \"Sign in first, then add a passkey to your account.\",\n })\n : Fx.succeed(userIdFromIdentitySubject(id.subject)),\n ),\n ),\n resolveRpOptionsFx(provider),\n ).pipe(\n Fx.chain(([userId, rp]) =>\n requirePasskeyVerifierFx(args.verifier).pipe(\n Fx.chain((verifier) => {\n const clientDataJSON = decodeBase64urlIgnorePadding(\n params.clientDataJSON,\n );\n const clientData = parseClientDataJSON(clientDataJSON);\n\n const verifiedClientDataFx = Fx.succeed(clientData).pipe(\n Fx.chain(\n verifyClientDataType(\n ClientDataType.Create,\n \"webauthn.create\",\n ),\n ),\n Fx.chain(verifyOrigin(rp)),\n Fx.chain(verifyAndConsumeChallenge(ctx, verifier)),\n Fx.map(() => {\n const attestationObjectBytes =\n decodeBase64urlIgnorePadding(params.attestationObject);\n const attestation = parseAttestationObject(\n attestationObjectBytes,\n );\n return attestation.authenticatorData;\n }),\n );\n\n return verifiedClientDataFx.pipe(\n Fx.chain(verifyRpId(rp.rpId)),\n Fx.chain(verifyUserFlags(rp)),\n Fx.chain((authData) => {\n if (authData.credential == null) {\n return Cv.fail({\n code: \"PASSKEY_NO_CREDENTIAL\",\n message: \"No credential in attestation.\",\n });\n }\n return Fx.succeed({\n authData,\n credential: authData.credential,\n });\n }),\n Fx.chain(({ authData, credential }) => {\n const credentialId = encodeBase64urlNoPadding(\n credential.id,\n );\n const publicKey = credential.publicKey;\n\n let algorithm: number;\n if (publicKey.isAlgorithmDefined()) {\n algorithm = publicKey.algorithm();\n } else {\n const keyType = publicKey.type();\n algorithm =\n keyType === COSEKeyType.EC2\n ? coseAlgorithmES256\n : keyType === COSEKeyType.RSA\n ? coseAlgorithmRS256\n : coseAlgorithmES256;\n }\n\n const handlers: Record<\n number,\n (() => FxType<Uint8Array, ConvexError<any>>) | undefined\n > = {\n [coseAlgorithmES256]: () => {\n const ec2 = publicKey.ec2();\n const xBytes = new Uint8Array(32);\n let vx = ec2.x;\n for (let i = 31; i >= 0; i--) {\n xBytes[i] = Number(vx & 0xffn);\n vx >>= 8n;\n }\n const yBytes = new Uint8Array(32);\n let vy = ec2.y;\n for (let i = 31; i >= 0; i--) {\n yBytes[i] = Number(vy & 0xffn);\n vy >>= 8n;\n }\n const bytes = new Uint8Array(65);\n bytes[0] = 0x04;\n bytes.set(xBytes, 1);\n bytes.set(yBytes, 33);\n return Fx.succeed(bytes);\n },\n [coseAlgorithmRS256]: () => {\n const rsa = publicKey.rsa();\n const rsaPubKey = new RSAPublicKey(rsa.n, rsa.e);\n return Fx.succeed(rsaPubKey.encodePKCS1());\n },\n };\n\n const handler = handlers[algorithm];\n return (\n handler\n ? handler()\n : Cv.fail({\n code: \"PASSKEY_UNSUPPORTED_ALGORITHM\",\n message: `Unsupported algorithm: ${algorithm}`,\n })\n ).pipe(\n Fx.chain((publicKeyBytes) =>\n Fx.from({\n ok: async () => {\n const deviceType =\n params.deviceType ?? \"single-device\";\n const backedUp = params.backedUp ?? false;\n\n const db = authDb(ctx, ctx.auth.config);\n await db.accounts.create({\n userId,\n provider: provider.id,\n providerAccountId: credentialId,\n });\n\n await mutatePasskeyInsert(ctx, {\n userId,\n credentialId,\n publicKey: publicKeyBytes.buffer.slice(\n publicKeyBytes.byteOffset,\n publicKeyBytes.byteOffset +\n publicKeyBytes.byteLength,\n ),\n algorithm,\n counter: authData.signatureCounter,\n transports: params.transports,\n deviceType,\n backedUp,\n name: params.passkeyName,\n createdAt: Date.now(),\n });\n\n const signInResult = await callSignIn(ctx, {\n userId,\n generateTokens: true,\n });\n\n return {\n kind: \"signedIn\" as const,\n signedIn: signInResult,\n };\n },\n err: () =>\n Cv.error({\n code: \"INTERNAL_ERROR\",\n message: \"An unexpected error occurred.\",\n }),\n }),\n ),\n );\n }),\n );\n }),\n ),\n ),\n ),\n authOptions: (_) =>\n resolveRpOptionsFx(provider).pipe(\n Fx.chain((rp) => {\n const challenge = new Uint8Array(32);\n crypto.getRandomValues(challenge);\n const challengeHash = encodeBase64urlNoPadding(\n new Uint8Array(sha256(challenge)),\n );\n\n return Fx.from({\n ok: async () => {\n const verifier = await callVerifier(ctx);\n await callVerifierSignature(ctx, {\n verifier,\n signature: challengeHash,\n });\n\n let allowCredentials:\n | Array<{\n type: string;\n id: string;\n transports?: string[];\n }>\n | undefined;\n if (params.email) {\n const user = await queryUserByVerifiedEmail(\n ctx,\n params.email,\n );\n if (user) {\n const passkeys = await queryPasskeysByUserId(\n ctx,\n user._id,\n );\n if (passkeys.length > 0) {\n allowCredentials = passkeys.map((pk) => ({\n type: \"public-key\",\n id: pk.credentialId,\n transports: pk.transports,\n }));\n }\n }\n }\n\n const options: Record<string, any> = {\n challenge: encodeBase64urlNoPadding(challenge),\n timeout: rp.challengeExpirationMs,\n rpId: rp.rpId,\n userVerification: rp.userVerification,\n };\n\n if (allowCredentials) {\n options.allowCredentials = allowCredentials;\n }\n\n return {\n kind: \"passkeyOptions\" as const,\n options,\n verifier,\n };\n },\n err: () =>\n Cv.error({\n code: \"INTERNAL_ERROR\",\n message: \"An unexpected error occurred.\",\n }),\n });\n }),\n ),\n authVerify: (_) =>\n Fx.zip(\n resolveRpOptionsFx(provider),\n requirePasskeyVerifierFx(args.verifier),\n ).pipe(\n Fx.chain(([rp, verifier]) => {\n const clientDataJSON = decodeBase64urlIgnorePadding(\n params.clientDataJSON,\n );\n const clientData = parseClientDataJSON(clientDataJSON);\n\n const verifiedClientDataFx = Fx.succeed(clientData).pipe(\n Fx.chain(\n verifyClientDataType(ClientDataType.Get, \"webauthn.get\"),\n ),\n Fx.chain(verifyOrigin(rp)),\n Fx.chain(verifyAndConsumeChallenge(ctx, verifier)),\n Fx.chain(() =>\n params.credentialId != null\n ? Fx.succeed(params.credentialId as string)\n : Cv.fail({\n code: \"PASSKEY_UNKNOWN_CREDENTIAL\",\n message: \"Missing credential ID\",\n }),\n ),\n );\n\n return verifiedClientDataFx.pipe(\n Fx.chain((credentialId) =>\n Fx.from({\n ok: () => queryPasskeyByCredentialId(ctx, credentialId),\n err: () =>\n Cv.error({\n code: \"PASSKEY_UNKNOWN_CREDENTIAL\",\n message: \"Unknown passkey credential.\",\n }),\n }).pipe(\n Fx.chain((passkey) =>\n passkey\n ? Fx.succeed(passkey)\n : Cv.fail({\n code: \"PASSKEY_UNKNOWN_CREDENTIAL\",\n message: \"Unknown credential\",\n }),\n ),\n ),\n ),\n Fx.chain((passkey) => {\n const authenticatorDataBytes = decodeBase64urlIgnorePadding(\n params.authenticatorData,\n );\n const authenticatorData = parseAuthenticatorData(\n authenticatorDataBytes,\n );\n\n const signature = decodeBase64urlIgnorePadding(\n params.signature,\n );\n const signatureMessage = createAssertionSignatureMessage(\n authenticatorDataBytes,\n clientDataJSON,\n );\n const messageHash = sha256(signatureMessage);\n\n const checkedAuthenticatorFx = Fx.succeed(\n authenticatorData,\n ).pipe(\n Fx.chain(verifyRpId(rp.rpId)),\n Fx.chain(verifyUserFlags(rp)),\n );\n\n const signatureVerifiedFx = checkedAuthenticatorFx.pipe(\n Fx.chain(() => {\n const storedPublicKeyBytes = new Uint8Array(\n passkey.publicKey,\n );\n const algorithmHandlers: Record<\n number,\n (() => FxType<void, ConvexError<any>>) | undefined\n > = {\n [coseAlgorithmES256]: () => {\n const ecPublicKey = decodeSEC1PublicKey(\n p256,\n storedPublicKeyBytes,\n );\n const ecdsaSignature =\n decodePKIXECDSASignature(signature);\n const valid = verifyECDSASignature(\n ecPublicKey,\n messageHash,\n ecdsaSignature,\n );\n return valid\n ? Fx.succeed(undefined as void)\n : Cv.fail({\n code: \"PASSKEY_INVALID_SIGNATURE\",\n message: \"Invalid passkey signature.\",\n });\n },\n [coseAlgorithmRS256]: () => {\n const rsaPublicKey =\n decodePKCS1RSAPublicKey(storedPublicKeyBytes);\n const valid = verifyRSASSAPKCS1v15Signature(\n rsaPublicKey,\n sha256ObjectIdentifier,\n messageHash,\n signature,\n );\n return valid\n ? Fx.succeed(undefined as void)\n : Cv.fail({\n code: \"PASSKEY_INVALID_SIGNATURE\",\n message: \"Invalid passkey signature.\",\n });\n },\n };\n\n const handler = algorithmHandlers[passkey.algorithm];\n return handler\n ? handler()\n : Cv.fail({\n code: \"PASSKEY_UNSUPPORTED_ALGORITHM\",\n message: `Unsupported algorithm: ${passkey.algorithm}`,\n });\n }),\n );\n\n const counterValidatedFx = signatureVerifiedFx.pipe(\n Fx.chain(() =>\n passkey.counter !== 0 &&\n authenticatorData.signatureCounter !== 0 &&\n authenticatorData.signatureCounter <= passkey.counter\n ? Cv.fail({\n code: \"PASSKEY_COUNTER_ERROR\",\n message:\n \"Authenticator counter did not increase — possible credential cloning detected.\",\n })\n : Fx.succeed(authenticatorData),\n ),\n );\n\n return counterValidatedFx.pipe(\n Fx.chain(() =>\n Fx.from({\n ok: async () => {\n await mutatePasskeyUpdateCounter(\n ctx,\n passkey._id,\n authenticatorData.signatureCounter,\n Date.now(),\n );\n\n const signInResult = await callSignIn(ctx, {\n userId: passkey.userId,\n generateTokens: true,\n });\n\n return {\n kind: \"signedIn\" as const,\n signedIn: signInResult,\n };\n },\n err: () =>\n Cv.error({\n code: \"INTERNAL_ERROR\",\n message: \"An unexpected error occurred.\",\n }),\n }),\n ),\n );\n }),\n );\n }),\n ),\n });\n return flowFx;\n }),\n );\n}\n"],"mappings":"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"}
@@ -1,16 +1,22 @@
1
- import { AuthError } from "./authError.js";
2
1
  import { requireEnv } from "./utils.js";
2
+ import { Cv } from "@robelest/fx/convex";
3
3
 
4
4
  //#region src/server/redirects.ts
5
5
  /** @internal */
6
6
  async function redirectAbsoluteUrl(config, params) {
7
7
  if (params.redirectTo === void 0) return requireEnv("SITE_URL").replace(/\/$/, "");
8
- if (typeof params.redirectTo !== "string") throw new AuthError("INVALID_REDIRECT", `Expected \`redirectTo\` to be a string, got ${params.redirectTo}`);
8
+ if (typeof params.redirectTo !== "string") throw Cv.error({
9
+ code: "INVALID_REDIRECT",
10
+ message: `Expected \`redirectTo\` to be a string, got ${params.redirectTo}`
11
+ });
9
12
  const redirectCallback = config.callbacks?.redirect ?? defaultRedirectCallback;
10
13
  try {
11
14
  return await redirectCallback({ redirectTo: params.redirectTo });
12
15
  } catch {
13
- throw new AuthError("INTERNAL_ERROR");
16
+ throw Cv.error({
17
+ code: "INTERNAL_ERROR",
18
+ message: "An unexpected error occurred."
19
+ });
14
20
  }
15
21
  }
16
22
  async function defaultRedirectCallback({ redirectTo }) {
@@ -1 +1 @@
1
- {"version":3,"file":"redirects.js","names":[],"sources":["../../src/server/redirects.ts"],"sourcesContent":["import { AuthError } from \"./authError\";\nimport { ConvexAuthMaterializedConfig } from \"./types\";\nimport { requireEnv } from \"./utils\";\n\n/** @internal */\nexport async function redirectAbsoluteUrl(\n config: ConvexAuthMaterializedConfig,\n params: { redirectTo: unknown },\n) {\n if (params.redirectTo === undefined) {\n return requireEnv(\"SITE_URL\").replace(/\\/$/, \"\");\n }\n if (typeof params.redirectTo !== \"string\") {\n throw new AuthError(\n \"INVALID_REDIRECT\",\n `Expected \\`redirectTo\\` to be a string, got ${params.redirectTo as any}`,\n );\n }\n const redirectCallback =\n config.callbacks?.redirect ?? defaultRedirectCallback;\n try {\n return await redirectCallback({ redirectTo: params.redirectTo });\n } catch {\n throw new AuthError(\"INTERNAL_ERROR\");\n }\n}\n\nasync function defaultRedirectCallback({ redirectTo }: { redirectTo: string }) {\n // Resolve relative paths against SITE_URL; absolute URLs are passed through\n // as-is. The developer is trusted to provide valid redirect targets.\n if (redirectTo.startsWith(\"?\") || redirectTo.startsWith(\"/\")) {\n return `${requireEnv(\"SITE_URL\").replace(/\\/$/, \"\")}${redirectTo}`;\n }\n return redirectTo;\n}\n\n// Temporary work-around because Convex doesn't support\n// schemes other than http and https.\n/** @internal */\nexport function setURLSearchParam(\n absoluteUrl: string,\n param: string,\n value: string,\n) {\n const pattern = /([^:]+):(.*)/;\n const [, scheme, rest] = absoluteUrl.match(pattern)!;\n const hasNoDomain = /^\\/\\/(?:\\/|$|\\?)/.test(rest);\n const startsWithPath = hasNoDomain && rest.startsWith(\"///\");\n const url = new URL(\n `http:${hasNoDomain ? \"//googblibok\" + rest.slice(2) : rest}`,\n );\n url.searchParams.set(param, value);\n const [, , withParam] = url.toString().match(pattern)!;\n return `${scheme}:${hasNoDomain ? (startsWithPath ? \"/\" : \"\") + \"//\" + withParam.slice(13) : withParam}`;\n}\n"],"mappings":";;;;;AAKA,eAAsB,oBACpB,QACA,QACA;AACA,KAAI,OAAO,eAAe,OACxB,QAAO,WAAW,WAAW,CAAC,QAAQ,OAAO,GAAG;AAElD,KAAI,OAAO,OAAO,eAAe,SAC/B,OAAM,IAAI,UACR,oBACA,+CAA+C,OAAO,aACvD;CAEH,MAAM,mBACJ,OAAO,WAAW,YAAY;AAChC,KAAI;AACF,SAAO,MAAM,iBAAiB,EAAE,YAAY,OAAO,YAAY,CAAC;SAC1D;AACN,QAAM,IAAI,UAAU,iBAAiB;;;AAIzC,eAAe,wBAAwB,EAAE,cAAsC;AAG7E,KAAI,WAAW,WAAW,IAAI,IAAI,WAAW,WAAW,IAAI,CAC1D,QAAO,GAAG,WAAW,WAAW,CAAC,QAAQ,OAAO,GAAG,GAAG;AAExD,QAAO;;;AAMT,SAAgB,kBACd,aACA,OACA,OACA;CACA,MAAM,UAAU;CAChB,MAAM,GAAG,QAAQ,QAAQ,YAAY,MAAM,QAAQ;CACnD,MAAM,cAAc,mBAAmB,KAAK,KAAK;CACjD,MAAM,iBAAiB,eAAe,KAAK,WAAW,MAAM;CAC5D,MAAM,MAAM,IAAI,IACd,QAAQ,cAAc,iBAAiB,KAAK,MAAM,EAAE,GAAG,OACxD;AACD,KAAI,aAAa,IAAI,OAAO,MAAM;CAClC,MAAM,KAAK,aAAa,IAAI,UAAU,CAAC,MAAM,QAAQ;AACrD,QAAO,GAAG,OAAO,GAAG,eAAe,iBAAiB,MAAM,MAAM,OAAO,UAAU,MAAM,GAAG,GAAG"}
1
+ {"version":3,"file":"redirects.js","names":[],"sources":["../../src/server/redirects.ts"],"sourcesContent":["import { Cv } from \"@robelest/fx/convex\";\n\nimport { ConvexAuthMaterializedConfig } from \"./types\";\nimport { requireEnv } from \"./utils\";\n\n/** @internal */\nexport async function redirectAbsoluteUrl(\n config: ConvexAuthMaterializedConfig,\n params: { redirectTo: unknown },\n) {\n if (params.redirectTo === undefined) {\n return requireEnv(\"SITE_URL\").replace(/\\/$/, \"\");\n }\n if (typeof params.redirectTo !== \"string\") {\n throw Cv.error({\n code: \"INVALID_REDIRECT\",\n message: `Expected \\`redirectTo\\` to be a string, got ${params.redirectTo as any}`,\n });\n }\n const redirectCallback =\n config.callbacks?.redirect ?? defaultRedirectCallback;\n try {\n return await redirectCallback({ redirectTo: params.redirectTo });\n } catch {\n throw Cv.error({\n code: \"INTERNAL_ERROR\",\n message: \"An unexpected error occurred.\",\n });\n }\n}\n\nasync function defaultRedirectCallback({ redirectTo }: { redirectTo: string }) {\n // Resolve relative paths against SITE_URL; absolute URLs are passed through\n // as-is. The developer is trusted to provide valid redirect targets.\n if (redirectTo.startsWith(\"?\") || redirectTo.startsWith(\"/\")) {\n return `${requireEnv(\"SITE_URL\").replace(/\\/$/, \"\")}${redirectTo}`;\n }\n return redirectTo;\n}\n\n// Temporary work-around because Convex doesn't support\n// schemes other than http and https.\n/** @internal */\nexport function setURLSearchParam(\n absoluteUrl: string,\n param: string,\n value: string,\n) {\n const pattern = /([^:]+):(.*)/;\n const [, scheme, rest] = absoluteUrl.match(pattern)!;\n const hasNoDomain = /^\\/\\/(?:\\/|$|\\?)/.test(rest);\n const startsWithPath = hasNoDomain && rest.startsWith(\"///\");\n const url = new URL(\n `http:${hasNoDomain ? \"//googblibok\" + rest.slice(2) : rest}`,\n );\n url.searchParams.set(param, value);\n const [, , withParam] = url.toString().match(pattern)!;\n return `${scheme}:${hasNoDomain ? (startsWithPath ? \"/\" : \"\") + \"//\" + withParam.slice(13) : withParam}`;\n}\n"],"mappings":";;;;;AAMA,eAAsB,oBACpB,QACA,QACA;AACA,KAAI,OAAO,eAAe,OACxB,QAAO,WAAW,WAAW,CAAC,QAAQ,OAAO,GAAG;AAElD,KAAI,OAAO,OAAO,eAAe,SAC/B,OAAM,GAAG,MAAM;EACb,MAAM;EACN,SAAS,+CAA+C,OAAO;EAChE,CAAC;CAEJ,MAAM,mBACJ,OAAO,WAAW,YAAY;AAChC,KAAI;AACF,SAAO,MAAM,iBAAiB,EAAE,YAAY,OAAO,YAAY,CAAC;SAC1D;AACN,QAAM,GAAG,MAAM;GACb,MAAM;GACN,SAAS;GACV,CAAC;;;AAIN,eAAe,wBAAwB,EAAE,cAAsC;AAG7E,KAAI,WAAW,WAAW,IAAI,IAAI,WAAW,WAAW,IAAI,CAC1D,QAAO,GAAG,WAAW,WAAW,CAAC,QAAQ,OAAO,GAAG,GAAG;AAExD,QAAO;;;AAMT,SAAgB,kBACd,aACA,OACA,OACA;CACA,MAAM,UAAU;CAChB,MAAM,GAAG,QAAQ,QAAQ,YAAY,MAAM,QAAQ;CACnD,MAAM,cAAc,mBAAmB,KAAK,KAAK;CACjD,MAAM,iBAAiB,eAAe,KAAK,WAAW,MAAM;CAC5D,MAAM,MAAM,IAAI,IACd,QAAQ,cAAc,iBAAiB,KAAK,MAAM,EAAE,GAAG,OACxD;AACD,KAAI,aAAa,IAAI,OAAO,MAAM;CAClC,MAAM,KAAK,aAAa,IAAI,UAAU,CAAC,MAAM,QAAQ;AACrD,QAAO,GAAG,OAAO,GAAG,eAAe,iBAAiB,MAAM,MAAM,OAAO,UAAU,MAAM,GAAG,GAAG"}
@@ -1,7 +1,7 @@
1
- import { AuthError } from "./authError.js";
2
1
  import { LOG_LEVELS, REFRESH_TOKEN_DIVIDER, logWithLevel, maybeRedact } from "./utils.js";
3
2
  import { authDb } from "./db.js";
4
3
  import { Fx } from "@robelest/fx";
4
+ import { Cv } from "@robelest/fx/convex";
5
5
 
6
6
  //#region src/server/refresh.ts
7
7
  const DEFAULT_SESSION_INACTIVE_DURATION_MS = 1e3 * 60 * 60 * 24 * 30;
@@ -26,8 +26,14 @@ async function createRefreshToken(ctx, config, sessionId, parentRefreshTokenId)
26
26
  const parseRefreshToken = (refreshToken) => {
27
27
  const [refreshTokenId, sessionId] = refreshToken.split(REFRESH_TOKEN_DIVIDER);
28
28
  const msg = `Can't parse refresh token: ${maybeRedact(refreshToken)}`;
29
- return (refreshTokenId != null ? Fx.succeed(refreshTokenId) : Fx.fail(new AuthError("INVALID_REFRESH_TOKEN", msg))).pipe(Fx.chain((rtId) => {
30
- return (sessionId != null ? Fx.succeed(sessionId) : Fx.fail(new AuthError("INVALID_REFRESH_TOKEN", msg))).pipe(Fx.map((sId) => ({
29
+ return (refreshTokenId != null ? Fx.succeed(refreshTokenId) : Cv.fail({
30
+ code: "INVALID_REFRESH_TOKEN",
31
+ message: msg
32
+ })).pipe(Fx.chain((rtId) => {
33
+ return (sessionId != null ? Fx.succeed(sessionId) : Cv.fail({
34
+ code: "INVALID_REFRESH_TOKEN",
35
+ message: msg
36
+ })).pipe(Fx.map((sId) => ({
31
37
  refreshTokenId: rtId,
32
38
  sessionId: sId
33
39
  })));
@@ -56,10 +62,7 @@ async function invalidateRefreshTokensInSubtree(ctx, refreshToken, config) {
56
62
  }
57
63
  frontier = nextFrontier;
58
64
  }
59
- await Fx.run(Fx.each(tokensToInvalidate, (token) => token.firstUsedTime === void 0 || token.firstUsedTime > Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS ? Fx.from({
60
- ok: () => db.refreshTokens.patch(token._id, { firstUsedTime: Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS }),
61
- err: (e) => e
62
- }) : Fx.unit));
65
+ await Fx.run(Fx.each(tokensToInvalidate, (token) => token.firstUsedTime === void 0 || token.firstUsedTime > Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS ? Fx.promise(() => db.refreshTokens.patch(token._id, { firstUsedTime: Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS })) : Fx.unit));
63
66
  return tokensToInvalidate;
64
67
  }
65
68
  /**
@@ -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 \"./authError\";\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
+ {"version":3,"file":"refresh.js","names":[],"sources":["../../src/server/refresh.ts"],"sourcesContent":["import { Fx } from \"@robelest/fx\";\nimport { Cv } from \"@robelest/fx/convex\";\nimport { ConvexError, GenericId } from \"convex/values\";\n\nimport { authDb } from \"./db\";\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 ConvexError<any>\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, ConvexError<any>> = refreshTokenId != null\n ? Fx.succeed(refreshTokenId)\n : Cv.fail({ code: \"INVALID_REFRESH_TOKEN\", message: msg });\n\n return refreshTokenIdFx.pipe(\n Fx.chain((rtId) => {\n const sessionIdFx: Fx<string, ConvexError<any>> = sessionId != null\n ? Fx.succeed(sessionId)\n : Cv.fail({ code: \"INVALID_REFRESH_TOKEN\", message: 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.promise(() =>\n db.refreshTokens.patch(token._id, {\n firstUsedTime: Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS,\n }),\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":";;;;;;AAcA,MAAM,uCAAuC,MAAO,KAAK,KAAK,KAAK;;AAEnE,MAAa,gCAAgC,KAAK;;;;;AAUlD,eAAsB,mBACpB,KACA,QACA,WACA,sBACoC;CACpC,MAAM,iBACJ,KAAK,KAAK,IACT,OAAO,SAAS,uBACd,QAAQ,IAAI,sCAAsC,SAC/C,OAAO,QAAQ,IAAI,kCAAkC,GACrD,WACJ;AAEJ,QAAO,OAAO,KAAK,OAAO,CAAC,cAAc,OAAO;EAC9C;EACA;EACA,sBAAsB,wBAAwB;EAC/C,CAAC;;;;;;AAOJ,MAAa,qBACX,iBAOG;CACH,MAAM,CAAC,gBAAgB,aAAa,aAAa,MAAM,sBAAsB;CAC7E,MAAM,MAAM,8BAA8B,YAAY,aAAa;AAKnE,SAJuD,kBAAkB,OACrE,GAAG,QAAQ,eAAe,GAC1B,GAAG,KAAK;EAAE,MAAM;EAAyB,SAAS;EAAK,CAAC,EAEpC,KACtB,GAAG,OAAO,SAAS;AAIjB,UAHkD,aAAa,OAC3D,GAAG,QAAQ,UAAU,GACrB,GAAG,KAAK;GAAE,MAAM;GAAyB,SAAS;GAAK,CAAC,EACzC,KACjB,GAAG,KAAK,SAAS;GACf,gBAAgB;GAChB,WAAW;GACZ,EAAE,CACJ;GACD,CACH;;;;;;;AAQH,eAAsB,iCACpB,KACA,cACA,QACA;CACA,MAAM,KAAK,OAAO,KAAK,OAAO;CAC9B,MAAM,qBAAqB,CAAC,aAAa;CACzC,MAAM,UAAU,IAAI,IAA+B,CAAC,aAAa,IAAI,CAAC;CACtE,IAAI,WAAwC,CAAC,aAAa,IAAI;AAC9D,QAAO,SAAS,SAAS,GAAG;EAC1B,MAAM,eAA4C,EAAE;AACpD,OAAK,MAAM,kBAAkB,UAAU;GACrC,MAAM,WAAY,MAAM,GAAG,cAAc,YACvC,aAAa,WACb,eACD;AACD,QAAK,MAAM,SAAS,UAAU;AAC5B,QAAI,QAAQ,IAAI,MAAM,IAAI,CAAE;AAC5B,YAAQ,IAAI,MAAM,IAAI;AACtB,uBAAmB,KAAK,MAAM;AAC9B,iBAAa,KAAK,MAAM,IAAI;;;AAGhC,aAAW;;AAEb,OAAM,GAAG,IACP,GAAG,KAAK,qBAAqB,UAC3B,MAAM,kBAAkB,UACxB,MAAM,gBAAgB,KAAK,KAAK,GAAG,gCAC/B,GAAG,cACD,GAAG,cAAc,MAAM,MAAM,KAAK,EAChC,eAAe,KAAK,KAAK,GAAG,+BAC7B,CAAC,CACH,GACD,GAAG,KACR,CACF;AACD,QAAO;;;;;;;;;;AAeT,MAAa,uBACX,KACA,gBACA,gBACA,WAIG;CACH,MAAM,KAAK,OAAO,KAAK,OAAO;CAE9B,MAAM,YACJ,SACA,YAEA,GAAG,KAAK;EAAE,IAAI;EAAS,WAAW;EAAS,CAAC,CAAC,KAC3C,GAAG,SAAS,QAAQ;AAClB,eAAa,WAAW,OAAO,IAAI;AACnC,SAAO,GAAG,QAAQ,KAAiB;GACnC,CACH;AAIH,QAAO,eAEH,GAAG,cAAc,QACf,eACD,EACH,+BACD,CACE,KACC,GAAG,OAAO,QACR,QAAQ,OAAO,GAAG,QAAQ,IAAI,GAAG,GAAG,KAAK,wBAAwB,CAClE,EACD,GAAG,OAAO,QACR,IAAI,kBAAkB,KAAK,KAAK,GAC5B,GAAG,QAAQ,IAAI,GACf,GAAG,KAAK,wBAAwB,CACrC,EACD,GAAG,OAAO,QACR,IAAI,cAAc,iBACd,GAAG,QAAQ,IAAI,GACf,GAAG,KAAK,mCAAmC,CAChD,CACF,CACA,KACC,GAAG,OAAO,QACR,eAEI,GAAG,SAAS,QACV,IAAI,UACL,EACH,uCACD,CAAC,KACA,GAAG,OAAO,YACR,YAAY,OACR,GAAG,QAAQ,QAAQ,GACnB,GAAG,KAAK,gCAAgC,CAC7C,EACD,GAAG,OAAO,YACR,QAAQ,kBAAkB,KAAK,KAAK,GAChC,GAAG,QAAQ,QAAQ,GACnB,GAAG,KAAK,gCAAgC,CAC7C,EACD,GAAG,KAAK,aAAa;EACnB;EACA,iBAAiB;EAClB,EAAE,CACJ,CACF,EACD,GAAG,KAAK;EACN,KAAK,WAAW;EAChB,MAAM,QAAQ;AACZ,gBAAa,WAAW,OAAO,IAAI;AACnC,UAAO;;EAEV,CAAC,CACH"}
@@ -1,6 +1,6 @@
1
1
  import { ConvexAuthConfig, Doc, SessionInfo } from "./types.js";
2
2
  import * as convex_server63 from "convex/server";
3
- import * as convex_values114 from "convex/values";
3
+ import * as convex_values90 from "convex/values";
4
4
 
5
5
  //#region src/server/runtime.d.ts
6
6
  /**
@@ -33,9 +33,9 @@ declare function Auth(config_: ConvexAuthConfig): {
33
33
  */
34
34
  signIn: convex_server63.RegisteredAction<"public", {
35
35
  provider?: string | undefined;
36
- refreshToken?: string | undefined;
37
36
  params?: any;
38
37
  verifier?: string | undefined;
38
+ refreshToken?: string | undefined;
39
39
  calledBy?: string | undefined;
40
40
  }, Promise<SignInActionResult>>;
41
41
  /**
@@ -49,8 +49,8 @@ declare function Auth(config_: ConvexAuthConfig): {
49
49
  store: convex_server63.RegisteredMutation<"internal", {
50
50
  args: {
51
51
  sessionId?: string | undefined;
52
- userId: string;
53
52
  type: "signIn";
53
+ userId: string;
54
54
  generateTokens: boolean;
55
55
  } | {
56
56
  type: "signOut";
@@ -61,9 +61,9 @@ declare function Auth(config_: ConvexAuthConfig): {
61
61
  provider?: string | undefined;
62
62
  verifier?: string | undefined;
63
63
  type: "verifyCodeAndSignIn";
64
- allowExtraProviders: boolean;
65
- generateTokens: boolean;
66
64
  params: any;
65
+ generateTokens: boolean;
66
+ allowExtraProviders: boolean;
67
67
  } | {
68
68
  type: "verifier";
69
69
  } | {
@@ -78,24 +78,24 @@ declare function Auth(config_: ConvexAuthConfig): {
78
78
  providerAccountId: string;
79
79
  profile: any;
80
80
  } | {
81
- email?: string | undefined;
82
- accountId?: string | undefined;
83
81
  phone?: string | undefined;
82
+ accountId?: string | undefined;
83
+ email?: string | undefined;
84
84
  type: "createVerificationCode";
85
85
  provider: string;
86
+ allowExtraProviders: boolean;
86
87
  code: string;
87
88
  expirationTime: number;
88
- allowExtraProviders: boolean;
89
89
  } | {
90
90
  shouldLinkViaEmail?: boolean | undefined;
91
91
  shouldLinkViaPhone?: boolean | undefined;
92
92
  type: "createAccountFromCredentials";
93
93
  provider: string;
94
+ profile: any;
94
95
  account: {
95
96
  secret?: string | undefined;
96
97
  id: string;
97
98
  };
98
- profile: any;
99
99
  } | {
100
100
  type: "retrieveAccountWithCredentials";
101
101
  provider: string;
@@ -112,15 +112,15 @@ declare function Auth(config_: ConvexAuthConfig): {
112
112
  };
113
113
  } | {
114
114
  except?: string[] | undefined;
115
- userId: string;
116
115
  type: "invalidateSessions";
116
+ userId: string;
117
117
  };
118
- }, Promise<string | void | SessionInfo | {
119
- userId: convex_values114.GenericId<"User">;
120
- sessionId: convex_values114.GenericId<"Session">;
118
+ }, Promise<string | void | {
119
+ userId: convex_values90.GenericId<"User">;
120
+ sessionId: convex_values90.GenericId<"Session">;
121
121
  } | (string & {
122
122
  __tableName: "AuthVerifier";
123
- }) | {
123
+ }) | SessionInfo | {
124
124
  token: string;
125
125
  refreshToken: string;
126
126
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","names":[],"sources":["../../src/server/runtime.ts"],"mappings":";;;;;;;;;AAqJA;;;;;;;;;;;;;;iBAAgB,IAAA,CAAK,OAAA,EAAS,gBAAA"}
1
+ {"version":3,"file":"runtime.d.ts","names":[],"sources":["../../src/server/runtime.ts"],"mappings":";;;;;;;;;AAoJA;;;;;;;;;;;;;;iBAAgB,IAAA,CAAK,OAAA,EAAS,gBAAA"}
@@ -1,6 +1,6 @@
1
- import { AuthError } from "./authError.js";
2
- import { LOG_LEVELS, decryptSecret, encryptSecret, generateRandomString, logError, logWithLevel, requireEnv, sha256 } from "./utils.js";
3
1
  import { configDefaults, listAvailableProviders } from "./config.js";
2
+ import { LOG_LEVELS, decryptSecret, encryptSecret, generateRandomString, logError, logWithLevel, requireEnv, sha256 } from "./utils.js";
3
+ import { redirectToParamCookie, useRedirectToParam } from "./cookies.js";
4
4
  import { callModifyAccount } from "./mutations/account.js";
5
5
  import { callInvalidateSessions } from "./mutations/invalidate.js";
6
6
  import { enterpriseOidcProviderId, getEnterpriseOidcUrls, isEnterpriseSamlSourceActive, normalizeDomain } from "./enterprise/shared.js";
@@ -14,15 +14,15 @@ import { storeArgs, storeImpl } from "./mutations/store.js";
14
14
  import { redirectAbsoluteUrl, setURLSearchParam } from "./redirects.js";
15
15
  import { signInImpl } from "./signin.js";
16
16
  import { createCoreDomains } from "./core.js";
17
- import { redirectToParamCookie, useRedirectToParam } from "./cookies.js";
17
+ import { getOidcConfig, getPublicOidcConfig, getSamlConfig, upsertProtocolConfig, withOidcSecretState } from "./enterprise/config.js";
18
18
  import { createEnterpriseDomain } from "./enterprise/domain.js";
19
19
  import { addAuthRoutes, addOpenIdRoutes, convertErrorsToResponse, createHttpAction, createHttpRoute, getCookies } from "./http.js";
20
20
  import { createOAuthAuthorizationURL, handleOAuthCallback } from "./oauth.js";
21
- import { getOidcConfig, getPublicOidcConfig, getSamlConfig, upsertProtocolConfig, withOidcSecretState } from "./enterprise/config.js";
22
21
  import { createServiceProviderMetadata, getSamlServiceProviderOptions, parseSamlIdpMetadata } from "./enterprise/saml.js";
23
22
  import { parseScimPath } from "./enterprise/scim.js";
24
23
  import { addEnterpriseHttpRuntime } from "./enterprise/http.js";
25
24
  import { Fx } from "@robelest/fx";
25
+ import { Cv } from "@robelest/fx/convex";
26
26
  import { actionGeneric, internalMutationGeneric } from "convex/server";
27
27
  import { v } from "convex/values";
28
28
  import { serialize } from "cookie";
@@ -56,7 +56,11 @@ function Auth(config_) {
56
56
  if (provider === void 0) {
57
57
  const detail = `Provider \`${id}\` is not configured, available providers are ${listAvailableProviders(config, allowExtraProviders)}.`;
58
58
  logWithLevel(LOG_LEVELS.ERROR, detail);
59
- throw new AuthError("PROVIDER_NOT_CONFIGURED", detail, { provider: id }).toConvexError();
59
+ throw Cv.error({
60
+ code: "PROVIDER_NOT_CONFIGURED",
61
+ message: detail,
62
+ provider: id
63
+ });
60
64
  }
61
65
  return provider;
62
66
  };
@@ -81,12 +85,18 @@ function Auth(config_) {
81
85
  const getPolicyFromEnterprise = (enterprise) => normalizeEnterprisePolicy(enterprise.policy);
82
86
  const loadEnterpriseOrThrow = async (ctx, enterpriseId) => {
83
87
  const enterprise = await ctx.runQuery(config.component.public.enterpriseGet, { enterpriseId });
84
- if (!enterprise) throw new AuthError("INVALID_PARAMETERS", enterpriseNotFoundError).toConvexError();
88
+ if (!enterprise) throw Cv.error({
89
+ code: "INVALID_PARAMETERS",
90
+ message: enterpriseNotFoundError
91
+ });
85
92
  return enterprise;
86
93
  };
87
94
  const loadActiveEnterpriseOrThrow = async (ctx, enterpriseId) => {
88
95
  const enterprise = await loadEnterpriseOrThrow(ctx, enterpriseId);
89
- if (enterprise.status !== "active") throw new AuthError("INVALID_PARAMETERS", "Enterprise connection is not active.").toConvexError();
96
+ if (enterprise.status !== "active") throw Cv.error({
97
+ code: "INVALID_PARAMETERS",
98
+ message: "Enterprise connection is not active."
99
+ });
90
100
  return enterprise;
91
101
  };
92
102
  const loadActiveEnterpriseSamlOrThrow = async (ctx, enterpriseId) => {
@@ -100,9 +110,15 @@ function Auth(config_) {
100
110
  status: enterprise.status,
101
111
  enterprise
102
112
  };
103
- if (!isEnterpriseSamlSourceActive(loaded)) throw new AuthError("INVALID_PARAMETERS", "Enterprise connection is not active.").toConvexError();
113
+ if (!isEnterpriseSamlSourceActive(loaded)) throw Cv.error({
114
+ code: "INVALID_PARAMETERS",
115
+ message: "Enterprise connection is not active."
116
+ });
104
117
  const saml = getSamlConfig(loaded.config);
105
- if (!saml.idp?.metadataXml) throw new AuthError("PROVIDER_NOT_CONFIGURED", "SAML is not configured for this enterprise.").toConvexError();
118
+ if (!saml.idp?.metadataXml) throw Cv.error({
119
+ code: "PROVIDER_NOT_CONFIGURED",
120
+ message: "SAML is not configured for this enterprise."
121
+ });
106
122
  return {
107
123
  loaded,
108
124
  enterprise,
@@ -112,7 +128,10 @@ function Auth(config_) {
112
128
  const loadEnterpriseOidcOrThrow = async (ctx, enterpriseId) => {
113
129
  const enterprise = await loadActiveEnterpriseOrThrow(ctx, enterpriseId);
114
130
  const oidc = await getEnterpriseOidcConfigWithSecret(ctx, enterprise);
115
- if (oidc.enabled !== true) throw new AuthError("PROVIDER_NOT_CONFIGURED", "OIDC is not configured for this enterprise.").toConvexError();
131
+ if (oidc.enabled !== true) throw Cv.error({
132
+ code: "PROVIDER_NOT_CONFIGURED",
133
+ message: "OIDC is not configured for this enterprise."
134
+ });
116
135
  return {
117
136
  enterprise,
118
137
  oidc
@@ -164,14 +183,26 @@ function Auth(config_) {
164
183
  };
165
184
  const getEnterpriseScimContext = async (ctx, request) => {
166
185
  const authHeader = request.headers.get("Authorization");
167
- if (!authHeader?.startsWith("Bearer ")) throw new AuthError("MISSING_BEARER_TOKEN").toConvexError();
186
+ if (!authHeader?.startsWith("Bearer ")) throw Cv.error({
187
+ code: "MISSING_BEARER_TOKEN",
188
+ message: "Missing or malformed Authorization: Bearer header."
189
+ });
168
190
  const token = authHeader.slice(7);
169
191
  const scimConfig = await ctx.runQuery(config.component.public.enterpriseScimConfigGetByTokenHash, { tokenHash: await sha256(token) });
170
- if (!scimConfig || scimConfig.status !== "active") throw new AuthError("INVALID_API_KEY", "Invalid SCIM token.").toConvexError();
192
+ if (!scimConfig || scimConfig.status !== "active") throw Cv.error({
193
+ code: "INVALID_API_KEY",
194
+ message: "Invalid SCIM token."
195
+ });
171
196
  const parsedPath = parseScimPath(new URL(request.url).pathname);
172
- if (parsedPath.enterpriseId !== scimConfig.enterpriseId) throw new AuthError("INVALID_API_KEY", "SCIM token/tenant mismatch.").toConvexError();
197
+ if (parsedPath.enterpriseId !== scimConfig.enterpriseId) throw Cv.error({
198
+ code: "INVALID_API_KEY",
199
+ message: "SCIM token/tenant mismatch."
200
+ });
173
201
  const enterprise = await ctx.runQuery(config.component.public.enterpriseGet, { enterpriseId: scimConfig.enterpriseId });
174
- if (enterprise === null) throw new AuthError("INVALID_PARAMETERS", "Enterprise not found.").toConvexError();
202
+ if (enterprise === null) throw Cv.error({
203
+ code: "INVALID_PARAMETERS",
204
+ message: "Enterprise not found."
205
+ });
175
206
  return {
176
207
  scimConfig,
177
208
  enterprise,
@@ -248,10 +279,17 @@ function Auth(config_) {
248
279
  if (hasOAuth) addAuthRoutes(http, {
249
280
  handleSignIn: convertErrorsToResponse(400, async (ctx, request) => {
250
281
  const url = new URL(request.url);
251
- const providerId = url.pathname.split("/").at(-1);
252
- if (providerId === null) throw new AuthError("OAUTH_MISSING_PROVIDER").toConvexError();
282
+ const pathParts = url.pathname.split("/");
283
+ const providerId = pathParts[pathParts.length - 1];
284
+ if (providerId === null) throw Cv.error({
285
+ code: "OAUTH_MISSING_PROVIDER",
286
+ message: "Missing OAuth provider ID."
287
+ });
253
288
  const verifier = url.searchParams.get("code");
254
- if (verifier === null) throw new AuthError("OAUTH_MISSING_VERIFIER").toConvexError();
289
+ if (verifier === null) throw Cv.error({
290
+ code: "OAUTH_MISSING_VERIFIER",
291
+ message: "Missing sign-in verifier."
292
+ });
255
293
  const oauthConfig = getProviderOrThrow(providerId);
256
294
  const { redirect, cookies, signature } = await createOAuthAuthorizationURL(providerId, oauthConfig.provider, oauthConfig);
257
295
  await callVerifierSignature(ctx, {
@@ -269,8 +307,12 @@ function Auth(config_) {
269
307
  }),
270
308
  handleCallback: async (ctx, request) => {
271
309
  const url = new URL(request.url);
272
- const providerId = new URL(request.url).pathname.split("/").at(-1);
273
- if (!providerId) throw new AuthError("OAUTH_MISSING_PROVIDER").toConvexError();
310
+ const callbackPathParts = new URL(request.url).pathname.split("/");
311
+ const providerId = callbackPathParts[callbackPathParts.length - 1];
312
+ if (!providerId) throw Cv.error({
313
+ code: "OAUTH_MISSING_PROVIDER",
314
+ message: "Missing OAuth provider ID."
315
+ });
274
316
  logWithLevel(LOG_LEVELS.DEBUG, "Handling OAuth callback for provider:", providerId);
275
317
  const provider = getProviderOrThrow(providerId);
276
318
  const cookies = getCookies(request);