@robelest/convex-auth 0.0.4-preview.22 → 0.0.4-preview.24

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 (314) hide show
  1. package/README.md +10 -11
  2. package/dist/authorization/index.d.ts +1 -1
  3. package/dist/authorization/index.js +1 -1
  4. package/dist/authorization/index.js.map +1 -1
  5. package/dist/client/index.d.ts +1 -2
  6. package/dist/client/index.d.ts.map +1 -1
  7. package/dist/client/index.js +36 -39
  8. package/dist/client/index.js.map +1 -1
  9. package/dist/component/client/index.d.ts +1 -2
  10. package/dist/component/index.js +2 -2
  11. package/dist/component/model.d.ts +9 -9
  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 +41 -41
  53. package/dist/component/server/auth.d.ts +127 -130
  54. package/dist/component/server/auth.d.ts.map +1 -1
  55. package/dist/component/server/auth.js +100 -64
  56. package/dist/component/server/auth.js.map +1 -1
  57. package/dist/component/server/context.js +53 -0
  58. package/dist/component/server/context.js.map +1 -0
  59. package/dist/component/server/core.js +113 -250
  60. package/dist/component/server/core.js.map +1 -1
  61. package/dist/component/server/crypto.js +25 -7
  62. package/dist/component/server/crypto.js.map +1 -1
  63. package/dist/component/server/device.js +59 -16
  64. package/dist/component/server/device.js.map +1 -1
  65. package/dist/component/server/enterprise/domain.js +148 -59
  66. package/dist/component/server/enterprise/domain.js.map +1 -1
  67. package/dist/component/server/enterprise/http.js +36 -15
  68. package/dist/component/server/enterprise/http.js.map +1 -1
  69. package/dist/component/server/enterprise/oidc.js +1 -1
  70. package/dist/component/server/http.d.ts +85 -0
  71. package/dist/component/server/http.d.ts.map +1 -0
  72. package/dist/component/server/http.js +85 -22
  73. package/dist/component/server/http.js.map +1 -1
  74. package/dist/component/server/identity.js +5 -2
  75. package/dist/component/server/identity.js.map +1 -1
  76. package/dist/component/server/limits.js +21 -30
  77. package/dist/component/server/limits.js.map +1 -1
  78. package/dist/component/server/mutations/account.js +12 -10
  79. package/dist/component/server/mutations/account.js.map +1 -1
  80. package/dist/component/server/mutations/code.js +5 -2
  81. package/dist/component/server/mutations/code.js.map +1 -1
  82. package/dist/component/server/mutations/invalidate.js +1 -1
  83. package/dist/component/server/mutations/invalidate.js.map +1 -1
  84. package/dist/component/server/mutations/oauth.js +10 -4
  85. package/dist/component/server/mutations/oauth.js.map +1 -1
  86. package/dist/component/server/mutations/refresh.js +2 -2
  87. package/dist/component/server/mutations/refresh.js.map +1 -1
  88. package/dist/component/server/mutations/register.js +46 -42
  89. package/dist/component/server/mutations/register.js.map +1 -1
  90. package/dist/component/server/mutations/retrieve.js +21 -25
  91. package/dist/component/server/mutations/retrieve.js.map +1 -1
  92. package/dist/component/server/mutations/signature.js +10 -4
  93. package/dist/component/server/mutations/signature.js.map +1 -1
  94. package/dist/component/server/mutations/signout.js.map +1 -1
  95. package/dist/component/server/mutations/store.js +9 -24
  96. package/dist/component/server/mutations/store.js.map +1 -1
  97. package/dist/component/server/mutations/verifier.js.map +1 -1
  98. package/dist/component/server/mutations/verify.js +1 -1
  99. package/dist/component/server/mutations/verify.js.map +1 -1
  100. package/dist/component/server/oauth.js +53 -16
  101. package/dist/component/server/oauth.js.map +1 -1
  102. package/dist/component/server/passkey.js +115 -31
  103. package/dist/component/server/passkey.js.map +1 -1
  104. package/dist/component/server/redirects.js +9 -3
  105. package/dist/component/server/redirects.js.map +1 -1
  106. package/dist/component/server/refresh.js +10 -7
  107. package/dist/component/server/refresh.js.map +1 -1
  108. package/dist/component/server/runtime.d.ts +5 -5
  109. package/dist/component/server/runtime.js +156 -113
  110. package/dist/component/server/runtime.js.map +1 -1
  111. package/dist/component/server/signin.js +34 -10
  112. package/dist/component/server/signin.js.map +1 -1
  113. package/dist/component/server/totp.js +79 -19
  114. package/dist/component/server/totp.js.map +1 -1
  115. package/dist/component/server/types.d.ts +12 -20
  116. package/dist/component/server/types.d.ts.map +1 -1
  117. package/dist/component/server/types.js.map +1 -1
  118. package/dist/component/server/users.js +6 -3
  119. package/dist/component/server/users.js.map +1 -1
  120. package/dist/component/server/utils.js +10 -4
  121. package/dist/component/server/utils.js.map +1 -1
  122. package/dist/core/types.d.ts +14 -22
  123. package/dist/core/types.d.ts.map +1 -1
  124. package/dist/factors/device.js +8 -9
  125. package/dist/factors/device.js.map +1 -1
  126. package/dist/factors/passkey.js +18 -21
  127. package/dist/factors/passkey.js.map +1 -1
  128. package/dist/providers/password.js +66 -81
  129. package/dist/providers/password.js.map +1 -1
  130. package/dist/runtime/invite.js +2 -8
  131. package/dist/runtime/invite.js.map +1 -1
  132. package/dist/server/auth.d.ts +127 -130
  133. package/dist/server/auth.d.ts.map +1 -1
  134. package/dist/server/auth.js +100 -64
  135. package/dist/server/auth.js.map +1 -1
  136. package/dist/server/context.d.ts +1 -0
  137. package/dist/server/context.js +53 -0
  138. package/dist/server/context.js.map +1 -0
  139. package/dist/server/core.d.ts +74 -195
  140. package/dist/server/core.d.ts.map +1 -1
  141. package/dist/server/core.js +113 -250
  142. package/dist/server/core.js.map +1 -1
  143. package/dist/server/crypto.d.ts.map +1 -1
  144. package/dist/server/crypto.js +25 -7
  145. package/dist/server/crypto.js.map +1 -1
  146. package/dist/server/device.js +59 -16
  147. package/dist/server/device.js.map +1 -1
  148. package/dist/server/enterprise/domain.d.ts +0 -8
  149. package/dist/server/enterprise/domain.d.ts.map +1 -1
  150. package/dist/server/enterprise/domain.js +148 -59
  151. package/dist/server/enterprise/domain.js.map +1 -1
  152. package/dist/server/enterprise/http.d.ts.map +1 -1
  153. package/dist/server/enterprise/http.js +35 -14
  154. package/dist/server/enterprise/http.js.map +1 -1
  155. package/dist/server/http.d.ts +81 -3
  156. package/dist/server/http.d.ts.map +1 -1
  157. package/dist/server/http.js +84 -21
  158. package/dist/server/http.js.map +1 -1
  159. package/dist/server/identity.js +5 -2
  160. package/dist/server/identity.js.map +1 -1
  161. package/dist/server/index.d.ts +3 -2
  162. package/dist/server/index.js +2 -2
  163. package/dist/server/limits.js +21 -30
  164. package/dist/server/limits.js.map +1 -1
  165. package/dist/server/mounts.d.ts +25 -63
  166. package/dist/server/mounts.d.ts.map +1 -1
  167. package/dist/server/mounts.js +46 -107
  168. package/dist/server/mounts.js.map +1 -1
  169. package/dist/server/mutations/account.d.ts +8 -9
  170. package/dist/server/mutations/account.d.ts.map +1 -1
  171. package/dist/server/mutations/account.js +11 -9
  172. package/dist/server/mutations/account.js.map +1 -1
  173. package/dist/server/mutations/code.d.ts +12 -12
  174. package/dist/server/mutations/code.d.ts.map +1 -1
  175. package/dist/server/mutations/code.js +5 -2
  176. package/dist/server/mutations/code.js.map +1 -1
  177. package/dist/server/mutations/invalidate.d.ts +4 -4
  178. package/dist/server/mutations/invalidate.d.ts.map +1 -1
  179. package/dist/server/mutations/invalidate.js.map +1 -1
  180. package/dist/server/mutations/oauth.d.ts +14 -12
  181. package/dist/server/mutations/oauth.d.ts.map +1 -1
  182. package/dist/server/mutations/oauth.js +9 -3
  183. package/dist/server/mutations/oauth.js.map +1 -1
  184. package/dist/server/mutations/refresh.d.ts +3 -3
  185. package/dist/server/mutations/refresh.d.ts.map +1 -1
  186. package/dist/server/mutations/refresh.js +1 -1
  187. package/dist/server/mutations/refresh.js.map +1 -1
  188. package/dist/server/mutations/register.d.ts +11 -11
  189. package/dist/server/mutations/register.d.ts.map +1 -1
  190. package/dist/server/mutations/register.js +45 -41
  191. package/dist/server/mutations/register.js.map +1 -1
  192. package/dist/server/mutations/retrieve.d.ts +6 -6
  193. package/dist/server/mutations/retrieve.d.ts.map +1 -1
  194. package/dist/server/mutations/retrieve.js +20 -24
  195. package/dist/server/mutations/retrieve.js.map +1 -1
  196. package/dist/server/mutations/signature.d.ts +6 -7
  197. package/dist/server/mutations/signature.d.ts.map +1 -1
  198. package/dist/server/mutations/signature.js +9 -3
  199. package/dist/server/mutations/signature.js.map +1 -1
  200. package/dist/server/mutations/signin.d.ts +5 -5
  201. package/dist/server/mutations/signout.js.map +1 -1
  202. package/dist/server/mutations/store.d.ts +83 -83
  203. package/dist/server/mutations/store.js +8 -23
  204. package/dist/server/mutations/store.js.map +1 -1
  205. package/dist/server/mutations/verifier.js.map +1 -1
  206. package/dist/server/mutations/verify.d.ts +7 -7
  207. package/dist/server/mutations/verify.d.ts.map +1 -1
  208. package/dist/server/mutations/verify.js.map +1 -1
  209. package/dist/server/oauth.js +53 -16
  210. package/dist/server/oauth.js.map +1 -1
  211. package/dist/server/passkey.d.ts +2 -2
  212. package/dist/server/passkey.d.ts.map +1 -1
  213. package/dist/server/passkey.js +114 -30
  214. package/dist/server/passkey.js.map +1 -1
  215. package/dist/server/redirects.js +9 -3
  216. package/dist/server/redirects.js.map +1 -1
  217. package/dist/server/refresh.js +10 -7
  218. package/dist/server/refresh.js.map +1 -1
  219. package/dist/server/runtime.d.ts +11 -11
  220. package/dist/server/runtime.js +155 -112
  221. package/dist/server/runtime.js.map +1 -1
  222. package/dist/server/signin.js +34 -10
  223. package/dist/server/signin.js.map +1 -1
  224. package/dist/server/ssr.d.ts.map +1 -1
  225. package/dist/server/ssr.js +175 -184
  226. package/dist/server/ssr.js.map +1 -1
  227. package/dist/server/totp.js +78 -18
  228. package/dist/server/totp.js.map +1 -1
  229. package/dist/server/types.d.ts +13 -21
  230. package/dist/server/types.d.ts.map +1 -1
  231. package/dist/server/types.js.map +1 -1
  232. package/dist/server/users.js +6 -3
  233. package/dist/server/users.js.map +1 -1
  234. package/dist/server/utils.js +10 -4
  235. package/dist/server/utils.js.map +1 -1
  236. package/package.json +1 -5
  237. package/src/authorization/index.ts +1 -1
  238. package/src/client/core/types.ts +14 -14
  239. package/src/client/factors/device.ts +10 -12
  240. package/src/client/factors/passkey.ts +23 -26
  241. package/src/client/index.ts +54 -64
  242. package/src/client/runtime/invite.ts +5 -7
  243. package/src/component/index.ts +9 -3
  244. package/src/component/public/enterprise/audit.ts +6 -1
  245. package/src/component/public/enterprise/core.ts +1 -0
  246. package/src/component/public/enterprise/domains.ts +5 -1
  247. package/src/component/public/enterprise/scim.ts +1 -0
  248. package/src/component/public/enterprise/secrets.ts +1 -0
  249. package/src/component/public/enterprise/webhooks.ts +1 -0
  250. package/src/component/public/factors/devices.ts +1 -0
  251. package/src/component/public/factors/passkeys.ts +1 -0
  252. package/src/component/public/factors/totp.ts +1 -0
  253. package/src/component/public/groups/core.ts +1 -1
  254. package/src/component/public/groups/invites.ts +7 -1
  255. package/src/component/public/groups/members.ts +1 -0
  256. package/src/component/public/identity/accounts.ts +1 -0
  257. package/src/component/public/identity/codes.ts +1 -0
  258. package/src/component/public/identity/sessions.ts +1 -0
  259. package/src/component/public/identity/tokens.ts +1 -0
  260. package/src/component/public/identity/users.ts +1 -0
  261. package/src/component/public/identity/verifiers.ts +1 -0
  262. package/src/component/public/security/keys.ts +1 -0
  263. package/src/component/public/security/limits.ts +1 -0
  264. package/src/providers/password.ts +89 -110
  265. package/src/server/auth.ts +240 -182
  266. package/src/server/context.ts +90 -0
  267. package/src/server/core.ts +195 -286
  268. package/src/server/crypto.ts +31 -29
  269. package/src/server/device.ts +65 -32
  270. package/src/server/enterprise/domain.ts +158 -170
  271. package/src/server/enterprise/http.ts +46 -39
  272. package/src/server/http.ts +289 -30
  273. package/src/server/identity.ts +5 -5
  274. package/src/server/index.ts +9 -3
  275. package/src/server/limits.ts +53 -80
  276. package/src/server/mounts.ts +56 -80
  277. package/src/server/mutations/account.ts +22 -36
  278. package/src/server/mutations/code.ts +6 -6
  279. package/src/server/mutations/invalidate.ts +1 -1
  280. package/src/server/mutations/oauth.ts +14 -8
  281. package/src/server/mutations/refresh.ts +5 -4
  282. package/src/server/mutations/register.ts +87 -132
  283. package/src/server/mutations/retrieve.ts +44 -44
  284. package/src/server/mutations/signature.ts +13 -6
  285. package/src/server/mutations/signout.ts +1 -1
  286. package/src/server/mutations/store.ts +16 -31
  287. package/src/server/mutations/verifier.ts +1 -1
  288. package/src/server/mutations/verify.ts +3 -5
  289. package/src/server/oauth.ts +60 -69
  290. package/src/server/passkey.ts +567 -517
  291. package/src/server/redirects.ts +10 -6
  292. package/src/server/refresh.ts +14 -18
  293. package/src/server/runtime.ts +340 -302
  294. package/src/server/signin.ts +44 -37
  295. package/src/server/ssr.ts +390 -407
  296. package/src/server/totp.ts +85 -35
  297. package/src/server/types.ts +19 -22
  298. package/src/server/users.ts +7 -6
  299. package/src/server/utils.ts +10 -12
  300. package/dist/component/server/authError.js +0 -34
  301. package/dist/component/server/authError.js.map +0 -1
  302. package/dist/component/server/errors.d.ts +0 -1
  303. package/dist/component/server/errors.js +0 -137
  304. package/dist/component/server/errors.js.map +0 -1
  305. package/dist/server/authError.d.ts +0 -46
  306. package/dist/server/authError.d.ts.map +0 -1
  307. package/dist/server/authError.js +0 -34
  308. package/dist/server/authError.js.map +0 -1
  309. package/dist/server/errors.d.ts +0 -177
  310. package/dist/server/errors.d.ts.map +0 -1
  311. package/dist/server/errors.js +0 -212
  312. package/dist/server/errors.js.map +0 -1
  313. package/src/server/authError.ts +0 -44
  314. package/src/server/errors.ts +0 -290
