@robelest/convex-auth 0.0.3-preview → 0.0.3-preview.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/dist/bin.cjs +15 -15
  2. package/dist/client/index.d.ts +40 -12
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +73 -12
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/component/_generated/api.d.ts +2 -2
  7. package/dist/component/_generated/api.d.ts.map +1 -1
  8. package/dist/component/_generated/component.d.ts +1 -1
  9. package/dist/component/_generated/component.d.ts.map +1 -1
  10. package/dist/component/{portalBridge.d.ts → bridge.d.ts} +2 -2
  11. package/dist/component/bridge.d.ts.map +1 -0
  12. package/dist/component/{portalBridge.js → bridge.js} +2 -2
  13. package/dist/component/bridge.js.map +1 -0
  14. package/dist/component/index.d.ts +11 -4
  15. package/dist/component/index.d.ts.map +1 -1
  16. package/dist/component/index.js +8 -2
  17. package/dist/component/index.js.map +1 -1
  18. package/dist/component/public.d.ts +24 -17
  19. package/dist/component/public.d.ts.map +1 -1
  20. package/dist/component/public.js +23 -4
  21. package/dist/component/public.js.map +1 -1
  22. package/dist/component/schema.d.ts +11 -7
  23. package/dist/component/schema.d.ts.map +1 -1
  24. package/dist/component/schema.js +4 -1
  25. package/dist/component/schema.js.map +1 -1
  26. package/dist/providers/anonymous.d.ts +3 -0
  27. package/dist/providers/anonymous.d.ts.map +1 -1
  28. package/dist/providers/anonymous.js +3 -0
  29. package/dist/providers/anonymous.js.map +1 -1
  30. package/dist/providers/credentials.d.ts +3 -0
  31. package/dist/providers/credentials.d.ts.map +1 -1
  32. package/dist/providers/credentials.js +3 -0
  33. package/dist/providers/credentials.js.map +1 -1
  34. package/dist/providers/email.d.ts +3 -0
  35. package/dist/providers/email.d.ts.map +1 -1
  36. package/dist/providers/email.js +3 -0
  37. package/dist/providers/email.js.map +1 -1
  38. package/dist/providers/passkey.d.ts +7 -1
  39. package/dist/providers/passkey.d.ts.map +1 -1
  40. package/dist/providers/passkey.js +7 -1
  41. package/dist/providers/passkey.js.map +1 -1
  42. package/dist/providers/password.d.ts +3 -0
  43. package/dist/providers/password.d.ts.map +1 -1
  44. package/dist/providers/password.js +3 -0
  45. package/dist/providers/password.js.map +1 -1
  46. package/dist/providers/phone.d.ts +3 -0
  47. package/dist/providers/phone.d.ts.map +1 -1
  48. package/dist/providers/phone.js +3 -0
  49. package/dist/providers/phone.js.map +1 -1
  50. package/dist/providers/totp.d.ts +8 -0
  51. package/dist/providers/totp.d.ts.map +1 -1
  52. package/dist/providers/totp.js +8 -0
  53. package/dist/providers/totp.js.map +1 -1
  54. package/dist/server/{convex-auth.d.ts → auth.d.ts} +226 -36
  55. package/dist/server/auth.d.ts.map +1 -0
  56. package/dist/server/{convex-auth.js → auth.js} +287 -111
  57. package/dist/server/auth.js.map +1 -0
  58. package/dist/server/errors.d.ts +148 -0
  59. package/dist/server/errors.d.ts.map +1 -0
  60. package/dist/server/errors.js +179 -0
  61. package/dist/server/errors.js.map +1 -0
  62. package/dist/server/implementation/index.d.ts +170 -48
  63. package/dist/server/implementation/index.d.ts.map +1 -1
  64. package/dist/server/implementation/index.js +383 -167
  65. package/dist/server/implementation/index.js.map +1 -1
  66. package/dist/server/implementation/{apiKey.d.ts → keys.d.ts} +1 -1
  67. package/dist/server/implementation/keys.d.ts.map +1 -0
  68. package/dist/server/implementation/{apiKey.js → keys.js} +4 -5
  69. package/dist/server/implementation/keys.js.map +1 -0
  70. package/dist/server/implementation/mutations/{modifyAccount.d.ts → account.d.ts} +3 -3
  71. package/dist/server/implementation/mutations/account.d.ts.map +1 -0
  72. package/dist/server/implementation/mutations/{modifyAccount.js → account.js} +4 -3
  73. package/dist/server/implementation/mutations/account.js.map +1 -0
  74. package/dist/server/implementation/mutations/{createVerificationCode.d.ts → code.d.ts} +1 -1
  75. package/dist/server/implementation/mutations/code.d.ts.map +1 -0
  76. package/dist/server/implementation/mutations/{createVerificationCode.js → code.js} +2 -2
  77. package/dist/server/implementation/mutations/code.js.map +1 -0
  78. package/dist/server/implementation/mutations/index.d.ts +33 -33
  79. package/dist/server/implementation/mutations/index.d.ts.map +1 -1
  80. package/dist/server/implementation/mutations/index.js +22 -22
  81. package/dist/server/implementation/mutations/index.js.map +1 -1
  82. package/dist/server/implementation/mutations/{invalidateSessions.d.ts → invalidate.d.ts} +1 -1
  83. package/dist/server/implementation/mutations/invalidate.d.ts.map +1 -0
  84. package/dist/server/implementation/mutations/{invalidateSessions.js → invalidate.js} +2 -2
  85. package/dist/server/implementation/mutations/invalidate.js.map +1 -0
  86. package/dist/server/implementation/mutations/{userOAuth.d.ts → oauth.d.ts} +3 -3
  87. package/dist/server/implementation/mutations/oauth.d.ts.map +1 -0
  88. package/dist/server/implementation/mutations/{userOAuth.js → oauth.js} +4 -3
  89. package/dist/server/implementation/mutations/oauth.js.map +1 -0
  90. package/dist/server/implementation/mutations/{refreshSession.d.ts → refresh.d.ts} +1 -1
  91. package/dist/server/implementation/mutations/refresh.d.ts.map +1 -0
  92. package/dist/server/implementation/mutations/{refreshSession.js → refresh.js} +3 -3
  93. package/dist/server/implementation/mutations/refresh.js.map +1 -0
  94. package/dist/server/implementation/mutations/{createAccountFromCredentials.d.ts → register.d.ts} +4 -4
  95. package/dist/server/implementation/mutations/register.d.ts.map +1 -0
  96. package/dist/server/implementation/mutations/{createAccountFromCredentials.js → register.js} +4 -3
  97. package/dist/server/implementation/mutations/register.js.map +1 -0
  98. package/dist/server/implementation/mutations/{retrieveAccountWithCredentials.d.ts → retrieve.d.ts} +3 -3
  99. package/dist/server/implementation/mutations/retrieve.d.ts.map +1 -0
  100. package/dist/server/implementation/mutations/{retrieveAccountWithCredentials.js → retrieve.js} +3 -3
  101. package/dist/server/implementation/mutations/retrieve.js.map +1 -0
  102. package/dist/server/implementation/mutations/{verifierSignature.d.ts → signature.d.ts} +1 -1
  103. package/dist/server/implementation/mutations/signature.d.ts.map +1 -0
  104. package/dist/server/implementation/mutations/{verifierSignature.js → signature.js} +4 -3
  105. package/dist/server/implementation/mutations/signature.js.map +1 -0
  106. package/dist/server/implementation/mutations/{signIn.d.ts → signin.d.ts} +1 -1
  107. package/dist/server/implementation/mutations/{signIn.d.ts.map → signin.d.ts.map} +1 -1
  108. package/dist/server/implementation/mutations/{signIn.js → signin.js} +2 -2
  109. package/dist/server/implementation/mutations/{signIn.js.map → signin.js.map} +1 -1
  110. package/dist/server/implementation/mutations/{signOut.d.ts → signout.d.ts} +1 -1
  111. package/dist/server/implementation/mutations/{signOut.d.ts.map → signout.d.ts.map} +1 -1
  112. package/dist/server/implementation/mutations/{signOut.js → signout.js} +2 -2
  113. package/dist/server/implementation/mutations/{signOut.js.map → signout.js.map} +1 -1
  114. package/dist/server/implementation/mutations/{storeRef.d.ts → store.d.ts} +1 -1
  115. package/dist/server/implementation/mutations/store.d.ts.map +1 -0
  116. package/dist/server/implementation/mutations/{storeRef.js → store.js} +1 -1
  117. package/dist/server/implementation/mutations/store.js.map +1 -0
  118. package/dist/server/implementation/mutations/verifier.js +1 -1
  119. package/dist/server/implementation/mutations/verifier.js.map +1 -1
  120. package/dist/server/implementation/mutations/{verifyCodeAndSignIn.d.ts → verify.d.ts} +1 -1
  121. package/dist/server/implementation/mutations/verify.d.ts.map +1 -0
  122. package/dist/server/implementation/mutations/{verifyCodeAndSignIn.js → verify.js} +3 -3
  123. package/dist/server/implementation/mutations/verify.js.map +1 -0
  124. package/dist/server/implementation/passkey.d.ts.map +1 -1
  125. package/dist/server/implementation/passkey.js +47 -55
  126. package/dist/server/implementation/passkey.js.map +1 -1
  127. package/dist/server/implementation/provider.d.ts.map +1 -1
  128. package/dist/server/implementation/provider.js +5 -4
  129. package/dist/server/implementation/provider.js.map +1 -1
  130. package/dist/server/implementation/{rateLimit.d.ts → ratelimit.d.ts} +1 -1
  131. package/dist/server/implementation/{rateLimit.d.ts.map → ratelimit.d.ts.map} +1 -1
  132. package/dist/server/implementation/{rateLimit.js → ratelimit.js} +1 -1
  133. package/dist/server/implementation/{rateLimit.js.map → ratelimit.js.map} +1 -1
  134. package/dist/server/implementation/redirects.d.ts.map +1 -1
  135. package/dist/server/implementation/redirects.js +2 -1
  136. package/dist/server/implementation/redirects.js.map +1 -1
  137. package/dist/server/implementation/{refreshTokens.d.ts → refresh.d.ts} +1 -1
  138. package/dist/server/implementation/refresh.d.ts.map +1 -0
  139. package/dist/server/implementation/{refreshTokens.js → refresh.js} +3 -2
  140. package/dist/server/implementation/refresh.js.map +1 -0
  141. package/dist/server/implementation/sessions.js +1 -1
  142. package/dist/server/implementation/sessions.js.map +1 -1
  143. package/dist/server/implementation/{signIn.d.ts → signin.d.ts} +1 -1
  144. package/dist/server/implementation/{signIn.d.ts.map → signin.d.ts.map} +1 -1
  145. package/dist/server/implementation/{signIn.js → signin.js} +12 -8
  146. package/dist/server/implementation/signin.js.map +1 -0
  147. package/dist/server/implementation/totp.d.ts.map +1 -1
  148. package/dist/server/implementation/totp.js +29 -29
  149. package/dist/server/implementation/totp.js.map +1 -1
  150. package/dist/server/implementation/types.d.ts +131 -1
  151. package/dist/server/implementation/types.d.ts.map +1 -1
  152. package/dist/server/implementation/types.js +65 -1
  153. package/dist/server/implementation/types.js.map +1 -1
  154. package/dist/server/implementation/users.d.ts.map +1 -1
  155. package/dist/server/implementation/users.js +3 -2
  156. package/dist/server/implementation/users.js.map +1 -1
  157. package/dist/server/index.d.ts +131 -1
  158. package/dist/server/index.d.ts.map +1 -1
  159. package/dist/server/index.js +117 -1
  160. package/dist/server/index.js.map +1 -1
  161. package/dist/server/oauth/{authorizationUrl.d.ts → authorization.d.ts} +1 -1
  162. package/dist/server/oauth/authorization.d.ts.map +1 -0
  163. package/dist/server/oauth/{authorizationUrl.js → authorization.js} +4 -3
  164. package/dist/server/oauth/authorization.js.map +1 -0
  165. package/dist/server/oauth/callback.d.ts.map +1 -1
  166. package/dist/server/oauth/callback.js +7 -6
  167. package/dist/server/oauth/callback.js.map +1 -1
  168. package/dist/server/oauth/checks.d.ts.map +1 -1
  169. package/dist/server/oauth/checks.js +2 -1
  170. package/dist/server/oauth/checks.js.map +1 -1
  171. package/dist/server/oauth/{convexAuth.d.ts → helpers.d.ts} +1 -1
  172. package/dist/server/oauth/helpers.d.ts.map +1 -0
  173. package/dist/server/oauth/{convexAuth.js → helpers.js} +6 -5
  174. package/dist/server/oauth/helpers.js.map +1 -0
  175. package/dist/server/oauth/lib/utils/{customFetch.d.ts → fetch.d.ts} +1 -1
  176. package/dist/server/oauth/lib/utils/fetch.d.ts.map +1 -0
  177. package/dist/server/oauth/lib/utils/{customFetch.js → fetch.js} +1 -1
  178. package/dist/server/oauth/lib/utils/fetch.js.map +1 -0
  179. package/dist/server/{provider_utils.d.ts → providers.d.ts} +1 -1
  180. package/dist/server/providers.d.ts.map +1 -0
  181. package/dist/server/{provider_utils.js → providers.js} +1 -1
  182. package/dist/server/providers.js.map +1 -0
  183. package/dist/server/{email-templates.d.ts → templates.d.ts} +8 -1
  184. package/dist/server/templates.d.ts.map +1 -0
  185. package/dist/server/{portal-email.js → templates.js} +74 -3
  186. package/dist/server/templates.js.map +1 -0
  187. package/dist/server/types.d.ts +88 -5
  188. package/dist/server/types.d.ts.map +1 -1
  189. package/dist/server/utils.d.ts.map +1 -1
  190. package/dist/server/utils.js +2 -1
  191. package/dist/server/utils.js.map +1 -1
  192. package/dist/server/version.d.ts +1 -1
  193. package/dist/server/version.d.ts.map +1 -1
  194. package/dist/server/version.js +1 -1
  195. package/dist/server/version.js.map +1 -1
  196. package/package.json +5 -1
  197. package/src/cli/index.ts +5 -5
  198. package/src/cli/{portal-link.ts → link.ts} +1 -1
  199. package/src/cli/utils.ts +1 -1
  200. package/src/client/index.ts +102 -17
  201. package/src/component/_generated/api.ts +2 -2
  202. package/src/component/_generated/component.ts +1 -1
  203. package/src/component/{portalBridge.ts → bridge.ts} +2 -2
  204. package/src/component/index.ts +10 -2
  205. package/src/component/public.ts +25 -4
  206. package/src/component/schema.ts +4 -1
  207. package/src/providers/anonymous.ts +3 -0
  208. package/src/providers/credentials.ts +3 -0
  209. package/src/providers/email.ts +3 -0
  210. package/src/providers/passkey.ts +8 -1
  211. package/src/providers/password.ts +3 -0
  212. package/src/providers/phone.ts +3 -0
  213. package/src/providers/totp.ts +9 -0
  214. package/src/server/auth.ts +969 -0
  215. package/src/server/errors.ts +275 -0
  216. package/src/server/implementation/index.ts +370 -88
  217. package/src/server/implementation/{apiKey.ts → keys.ts} +7 -6
  218. package/src/server/implementation/mutations/{modifyAccount.ts → account.ts} +3 -4
  219. package/src/server/implementation/mutations/{createVerificationCode.ts → code.ts} +1 -1
  220. package/src/server/implementation/mutations/index.ts +22 -22
  221. package/src/server/implementation/mutations/{invalidateSessions.ts → invalidate.ts} +1 -1
  222. package/src/server/implementation/mutations/{userOAuth.ts → oauth.ts} +3 -2
  223. package/src/server/implementation/mutations/{refreshSession.ts → refresh.ts} +2 -2
  224. package/src/server/implementation/mutations/{createAccountFromCredentials.ts → register.ts} +3 -2
  225. package/src/server/implementation/mutations/{retrieveAccountWithCredentials.ts → retrieve.ts} +2 -2
  226. package/src/server/implementation/mutations/{verifierSignature.ts → signature.ts} +3 -2
  227. package/src/server/implementation/mutations/{signIn.ts → signin.ts} +1 -1
  228. package/src/server/implementation/mutations/{signOut.ts → signout.ts} +1 -1
  229. package/src/server/implementation/mutations/verifier.ts +1 -1
  230. package/src/server/implementation/mutations/{verifyCodeAndSignIn.ts → verify.ts} +2 -2
  231. package/src/server/implementation/passkey.ts +86 -116
  232. package/src/server/implementation/provider.ts +5 -8
  233. package/src/server/implementation/redirects.ts +2 -3
  234. package/src/server/implementation/{refreshTokens.ts → refresh.ts} +2 -1
  235. package/src/server/implementation/sessions.ts +1 -1
  236. package/src/server/implementation/{signIn.ts → signin.ts} +13 -11
  237. package/src/server/implementation/totp.ts +60 -84
  238. package/src/server/implementation/types.ts +316 -1
  239. package/src/server/implementation/users.ts +4 -7
  240. package/src/server/index.ts +142 -3
  241. package/src/server/oauth/{authorizationUrl.ts → authorization.ts} +3 -2
  242. package/src/server/oauth/callback.ts +7 -6
  243. package/src/server/oauth/checks.ts +3 -1
  244. package/src/server/oauth/{convexAuth.ts → helpers.ts} +8 -5
  245. package/src/server/{portal-email.ts → templates.ts} +78 -2
  246. package/src/server/types.ts +133 -4
  247. package/src/server/utils.ts +3 -1
  248. package/src/server/version.ts +1 -1
  249. package/dist/component/portalBridge.d.ts.map +0 -1
  250. package/dist/component/portalBridge.js.map +0 -1
  251. package/dist/server/convex-auth.d.ts.map +0 -1
  252. package/dist/server/convex-auth.js.map +0 -1
  253. package/dist/server/convex_types.d.ts +0 -17
  254. package/dist/server/convex_types.d.ts.map +0 -1
  255. package/dist/server/convex_types.js +0 -2
  256. package/dist/server/convex_types.js.map +0 -1
  257. package/dist/server/email-templates.d.ts.map +0 -1
  258. package/dist/server/email-templates.js +0 -74
  259. package/dist/server/email-templates.js.map +0 -1
  260. package/dist/server/implementation/apiKey.d.ts.map +0 -1
  261. package/dist/server/implementation/apiKey.js.map +0 -1
  262. package/dist/server/implementation/mutations/createAccountFromCredentials.d.ts.map +0 -1
  263. package/dist/server/implementation/mutations/createAccountFromCredentials.js.map +0 -1
  264. package/dist/server/implementation/mutations/createVerificationCode.d.ts.map +0 -1
  265. package/dist/server/implementation/mutations/createVerificationCode.js.map +0 -1
  266. package/dist/server/implementation/mutations/invalidateSessions.d.ts.map +0 -1
  267. package/dist/server/implementation/mutations/invalidateSessions.js.map +0 -1
  268. package/dist/server/implementation/mutations/modifyAccount.d.ts.map +0 -1
  269. package/dist/server/implementation/mutations/modifyAccount.js.map +0 -1
  270. package/dist/server/implementation/mutations/refreshSession.d.ts.map +0 -1
  271. package/dist/server/implementation/mutations/refreshSession.js.map +0 -1
  272. package/dist/server/implementation/mutations/retrieveAccountWithCredentials.d.ts.map +0 -1
  273. package/dist/server/implementation/mutations/retrieveAccountWithCredentials.js.map +0 -1
  274. package/dist/server/implementation/mutations/storeRef.d.ts.map +0 -1
  275. package/dist/server/implementation/mutations/storeRef.js.map +0 -1
  276. package/dist/server/implementation/mutations/userOAuth.d.ts.map +0 -1
  277. package/dist/server/implementation/mutations/userOAuth.js.map +0 -1
  278. package/dist/server/implementation/mutations/verifierSignature.d.ts.map +0 -1
  279. package/dist/server/implementation/mutations/verifierSignature.js.map +0 -1
  280. package/dist/server/implementation/mutations/verifyCodeAndSignIn.d.ts.map +0 -1
  281. package/dist/server/implementation/mutations/verifyCodeAndSignIn.js.map +0 -1
  282. package/dist/server/implementation/refreshTokens.d.ts.map +0 -1
  283. package/dist/server/implementation/refreshTokens.js.map +0 -1
  284. package/dist/server/implementation/signIn.js.map +0 -1
  285. package/dist/server/oauth/authorizationUrl.d.ts.map +0 -1
  286. package/dist/server/oauth/authorizationUrl.js.map +0 -1
  287. package/dist/server/oauth/convexAuth.d.ts.map +0 -1
  288. package/dist/server/oauth/convexAuth.js.map +0 -1
  289. package/dist/server/oauth/lib/utils/customFetch.d.ts.map +0 -1
  290. package/dist/server/oauth/lib/utils/customFetch.js.map +0 -1
  291. package/dist/server/portal-email.d.ts +0 -19
  292. package/dist/server/portal-email.d.ts.map +0 -1
  293. package/dist/server/portal-email.js.map +0 -1
  294. package/dist/server/provider_utils.d.ts.map +0 -1
  295. package/dist/server/provider_utils.js.map +0 -1
  296. package/src/server/convex-auth.ts +0 -602
  297. package/src/server/convex_types.ts +0 -55
  298. package/src/server/email-templates.ts +0 -77
  299. /package/src/cli/{generateKeys.ts → keys.ts} +0 -0
  300. /package/src/cli/{portal-upload.ts → upload.ts} +0 -0
  301. /package/src/server/implementation/mutations/{storeRef.ts → store.ts} +0 -0
  302. /package/src/server/implementation/{rateLimit.ts → ratelimit.ts} +0 -0
  303. /package/src/server/oauth/lib/utils/{customFetch.ts → fetch.ts} +0 -0
  304. /package/src/server/{provider_utils.ts → providers.ts} +0 -0
