@robelest/convex-auth 0.0.2-preview.0 → 0.0.2-preview.2

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 (166) hide show
  1. package/dist/bin.cjs +17 -15
  2. package/dist/client/index.d.ts +84 -30
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +259 -59
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/component/_generated/component.d.ts +46 -120
  7. package/dist/component/_generated/component.d.ts.map +1 -1
  8. package/dist/component/index.d.ts +2 -4
  9. package/dist/component/index.d.ts.map +1 -1
  10. package/dist/component/index.js +2 -4
  11. package/dist/component/index.js.map +1 -1
  12. package/dist/component/public.d.ts +233 -167
  13. package/dist/component/public.d.ts.map +1 -1
  14. package/dist/component/public.js +328 -155
  15. package/dist/component/public.js.map +1 -1
  16. package/dist/component/schema.d.ts +127 -12
  17. package/dist/component/schema.d.ts.map +1 -1
  18. package/dist/component/schema.js +136 -10
  19. package/dist/component/schema.js.map +1 -1
  20. package/dist/providers/{Anonymous.d.ts → anonymous.d.ts} +8 -8
  21. package/dist/providers/{Anonymous.d.ts.map → anonymous.d.ts.map} +1 -1
  22. package/dist/providers/{Anonymous.js → anonymous.js} +9 -10
  23. package/dist/providers/anonymous.js.map +1 -0
  24. package/dist/providers/{ConvexCredentials.d.ts → credentials.d.ts} +11 -11
  25. package/dist/providers/credentials.d.ts.map +1 -0
  26. package/dist/providers/{ConvexCredentials.js → credentials.js} +8 -8
  27. package/dist/providers/credentials.js.map +1 -0
  28. package/dist/providers/{Email.d.ts → email.d.ts} +6 -6
  29. package/dist/providers/email.d.ts.map +1 -0
  30. package/dist/providers/{Email.js → email.js} +6 -6
  31. package/dist/providers/email.js.map +1 -0
  32. package/dist/providers/{Password.d.ts → password.d.ts} +10 -10
  33. package/dist/providers/{Password.d.ts.map → password.d.ts.map} +1 -1
  34. package/dist/providers/{Password.js → password.js} +19 -20
  35. package/dist/providers/password.js.map +1 -0
  36. package/dist/providers/{Phone.d.ts → phone.d.ts} +3 -3
  37. package/dist/providers/{Phone.d.ts.map → phone.d.ts.map} +1 -1
  38. package/dist/providers/{Phone.js → phone.js} +3 -3
  39. package/dist/providers/{Phone.js.map → phone.js.map} +1 -1
  40. package/dist/server/implementation/db.d.ts +5 -2
  41. package/dist/server/implementation/db.d.ts.map +1 -1
  42. package/dist/server/implementation/db.js +2 -1
  43. package/dist/server/implementation/db.js.map +1 -1
  44. package/dist/server/implementation/index.d.ts +285 -180
  45. package/dist/server/implementation/index.d.ts.map +1 -1
  46. package/dist/server/implementation/index.js +280 -173
  47. package/dist/server/implementation/index.js.map +1 -1
  48. package/dist/server/implementation/mutations/createAccountFromCredentials.d.ts.map +1 -1
  49. package/dist/server/implementation/mutations/createAccountFromCredentials.js +8 -18
  50. package/dist/server/implementation/mutations/createAccountFromCredentials.js.map +1 -1
  51. package/dist/server/implementation/mutations/createVerificationCode.d.ts.map +1 -1
  52. package/dist/server/implementation/mutations/createVerificationCode.js +16 -44
  53. package/dist/server/implementation/mutations/createVerificationCode.js.map +1 -1
  54. package/dist/server/implementation/mutations/invalidateSessions.d.ts.map +1 -1
  55. package/dist/server/implementation/mutations/invalidateSessions.js +4 -8
  56. package/dist/server/implementation/mutations/invalidateSessions.js.map +1 -1
  57. package/dist/server/implementation/mutations/modifyAccount.d.ts.map +1 -1
  58. package/dist/server/implementation/mutations/modifyAccount.js +8 -19
  59. package/dist/server/implementation/mutations/modifyAccount.js.map +1 -1
  60. package/dist/server/implementation/mutations/refreshSession.d.ts.map +1 -1
  61. package/dist/server/implementation/mutations/refreshSession.js +9 -23
  62. package/dist/server/implementation/mutations/refreshSession.js.map +1 -1
  63. package/dist/server/implementation/mutations/retrieveAccountWithCredentials.d.ts.map +1 -1
  64. package/dist/server/implementation/mutations/retrieveAccountWithCredentials.js +6 -12
  65. package/dist/server/implementation/mutations/retrieveAccountWithCredentials.js.map +1 -1
  66. package/dist/server/implementation/mutations/signIn.d.ts.map +1 -1
  67. package/dist/server/implementation/mutations/signIn.js +2 -1
  68. package/dist/server/implementation/mutations/signIn.js.map +1 -1
  69. package/dist/server/implementation/mutations/signOut.d.ts.map +1 -1
  70. package/dist/server/implementation/mutations/signOut.js +5 -6
  71. package/dist/server/implementation/mutations/signOut.js.map +1 -1
  72. package/dist/server/implementation/mutations/storeRef.d.ts +8 -0
  73. package/dist/server/implementation/mutations/storeRef.d.ts.map +1 -0
  74. package/dist/server/implementation/mutations/storeRef.js +8 -0
  75. package/dist/server/implementation/mutations/storeRef.js.map +1 -0
  76. package/dist/server/implementation/mutations/userOAuth.d.ts.map +1 -1
  77. package/dist/server/implementation/mutations/userOAuth.js +16 -53
  78. package/dist/server/implementation/mutations/userOAuth.js.map +1 -1
  79. package/dist/server/implementation/mutations/verifier.d.ts.map +1 -1
  80. package/dist/server/implementation/mutations/verifier.js +4 -8
  81. package/dist/server/implementation/mutations/verifier.js.map +1 -1
  82. package/dist/server/implementation/mutations/verifierSignature.d.ts.map +1 -1
  83. package/dist/server/implementation/mutations/verifierSignature.js +6 -10
  84. package/dist/server/implementation/mutations/verifierSignature.js.map +1 -1
  85. package/dist/server/implementation/mutations/verifyCodeAndSignIn.d.ts.map +1 -1
  86. package/dist/server/implementation/mutations/verifyCodeAndSignIn.js +7 -16
  87. package/dist/server/implementation/mutations/verifyCodeAndSignIn.js.map +1 -1
  88. package/dist/server/implementation/provider.d.ts +2 -1
  89. package/dist/server/implementation/provider.d.ts.map +1 -1
  90. package/dist/server/implementation/provider.js.map +1 -1
  91. package/dist/server/implementation/rateLimit.d.ts.map +1 -1
  92. package/dist/server/implementation/rateLimit.js +13 -39
  93. package/dist/server/implementation/rateLimit.js.map +1 -1
  94. package/dist/server/implementation/refreshTokens.d.ts +1 -8
  95. package/dist/server/implementation/refreshTokens.d.ts.map +1 -1
  96. package/dist/server/implementation/refreshTokens.js +14 -58
  97. package/dist/server/implementation/refreshTokens.js.map +1 -1
  98. package/dist/server/implementation/sessions.d.ts +2 -20
  99. package/dist/server/implementation/sessions.d.ts.map +1 -1
  100. package/dist/server/implementation/sessions.js +8 -35
  101. package/dist/server/implementation/sessions.js.map +1 -1
  102. package/dist/server/implementation/types.d.ts +11 -267
  103. package/dist/server/implementation/types.d.ts.map +1 -1
  104. package/dist/server/implementation/types.js +1 -181
  105. package/dist/server/implementation/types.js.map +1 -1
  106. package/dist/server/implementation/users.d.ts.map +1 -1
  107. package/dist/server/implementation/users.js +19 -67
  108. package/dist/server/implementation/users.js.map +1 -1
  109. package/dist/server/index.d.ts +18 -0
  110. package/dist/server/index.d.ts.map +1 -1
  111. package/dist/server/index.js +255 -0
  112. package/dist/server/index.js.map +1 -1
  113. package/dist/server/provider_utils.d.ts +1 -1
  114. package/dist/server/provider_utils.d.ts.map +1 -1
  115. package/dist/server/provider_utils.js +2 -2
  116. package/dist/server/provider_utils.js.map +1 -1
  117. package/dist/server/types.d.ts +91 -52
  118. package/dist/server/types.d.ts.map +1 -1
  119. package/package.json +3 -6
  120. package/src/cli/index.ts +20 -19
  121. package/src/client/index.ts +347 -110
  122. package/src/component/_generated/component.ts +55 -214
  123. package/src/component/index.ts +1 -11
  124. package/src/component/public.ts +366 -178
  125. package/src/component/schema.ts +150 -19
  126. package/src/providers/{Anonymous.ts → anonymous.ts} +10 -11
  127. package/src/providers/{ConvexCredentials.ts → credentials.ts} +11 -11
  128. package/src/providers/{Email.ts → email.ts} +5 -5
  129. package/src/providers/{Password.ts → password.ts} +22 -27
  130. package/src/providers/{Phone.ts → phone.ts} +2 -2
  131. package/src/server/implementation/db.ts +5 -2
  132. package/src/server/implementation/index.ts +368 -313
  133. package/src/server/implementation/mutations/createAccountFromCredentials.ts +11 -25
  134. package/src/server/implementation/mutations/createVerificationCode.ts +16 -47
  135. package/src/server/implementation/mutations/invalidateSessions.ts +4 -9
  136. package/src/server/implementation/mutations/modifyAccount.ts +8 -22
  137. package/src/server/implementation/mutations/refreshSession.ts +11 -24
  138. package/src/server/implementation/mutations/retrieveAccountWithCredentials.ts +9 -17
  139. package/src/server/implementation/mutations/signIn.ts +2 -1
  140. package/src/server/implementation/mutations/signOut.ts +5 -8
  141. package/src/server/implementation/mutations/storeRef.ts +7 -0
  142. package/src/server/implementation/mutations/userOAuth.ts +10 -50
  143. package/src/server/implementation/mutations/verifier.ts +4 -9
  144. package/src/server/implementation/mutations/verifierSignature.ts +6 -12
  145. package/src/server/implementation/mutations/verifyCodeAndSignIn.ts +7 -18
  146. package/src/server/implementation/provider.ts +2 -1
  147. package/src/server/implementation/rateLimit.ts +15 -41
  148. package/src/server/implementation/refreshTokens.ts +26 -76
  149. package/src/server/implementation/sessions.ts +8 -39
  150. package/src/server/implementation/types.ts +16 -191
  151. package/src/server/implementation/users.ts +19 -66
  152. package/src/server/index.ts +373 -0
  153. package/src/server/provider_utils.ts +2 -2
  154. package/src/server/types.ts +116 -51
  155. package/dist/providers/Anonymous.js.map +0 -1
  156. package/dist/providers/ConvexCredentials.d.ts.map +0 -1
  157. package/dist/providers/ConvexCredentials.js.map +0 -1
  158. package/dist/providers/Email.d.ts.map +0 -1
  159. package/dist/providers/Email.js.map +0 -1
  160. package/dist/providers/Password.js.map +0 -1
  161. package/providers/Anonymous/package.json +0 -6
  162. package/providers/ConvexCredentials/package.json +0 -6
  163. package/providers/Email/package.json +0 -6
  164. package/providers/Password/package.json +0 -6
  165. package/providers/Phone/package.json +0 -6
  166. package/server/package.json +0 -6