@@ -4,12 +4,16 @@
4
4
  * @module
5
5
  */
6
6
 
7
+ import { Cv } from "@robelest/fx/convex";
7
8
  import type { UserIdentity } from "convex/server";
8
9
  import type { GenericId } from "convex/values";
9
10
 
10
11
  import type { AuthApiRefs } from "../client/index";
12
+ import {
13
+ createUnauthenticatedAuthContext,
14
+ getAuthContext as getResolvedAuthContext,
15
+ } from "./context";
11
16
  import { Auth as AuthFactory } from "./runtime";
12
- import { AuthError } from "./authError";
13
17
  import type { Doc } from "./types";
14
18
  import type {
15
19
  AuthAuthorizationConfig,
@@ -41,7 +45,7 @@ type MemberApiWithAuthorization<
41
45
  TAuthorization extends AuthAuthorizationConfig | undefined,
42
46
  > = Omit<
43
47
  ReturnType<typeof AuthFactory>["auth"]["member"],
44
- "create" | "list" | "update" | "resolve"
48
+ "create" | "list" | "update" | "inspect" | "require"
45
49
  > & {
46
50
  create: (
47
51
  ctx: Parameters<
@@ -54,7 +58,7 @@ type MemberApiWithAuthorization<
54
58
  status?: string;
55
59
  extend?: Record<string, unknown>;
56
60
  },
57
- ) => Promise<{ ok: true; memberId: string }>;
61
+ ) => Promise<{ memberId: string }>;
58
62
  list: (
59
63
  ctx: Parameters<
60
64
  ReturnType<typeof AuthFactory>["auth"]["member"]["list"]
@@ -78,10 +82,21 @@ type MemberApiWithAuthorization<
78
82
  >[0],
79
83
  memberId: string,
80
84
  data: Record<string, unknown> & { roleIds?: AuthRoleId<TAuthorization>[] },
81
- ) => Promise<{ ok: true; memberId: string }>;
82
- resolve: (
85
+ ) => Promise<{ memberId: string }>;
86
+ inspect: (
83
87
  ctx: Parameters<
84
- ReturnType<typeof AuthFactory>["auth"]["member"]["resolve"]
88
+ ReturnType<typeof AuthFactory>["auth"]["member"]["inspect"]
89
+ >[0],
90
+ opts: {
91
+ userId: string;
92
+ groupId: string;
93
+ ancestry?: boolean;
94
+ maxDepth?: number;
95
+ },
96
+ ) => ReturnType<ReturnType<typeof AuthFactory>["auth"]["member"]["inspect"]>;
97
+ require: (
98
+ ctx: Parameters<
99
+ ReturnType<typeof AuthFactory>["auth"]["member"]["require"]
85
100
  >[0],
86
101
  opts: {
87
102
  userId: string;
@@ -91,10 +106,9 @@ type MemberApiWithAuthorization<
91
106
  grants?: AuthGrant<TAuthorization>[];
92
107
  maxDepth?: number;
93
108
  },
94
- ) => ReturnType<ReturnType<typeof AuthFactory>["auth"]["member"]["resolve"]>;
109
+ ) => ReturnType<ReturnType<typeof AuthFactory>["auth"]["member"]["require"]>;
95
110
  };