@@ -1,16 +1,17 @@
1
1
  import { actionGeneric, httpActionGeneric, internalMutationGeneric, } from "convex/server";
2
2
  import { ConvexError, v } from "convex/values";
3
+ import { throwAuthError, isAuthError } from "../errors.js";
3
4
  import { parse as parseCookies, serialize as serializeCookie } from "cookie";
4
5
  import { redirectToParamCookie, useRedirectToParam } from "../cookies.js";
5
- import { configDefaults, listAvailableProviders, materializeProvider, } from "../provider_utils.js";
6
+ import { configDefaults, listAvailableProviders, materializeProvider, } from "../providers.js";
6
7
  import { requireEnv } from "../utils.js";
7
8
  import { LOG_LEVELS, TOKEN_SUB_CLAIM_DIVIDER, logError, logWithLevel, } from "./utils.js";
8
9
  import { callCreateAccountFromCredentials, callInvalidateSessions, callModifyAccount, callRetreiveAccountWithCredentials, callSignOut, callUserOAuth, callVerifierSignature, storeArgs, storeImpl, } from "./mutations/index.js";
9
- import { signInImpl } from "./signIn.js";
10
+ import { signInImpl } from "./signin.js";
10
11
  import { redirectAbsoluteUrl, setURLSearchParam } from "./redirects.js";
