kitcn 0.0.1 → 0.12.1
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.
- package/bin/intent.js +3 -0
- package/dist/aggregate/index.d.ts +388 -0
- package/dist/aggregate/index.js +37 -0
- package/dist/api-entry-BckXqaLb.js +66 -0
- package/dist/auth/client/index.d.ts +37 -0
- package/dist/auth/client/index.js +217 -0
- package/dist/auth/config/index.d.ts +45 -0
- package/dist/auth/config/index.js +24 -0
- package/dist/auth/generated/index.d.ts +2 -0
- package/dist/auth/generated/index.js +3 -0
- package/dist/auth/http/index.d.ts +64 -0
- package/dist/auth/http/index.js +461 -0
- package/dist/auth/index.d.ts +221 -0
- package/dist/auth/index.js +1398 -0
- package/dist/auth/nextjs/index.d.ts +50 -0
- package/dist/auth/nextjs/index.js +81 -0
- package/dist/auth-store-Cljlmdmi.js +197 -0
- package/dist/builder-CBdG5W6A.js +1974 -0
- package/dist/caller-factory-cTXNvYdz.js +216 -0
- package/dist/cli.mjs +13264 -0
- package/dist/codegen-lF80HSWu.mjs +3416 -0
- package/dist/context-utils-HPC5nXzx.d.ts +17 -0
- package/dist/create-schema-odyF4kCy.js +156 -0
- package/dist/create-schema-orm-DOyiNDCx.js +246 -0
- package/dist/crpc/index.d.ts +105 -0
- package/dist/crpc/index.js +169 -0
- package/dist/customFunctions-C0voKmtx.js +144 -0
- package/dist/error-BZEnI7Sq.js +41 -0
- package/dist/generated-contract-disabled-Cih4eITO.js +50 -0
- package/dist/generated-contract-disabled-D-sOFy92.d.ts +354 -0
- package/dist/http-types-DqJubRPJ.d.ts +292 -0
- package/dist/meta-utils-0Pu0Nrap.js +117 -0
- package/dist/middleware-BUybuv9n.d.ts +34 -0
- package/dist/middleware-C2qTZ3V7.js +84 -0
- package/dist/orm/index.d.ts +17 -0
- package/dist/orm/index.js +10713 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +3 -0
- package/dist/procedure-caller-DtxLmGwA.d.ts +1467 -0
- package/dist/procedure-caller-MWcxhQDv.js +349 -0
- package/dist/query-context-B8o6-8kC.js +1518 -0
- package/dist/query-context-CFZqIvD7.d.ts +42 -0
- package/dist/query-options-Dw7cOyXl.js +121 -0
- package/dist/ratelimit/index.d.ts +269 -0
- package/dist/ratelimit/index.js +856 -0
- package/dist/ratelimit/react/index.d.ts +76 -0
- package/dist/ratelimit/react/index.js +183 -0
- package/dist/react/index.d.ts +1284 -0
- package/dist/react/index.js +2526 -0
- package/dist/rsc/index.d.ts +276 -0
- package/dist/rsc/index.js +233 -0
- package/dist/runtime-CtvJPkur.js +2453 -0
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.js +6 -0
- package/dist/solid/index.d.ts +1221 -0
- package/dist/solid/index.js +2940 -0
- package/dist/transformer-DtDhR3Lc.js +194 -0
- package/dist/types-BTb_4BaU.d.ts +42 -0
- package/dist/types-BiJE7qxR.d.ts +4 -0
- package/dist/types-DEJpkIhw.d.ts +88 -0
- package/dist/types-HhO_R6pd.d.ts +213 -0
- package/dist/validators-B7oIJCAp.js +279 -0
- package/dist/validators-vzRKjBJC.d.ts +88 -0
- package/dist/watcher.mjs +96 -0
- package/dist/where-clause-compiler-DdjN63Io.d.ts +4756 -0
- package/package.json +107 -34
- package/skills/convex/SKILL.md +486 -0
- package/skills/convex/references/features/aggregates.md +353 -0
- package/skills/convex/references/features/auth-admin.md +446 -0
- package/skills/convex/references/features/auth-organizations.md +1141 -0
- package/skills/convex/references/features/auth-polar.md +579 -0
- package/skills/convex/references/features/auth.md +470 -0
- package/skills/convex/references/features/create-plugins.md +153 -0
- package/skills/convex/references/features/http.md +676 -0
- package/skills/convex/references/features/migrations.md +162 -0
- package/skills/convex/references/features/orm.md +1166 -0
- package/skills/convex/references/features/react.md +657 -0
- package/skills/convex/references/features/scheduling.md +267 -0
- package/skills/convex/references/features/testing.md +209 -0
- package/skills/convex/references/setup/auth.md +501 -0
- package/skills/convex/references/setup/biome.md +190 -0
- package/skills/convex/references/setup/doc-guidelines.md +145 -0
- package/skills/convex/references/setup/index.md +761 -0
- package/skills/convex/references/setup/next.md +116 -0
- package/skills/convex/references/setup/react.md +175 -0
- package/skills/convex/references/setup/server.md +473 -0
- package/skills/convex/references/setup/start.md +67 -0
- package/LICENSE +0 -21
- package/README.md +0 -0
- package/dist/index.d.mts +0 -5
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -6
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
# Auth Admin Plugin
|
|
2
|
+
|
|
3
|
+
Role-based admin features: middleware, user management, banning, impersonation, custom permissions.
|
|
4
|
+
|
|
5
|
+
Prerequisites: `setup/auth.md`, `setup/server.md`.
|
|
6
|
+
|
|
7
|
+
See [Better Auth Admin Plugin](https://www.better-auth.com/docs/plugins/admin) for full API reference.
|
|
8
|
+
|
|
9
|
+
## Server Config
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
// convex/functions/auth.ts
|
|
13
|
+
import { admin } from 'better-auth/plugins';
|
|
14
|
+
import { defineAuth } from './generated/auth';
|
|
15
|
+
|
|
16
|
+
export default defineAuth((ctx) => ({
|
|
17
|
+
plugins: [
|
|
18
|
+
admin({
|
|
19
|
+
defaultRole: 'user',
|
|
20
|
+
// adminUserIds: ['user_id_1'], // Always admin regardless of role
|
|
21
|
+
// impersonationSessionDuration: 60 * 60, // 1 hour default
|
|
22
|
+
// defaultBanReason: 'No reason',
|
|
23
|
+
// bannedUserMessage: 'You have been banned',
|
|
24
|
+
}),
|
|
25
|
+
],
|
|
26
|
+
}));
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Admin Assignment via Environment
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# convex/.env
|
|
33
|
+
ADMIN=admin@domain.test,ops@domain.test
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
// convex/functions/auth.ts
|
|
38
|
+
import { defineAuth } from './generated/auth';
|
|
39
|
+
|
|
40
|
+
export default defineAuth((ctx) => ({
|
|
41
|
+
triggers: {
|
|
42
|
+
user: {
|
|
43
|
+
create: {
|
|
44
|
+
before: async (data, triggerCtx) => {
|
|
45
|
+
const env = getEnv();
|
|
46
|
+
const adminEmails = env.ADMIN;
|
|
47
|
+
const role =
|
|
48
|
+
data.role !== 'admin' && adminEmails?.includes(data.email)
|
|
49
|
+
? 'admin'
|
|
50
|
+
: data.role;
|
|
51
|
+
return { data: { ...data, role } };
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
}));
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Client Config
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// src/lib/convex/auth-client.ts
|
|
63
|
+
import { adminClient } from 'better-auth/client/plugins';
|
|
64
|
+
|
|
65
|
+
export const authClient = createAuthClient({
|
|
66
|
+
plugins: [
|
|
67
|
+
adminClient(),
|
|
68
|
+
// ... other plugins
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Schema
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// convex/functions/schema.ts
|
|
77
|
+
import { boolean, convexTable, defineSchema, integer, text } from 'kitcn/orm';
|
|
78
|
+
|
|
79
|
+
export const user = convexTable('user', {
|
|
80
|
+
// ... existing fields
|
|
81
|
+
role: text(), // 'admin' | 'user'
|
|
82
|
+
banned: boolean(),
|
|
83
|
+
banReason: text(),
|
|
84
|
+
banExpires: integer(),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export const session = convexTable('session', {
|
|
88
|
+
// ... existing fields
|
|
89
|
+
impersonatedBy: text(), // Admin user ID during impersonation
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export const tables = { user, session };
|
|
93
|
+
export default defineSchema(tables, { strict: false });
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Access Control
|
|
97
|
+
|
|
98
|
+
### Role Middleware
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
// convex/lib/crpc.ts
|
|
102
|
+
const c = initCRPC
|
|
103
|
+
.meta<{ auth?: 'optional' | 'required'; role?: 'admin' }>()
|
|
104
|
+
.create();
|
|
105
|
+
|
|
106
|
+
const roleMiddleware = c.middleware<object>(({ ctx, meta, next }) => {
|
|
107
|
+
const user = (ctx as { user?: { role?: string | null } }).user;
|
|
108
|
+
if (meta.role === 'admin' && user?.role !== 'admin') {
|
|
109
|
+
throw new CRPCError({
|
|
110
|
+
code: 'FORBIDDEN',
|
|
111
|
+
message: 'Admin access required',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return next({ ctx });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export const authQuery = c.query
|
|
118
|
+
.meta({ auth: 'required' })
|
|
119
|
+
.use(devMiddleware)
|
|
120
|
+
.use(authMiddleware)
|
|
121
|
+
.use(roleMiddleware);
|
|
122
|
+
|
|
123
|
+
export const authMutation = c.mutation
|
|
124
|
+
.meta({ auth: 'required' })
|
|
125
|
+
.use(devMiddleware)
|
|
126
|
+
.use(authMiddleware)
|
|
127
|
+
.use(roleMiddleware)
|
|
128
|
+
.use(ratelimit.middleware());
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Role Guard Helper
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
// convex/lib/auth/role-guard.ts
|
|
135
|
+
import { CRPCError } from 'kitcn/server';
|
|
136
|
+
|
|
137
|
+
export function roleGuard(
|
|
138
|
+
role: 'admin',
|
|
139
|
+
user: { role?: string | null } | null
|
|
140
|
+
) {
|
|
141
|
+
if (!user) {
|
|
142
|
+
throw new CRPCError({ code: 'FORBIDDEN', message: 'Access denied' });
|
|
143
|
+
}
|
|
144
|
+
if (role === 'admin' && user.role !== 'admin') {
|
|
145
|
+
throw new CRPCError({ code: 'FORBIDDEN', message: 'Admin access required' });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Custom Access Control
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
// convex/shared/permissions.ts
|
|
154
|
+
import { createAccessControl } from 'better-auth/plugins/access';
|
|
155
|
+
import { defaultStatements, adminAc } from 'better-auth/plugins/admin/access';
|
|
156
|
+
|
|
157
|
+
const statement = {
|
|
158
|
+
...defaultStatements,
|
|
159
|
+
project: ['create', 'read', 'update', 'delete'],
|
|
160
|
+
} as const;
|
|
161
|
+
|
|
162
|
+
export const ac = createAccessControl(statement);
|
|
163
|
+
|
|
164
|
+
export const admin = ac.newRole({
|
|
165
|
+
...adminAc.statements,
|
|
166
|
+
project: ['create', 'read', 'update', 'delete'],
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
export const user = ac.newRole({
|
|
170
|
+
project: ['create', 'read'],
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Pass to plugins:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
// Server
|
|
178
|
+
admin({ ac, roles: { admin, user } })
|
|
179
|
+
|
|
180
|
+
// Client (src/lib/convex/auth-client.ts)
|
|
181
|
+
adminClient({ ac, roles: { admin, user } })
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Admin Functions
|
|
185
|
+
|
|
186
|
+
### Check Admin Status
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
export const checkUserAdminStatus = authQuery
|
|
190
|
+
.meta({ role: 'admin' })
|
|
191
|
+
.input(z.object({ userId: z.string() }))
|
|
192
|
+
.output(z.object({ isAdmin: z.boolean(), role: z.string().nullish() }))
|
|
193
|
+
.query(async ({ ctx, input }) => {
|
|
194
|
+
const user = await ctx.orm.query.user.findFirstOrThrow({ where: { id: input.userId } });
|
|
195
|
+
return { isAdmin: user.role === 'admin', role: user.role };
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Update User Role
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
export const updateUserRole = authMutation
|
|
203
|
+
.meta({ role: 'admin' })
|
|
204
|
+
.input(z.object({ role: z.enum(['user', 'admin']), userId: z.string() }))
|
|
205
|
+
.output(z.boolean())
|
|
206
|
+
.mutation(async ({ ctx, input }) => {
|
|
207
|
+
if (input.role === 'admin' && !ctx.user.isAdmin) {
|
|
208
|
+
throw new CRPCError({ code: 'FORBIDDEN', message: 'Only admin can promote users to admin' });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const targetUser = await ctx.orm.query.user.findFirstOrThrow({ where: { id: input.userId } });
|
|
212
|
+
|
|
213
|
+
if (targetUser.role === 'admin' && !ctx.user.isAdmin) {
|
|
214
|
+
throw new CRPCError({ code: 'FORBIDDEN', message: 'Cannot modify admin users' });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
await ctx.orm
|
|
218
|
+
.update(userTable)
|
|
219
|
+
.set({ role: input.role.toLowerCase() })
|
|
220
|
+
.where(eq(userTable.id, targetUser.id));
|
|
221
|
+
return true;
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Grant Admin by Email
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
export const grantAdminByEmail = authMutation
|
|
229
|
+
.meta({ role: 'admin' })
|
|
230
|
+
.input(z.object({ email: z.string().email(), role: z.enum(['admin']) }))
|
|
231
|
+
.output(z.object({ success: z.boolean(), userId: z.string().optional() }))
|
|
232
|
+
.mutation(async ({ ctx, input }) => {
|
|
233
|
+
const user = await ctx.orm.query.user.findFirst({ where: { email: input.email } });
|
|
234
|
+
if (!user) return { success: false };
|
|
235
|
+
|
|
236
|
+
await ctx.orm
|
|
237
|
+
.update(userTable)
|
|
238
|
+
.set({ role: input.role.toLowerCase() })
|
|
239
|
+
.where(eq(userTable.id, user.id));
|
|
240
|
+
return { success: true, userId: user.id };
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### List All Users (Paginated)
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
const UserListItemSchema = z.object({
|
|
248
|
+
id: z.string(),
|
|
249
|
+
createdAt: z.date(),
|
|
250
|
+
name: z.string().optional(),
|
|
251
|
+
email: z.string(),
|
|
252
|
+
image: z.string().nullish(),
|
|
253
|
+
role: z.string(),
|
|
254
|
+
isBanned: z.boolean().nullish(),
|
|
255
|
+
banReason: z.string().nullish(),
|
|
256
|
+
banExpiresAt: z.date().nullish(),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
export const getAllUsers = authQuery
|
|
260
|
+
.input(z.object({
|
|
261
|
+
role: z.enum(['all', 'user', 'admin']).optional(),
|
|
262
|
+
search: z.string().optional(),
|
|
263
|
+
}))
|
|
264
|
+
.paginated({ limit: 20, item: UserListItemSchema.nullable() })
|
|
265
|
+
.query(async ({ ctx, input }) => {
|
|
266
|
+
const result = await ctx.orm.query.user.findMany({
|
|
267
|
+
cursor: input.cursor,
|
|
268
|
+
limit: input.limit,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const enrichedPage = result.page
|
|
272
|
+
.map((user) => {
|
|
273
|
+
const userData = {
|
|
274
|
+
...user,
|
|
275
|
+
banExpiresAt: user?.banExpires,
|
|
276
|
+
banReason: user?.banReason,
|
|
277
|
+
email: user?.email || '',
|
|
278
|
+
isBanned: user?.banned,
|
|
279
|
+
role: user?.role || 'user',
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
if (input.search) {
|
|
283
|
+
const searchLower = input.search.toLowerCase();
|
|
284
|
+
if (!(userData.name?.toLowerCase().includes(searchLower) || userData.email.toLowerCase().includes(searchLower))) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (input.role && input.role !== 'all' && userData.role !== input.role) return null;
|
|
289
|
+
|
|
290
|
+
return userData;
|
|
291
|
+
})
|
|
292
|
+
.filter((row): row is NonNullable<typeof row> => row !== null);
|
|
293
|
+
|
|
294
|
+
return { ...result, page: enrichedPage };
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Dashboard Stats
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
export const getDashboardStats = authQuery
|
|
302
|
+
.meta({ role: 'admin' })
|
|
303
|
+
.output(z.object({
|
|
304
|
+
recentUsers: z.array(z.object({ id: z.string(), createdAt: z.date(), image: z.string().nullish(), name: z.string().optional() })),
|
|
305
|
+
totalAdmins: z.number(),
|
|
306
|
+
totalUsers: z.number(),
|
|
307
|
+
userGrowth: z.array(z.object({ count: z.number(), date: z.string() })),
|
|
308
|
+
}))
|
|
309
|
+
.query(async ({ ctx }) => {
|
|
310
|
+
const toRows = <TRow>(result: TRow[] | { page: TRow[] }): TRow[] =>
|
|
311
|
+
Array.isArray(result) ? result : result.page;
|
|
312
|
+
|
|
313
|
+
const recentUsers = toRows(await ctx.orm.query.user.findMany({
|
|
314
|
+
limit: 5, orderBy: { createdAt: 'desc' },
|
|
315
|
+
columns: { id: true, createdAt: true, image: true, name: true },
|
|
316
|
+
}));
|
|
317
|
+
|
|
318
|
+
const usersLast7Days = toRows(await ctx.orm.query.user.findMany({
|
|
319
|
+
where: { createdAt: { gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) } },
|
|
320
|
+
limit: 1000,
|
|
321
|
+
}));
|
|
322
|
+
|
|
323
|
+
const userGrowth: { count: number; date: string }[] = [];
|
|
324
|
+
const now = Date.now();
|
|
325
|
+
const oneDay = 24 * 60 * 60 * 1000;
|
|
326
|
+
for (let i = 6; i >= 0; i--) {
|
|
327
|
+
const date = new Date(now - i * oneDay);
|
|
328
|
+
const startOfDay = new Date(date.setHours(0, 0, 0, 0)).getTime();
|
|
329
|
+
const endOfDay = new Date(date.setHours(23, 59, 59, 999)).getTime();
|
|
330
|
+
userGrowth.push({
|
|
331
|
+
count: usersLast7Days.filter((u) => {
|
|
332
|
+
const t = u.createdAt.getTime();
|
|
333
|
+
return t >= startOfDay && t <= endOfDay;
|
|
334
|
+
}).length,
|
|
335
|
+
date: new Date(startOfDay).toISOString().split('T')[0],
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const sampleUsers = toRows(await ctx.orm.query.user.findMany({ limit: 100 }));
|
|
340
|
+
const adminCount = sampleUsers.filter((u) => u.role === 'admin').length;
|
|
341
|
+
const totalUsers = await ctx.orm.query.user.count();
|
|
342
|
+
const totalAdmins = Math.round((adminCount / sampleUsers.length) * totalUsers);
|
|
343
|
+
|
|
344
|
+
return { recentUsers, totalAdmins, totalUsers, userGrowth };
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Client Usage
|
|
349
|
+
|
|
350
|
+
### Check Admin Status
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
const { data: session } = authClient.useSession();
|
|
354
|
+
const isAdmin = session?.user?.role === 'admin';
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Ban/Unban Users
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
await authClient.admin.banUser({
|
|
361
|
+
userId: 'user_123',
|
|
362
|
+
banReason: 'Violation of terms',
|
|
363
|
+
banExpiresIn: 60 * 60 * 24 * 7, // 7 days
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
await authClient.admin.unbanUser({ userId: 'user_123' });
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Session Management
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
const { data: sessions } = await authClient.admin.listUserSessions({ userId: 'user_123' });
|
|
373
|
+
await authClient.admin.revokeUserSession({ sessionToken: 'session_token' });
|
|
374
|
+
await authClient.admin.revokeUserSessions({ userId: 'user_123' }); // All sessions
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Impersonation
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
await authClient.admin.impersonateUser({ userId: 'user_123' });
|
|
381
|
+
await authClient.admin.stopImpersonating();
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### User Management
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
// Create user
|
|
388
|
+
await authClient.admin.createUser({
|
|
389
|
+
email: 'user@domain.test', password: 'password', name: 'John Doe', role: 'user',
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// List users (with filtering/sorting/pagination)
|
|
393
|
+
const { users, total } = await authClient.admin.listUsers({
|
|
394
|
+
searchValue: 'john', searchField: 'name', limit: 20, offset: 0,
|
|
395
|
+
sortBy: 'createdAt', sortDirection: 'desc',
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Set role
|
|
399
|
+
await authClient.admin.setRole({ userId, role: 'admin' });
|
|
400
|
+
|
|
401
|
+
// Set password
|
|
402
|
+
await authClient.admin.setUserPassword({ userId, newPassword });
|
|
403
|
+
|
|
404
|
+
// Update user
|
|
405
|
+
await authClient.admin.updateUser({ userId, data: { name: 'New Name' } });
|
|
406
|
+
|
|
407
|
+
// Delete user
|
|
408
|
+
await authClient.admin.removeUser({ userId });
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Permission Checking
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
// Server call
|
|
415
|
+
const { success } = await authClient.admin.hasPermission({
|
|
416
|
+
permissions: { project: ['create', 'update'] },
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// Client-side, no server call
|
|
420
|
+
const canDelete = authClient.admin.checkRolePermission({
|
|
421
|
+
role: 'admin',
|
|
422
|
+
permissions: { project: ['delete'] },
|
|
423
|
+
});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## API Reference
|
|
427
|
+
|
|
428
|
+
| Operation | Method | Admin Required |
|
|
429
|
+
|-----------|--------|----------------|
|
|
430
|
+
| Create user | `authClient.admin.createUser` | Yes |
|
|
431
|
+
| List users | `authClient.admin.listUsers` | Yes |
|
|
432
|
+
| Set role | `authClient.admin.setRole` | Yes |
|
|
433
|
+
| Set password | `authClient.admin.setUserPassword` | Yes |
|
|
434
|
+
| Update user | `authClient.admin.updateUser` | Yes |
|
|
435
|
+
| Ban user | `authClient.admin.banUser` | Yes |
|
|
436
|
+
| Unban user | `authClient.admin.unbanUser` | Yes |
|
|
437
|
+
| List sessions | `authClient.admin.listUserSessions` | Yes |
|
|
438
|
+
| Revoke session | `authClient.admin.revokeUserSession` | Yes |
|
|
439
|
+
| Revoke all sessions | `authClient.admin.revokeUserSessions` | Yes |
|
|
440
|
+
| Impersonate | `authClient.admin.impersonateUser` | Yes |
|
|
441
|
+
| Stop impersonating | `authClient.admin.stopImpersonating` | Yes |
|
|
442
|
+
| Remove user | `authClient.admin.removeUser` | Yes |
|
|
443
|
+
| Check permission | `authClient.admin.hasPermission` | No |
|
|
444
|
+
| Check role permission | `authClient.admin.checkRolePermission` | No |
|
|
445
|
+
|
|
446
|
+
Use Convex functions for custom admin operations. Use Better Auth client API for standard operations like user management, banning, and session management.
|