96
111
 
97
-
98
112
  /**
99
113
  * The base auth API surface returned by {@link createAuth}.
100
114
  *
@@ -126,30 +140,40 @@ export type AuthApiBase<
126
140
  key: ReturnType<typeof AuthFactory>["auth"]["key"];
127
141
  http: ReturnType<typeof AuthFactory>["auth"]["http"];
128
142
  /**
129
- * Resolve the current user's auth context. Framework-agnostic — use
143
+ * Resolve the current request's auth context. Framework-agnostic — use
130
144
  * this in fluent-convex middleware, custom wrappers, or anywhere you
131
- * need the resolved `{ userId, user, groupId, role, grants }` object.
145
+ * need the current `{ userId, user, groupId, role, grants }` object.
132
146
  *
133
- * Returns `null` when unauthenticated. Does not throw.
147
+ * Throws a structured `ConvexError` when unauthenticated by default.
148
+ * Pass `{ optional: true }` to get a null-shaped auth object instead.
134
149
  *
135
150
  * @param ctx - Convex query, mutation, or action context.
136
- * @returns The resolved auth context, or `null`.
151
+ * @param config - Optional auth resolution config. Supports `optional`,
152
+ * `resolve`, and `authResolve`.
153
+ * @returns The current auth context.
137
154
  *
138
155
  * @example fluent-convex middleware
139
156
  * ```ts
140
157
  * const withAuth = convex.createMiddleware(async (ctx, next) => {
141
- * return next({ ...ctx, auth: await auth.resolve(ctx) });
158
+ * return next({ ...ctx, auth: await auth.context(ctx) });
142
159
  * });
143
160
  * ```
144
161
  *
145
162
  * @example Direct usage in a handler
146
- * ```ts
147
- * const resolved = await auth.resolve(ctx);
148
- * if (!resolved) return { ok: false, code: "NOT_SIGNED_IN" };
149
- * const { userId, grants } = resolved;
150
- * ```
151
- */
152
- resolve: (ctx: any) => Promise<AuthResolvedContext | null>;
163
+ * ```ts
164
+ * const authContext = await auth.context(ctx);
165
+ * const { userId, grants } = authContext;
166
+ * ```
167
+ *
168
+ * @example Optional usage
169
+ * ```ts
170
+ * const authContext = await auth.context(ctx, { optional: true });
171
+ * if (authContext.userId === null) {
172
+ * return null;
173
+ * }
174
+ * ```
175
+ */
176
+ context: AuthContextResolver;
153
177
  /**
154
178
  * Context enrichment for convex-helpers `customQuery` / `customMutation` /
155
179
  * `customAction`.
@@ -158,9 +182,9 @@ export type AuthApiBase<
158
182
  * and grants, then attaches them to `ctx.auth`. Returns a `Customization`
159
183
  * object compatible with convex-helpers' custom function builders.
160
184
  *
161
- * `ctx.auth` is `{ userId, user, groupId, role, grants }` when
162
- * authenticated, `null` when unauthenticated. No throwing your
163
- * handler decides how to respond.
185
+ * `ctx.auth` is the current request auth context.
186
+ * By default this throws when unauthenticated so handlers can assume
187
+ * `ctx.auth.userId` and `ctx.auth.user` exist.
164
188
  *
165
189
  * @returns A convex-helpers `Customization` object.
166
190
  *
@@ -182,36 +206,30 @@ export type AuthApiBase<
182
206
  * export const list = authQuery({
183
207
  * args: { workspaceId: v.string() },
184
208
  * handler: async (ctx, args) => {
185
- * if (!ctx.auth) return [];
186
209
  * const { userId, groupId, grants } = ctx.auth;
187
210
  * // business logic
188
211
  * },
189
212
  * });
190
213
  * ```
191
214
  */