11
- import { generateApiKey, hashApiKey, buildScopeChecker, validateScopes, checkKeyRateLimit, } from "./apiKey.js";
12
- import { getAuthorizationUrl } from "../oauth/authorizationUrl.js";
13
- import { defaultCookiesOptions, oAuthConfigToInternalProvider, } from "../oauth/convexAuth.js";
12
+ import { generateApiKey, hashApiKey, buildScopeChecker, validateScopes, checkKeyRateLimit, } from "./keys.js";
13
+ import { getAuthorizationUrl } from "../oauth/authorization.js";
14
+ import { defaultCookiesOptions, oAuthConfigToInternalProvider, } from "../oauth/helpers.js";
14
15
  import { handleOAuth } from "../oauth/callback.js";
15
16
  /**
16
17
  * Configure the Convex Auth library. Returns an object with
@@ -42,10 +43,10 @@ export function Auth(config_) {
42
43
  const getProviderOrThrow = (id, allowExtraProviders = false) => {
43
44
  const provider = getProvider(id, allowExtraProviders);
44
45
  if (provider === undefined) {
45
- const message = `Provider \`${id}\` is not configured, ` +
46
+ const detail = `Provider \`${id}\` is not configured, ` +
46
47
  `available providers are ${listAvailableProviders(config, allowExtraProviders)}.`;
47
- logWithLevel(LOG_LEVELS.ERROR, message);
48
- throw new Error(message);
48
+ logWithLevel(LOG_LEVELS.ERROR, detail);
49
+ throwAuthError("PROVIDER_NOT_CONFIGURED", detail, { provider: id });
49
50
  }
50
51
  return provider;
51
52
  };
@@ -54,6 +55,9 @@ export function Auth(config_) {
54
55
  /**
55
56
  * Get the current user's ID from the auth context, or `null` if
56
57
  * not signed in.
58
+ *
59
+ * @param ctx - Any Convex context with an `auth` field (query, mutation, or action).
60
+ * @returns The user's `Id<"user">`, or `null` when unauthenticated.
57
61
  */