@@ -1,19 +1,17 @@
1
1
  import { OAuth2Config, OAuthConfig } from "@auth/core/providers";
2
2
  import {
3
3
  Auth,
4
- DocumentByName,
5
4
  GenericActionCtx,
6
5
  GenericDataModel,
7
6
  HttpRouter,
8
- WithoutSystemFields,
9
7
  actionGeneric,
10
8
  httpActionGeneric,
11
9
  internalMutationGeneric,
12
10
  } from "convex/server";
13
- import { ConvexError, GenericId, Value, v } from "convex/values";
11
+ import { ConvexError, GenericId, v } from "convex/values";
14
12
  import { parse as parseCookies, serialize as serializeCookie } from "cookie";
15
13
  import { redirectToParamCookie, useRedirectToParam } from "../cookies.js";
16
- import { FunctionReferenceFromExport, GenericDoc } from "../convex_types.js";
14
+ import { FunctionReferenceFromExport } from "../convex_types.js";
17
15
  import {
18
16
  configDefaults,
19
17
  listAvailableProviders,
@@ -22,11 +20,10 @@ import {
22
20
  import {
23
21
  AuthProviderConfig,
24
22
  ConvexAuthConfig,
25
- GenericActionCtxWithAuthConfig,
26
23
  } from "../types.js";
27
24
  import { requireEnv } from "../utils.js";
28
25
  import { ActionCtx, MutationCtx, Tokens } from "./types.js";
29
- export { authTables, Doc, Tokens } from "./types.js";
26
+ export { Doc, Tokens } from "./types.js";
30
27
  import {
31
28
  LOG_LEVELS,
32
29
  TOKEN_SUB_CLAIM_DIVIDER,
@@ -53,7 +50,6 @@ import {
53
50
  oAuthConfigToInternalProvider,
54
51
  } from "../oauth/convexAuth.js";
55
52
  import { handleOAuth } from "../oauth/callback.js";
56
- export { getAuthSessionId } from "./sessions.js";
57
53
 
58
54
  /**
59
55
  * The type of the signIn Convex Action returned from the auth() helper.
@@ -80,8 +76,10 @@ export type SignOutAction = FunctionReferenceFromExport<
80
76
  *
81
77
  * ```ts filename="convex/auth.ts"
82
78
  * import { Auth } from "@robelest/convex-auth/component";
79
+ * import { components } from "./_generated/api";
83
80
  *
84
81
  * export const { auth, signIn, signOut, store } = Auth({
82
+ * component: components.auth,
85
83
  * providers: [],
86
84
  * });
87
85
  * ```
@@ -116,26 +114,32 @@ export function Auth(config_: ConvexAuthConfig) {
116
114
  }
117
115
  return provider;
118
116
  };
119
- const enrichCtx = <DataModel extends GenericDataModel>(
120
- ctx: GenericActionCtx<DataModel>,
121
- ) => ({ ...ctx, auth: { ...ctx.auth, config } });
122
- const requireComponent = () => {
123
- if (config.component === undefined) {
124
- throw new Error(
125
- "Auth component is not configured. Pass `component: components.auth` in Auth config.",
126
- );
127
- }
128
- return config.component;
129
- };
130
117
  type ComponentCtx = Pick<
131
118
  GenericActionCtx<GenericDataModel>,
132
119
  "runQuery" | "runMutation"
133
120
  >;
134
121
  type ComponentReadCtx = Pick<GenericActionCtx<GenericDataModel>, "runQuery">;
135
122
  type ComponentAuthReadCtx = ComponentReadCtx & { auth: Auth };
123
+ type AccountCredentials = { id: string; secret?: string };
124
+ type CreateAccountArgs = {
125
+ provider: string;
126
+ account: AccountCredentials;
127
+ profile: Record<string, unknown>;
128
+ shouldLinkViaEmail?: boolean;
129
+ shouldLinkViaPhone?: boolean;
130
+ };
131
+ type RetrieveAccountArgs = { provider: string; account: AccountCredentials };
132
+ type UpdateAccountCredentialsArgs = {
133
+ provider: string;
134
+ account: { id: string; secret: string };
135
+ };
136
136
 
137
137
  const auth = {
138
138
  user: {
139
+ /**
140
+ * Get the current user's ID from the auth context, or `null` if
141
+ * not signed in.
142
+ */
139
143
  current: async (ctx: { auth: Auth }) => {
140
144
  const identity = await ctx.auth.getUserIdentity();
141
145
  if (identity === null) {
@@ -144,6 +148,10 @@ export function Auth(config_: ConvexAuthConfig) {
144
148
  const [userId] = identity.subject.split(TOKEN_SUB_CLAIM_DIVIDER);
145
149
  return userId as GenericId<"user">;
146
150
  },
151
+ /**
152
+ * Get the current user's ID, or throw if not signed in.
153
+ * Use this when authentication is required.
154
+ */
147
155
  require: async (ctx: { auth: Auth }) => {
148
156
  const identity = await ctx.auth.getUserIdentity();
149
157
  if (identity === null) {
@@ -152,127 +160,383 @@ export function Auth(config_: ConvexAuthConfig) {
152
160
  const [userId] = identity.subject.split(TOKEN_SUB_CLAIM_DIVIDER);
153
161
  return userId as GenericId<"user">;
154
162
  },
163
+ /**
164
+ * Retrieve a user document by their ID.
165
+ */
155
166
  get: async (ctx: ComponentReadCtx, userId: string) => {
156
- const component = requireComponent();
157
- return await ctx.runQuery(component.public.userGetById, { userId });
167
+ return await ctx.runQuery(config.component.public.userGetById, { userId });
158
168
  },
169
+ /**
170
+ * Get the currently signed-in user's document, or `null` if not
171
+ * signed in. Convenience method combining `current` + `get`.
172
+ */
159
173
  viewer: async (ctx: ComponentAuthReadCtx) => {
160
174
  const userId = await auth.user.current(ctx);
161
175
  if (userId === null) {
162
176
  return null;
163
177
  }
164
- const component = requireComponent();
165
- return await ctx.runQuery(component.public.userGetById, { userId });
178
+ return await ctx.runQuery(config.component.public.userGetById, { userId });
179
+ },
180
+ /**
181
+ * Query a user's group memberships.
182
+ */
183
+ group: {
184
+ /**
185
+ * List all groups a user belongs to. Returns member records which
186
+ * include the `groupId`, `role`, `status`, and `extend` for each.
187
+ */
188
+ list: async (ctx: ComponentReadCtx, opts: { userId: string }) => {
189
+ return await ctx.runQuery(config.component.public.memberListByUser, opts);
190
+ },
191
+ /**
192
+ * Look up a user's membership in a specific group. Returns the member
193
+ * record (with role, status, extend) or `null` if the user is not
194
+ * a member.
195
+ */
196
+ get: async (
197
+ ctx: ComponentReadCtx,
198
+ opts: { userId: string; groupId: string },
199
+ ) => {
200
+ return await ctx.runQuery(
201
+ config.component.public.memberGetByGroupAndUser,
202
+ opts,
203
+ );
204
+ },
205
+ },
206
+ },
207
+ session: {
208
+ /**
209
+ * Get the current session ID from the auth context, or `null` if
210
+ * not signed in.
211
+ */
212
+ current: async (ctx: { auth: Auth }) => {
213
+ const identity = await ctx.auth.getUserIdentity();
214
+ if (identity === null) {
215
+ return null;
216
+ }
217
+ const [, sessionId] = identity.subject.split(TOKEN_SUB_CLAIM_DIVIDER);
218
+ return sessionId as GenericId<"session">;
219
+ },
220
+ /**
221
+ * Invalidate sessions for a user, optionally preserving specific sessions.
222
+ */
223
+ invalidate: async <DataModel extends GenericDataModel>(
224
+ ctx: GenericActionCtx<DataModel>,
225
+ args: {
226
+ userId: GenericId<"user">;
227
+ except?: GenericId<"session">[];
228
+ },
229
+ ): Promise<void> => {
230
+ const actionCtx = ctx as unknown as ActionCtx;
231
+ return await callInvalidateSessions(actionCtx, args);
232
+ },
233
+ },
234
+ account: {
235
+ /**
236
+ * Create an account and user for a credentials provider.
237
+ */
238
+ create: async <DataModel extends GenericDataModel>(
239
+ ctx: GenericActionCtx<DataModel>,
240
+ args: CreateAccountArgs,
241
+ ) => {
242
+ const actionCtx = ctx as unknown as ActionCtx;
243
+ return await callCreateAccountFromCredentials(actionCtx, args as any);
244
+ },
245
+ /**
246
+ * Retrieve an account and user for a credentials provider.
247
+ */
248
+ get: async <DataModel extends GenericDataModel>(
249
+ ctx: GenericActionCtx<DataModel>,
250
+ args: RetrieveAccountArgs,
251
+ ) => {
252
+ const actionCtx = ctx as unknown as ActionCtx;
253
+ const result = await callRetreiveAccountWithCredentials(actionCtx, args);
254
+ if (typeof result === "string") {
255
+ throw new Error(result);
256
+ }
257
+ return result;
258
+ },
259
+ /**
260
+ * Update credentials for an existing account.
261
+ */
262
+ updateCredentials: async <DataModel extends GenericDataModel>(
263
+ ctx: GenericActionCtx<DataModel>,
264
+ args: UpdateAccountCredentialsArgs,
265
+ ): Promise<void> => {
266
+ const actionCtx = ctx as unknown as ActionCtx;
267
+ return await callModifyAccount(actionCtx, args);
268
+ },
269
+ },
270
+ provider: {
271
+ /**
272
+ * Sign in via another provider, typically from a credentials flow.
273
+ */
274
+ signIn: async <DataModel extends GenericDataModel>(
275
+ ctx: GenericActionCtx<DataModel>,
276
+ provider: AuthProviderConfig,
277
+ args: {
278
+ accountId?: GenericId<"account">;
279
+ params?: Record<string, unknown>;
280
+ },
281
+ ) => {
282
+ const result = await signInImpl(
283
+ enrichCtx(ctx),
284
+ materializeProvider(provider),
285
+ args as any,
286
+ {
287
+ generateTokens: false,
288
+ allowExtraProviders: true,
289
+ },
290
+ );
291
+ return result.kind === "signedIn"
292
+ ? result.signedIn !== null
293
+ ? { userId: result.signedIn.userId, sessionId: result.signedIn.sessionId }
294
+ : null
295
+ : null;
166
296
  },
167
297
  },
168
- organization: {
298
+ /**
299
+ * Hierarchical group management. Groups can nest arbitrarily deep
300
+ * via `parentGroupId`. A root group has no parent.
301
+ *
302
+ * ```ts
303
+ * const groupId = await auth.group.create(ctx, { name: "Acme Corp" });
304
+ * const subGroupId = await auth.group.create(ctx, {
305
+ * name: "Engineering",
306
+ * parentGroupId: groupId,
307
+ * });
308
+ * ```
309
+ */
310
+ group: {
311
+ /**
312
+ * Create a new group. Omit `parentGroupId` for a root-level group,
313
+ * or provide it to create a nested group.
314
+ *
315
+ * @returns The ID of the newly created group.
316
+ */
169
317
  create: async (
170
318
  ctx: ComponentCtx,
171
- data: Record<string, unknown>,
319
+ data: {
320
+ name: string;
321
+ slug?: string;
322
+ parentGroupId?: string;
323
+ extend?: Record<string, unknown>;
324
+ },
172
325
  ): Promise<string> => {
173
- const component = requireComponent();
174
- return (await ctx.runMutation(component.public.organizationCreate!, {
326
+ return (await ctx.runMutation(
327
+ config.component.public.groupCreate,
175
328
  data,
176
- })) as string;
329
+ )) as string;
177
330
  },
178
- get: async (ctx: ComponentCtx, organizationId: string) => {
179
- const component = requireComponent();
180
- return await ctx.runQuery(component.public.organizationGet!, {
181
- organizationId,
182
- });
331
+ /**
332
+ * Retrieve a group by its ID. Returns `null` if not found.
333
+ */
334
+ get: async (ctx: ComponentReadCtx, groupId: string) => {
335
+ return await ctx.runQuery(config.component.public.groupGet, { groupId });
183
336
  },
184
- list: async (
185
- ctx: ComponentCtx,
186
- ownerUserId?: string,
187
- ) => {
188
- const component = requireComponent();
189
- return await ctx.runQuery(component.public.organizationList!, {
190
- ownerUserId,
337
+ /**
338
+ * List groups. When `parentGroupId` is provided, returns children of
339
+ * that group. When omitted, returns root-level groups (no parent).
340
+ */
341
+ list: async (ctx: ComponentReadCtx, opts?: { parentGroupId?: string }) => {
342
+ return await ctx.runQuery(config.component.public.groupList, {
343
+ parentGroupId: opts?.parentGroupId,
191
344
  });
192
345
  },
346
+ /**
347
+ * Update a group's fields (name, slug, extend, parentGroupId).
348
+ */
193
349
  update: async (
194
350
  ctx: ComponentCtx,
195
- organizationId: string,
351
+ groupId: string,
196
352
  data: Record<string, unknown>,
197
353
  ) => {
198
- const component = requireComponent();
199
- await ctx.runMutation(component.public.organizationUpdate!, {
200
- organizationId,
201
- data,
202
- });
354
+ await ctx.runMutation(config.component.public.groupUpdate, { groupId, data });
203
355
  },
204
- delete: async (ctx: ComponentCtx, organizationId: string) => {
205
- const component = requireComponent();
206
- await ctx.runMutation(component.public.organizationDelete!, {
207
- organizationId,
208
- });
356
+ /**
357
+ * Delete a group and cascade to all descendants. Deletes child groups
358
+ * (recursively), all members, and all invites for this group and its
359
+ * descendants.
360
+ */
361
+ delete: async (ctx: ComponentCtx, groupId: string) => {
362
+ await ctx.runMutation(config.component.public.groupDelete, { groupId });
209
363
  },
364
+
365
+ /**
366
+ * Manage group membership. A member links a user to a group with an
367
+ * application-defined role string (e.g. "owner", "admin", "member").
368
+ *
369
+ * The auth component stores roles but does not enforce access control.
370
+ * Your application defines what each role means.
371
+ */
210
372
  member: {
373
+ /**
374
+ * Add a user as a member of a group.
375
+ *
376
+ * @param data.groupId - The group to add the member to.
377
+ * @param data.userId - The user to add.
378
+ * @param data.role - Application-defined role (e.g. "owner", "admin", "member").
379
+ * @param data.status - Optional membership status (e.g. "active", "suspended").
380
+ * @param data.extend - Optional arbitrary JSON extension data.
381
+ * @throws ConvexError with code `DUPLICATE_MEMBERSHIP` if the user is
382
+ * already a member of the target group.
383
+ * @returns The ID of the new member record.
384
+ */
211
385
  add: async (
212
386
  ctx: ComponentCtx,
213
- data: Record<string, unknown>,
387
+ data: {
388
+ groupId: string;
389
+ userId: string;
390
+ role?: string;
391
+ status?: string;
392
+ extend?: Record<string, unknown>;
393
+ },
214
394
  ): Promise<string> => {
215
- const component = requireComponent();
216
- return (await ctx.runMutation(component.public.memberAdd!, {
395
+ return (await ctx.runMutation(
396
+ config.component.public.memberAdd,
217
397
  data,
218
- })) as string;
398
+ )) as string;
399
+ },
400
+ /**
401
+ * Retrieve a member record by its ID. Returns `null` if not found.
402
+ */
403
+ get: async (ctx: ComponentReadCtx, memberId: string) => {
404
+ return await ctx.runQuery(config.component.public.memberGet, { memberId });
219
405
  },
406
+ /**
407
+ * List all members of a group.
408
+ */
409
+ list: async (ctx: ComponentReadCtx, opts: { groupId: string }) => {
410
+ return await ctx.runQuery(config.component.public.memberList, opts);
411
+ },
412
+ /**
413
+ * Remove a member from a group by deleting the member record.
414
+ */
220
415
  remove: async (ctx: ComponentCtx, memberId: string) => {
221
- const component = requireComponent();
222
- await ctx.runMutation(component.public.memberRemove!, { memberId });
416
+ await ctx.runMutation(config.component.public.memberRemove, { memberId });
223
417
  },
224
- list: async (
418
+ /**
419
+ * Update a member's fields (role, status, extend).
420
+ *
421
+ * ```ts
422
+ * await auth.group.member.update(ctx, memberId, { role: "admin" });
423
+ * ```
424
+ */
425
+ update: async (
225
426
  ctx: ComponentCtx,
226
- args: { organizationId: string; teamId?: string },
427
+ memberId: string,
428
+ data: Record<string, unknown>,
227
429
  ) => {
228
- const component = requireComponent();
229
- return await ctx.runQuery(component.public.memberList!, args);
230
- },
231
- role: {
232
- set: async (ctx: ComponentCtx, memberId: string, role: string) => {
233
- const component = requireComponent();
234
- await ctx.runMutation(component.public.memberRoleSet!, {
235
- memberId,
236
- role,
237
- });
238
- },
239
- get: async (ctx: ComponentCtx, memberId: string) => {
240
- const component = requireComponent();
241
- return await ctx.runQuery(component.public.memberRoleGet!, {
242
- memberId,
243
- });
244
- },
430
+ await ctx.runMutation(config.component.public.memberUpdate, {
431
+ memberId,
432
+ data,
433
+ });
245
434
  },
246
435
  },
436
+
247
437
  },
438
+ /**
439
+ * Manage platform-level invitations.
440
+ *
441
+ * Invites can optionally target a group by setting `groupId`, but they do
442
+ * not require groups and can be used in apps with user-only collaboration.
443
+ */
248
444
  invite: {
445
+ /**
446
+ * Create a new invitation.
447
+ *
448
+ * @param data.groupId - Optional group to invite the user into.
449
+ * @param data.invitedByUserId - The user sending the invitation.
450
+ * @param data.email - The email address of the invitee.
451
+ * @param data.tokenHash - Hashed token for secure acceptance.
452
+ * @param data.role - Optional role to assign on acceptance.
453
+ * @param data.status - Initial status (typically "pending").
454
+ * @param data.expiresTime - Timestamp when the invite expires.
455
+ * @param data.extend - Optional arbitrary JSON extension data.
456
+ * @throws ConvexError with code `DUPLICATE_INVITE` if a pending invite
457
+ * already exists for this email and scope.
458
+ * @returns The ID of the new invite record.
459
+ */
249
460
  create: async (
250
461
  ctx: ComponentCtx,
251
- data: Record<string, unknown>,
462
+ data: {
463
+ groupId?: string;
464
+ invitedByUserId: string;
465
+ email: string;
466
+ tokenHash: string;
467
+ role?: string;
468
+ status: "pending" | "accepted" | "revoked" | "expired";
469
+ expiresTime: number;
470
+ extend?: Record<string, unknown>;
471
+ },
252
472
  ): Promise<string> => {
253
- const component = requireComponent();
254
- return (await ctx.runMutation(component.public.inviteCreate!, {
255
- data,
256
- })) as string;
473
+ return (await ctx.runMutation(config.component.public.inviteCreate, data)) as string;
257
474
  },
258
- get: async (ctx: ComponentCtx, inviteId: string) => {
259
- const component = requireComponent();
260
- return await ctx.runQuery(component.public.inviteGet!, { inviteId });
475
+ /**
476
+ * Retrieve an invite by its ID. Returns `null` if not found.
477
+ */
478
+ get: async (ctx: ComponentReadCtx, inviteId: string) => {
479
+ return await ctx.runQuery(config.component.public.inviteGet, { inviteId });
261
480
  },
481
+ /**
482
+ * List invites, optionally filtered by group and/or status.
483
+ */
262
484
  list: async (
263
- ctx: ComponentCtx,
264
- args: { organizationId?: string; status?: string },
485
+ ctx: ComponentReadCtx,
486
+ opts?: {
487
+ groupId?: string;
488
+ status?: "pending" | "accepted" | "revoked" | "expired";
489
+ },
265
490
  ) => {
266
- const component = requireComponent();
267
- return await ctx.runQuery(component.public.inviteList!, args);
491
+ return await ctx.runQuery(config.component.public.inviteList, {
492
+ groupId: opts?.groupId,
493
+ status: opts?.status,
494
+ });
268
495
  },
496
+ /**
497
+ * Accept an invitation. Marks the invite as "accepted" and records
498
+ * the timestamp. If the invite has a group, the caller is responsible
499
+ * for creating the member record via `auth.group.member.add` in the
500
+ * same Convex mutation for transactional safety.
501
+ *
502
+ * @throws ConvexError with code `INVITE_NOT_FOUND` when the invite does
503
+ * not exist.
504
+ * @throws ConvexError with code `INVITE_NOT_PENDING` when the invite is
505
+ * not in `pending` status.
506
+ *
507
+ * ```ts
508
+ * export const acceptInvite = mutation({
509
+ * args: { inviteId: v.string() },
510
+ * handler: async (ctx, { inviteId }) => {
511
+ * const userId = await auth.user.require(ctx);
512
+ * const invite = await auth.invite.get(ctx, inviteId);
513
+ * if (!invite) throw new Error("Invite not found");
514
+ *
515
+ * await auth.invite.accept(ctx, inviteId);
516
+ * if (invite.groupId) {
517
+ * await auth.group.member.add(ctx, {
518
+ * groupId: invite.groupId,
519
+ * userId,
520
+ * role: invite.role,
521
+ * });
522
+ * }
523
+ * },
524
+ * });
525
+ * ```
526
+ */
269
527
  accept: async (ctx: ComponentCtx, inviteId: string) => {
270
- const component = requireComponent();
271
- await ctx.runMutation(component.public.inviteAccept!, { inviteId });
528
+ await ctx.runMutation(config.component.public.inviteAccept, { inviteId });
272
529
  },
530
+ /**
531
+ * Revoke a pending invitation.
532
+ *
533
+ * @throws ConvexError with code `INVITE_NOT_FOUND` when the invite does
534
+ * not exist.
535
+ * @throws ConvexError with code `INVITE_NOT_PENDING` when the invite is
536
+ * not in `pending` status.
537
+ */
273
538
  revoke: async (ctx: ComponentCtx, inviteId: string) => {
274
- const component = requireComponent();
275
- await ctx.runMutation(component.public.inviteRevoke!, { inviteId });
539
+ await ctx.runMutation(config.component.public.inviteRevoke, { inviteId });
276
540
  },
277
541
  },
278
542
  /**
@@ -488,6 +752,19 @@ export function Auth(config_: ConvexAuthConfig) {
488
752
  }
489
753
  },
490
754
  };
755
+ const enrichCtx = <DataModel extends GenericDataModel>(
756
+ ctx: GenericActionCtx<DataModel>,
757
+ ) => ({
758
+ ...ctx,
759
+ auth: {
760
+ ...ctx.auth,
761
+ config,
762
+ account: auth.account,
763
+ session: auth.session,
764
+ provider: auth.provider,
765
+ },
766
+ });
767
+
491
768
  return {
492
769
  /**
493
770
  * Helper for configuring HTTP actions.
@@ -565,228 +842,6 @@ export function Auth(config_: ConvexAuthConfig) {
565
842
  };
566
843
  }
567
844
 
568
- /**
569
- * Return the currently signed-in user's ID.
570
- *
571
- * ```ts filename="convex/myFunctions.tsx"
572
- * import { mutation } from "./_generated/server";
573
- * import { getAuthUserId } from "@robelest/convex-auth/component";
574
- *
575
- * export const doSomething = mutation({
576
- * args: {/* ... *\/},
577
- * handler: async (ctx, args) => {
578
- * const userId = await getAuthUserId(ctx);
579
- * if (userId === null) {
580
- * throw new Error("Client is not authenticated!")
581
- * }
582
- * const user = await ctx.db.get(userId);
583
- * // ...
584
- * },
585
- * });
586
- * ```
587
- *
588
- * @param ctx query, mutation or action `ctx`
589
- * @returns the user ID or `null` if the client isn't authenticated
590
- */
591
- export async function getAuthUserId(ctx: { auth: Auth }) {
592
- const identity = await ctx.auth.getUserIdentity();
593
- if (identity === null) {
594
- return null;
595
- }
596
- const [userId] = identity.subject.split(TOKEN_SUB_CLAIM_DIVIDER);
597
- return userId as GenericId<"user">;
598
- }
599
-
600
- /**
601
- * Use this function from a
602
- * [`ConvexCredentials`](https://labs.convex.dev/auth/api_reference/providers/ConvexCredentials)
603
- * provider to create an account and a user with a unique account "id" (OAuth
604
- * provider ID, email address, phone number, username etc.).
605
- *
606
- * @returns user ID if it successfully creates the account
607
- * or throws an error.
608
- */
609
- export async function createAccount<
610
- DataModel extends GenericDataModel = GenericDataModel,
611
- >(
612
- ctx: GenericActionCtx<DataModel>,
613
- args: {
614
- /**
615
- * The provider ID (like "password"), used to disambiguate accounts.
616
- *
617
- * It is also used to configure account secret hashing via the provider's
618
- * `crypto` option.
619
- */
620
- provider: string;
621
- account: {
622
- /**
623
- * The unique external ID for the account, for example email address.
624
- */
625
- id: string;
626
- /**
627
- * The secret credential to store for this account, if given.
628
- */
629
- secret?: string;
630
- };
631
- /**
632
- * The profile data to store for the user.
633
- * These must fit the `users` table schema.
634
- */
635
- profile: WithoutSystemFields<DocumentByName<DataModel, "user">>;
636
- /**
637
- * If `true`, the account will be linked to an existing user
638
- * with the same verified email address.
639
- * This is only safe if the returned account's email is verified
640
- * before the user is allowed to sign in with it.
641
- */
642
- shouldLinkViaEmail?: boolean;
643
- /**
644
- * If `true`, the account will be linked to an existing user
645
- * with the same verified phone number.
646
- * This is only safe if the returned account's phone is verified
647
- * before the user is allowed to sign in with it.
648
- */
649
- shouldLinkViaPhone?: boolean;
650
- },
651
- ): Promise<{
652
- account: GenericDoc<DataModel, "account">;
653
- user: GenericDoc<DataModel, "user">;
654
- }> {
655
- const actionCtx = ctx as unknown as ActionCtx;
656
- return (await callCreateAccountFromCredentials(
657
- actionCtx,
658
- args as any,
659
- )) as any;
660
- }
661
-
662
- /**
663
- * Use this function from a
664
- * [`ConvexCredentials`](https://labs.convex.dev/auth/api_reference/providers/ConvexCredentials)
665
- * provider to retrieve a user given the account provider ID and
666
- * the provider-specific account ID.
667
- *
668
- * @returns the retrieved user document, or `null` if there is no account
669
- * for given account ID or throws if the provided
670
- * secret does not match.
671
- */
672
- export async function retrieveAccount<
673
- DataModel extends GenericDataModel = GenericDataModel,
674
- >(
675
- ctx: GenericActionCtx<DataModel>,
676
- args: {
677
- /**
678
- * The provider ID (like "password"), used to disambiguate accounts.
679
- *
680
- * It is also used to configure account secret hashing via the provider's
681
- * `crypto` option.
682
- */
683
- provider: string;
684
- account: {
685
- /**
686
- * The unique external ID for the account, for example email address.
687
- */
688
- id: string;
689
- /**
690
- * The secret that should match the stored credential, if given.
691
- */
692
- secret?: string;
693
- };
694
- },
695
- ): Promise<{
696
- account: GenericDoc<DataModel, "account">;
697
- user: GenericDoc<DataModel, "user">;
698
- }> {
699
- const actionCtx = ctx as unknown as ActionCtx;
700
- const result = await callRetreiveAccountWithCredentials(actionCtx, args);
701
- if (typeof result === "string") {
702
- throw new Error(result);
703
- }
704
- return result as any;
705
- }
706
-
707
- /**
708
- * Use this function to modify the account credentials
709
- * from a [`ConvexCredentials`](https://labs.convex.dev/auth/api_reference/providers/ConvexCredentials)
710
- * provider.
711
- */
712
- export async function modifyAccountCredentials<
713
- DataModel extends GenericDataModel = GenericDataModel,
714
- >(
715
- ctx: GenericActionCtx<DataModel>,
716
- args: {
717
- /**
718
- * The provider ID (like "password"), used to disambiguate accounts.
719
- *
720
- * It is also used to configure account secret hashing via the `crypto` option.
721
- */
722
- provider: string;
723
- account: {
724
- /**
725
- * The unique external ID for the account, for example email address.
726
- */
727
- id: string;
728
- /**
729
- * The new secret credential to store for this account.
730
- */
731
- secret: string;
732
- };
733
- },
734
- ): Promise<void> {
735
- const actionCtx = ctx as unknown as ActionCtx;
736
- return await callModifyAccount(actionCtx, args);
737
- }
738
-
739
- /**
740
- * Use this function to invalidate existing sessions.
741
- */
742
- export async function invalidateSessions<
743
- DataModel extends GenericDataModel = GenericDataModel,
744
- >(
745
- ctx: GenericActionCtx<DataModel>,
746
- args: {
747
- userId: GenericId<"user">;
748
- except?: GenericId<"session">[];
749
- },
750
- ): Promise<void> {
751
- const actionCtx = ctx as unknown as ActionCtx;
752
- return await callInvalidateSessions(actionCtx, args);
753
- }
754
-
755
- /**
756
- * Use this function from a
757
- * [`ConvexCredentials`](https://labs.convex.dev/auth/api_reference/providers/ConvexCredentials)
758
- * provider to sign in the user via another provider (usually
759
- * for email verification on sign up or password reset).
760
- *
761
- * Returns the user ID if the sign can proceed,
762
- * or `null`.
763
- */
764
- export async function signInViaProvider<
765
- DataModel extends GenericDataModel = GenericDataModel,
766
- >(
767
- ctx: GenericActionCtxWithAuthConfig<DataModel>,
768
- provider: AuthProviderConfig,
769
- args: {
770
- accountId?: GenericId<"account">;
771
- params?: Record<string, Value | undefined>;
772
- },
773
- ) {
774
- const result = await signInImpl(
775
- ctx,
776
- materializeProvider(provider),
777
- args as any,
778
- {
779
- generateTokens: false,
780
- allowExtraProviders: true,
781
- },
782
- );
783
- return result.kind === "signedIn"
784
- ? result.signedIn !== null
785
- ? { userId: result.signedIn.userId, sessionId: result.signedIn.sessionId }
786
- : null
787
- : null;
788
- }
789
-
790
845
  function convertErrorsToResponse(
791
846
  errorStatusCode: number,
792
847
  action: (ctx: GenericActionCtx<any>, request: Request) => Promise<Response>,