192
- ctx: () => {
193
- args: Record<string, never>;
194
- input: (ctx: any) => Promise<{
195
- ctx: { auth: AuthResolvedContext | null };
196
- args: Record<string, never>;
197
- }>;
198
- };
215
+ ctx: AuthContextFactory;
199
216
  };
200
217
 
201
218
  /**
202
- * Resolved auth context injected into `ctx.auth` by `auth.ctx()` and
203
- * {@link AuthCtx}. Also the expected return shape for custom
204
- * {@link AuthCtxConfig.authResolve | authResolve} hooks.
219
+ * Current request auth context injected into `ctx.auth` by `auth.ctx()`. This
220
+ * is the authenticated auth shape returned by {@link createAuth().context}.
221
+ * Optional context builders may still surface nullable fields when
222
+ * `optional: true` is used.
205
223
  *
206
- * - `null` when unauthenticated.
207
224
  * - `groupId` is `null` when the user has no active group set.
208
- * - `role` / `grants` are `null` / `[]` when no active group or no membership.
225
+ * - `role` is `null` when no active group or no membership is resolved.
226
+ * - `grants` is `[]` when no active group or no membership is resolved.
209
227
  *
210
228
  * @example
211
229
  * ```ts
212
- * import type { AuthResolvedContext } from "@robelest/convex-auth/server";
230
+ * import type { AuthContext } from "@robelest/convex-auth/server";
213
231
  *
214
- * const mockAuth: AuthResolvedContext = {
232
+ * const mockAuth: AuthContext = {
215
233
  * userId: "user123" as Id<"User">,
216
234
  * user: { _id: "user123", email: "test@example.com" },
217
235
  * groupId: "group456",
@@ -220,7 +238,7 @@ export type AuthApiBase<
220
238
  * };
221
239
  * ```
222
240
  */