58
62
  current: async (ctx) => {
59
63
  const identity = await ctx.auth.getUserIdentity();
@@ -66,24 +70,35 @@ export function Auth(config_) {
66
70
  /**
67
71
  * Get the current user's ID, or throw if not signed in.
68
72
  * Use this when authentication is required.
73
+ *
74
+ * @param ctx - Any Convex context with an `auth` field.
75
+ * @returns The user's `Id<"user">`.
76
+ * @throws `ConvexError` with code `NOT_SIGNED_IN` when unauthenticated.
69
77
  */
70
78
  require: async (ctx) => {
71
79
  const identity = await ctx.auth.getUserIdentity();
72
80
  if (identity === null) {
73
- throw new Error("Not signed in");
81
+ throwAuthError("NOT_SIGNED_IN");
74
82
  }
75
83
  const [userId] = identity.subject.split(TOKEN_SUB_CLAIM_DIVIDER);
76
84
  return userId;
77
85
  },
78
86
  /**
79
87
  * Retrieve a user document by their ID.
88
+ *
89
+ * @param ctx - Convex context with `runQuery`.
90
+ * @param userId - The user document ID.
91
+ * @returns The user document, or `null` if not found.
80
92
  */
81
93
  get: async (ctx, userId) => {
82
94
  return await ctx.runQuery(config.component.public.userGetById, { userId });
83
95
  },
84
96
  /**
85
97
  * Get the currently signed-in user's document, or `null` if not
86
- * signed in. Convenience method combining `current` + `get`.
98
+ * signed in. Convenience combining `current()` + `get()`.
99
+ *
100
+ * @param ctx - Convex context with `auth` and `runQuery`.
101
+ * @returns The user document, or `null` when unauthenticated.
87
102
  */
88
103
  viewer: async (ctx) => {
89
104
  const userId = await auth.user.current(ctx);
@@ -92,6 +107,19 @@ export function Auth(config_) {
92
107
  }
93
108
  return await ctx.runQuery(config.component.public.userGetById, { userId });
94
109
  },
110
+ /**
111
+ * Update a user document with partial data.
112
+ *
113
+ * @param ctx - Convex context with `runMutation`.
114
+ * @param userId - The user document ID.
115
+ * @param data - Partial data to merge into the user document.
116
+ */
117
+ patch: async (ctx, userId, data) => {
118
+ await ctx.runMutation(config.component.public.userPatch, {
119
+ userId,
120
+ data,
121
+ });
122
+ },
95
123
  /**
96
124
  * Query a user's group memberships.
97
125
  */
@@ -117,6 +145,9 @@ export function Auth(config_) {
117
145
  /**
118
146
  * Get the current session ID from the auth context, or `null` if
119
147
  * not signed in.
148
+ *
149
+ * @param ctx - Any Convex context with an `auth` field.
150
+ * @returns The session's `Id<"session">`, or `null` when unauthenticated.
120
151
  */
121
152
  current: async (ctx) => {
122
153
  const identity = await ctx.auth.getUserIdentity();
@@ -128,6 +159,10 @@ export function Auth(config_) {
128
159
  },
129
160
  /**
130
161
  * Invalidate sessions for a user, optionally preserving specific sessions.
162
+ *
163
+ * @param ctx - Convex action context.
164
+ * @param args.userId - The user whose sessions to invalidate.
165
+ * @param args.except - Session IDs to preserve (e.g. the current session).
131
166
  */
132
167
  invalidate: async (ctx, args) => {
133
168
  const actionCtx = ctx;
@@ -137,6 +172,10 @@ export function Auth(config_) {
137
172
  account: {
138
173
  /**
139
174
  * Create an account and user for a credentials provider.
175
+ *
176
+ * @param ctx - Convex action context.
177
+ * @param args - Provider ID, account credentials, profile data, and link flags.
178
+ * @returns `{ account, user }` — the created account and user documents.
140
179
  */
141
180
  create: async (ctx, args) => {
142
181
  const actionCtx = ctx;
@@ -144,17 +183,25 @@ export function Auth(config_) {
144
183
  },
145
184
  /**
146
185
  * Retrieve an account and user for a credentials provider.
186
+ *
187
+ * @param ctx - Convex action context.
188
+ * @param args - Provider ID and account credentials (id, optional secret).
189
+ * @returns `{ account, user }` — the matched account and user documents.
190
+ * @throws `ConvexError` with code `ACCOUNT_NOT_FOUND` when no match exists.
147
191
  */
148
192
  get: async (ctx, args) => {
149
193
  const actionCtx = ctx;
150
194
  const result = await callRetreiveAccountWithCredentials(actionCtx, args);
151
195
  if (typeof result === "string") {
152
- throw new Error(result);
196
+ throwAuthError("ACCOUNT_NOT_FOUND", result);
153
197
  }
154
198
  return result;
155
199
  },
156
200
  /**
157
- * Update credentials for an existing account.
201
+ * Update credentials (secret) for an existing account.
202
+ *
203
+ * @param ctx - Convex action context.
204
+ * @param args - Provider ID and new account credentials (id + secret).
158
205
  */
159
206
  updateCredentials: async (ctx, args) => {
160
207
  const actionCtx = ctx;
@@ -164,9 +211,16 @@ export function Auth(config_) {
164
211
  provider: {
165
212
  /**
166
213
  * Sign in via another provider, typically from a credentials flow.
214
+ *
215
+ * @param ctx - Convex action context.
216
+ * @param provider - The provider config to sign in with.
217
+ * @param args - Optional account ID and params.
218
+ * @returns `{ userId, sessionId }` on success, or `null`.
167
219
  */
168
220
  signIn: async (ctx, provider, args) => {
169
- const result = await signInImpl(enrichCtx(ctx), materializeProvider(provider), args, {
221
+ const result = await signInImpl(enrichCtx(ctx), materializeProvider(provider),
222
+ // params type widened: Record<string, unknown> → Record<string, any>
223
+ args, {
170
224
  generateTokens: false,
171
225
  allowExtraProviders: true,
172
226
  });
@@ -211,6 +265,7 @@ export function Auth(config_) {
211
265
  */
212
266
  list: async (ctx, opts) => {
213
267
  return await ctx.runQuery(config.component.public.groupList, {
268
+ type: opts?.type,
214
269
  parentGroupId: opts?.parentGroupId,
215
270
  });
216
271
  },
@@ -338,15 +393,17 @@ export function Auth(config_) {
338
393
  * the timestamp. If the invite has a group, the caller is responsible
339
394
  * for creating the member record via `auth.group.member.add` in the
340
395
  * same Convex mutation for transactional safety.
341
- *
342
- * @throws ConvexError with code `INVITE_NOT_FOUND` when the invite does
343
- * not exist.
344
- * @throws ConvexError with code `INVITE_NOT_PENDING` when the invite is
345
- * not in `pending` status.
346
396
  *
397
+ * @param ctx - Convex context with `runMutation`.
398
+ * @param inviteId - The invite document ID.
399
+ * @param acceptedByUserId - User accepting the invite (recorded for audit).
400
+ * @throws `ConvexError` with code `INVITE_NOT_FOUND` when the invite does not exist.
401
+ * @throws `ConvexError` with code `INVITE_NOT_PENDING` when the invite is not in `pending` status.
402
+ *
403
+ * @example
347
404
  * ```ts
348
- * export const acceptInvite = mutation({
349
- * args: { inviteId: v.string() },
405
+ * export const acceptInvite = mutation({
406
+ * args: { inviteId: v.string() },
350
407
  * handler: async (ctx, { inviteId }) => {
351
408
  * const userId = await auth.user.require(ctx);
352
409
  * const invite = await auth.invite.get(ctx, inviteId);
@@ -373,10 +430,10 @@ export function Auth(config_) {
373
430
  /**
374
431
  * Revoke a pending invitation.
375
432
  *
376
- * @throws ConvexError with code `INVITE_NOT_FOUND` when the invite does
377
- * not exist.
378
- * @throws ConvexError with code `INVITE_NOT_PENDING` when the invite is
379
- * not in `pending` status.
433
+ * @param ctx - Convex context with `runMutation`.
434
+ * @param inviteId - The invite document ID.
435
+ * @throws `ConvexError` with code `INVITE_NOT_FOUND` when the invite does not exist.
436
+ * @throws `ConvexError` with code `INVITE_NOT_PENDING` when the invite is not in `pending` status.
380
437
  */
381
438
  revoke: async (ctx, inviteId) => {
382
439
  await ctx.runMutation(config.component.public.inviteRevoke, { inviteId });
@@ -482,7 +539,7 @@ export function Auth(config_) {
482
539
  // Validate scopes against config if defined
483
540
  validateScopes(opts.scopes, config.apiKeys?.scopes);
484
541
  const { raw, hashedKey, displayPrefix } = await generateApiKey(prefix);
485
- const keyId = await ctx.runMutation(config.component.public.keyInsert, {
542
+ const keyId = (await ctx.runMutation(config.component.public.keyInsert, {
486
543
  userId: opts.userId,
487
544
  prefix: displayPrefix,
488
545
  hashedKey,
@@ -490,8 +547,8 @@ export function Auth(config_) {
490
547
  scopes: opts.scopes,
491
548
  rateLimit: opts.rateLimit ?? config.apiKeys?.defaultRateLimit,
492
549
  expiresAt: opts.expiresAt,
493
- });
494
- return { keyId: keyId, raw };
550
+ }));
551
+ return { keyId, raw };
495
552
  },
496
553
  /**
497
554
  * Verify a raw API key string. Returns the userId and a scope checker
@@ -503,22 +560,22 @@ export function Auth(config_) {
503
560
  */
504
561
  verify: async (ctx, rawKey) => {
505
562
  const hashedKey = await hashApiKey(rawKey);
506
- const key = await ctx.runQuery(config.component.public.keyGetByHashedKey, { hashedKey });
563
+ const key = (await ctx.runQuery(config.component.public.keyGetByHashedKey, { hashedKey }));
507
564
  if (!key) {
508
- throw new Error("Invalid API key");
565
+ throwAuthError("INVALID_API_KEY");
509
566
  }
510
567
  if (key.revoked) {
511
- throw new Error("API key has been revoked");
568
+ throwAuthError("API_KEY_REVOKED");
512
569
  }
513
570
  if (key.expiresAt && key.expiresAt < Date.now()) {
514
- throw new Error("API key has expired");
571
+ throwAuthError("API_KEY_EXPIRED");
515
572
  }
516
573
  // Check per-key rate limit
517
574
  const patchData = { lastUsedAt: Date.now() };
518
575
  if (key.rateLimit) {
519
576
  const { limited, newState } = checkKeyRateLimit(key.rateLimit, key.rateLimitState ?? undefined);
520
577
  if (limited) {
521
- throw new Error("API key rate limit exceeded");
578
+ throwAuthError("API_KEY_RATE_LIMITED");
522
579
  }
523
580
  patchData.rateLimitState = newState;
524
581
  }
@@ -538,14 +595,14 @@ export function Auth(config_) {
538
595
  * Never includes the raw key — only the display prefix.
539
596
  */
540
597
  list: async (ctx, opts) => {
541
- return await ctx.runQuery(config.component.public.keyListByUserId, { userId: opts.userId });
598
+ return (await ctx.runQuery(config.component.public.keyListByUserId, { userId: opts.userId }));
542
599
  },
543
600
  /**
544
601
  * Get a single API key by its document ID.
545
602
  * Returns `null` if not found.
546
603
  */
547
604
  get: async (ctx, keyId) => {
548
- return await ctx.runQuery(config.component.public.keyGetById, { keyId: keyId });
605
+ return (await ctx.runQuery(config.component.public.keyGetById, { keyId }));
549
606
  },
550
607
  /**
551
608
  * Update an API key's metadata (name, scopes, rate limit).
@@ -555,7 +612,7 @@ export function Auth(config_) {
555
612
  validateScopes(data.scopes, config.apiKeys?.scopes);
556
613
  }
557
614
  await ctx.runMutation(config.component.public.keyPatch, {
558
- keyId: keyId,
615
+ keyId,
559
616
  data,
560
617
  });
561
618
  },
@@ -565,7 +622,7 @@ export function Auth(config_) {
565
622
  */
566
623
  revoke: async (ctx, keyId) => {
567
624
  await ctx.runMutation(config.component.public.keyPatch, {
568
- keyId: keyId,
625
+ keyId,
569
626
  data: { revoked: true },
570
627
  });
571
628
  },
@@ -574,164 +631,317 @@ export function Auth(config_) {
574
631
  */
575
632
  remove: async (ctx, keyId) => {
576
633
  await ctx.runMutation(config.component.public.keyDelete, {
577
- keyId: keyId,
634
+ keyId,
578
635
  });
579
636
  },
580
637
  },
581
638
  /**
582
- * Add HTTP actions for JWT verification and OAuth sign-in.
583
- *
584
- * ```ts
585
- * import { httpRouter } from "convex/server";
586
- * import { auth } from "./auth.js";
587
- *
588
- * const http = httpRouter();
589
- *
590
- * auth.addHttpRoutes(http);
591
- *
592
- * export default http;
593
- * ```
594
- *
595
- * The following routes are handled always:
596
- *
597
- * - `/.well-known/openid-configuration`
598
- * - `/.well-known/jwks.json`
599
- *
600
- * The following routes are handled if OAuth is configured:
601
- *
602
- * - `/api/auth/signin/*`
603
- * - `/api/auth/callback/*`
604
- *
605
- * @param http your HTTP router
639
+ * HTTP namespace route registration and Bearer-authenticated endpoints.
606
640
  */
607
- addHttpRoutes: (http) => {
608
- http.route({
609
- path: "/.well-known/openid-configuration",
610
- method: "GET",
611
- handler: httpActionGeneric(async () => {
612
- return new Response(JSON.stringify({
613
- issuer: requireEnv("CONVEX_SITE_URL"),
614
- jwks_uri: requireEnv("CONVEX_SITE_URL") + "/.well-known/jwks.json",
615
- authorization_endpoint: requireEnv("CONVEX_SITE_URL") + "/oauth/authorize",
616
- }), {
617
- status: 200,
618
- headers: {
619
- "Content-Type": "application/json",
620
- "Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
621
- },
622
- });
623
- }),
624
- });
625
- http.route({
626
- path: "/.well-known/jwks.json",
627
- method: "GET",
628
- handler: httpActionGeneric(async () => {
629
- return new Response(requireEnv("JWKS"), {
630
- status: 200,
631
- headers: {
632
- "Content-Type": "application/json",
633
- "Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
634
- },
635
- });
636
- }),
637
- });
638
- if (hasOAuth) {
641
+ http: {
642
+ /**
643
+ * Register core HTTP routes for JWT verification and OAuth sign-in.
644
+ *
645
+ * ```ts
646
+ * import { httpRouter } from "convex/server";
647
+ * import { auth } from "./auth.js";
648
+ *
649
+ * const http = httpRouter();
650
+ *
651
+ * auth.http.add(http);
652
+ *
653
+ * export default http;
654
+ * ```
655
+ *
656
+ * The following routes are handled always:
657
+ *
658
+ * - `/.well-known/openid-configuration`
659
+ * - `/.well-known/jwks.json`
660
+ *
661
+ * The following routes are handled if OAuth is configured:
662
+ *
663
+ * - `/api/auth/signin/*`
664
+ * - `/api/auth/callback/*`
665
+ *
666
+ * @param http your HTTP router
667
+ */
668
+ add: (http) => {
639
669
  http.route({
640
- pathPrefix: "/api/auth/signin/",
670
+ path: "/.well-known/openid-configuration",
641
671
  method: "GET",
642
- handler: httpActionGeneric(convertErrorsToResponse(400, async (ctx, request) => {
672
+ handler: httpActionGeneric(async () => {
673
+ return new Response(JSON.stringify({
674
+ issuer: requireEnv("CONVEX_SITE_URL"),
675
+ jwks_uri: requireEnv("CONVEX_SITE_URL") + "/.well-known/jwks.json",
676
+ authorization_endpoint: requireEnv("CONVEX_SITE_URL") + "/oauth/authorize",
677
+ }), {
678
+ status: 200,
679
+ headers: {
680
+ "Content-Type": "application/json",
681
+ "Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
682
+ },
683
+ });
684
+ }),
685
+ });
686
+ http.route({
687
+ path: "/.well-known/jwks.json",
688
+ method: "GET",
689
+ handler: httpActionGeneric(async () => {
690
+ return new Response(requireEnv("JWKS"), {
691
+ status: 200,
692
+ headers: {
693
+ "Content-Type": "application/json",
694
+ "Cache-Control": "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400",
695
+ },
696
+ });
697
+ }),
698
+ });
699
+ if (hasOAuth) {
700
+ http.route({
701
+ pathPrefix: "/api/auth/signin/",
702
+ method: "GET",
703
+ handler: httpActionGeneric(convertErrorsToResponse(400, async (ctx, request) => {
704
+ const url = new URL(request.url);
705
+ const pathParts = url.pathname.split("/");
706
+ const providerId = pathParts.at(-1);
707
+ if (providerId === null) {
708
+ throwAuthError("OAUTH_MISSING_PROVIDER");
709
+ }
710
+ const verifier = url.searchParams.get("code");
711
+ if (verifier === null) {
712
+ throwAuthError("OAUTH_MISSING_VERIFIER");
713
+ }
714
+ const provider = getProviderOrThrow(providerId);
715
+ const { redirect, cookies, signature } = await getAuthorizationUrl({
716
+ provider: await oAuthConfigToInternalProvider(provider),
717
+ cookies: defaultCookiesOptions(providerId),
718
+ });
719
+ await callVerifierSignature(ctx, {
720
+ verifier,
721
+ signature,
722
+ });
723
+ const redirectTo = url.searchParams.get("redirectTo");
724
+ if (redirectTo !== null) {
725
+ cookies.push(redirectToParamCookie(providerId, redirectTo));
726
+ }
727
+ const headers = new Headers({ Location: redirect });
728
+ for (const { name, value, options } of cookies) {
729
+ headers.append("Set-Cookie", serializeCookie(name, value, options));
730
+ }
731
+ return new Response(null, { status: 302, headers });
732
+ })),
733
+ });
734
+ const callbackAction = httpActionGeneric(async (genericCtx, request) => {
735
+ const ctx = genericCtx;
643
736
  const url = new URL(request.url);
644
737
  const pathParts = url.pathname.split("/");
645
738
  const providerId = pathParts.at(-1);
646
- if (providerId === null) {
647
- throw new Error("Missing provider id");
648
- }
649
- const verifier = url.searchParams.get("code");
650
- if (verifier === null) {
651
- throw new Error("Missing sign-in verifier");
652
- }
739
+ logWithLevel(LOG_LEVELS.DEBUG, "Handling OAuth callback for provider:", providerId);
653
740
  const provider = getProviderOrThrow(providerId);
654
- const { redirect, cookies, signature } = await getAuthorizationUrl({
655
- provider: await oAuthConfigToInternalProvider(provider),
656
- cookies: defaultCookiesOptions(providerId),
657
- });
658
- await callVerifierSignature(ctx, {
659
- verifier,
660
- signature,
741
+ const cookies = getCookies(request);
742
+ const maybeRedirectTo = useRedirectToParam(provider.id, cookies);
743
+ const destinationUrl = await redirectAbsoluteUrl(config, {
744
+ redirectTo: maybeRedirectTo?.redirectTo,
661
745
  });
662
- const redirectTo = url.searchParams.get("redirectTo");
663
- if (redirectTo !== null) {
664
- cookies.push(redirectToParamCookie(providerId, redirectTo));
746
+ const params = url.searchParams;
747
+ // Handle OAuth providers that use formData (such as Apple)
748
+ if (request.headers.get("Content-Type") ===
749
+ "application/x-www-form-urlencoded") {
750
+ const formData = await request.formData();
751
+ for (const [key, value] of formData.entries()) {
752
+ if (typeof value === "string") {
753
+ params.append(key, value);
754
+ }
755
+ }
756
+ }
757
+ try {
758
+ const { profile, tokens, signature } = await handleOAuth(Object.fromEntries(params.entries()), cookies, {
759
+ provider: await oAuthConfigToInternalProvider(provider),
760
+ cookies: defaultCookiesOptions(provider.id),
761
+ });
762
+ const { id, ...profileFromCallback } = await provider.profile(profile, tokens);
763
+ if (typeof id !== "string") {
764
+ throwAuthError("OAUTH_INVALID_PROFILE", `The profile method of the ${providerId} config must return a string ID`, { provider: providerId });
765
+ }
766
+ const verificationCode = await callUserOAuth(ctx, {
767
+ provider: providerId,
768
+ providerAccountId: id,
769
+ profile: profileFromCallback,
770
+ signature,
771
+ });
772
+ return new Response(null, {
773
+ status: 302,
774
+ headers: {
775
+ Location: setURLSearchParam(destinationUrl, "code", verificationCode),
776
+ "Cache-Control": "must-revalidate",
777
+ },
778
+ });
665
779
  }
666
- const headers = new Headers({ Location: redirect });
667
- for (const { name, value, options } of cookies) {
668
- headers.append("Set-Cookie", serializeCookie(name, value, options));
780
+ catch (error) {
781
+ logError(error);
782
+ return Response.redirect(destinationUrl);
669
783
  }
670
- return new Response(null, { status: 302, headers });
671
- })),
784
+ });
785
+ http.route({
786
+ pathPrefix: "/api/auth/callback/",
787
+ method: "GET",
788
+ handler: callbackAction,
789
+ });
790
+ http.route({
791
+ pathPrefix: "/api/auth/callback/",
792
+ method: "POST",
793
+ handler: callbackAction,
794
+ });
795
+ }
796
+ },
797
+ /**
798
+ * Wrap an HTTP action handler with Bearer token authentication.
799
+ *
800
+ * Extracts the `Authorization: Bearer <key>` header, verifies the
801
+ * API key via `auth.key.verify()`, and injects `ctx.key` with the
802
+ * verified key info. Returns structured JSON error responses for
803
+ * missing/invalid/revoked/expired/rate-limited keys.
804
+ *
805
+ * If the handler returns a plain object, it is auto-wrapped in a
806
+ * `200 JSON` response. If it returns a `Response`, CORS headers
807
+ * are merged and the response is passed through.
808
+ *
809
+ * ```ts
810
+ * const handler = auth.http.action(async (ctx, request) => {
811
+ * const data = await ctx.runQuery(api.data.get, { userId: ctx.key.userId });
812
+ * return { data };
813
+ * });
814
+ * http.route({ path: "/api/data", method: "GET", handler });
815
+ * ```
816
+ *
817
+ * @param handler - Receives enriched `ctx` (with `ctx.key`) and the raw `Request`.
818
+ * @param options.scope - Optional scope check; returns 403 if the key lacks permission.
819
+ * @param options.cors - CORS config; defaults to permissive (`*`).
820
+ */
821
+ action: (handler, options) => {
822
+ const corsConfig = options?.cors ?? {};
823
+ const corsHeaders = {
824
+ "Access-Control-Allow-Origin": corsConfig.origin ?? "*",
825
+ "Access-Control-Allow-Methods": corsConfig.methods ?? "GET,POST,PUT,PATCH,DELETE,OPTIONS",
826
+ "Access-Control-Allow-Headers": corsConfig.headers ?? "Content-Type,Authorization",
827
+ };
828
+ const jsonError = (status, code, message) => new Response(JSON.stringify({ error: message, code }), {
829
+ status,
830
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
672
831
  });
673
- const callbackAction = httpActionGeneric(async (genericCtx, request) => {
832
+ return httpActionGeneric(async (genericCtx, request) => {
674
833
  const ctx = genericCtx;
675
- const url = new URL(request.url);
676
- const pathParts = url.pathname.split("/");
677
- const providerId = pathParts.at(-1);
678
- logWithLevel(LOG_LEVELS.DEBUG, "Handling OAuth callback for provider:", providerId);
679
- const provider = getProviderOrThrow(providerId);
680
- const cookies = getCookies(request);
681
- const maybeRedirectTo = useRedirectToParam(provider.id, cookies);
682
- const destinationUrl = await redirectAbsoluteUrl(config, {
683
- redirectTo: maybeRedirectTo?.redirectTo,
684
- });
685
- const params = url.searchParams;
686
- // Handle OAuth providers that use formData (such as Apple)
687
- if (request.headers.get("Content-Type") ===
688
- "application/x-www-form-urlencoded") {
689
- const formData = await request.formData();
690
- for (const [key, value] of formData.entries()) {
691
- if (typeof value === "string") {
692
- params.append(key, value);
834
+ try {
835
+ // 1. Extract Bearer token
836
+ const authHeader = request.headers.get("Authorization");
837
+ if (!authHeader?.startsWith("Bearer ")) {
838
+ return jsonError(401, "MISSING_BEARER_TOKEN", "Missing or malformed Authorization: Bearer header.");
839
+ }
840
+ const rawKey = authHeader.slice(7);
841
+ // 2. Verify API key
842
+ let keyResult;
843
+ try {
844
+ keyResult = await auth.key.verify(ctx, rawKey);
845
+ }
846
+ catch (error) {
847
+ if (isAuthError(error)) {
848
+ const { code, message } = error.data;
849
+ return jsonError(403, code, message);
693
850
  }
851
+ throw error;
694
852
  }
695
- }
696
- try {
697
- const { profile, tokens, signature } = await handleOAuth(Object.fromEntries(params.entries()), cookies, {
698
- provider: await oAuthConfigToInternalProvider(provider),
699
- cookies: defaultCookiesOptions(provider.id),
700
- });
701
- const { id, ...profileFromCallback } = await provider.profile(profile, tokens);
702
- if (typeof id !== "string") {
703
- throw new Error(`The profile method of the ${providerId} config must return a string ID`);
853
+ // 3. Optional scope check
854
+ if (options?.scope) {
855
+ if (!keyResult.scopes.can(options.scope.resource, options.scope.action)) {
856
+ return jsonError(403, "SCOPE_CHECK_FAILED", "This API key does not have the required permissions.");
857
+ }
704
858
  }
705
- const verificationCode = await callUserOAuth(ctx, {
706
- provider: providerId,
707
- providerAccountId: id,
708
- profile: profileFromCallback,
709
- signature,
710
- });
711
- return new Response(null, {
712
- status: 302,
713
- headers: {
714
- Location: setURLSearchParam(destinationUrl, "code", verificationCode),
715
- "Cache-Control": "must-revalidate",
859
+ // 4. Enrich context with key info
860
+ const enrichedCtx = Object.assign(ctx, {
861
+ key: {
862
+ userId: keyResult.userId,
863
+ keyId: keyResult.keyId,
864
+ scopes: keyResult.scopes,
716
865
  },
717
866
  });
867
+ // 5. Call handler
868
+ const result = await handler(enrichedCtx, request);
869
+ // 6. Auto-wrap plain objects as JSON responses
870
+ if (result instanceof Response) {
871
+ // Merge CORS headers into existing response
872
+ const headers = new Headers(result.headers);
873
+ for (const [k, val] of Object.entries(corsHeaders)) {
874
+ if (!headers.has(k))
875
+ headers.set(k, val);
876
+ }
877
+ return new Response(result.body, {
878
+ status: result.status,
879
+ statusText: result.statusText,
880
+ headers,
881
+ });
882
+ }
883
+ return new Response(JSON.stringify(result), {
884
+ status: 200,
885
+ headers: { ...corsHeaders, "Content-Type": "application/json" },
886
+ });
718
887
  }
719
888
  catch (error) {
720
889
  logError(error);
721
- return Response.redirect(destinationUrl);
890
+ return jsonError(500, "INTERNAL_ERROR", "An unexpected error occurred.");
722
891
  }
723
892
  });
893
+ },
894
+ /**
895
+ * Register a Bearer-authenticated route **and** its OPTIONS preflight
896
+ * in a single call.
897
+ *
898
+ * ```ts
899
+ * auth.http.route(http, {
900
+ * path: "/api/messages",
901
+ * method: "POST",
902
+ * handler: async (ctx, request) => {
903
+ * const { body } = await request.json();
904
+ * await ctx.runMutation(internal.messages.sendAsUser, {
905
+ * userId: ctx.key.userId,
906
+ * body,
907
+ * });
908
+ * return { success: true };
909
+ * },
910
+ * });
911
+ * ```
912
+ *
913
+ * @param http - The Convex HTTP router.
914
+ * @param routeConfig.path - The URL path to match.
915
+ * @param routeConfig.method - HTTP method (GET, POST, PUT, PATCH, DELETE).
916
+ * @param routeConfig.handler - Receives enriched `ctx` (with `ctx.key`) and the raw `Request`.
917
+ * @param routeConfig.scope - Optional scope check; returns 403 if the key lacks permission.
918
+ * @param routeConfig.cors - CORS config; defaults to permissive (`*`).
919
+ */
920
+ route: (http, routeConfig) => {
921
+ const corsConfig = routeConfig.cors ?? {};
922
+ const corsHeaders = {
923
+ "Access-Control-Allow-Origin": corsConfig.origin ?? "*",
924
+ "Access-Control-Allow-Methods": corsConfig.methods ?? "GET,POST,PUT,PATCH,DELETE,OPTIONS",
925
+ "Access-Control-Allow-Headers": corsConfig.headers ?? "Content-Type,Authorization",
926
+ };
927
+ // Register OPTIONS preflight
724
928
  http.route({
725
- pathPrefix: "/api/auth/callback/",
726
- method: "GET",
727
- handler: callbackAction,
929
+ path: routeConfig.path,
930
+ method: "OPTIONS",
931
+ handler: httpActionGeneric(async () => {
932
+ return new Response(null, { status: 204, headers: corsHeaders });
933
+ }),
728
934
  });
935
+ // Register the main route with Bearer auth wrapping
729
936
  http.route({
730
- pathPrefix: "/api/auth/callback/",
731
- method: "POST",
732
- handler: callbackAction,
937
+ path: routeConfig.path,
938
+ method: routeConfig.method,
939
+ handler: auth.http.action(routeConfig.handler, {
940
+ scope: routeConfig.scope,
941
+ cors: routeConfig.cors,
942
+ }),
733
943
  });
734
- }
944
+ },
735
945
  },
736
946
  };
737
947
  const enrichCtx = (ctx) => ({
@@ -789,7 +999,7 @@ export function Auth(config_) {
789
999
  return { totpSetup: { uri: result.uri, secret: result.secret, totpId: result.totpId }, verifier: result.verifier };
790
1000
  default: {
791
1001
  const _typecheck = result;
792
- throw new Error(`Unexpected result from signIn, ${result}`);
1002
+ throwAuthError("INTERNAL_ERROR", `Unexpected result from signIn, ${String(result)}`);
793
1003
  }
794
1004
  }
795
1005
  },
@@ -821,10 +1031,16 @@ function convertErrorsToResponse(errorStatusCode, action) {
821
1031
  return await action(ctx, request);
822
1032
  }
823
1033
  catch (error) {
824
- if (error instanceof ConvexError) {
1034
+ if (isAuthError(error)) {
1035
+ return new Response(JSON.stringify({ code: error.data.code, message: error.data.message }), {
1036
+ status: errorStatusCode,
1037
+ headers: { "Content-Type": "application/json" },
1038
+ });
1039
+ }
1040
+ else if (error instanceof ConvexError) {
825
1041
  return new Response(null, {
826
1042
  status: errorStatusCode,
827
- statusText: error.data,
1043
+ statusText: typeof error.data === "string" ? error.data : "Error",
828
1044
  });
829
1045
  }
830
1046
  else {