223
- export type AuthResolvedContext = {
241
+ export type AuthContext = {
224
242
  /** The authenticated user's document ID. */
225
243
  userId: GenericId<"User">;
226
244
  /** The authenticated user's full document. */
@@ -233,20 +251,84 @@ export type AuthResolvedContext = {
233
251
  grants: string[];
234
252
  };
235
253
 
236
- type AuthCtxBase = {
237
- getUserIdentity: () => Promise<UserIdentity | null>;
238
- };
239
-
240
- type RequiredAuthCtxState = AuthCtxBase & AuthResolvedContext;
241
-
242
- type OptionalAuthCtxState = AuthCtxBase & {
254
+ /**
255
+ * Nullable auth context returned by `auth.context(ctx, { optional: true })`
256
+ * and injected by `auth.ctx({ optional: true })`.
257
+ *
258
+ * Use this when callers may be unauthenticated but you still want a stable
259
+ * auth-shaped object.
260
+ *
261
+ * - `userId` and `user` are `null` when unauthenticated.
262
+ * - `groupId` and `role` are `null` when no active group is resolved.
263
+ * - `grants` is `[]` when no membership is resolved.
264
+ *
265
+ * @example
266
+ * ```ts
267
+ * const authContext = await auth.context(ctx, { optional: true });
268
+ * if (authContext.userId === null) {
269
+ * return null;
270
+ * }
271
+ * ```
272
+ */
273
+ export type OptionalAuthContext = {
274
+ /** The authenticated user's document ID, or `null` when unauthenticated. */
243
275
  userId: GenericId<"User"> | null;
276
+ /** The authenticated user's full document, or `null` when unauthenticated. */
244
277
  user: UserDoc | null;
278
+ /** The user's active group ID, or `null` if none is set. */
245
279
  groupId: string | null;
280
+ /** The user's primary role in the active group, or `null`. */
246
281
  role: string | null;
282
+ /** Resolved grant strings for the active membership, or `[]`. */
247
283
  grants: string[];
248
284
  };
249
285
 
286
+ type AuthContextBase = {
287
+ getUserIdentity: () => Promise<UserIdentity | null>;
288
+ };
289
+
290
+ type RequiredAuthContextState = AuthContextBase & AuthContext;
291
+
292
+ type OptionalAuthContextState = AuthContextBase & OptionalAuthContext;
293
+
294
+ type ResolvedAuthContext<TResolve> = AuthContext & TResolve;
295
+
296
+ type ResolvedOptionalAuthContext<TResolve> = OptionalAuthContext & TResolve;
297
+
298
+ type AuthContextResolver = {
299
+ <TResolve extends Record<string, unknown> = Record<string, never>>(
300
+ ctx: any,
301
+ config: AuthContextConfig<TResolve> & { optional: true },
302
+ ): Promise<ResolvedOptionalAuthContext<TResolve>>;
303
+ <TResolve extends Record<string, unknown> = Record<string, never>>(
304
+ ctx: any,
305
+ config?: AuthContextConfig<TResolve>,
306
+ ): Promise<ResolvedAuthContext<TResolve>>;
307
+ };
308
+
309
+ type AuthContextCustomization<TAuth> = {
310
+ args: {};
311
+ input: (
312
+ ctx: any,
313
+ _args: any,
314
+ _extra?: any,
315
+ ) => Promise<{
316
+ ctx: {
317
+ auth: TAuth;
318
+ };
319
+ args: {};
320
+ }>;
321
+ };
322
+
323
+ type AuthContextFactory = {
324
+ <TResolve extends Record<string, unknown> = Record<string, never>>(
325
+ config: AuthContextConfig<TResolve> & { optional: true },
326
+ ): AuthContextCustomization<OptionalAuthContextState & TResolve>;
327
+ <TResolve extends Record<string, unknown> = Record<string, never>>(
328
+ config?: AuthContextConfig<TResolve>,
329
+ ): AuthContextCustomization<RequiredAuthContextState & TResolve>;
330
+ };
331
+
250
332
  type InternalSsoApi = ReturnType<typeof AuthFactory>["auth"]["sso"];
251
333
 
252
334
  type PublicSsoAdminApi = {
@@ -262,7 +344,6 @@ type PublicSsoAdminApi = {
262
344
  isPrimary?: boolean;
263
345
  }>,
264
346
  ) => Promise<{
265
- ok: true;
266
347
  enterpriseId: string;
267
348
  domains: Array<{
268
349
  domainId: string;
@@ -277,7 +358,6 @@ type PublicSsoAdminApi = {
277
358
  ctx: Parameters<InternalSsoApi["connection"]["create"]>[0],
278
359
  args: { enterpriseId: string; domain: string },
279
360
  ) => Promise<{
280
- ok: true;
281
361
  enterpriseId: string;
282
362
  domain: string;
283
363
  requestedAt: number;
@@ -292,7 +372,6 @@ type PublicSsoAdminApi = {
292
372
  ctx: Parameters<InternalSsoApi["connection"]["create"]>[0],
293
373
  args: { enterpriseId: string; domain: string },
294
374
  ) => Promise<{
295
- ok: boolean;
296
375
  enterpriseId: string;
297
376
  domain: string;
298
377
  verifiedAt?: number;
@@ -431,38 +510,54 @@ export type AuthLike = Pick<AuthApiBase, "user" | "member">;
431
510
  * });
432
511
  * ```
433
512
  *
434
- * @see {@link AuthCtx}
513
+ * @see {@link AuthContextConfig}
435
514
  */
436
515
 
437
516
  // ---------------------------------------------------------------------------
438
517
  // Function builders — shared auth resolution logic
439
518
  // ---------------------------------------------------------------------------
440
519
 
441
- /**
442
- * Resolve auth context for the current user. Returns the enriched
443
- * `ctx.auth` object or `null` when unauthenticated.
444
- *
445
- * Resolution flow:
446
- * 1. `user.id(ctx)` userId or null (exit early)
447
- * 2. `user.get(ctx, userId)` → user doc (cached per-execution)
448
- * 3. `user.getActiveGroup(ctx, { userId })` → groupId or null
449
- * 4. If groupId → `member.resolve(ctx, { userId, groupId })` → role + grants
450
- */
451
- async function resolveAuthContext(auth: AuthLike, ctx: any) {
452
- const userId = await auth.user.id(ctx);
453
- if (!userId) return null;
454
- const user = await auth.user.get(ctx, userId);
455
- const groupId = await auth.user.getActiveGroup(ctx, { userId });
456
- let role: string | null = null;
457
- let grants: string[] = [];
458
- if (groupId) {
459
- const resolved = await auth.member.resolve(ctx, { userId, groupId });
460
- if (resolved.membership) {
461
- role = resolved.roleIds[0] ?? null;
462
- grants = resolved.grants;
520
+ async function resolveConfiguredAuthContext(
521
+ auth: AuthLike,
522
+ ctx: any,
523
+ config?: AuthContextConfig<any>,
524
+ ): Promise<AuthContext | null> {
525
+ const fallback = () => getResolvedAuthContext(auth, ctx);
526
+ const authOverride = config?.authResolve
527
+ ? await config.authResolve(ctx, fallback)
528
+ : undefined;
529
+ return authOverride === undefined ? await fallback() : authOverride;
530
+ }
531
+
532
+ function createNotSignedInError() {
533
+ return Cv.error({
534
+ code: "NOT_SIGNED_IN",
535
+ message: "Authentication required.",
536
+ });
537
+ }
538
+
539
+ async function createPublicAuthContext(
540
+ auth: AuthLike,
541
+ ctx: any,
542
+ config?: AuthContextConfig<any>,
543
+ ) {
544
+ const resolved = await resolveConfiguredAuthContext(auth, ctx, config);
545
+
546
+ if (resolved === null) {
547
+ if (config?.optional !== true) {
548
+ throw createNotSignedInError();
463
549
  }
550
+ return createUnauthenticatedAuthContext();
464
551
  }
465
- return { userId, user, groupId, role, grants };
552
+
553
+ const extra = config?.resolve
554
+ ? await config.resolve(ctx, resolved.user, resolved)
555
+ : {};
556
+
557
+ return {
558
+ ...resolved,
559
+ ...extra,
560
+ };
466
561
  }
467
562
 
468
563
  export function createAuth<
@@ -504,10 +599,10 @@ export function createAuth<
504
599
  ) => {
505
600
  const enterprise = await connectionApi.get(ctx, enterpriseId);
506
601
  if (enterprise === null) {
507
- throw new AuthError(
508
- "INVALID_PARAMETERS",
509
- "Enterprise not found.",
510
- ).toConvexError();
602
+ throw Cv.error({
603
+ code: "INVALID_PARAMETERS",
604
+ message: "Enterprise not found.",
605
+ });
511
606
  }
512
607
 
513
608
  const normalized = domains.map((entry: (typeof domains)[number]) => ({
@@ -517,16 +612,16 @@ export function createAuth<
517
612
  const deduped = new Map<string, (typeof normalized)[number]>();
518
613
  for (const entry of normalized) {
519
614
  if (entry.domain.length === 0) {
520
- throw new AuthError(
521
- "INVALID_PARAMETERS",
522
- "Domain must not be empty.",
523
- ).toConvexError();
615
+ throw Cv.error({
616
+ code: "INVALID_PARAMETERS",
617
+ message: "Domain must not be empty.",
618
+ });
524
619
  }
525
620
  if (deduped.has(entry.domain)) {
526
- throw new AuthError(
527
- "INVALID_PARAMETERS",
528
- `Duplicate domain: ${entry.domain}`,
529
- ).toConvexError();
621
+ throw Cv.error({
622
+ code: "INVALID_PARAMETERS",
623
+ message: `Duplicate domain: ${entry.domain}`,
624
+ });
530
625
  }
531
626
  deduped.set(entry.domain, entry);
532
627
  }
@@ -536,10 +631,10 @@ export function createAuth<
536
631
  (entry) => entry.isPrimary,
537
632
  ).length;
538
633
  if (primaryCount > 1) {
539
- throw new AuthError(
540
- "INVALID_PARAMETERS",
541
- "Only one primary domain may be set.",
542
- ).toConvexError();
634
+ throw Cv.error({
635
+ code: "INVALID_PARAMETERS",
636
+ message: "Only one primary domain may be set.",
637
+ });
543
638
  }
544
639
  if (nextDomains.length > 0 && primaryCount === 0) {
545
640
  nextDomains[0] = { ...nextDomains[0], isPrimary: true };
@@ -586,7 +681,6 @@ export function createAuth<
586
681
 
587
682
  const updatedDomains = await domainApi.list(ctx, enterpriseId);
588
683
  return {
589
- ok: true as const,
590
684
  enterpriseId,
591
685
  domains: updatedDomains.map(
592
686
  (domain: (typeof updatedDomains)[number]) => ({
@@ -660,59 +754,72 @@ export function createAuth<
660
754
  },
661
755
  http: authResult.auth.http,
662
756
 
663
- resolve: (ctx: any) => resolveAuthContext(authResult.auth, ctx),
757
+ context: ((ctx: any, config?: AuthContextConfig<any>) =>
758
+ createPublicAuthContext(authResult.auth, ctx, config)) as AuthContextResolver,
664
759
 
665
- ctx: () => ({
666
- args: {},
667
- input: async (ctx: any) => {
668
- const authCtx = await resolveAuthContext(authResult.auth, ctx);
669
- return { ctx: { auth: authCtx }, args: {} };
670
- },
671
- }),
760
+ ctx: ((config?: AuthContextConfig<any>) =>
761
+ createAuthContextCustomization(authResult.auth, config)) as AuthContextFactory,
672
762
  } as unknown as ConvexAuthResult<P, TAuthorization>;
673
763
  }
674
764
 
675
765
  // ============================================================================
676
- // AuthCtx — ctx enrichment for customQuery / customMutation
766
+ // auth.ctx() — ctx enrichment for customQuery / customMutation
677
767
  // ============================================================================
678
768
 
679
769
  /**
680
- * Configuration for {@link AuthCtx} context enrichment.
770
+ * Configuration for {@link createAuth().ctx} context enrichment.
771
+ *
772
+ * The same config shape is also used by {@link createAuth().context}.
681
773
  *
682
774
  * @typeParam TResolve - Extra fields returned from `resolve()` and merged into
683
775
  * the resulting `ctx.auth` object.
776
+ *
777
+ * @example
778
+ * ```ts
779
+ * const authContext = await auth.context(ctx, {
780
+ * resolve: async (_ctx, user, authState) => ({
781
+ * email: user.email,
782
+ * canWrite: authState.grants.includes("posts.write"),
783
+ * }),
784
+ * });
785
+ * ```
684
786
  */
685
- export type AuthCtxConfig<
787
+ export type AuthContextConfig<
686
788
  TResolve extends Record<string, unknown> = Record<string, never>,
687
789
  > = {
688
- /** Allow unauthenticated callers and return `userId: null` / `user: null`. */
790
+ /**
791
+ * Allow unauthenticated callers and return a null-shaped auth object instead
792
+ * of throwing `NOT_SIGNED_IN`.
793
+ */
689
794
  optional?: boolean;
690
795
  /**
691
796
  * Attach additional derived fields to the auth context after the base auth
692
797
  * context is resolved.
798
+ *
799
+ * This callback runs only when a user is authenticated.
693
800
  */
694
801
  resolve?: (
695
802
  ctx: any,
696
803
  user: UserDoc,
697
- auth: AuthResolvedContext,
804
+ auth: AuthContext,
698
805
  ) => Promise<TResolve> | TResolve;
699
806
  /**
700
- * Override or wrap the base auth resolution used by {@link AuthCtx}.
807
+ * Override or wrap the base auth resolution used by {@link createAuth().ctx}.
701
808
  *
702
809
  * Return `undefined` to fall back to the built-in resolver,
703
810
  * `null` for an explicit unauthenticated state, or an
704
- * {@link AuthResolvedContext} object to provide a pre-resolved auth state.
811
+ * {@link AuthContext} object to provide a pre-resolved auth state.
705
812
  * This is useful for tests, proxy auth, impersonation flows, or any
706
813
  * environment that needs to inject auth without depending on the standard
707
814
  * Convex auth tables.
708
815
  *
709
816
  * @param ctx - The Convex function context.
710
- * @param fallback - The built-in auth resolver used by {@link AuthCtx}.
817
+ * @param fallback - The built-in auth resolver used by {@link createAuth().ctx}.
711
818
  * @returns Resolved auth state, `null`, or `undefined` to use the fallback.
712
819
  *
713
820
  * @example
714
821
  * ```ts
715
- * const authCtx = AuthCtx(auth, {
822
+ * const authCtx = auth.ctx({
716
823
  * authResolve: async (ctx, fallback) => {
717
824
  * const injected = getInjectedAuth(ctx);
718
825
  * return injected ?? (await fallback());
@@ -722,12 +829,8 @@ export type AuthCtxConfig<
722
829
  */
723
830
  authResolve?: (
724
831
  ctx: any,
725
- fallback: () => Promise<AuthResolvedContext | null>,
726
- ) =>
727
- | Promise<AuthResolvedContext | null | undefined>
728
- | AuthResolvedContext
729
- | null
730
- | undefined;
832
+ fallback: () => Promise<AuthContext | null>,
833
+ ) => Promise<AuthContext | null | undefined> | AuthContext | null | undefined;
731
834
  };
732
835
 
733
836
  /**
@@ -737,7 +840,6 @@ export type AuthCtxConfig<
737
840
  * The enriched `ctx.auth` will have `userId: null`, `user: null`,
738
841
  * `groupId: null`, `role: null`, and `grants: []` for unauthenticated callers.
739
842
  *
740
- * @param auth - The auth API object returned by {@link createAuth}.
741
843
  * @param config - Configuration with `optional: true` and an optional
742
844
  * `resolve` callback for attaching extra fields to the auth context.
743
845
  * @returns An object with `args` and `input` compatible with Convex
@@ -745,42 +847,21 @@ export type AuthCtxConfig<
745
847
  *
746
848
  * @example
747
849
  * ```ts
748
- * const authCtx = AuthCtx(auth, {
749
- * optional: true,
750
- * resolve: async (_ctx, user) => ({ plan: user?.extend?.plan ?? null }),
751
- * });
752
- * ```
850
+ * const authCtx = auth.ctx({
851
+ * optional: true,
852
+ * resolve: async (_ctx, user) => ({ plan: user.extend?.plan ?? null }),
853
+ * });
854
+ * ```
753
855
  *
754
856
  * @see {@link createAuth}
755
857
  */
756
- export function AuthCtx<
757
- TResolve extends Record<string, unknown> = Record<string, never>,
758
- >(
759
- auth: AuthLike,
760
- config: AuthCtxConfig<TResolve> & { optional: true },
761
- ): {
762
- args: {};
763
- input: (
764
- ctx: any,
765
- _args: any,
766
- _extra?: any,
767
- ) => Promise<{
768
- ctx: {
769
- auth: OptionalAuthCtxState & TResolve;
770
- };
771
- args: {};
772
- }>;
773
- };
858
+
774
859
  /**
775
860
  * Create a context enrichment for `customQuery` / `customMutation` — required auth (default).
776
861
  *
777
- * When `optional` is omitted or `false`, the inferred type is the authenticated
778
- * auth shape. At runtime this helper still resolves instead of throwing, so if
779
- * no user is signed in the returned `ctx.auth.userId` / `ctx.auth.user` are
780
- * `null`, `ctx.auth.groupId` / `ctx.auth.role` are `null`, and
781
- * `ctx.auth.grants` is `[]`.
862
+ * When `optional` is omitted or `false`, unauthenticated requests throw a
863
+ * structured `ConvexError` before your handler runs.
782
864
  *
783
- * @param auth - The auth API object returned by {@link createAuth}.
784
865
  * @param config - Optional configuration with a `resolve` callback
785
866
  * for attaching extra fields to the auth context.
786
867
  * @returns An object with `args` and `input` compatible with Convex
@@ -788,56 +869,33 @@ export function AuthCtx<
788
869
  *
789
870
  * @example
790
871
  * ```ts
791
- * const authCtx = AuthCtx(auth, {
792
- * resolve: async (_ctx, user) => ({ email: user.email }),
793
- * });
794
- * ```
872
+ * const authCtx = auth.ctx({
873
+ * resolve: async (_ctx, user) => ({ email: user.email }),
874
+ * });
875
+ * ```
795
876
  *
796
877
  * @see {@link createAuth}
797
878
  */
798
- export function AuthCtx<
799
- TResolve extends Record<string, unknown> = Record<string, never>,
800
- >(
879
+ function createAuthContextCustomization(
801
880
  auth: AuthLike,
802
- config?: AuthCtxConfig<TResolve>,
803
- ): {
804
- args: {};
805
- input: (
806
- ctx: any,
807
- _args: any,
808
- _extra?: any,
809
- ) => Promise<{
810
- ctx: {
811
- auth: RequiredAuthCtxState & TResolve;
812
- };
813
- args: {};
814
- }>;
815
- };
816
- // Implementation
817
- export function AuthCtx(auth: AuthLike, config?: AuthCtxConfig<any>) {
881
+ config?: AuthContextConfig<any>,
882
+ ) {
818
883
  return {
819
884
  args: {},
820
885
  input: async (ctx: any, _args: any, _extra?: any) => {
821
886
  const nativeAuth = ctx.auth;
822
887
  const getUserIdentity = nativeAuth.getUserIdentity.bind(nativeAuth);
823
- const fallback = () => resolveAuthContext(auth, ctx);
824
-
825
- const authOverride = config?.authResolve
826
- ? await config.authResolve(ctx, fallback)
827
- : undefined;
828
- const resolved =
829
- authOverride === undefined ? await fallback() : authOverride;
888
+ const resolved = await resolveConfiguredAuthContext(auth, ctx, config);
830
889
 
831
890
  if (resolved === null) {
891
+ if (config?.optional !== true) {
892
+ throw createNotSignedInError();
893
+ }
832
894
  return {
833
895
  ctx: {
834
896
  auth: {
835
897
  getUserIdentity,
836
- userId: null,
837
- user: null,
838
- groupId: null,
839
- role: null,
840
- grants: [],
898
+ ...createUnauthenticatedAuthContext(),
841
899
  },
842
900
  },
843
901
  args: {},
@@ -863,21 +921,21 @@ export function AuthCtx(auth: AuthLike, config?: AuthCtxConfig<any>) {
863
921
  }
864
922
 
865
923
  /**
866
- * Extract the resolved `auth` context type from an {@link AuthCtx} instance.
924
+ * Extract the resolved `auth` context type from an `auth.ctx()` customization.
867
925
  *
868
926
  * Use this to type function parameters or variables that receive the
869
- * enriched auth context produced by `AuthCtx`. The inferred type includes
927
+ * enriched auth context produced by `auth.ctx()`. The inferred type includes
870
928
  * `userId`, `user`, `groupId`, `role`, `grants`, `getUserIdentity`, and any
871
929
  * additional fields added by the `resolve` callback. This is the generic
872
930
  * utility for reusing the enriched auth shape without manually duplicating
873
931
  * conditional auth types.
874
932
  *
875
- * @typeParam T - An `AuthCtx` return value (must have an `input` method
933
+ * @typeParam T - An `auth.ctx()` return value (must have an `input` method
876
934
  * that returns `{ ctx: { auth: ... } }`).
877
935
  *
878
936
  * @example
879
937
  * ```ts
880
- * const authCtx = AuthCtx(auth, {
938
+ * const authCtx = auth.ctx({
881
939
  * resolve: async (ctx, user) => ({ orgId: user.orgId }),
882
940
  * });
883
941
  * type Auth = InferAuth<typeof authCtx>;