@simple-auth-kit/cli 1.4.2 → 1.5.0
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/package.json +1 -1
- package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
- package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
- package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
- package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
- package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
- package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
- package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
- package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
- package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
- package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
- package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
- package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
- package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
- package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
- package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
- package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
- package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +57 -22
- package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +67 -19
- package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
- package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
- package/registry/combos/express-prisma/variants/base/src/seed.ts +78 -20
- package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
- package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +81 -26
- package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +73 -21
- package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
- package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
- package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
- package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +109 -29
- package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
- package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
- package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
- package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
- package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
- package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
- package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
- package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
- package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
- package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +59 -28
- package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
- package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +98 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
- package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +78 -20
- package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +55 -27
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +104 -29
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
- package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +109 -29
- package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
- package/registry.json +6 -4
- package/simple-auth-kit.ts +582 -124
- package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
- package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
- package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
- package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
- package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
- package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
- package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
- package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
- package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
- package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
- package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
|
@@ -1,11 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
and,
|
|
3
|
+
asc,
|
|
4
|
+
count,
|
|
5
|
+
desc,
|
|
6
|
+
eq,
|
|
7
|
+
ilike,
|
|
8
|
+
inArray,
|
|
9
|
+
type SQL,
|
|
10
|
+
} from "drizzle-orm";
|
|
2
11
|
import { resolvePermissions } from "@/lib/auth/core/rbac.js";
|
|
3
|
-
import type { Database } from "
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
import type { Database } from "../db.js";
|
|
13
|
+
import {
|
|
14
|
+
buildPageMeta,
|
|
15
|
+
normalizeLimit,
|
|
16
|
+
normalizePage,
|
|
17
|
+
type Paginated,
|
|
18
|
+
} from "../pagination.js";
|
|
19
|
+
import { PermissionCache } from "../permission-cache.js";
|
|
20
|
+
import {
|
|
21
|
+
permissionRole,
|
|
22
|
+
permissionUser,
|
|
23
|
+
permissions,
|
|
24
|
+
roleUser,
|
|
25
|
+
roles,
|
|
26
|
+
users,
|
|
27
|
+
} from "../schema.js";
|
|
28
|
+
import { HttpError } from "../http-error.js";
|
|
29
|
+
import { toId, toIdOrNull } from "../id.helper.js";
|
|
9
30
|
|
|
10
31
|
/**
|
|
11
32
|
* Every column the `users` table has, except the two secrets (`passwordHash`, `twoFactorSecret`
|
|
@@ -149,9 +170,15 @@ interface RoleRow {
|
|
|
149
170
|
isActive: boolean;
|
|
150
171
|
}
|
|
151
172
|
|
|
152
|
-
const asPermissionSummary = (row: PermissionRow): PermissionSummary => ({
|
|
173
|
+
const asPermissionSummary = (row: PermissionRow): PermissionSummary => ({
|
|
174
|
+
...row,
|
|
175
|
+
id: row.id.toString(),
|
|
176
|
+
});
|
|
153
177
|
|
|
154
|
-
const asRoleSummary = (row: RoleRow): RoleSummary => ({
|
|
178
|
+
const asRoleSummary = (row: RoleRow): RoleSummary => ({
|
|
179
|
+
...row,
|
|
180
|
+
id: row.id.toString(),
|
|
181
|
+
});
|
|
155
182
|
|
|
156
183
|
export class RbacRepository {
|
|
157
184
|
constructor(
|
|
@@ -177,7 +204,9 @@ export class RbacRepository {
|
|
|
177
204
|
* makes deactivating either one enforcement-changing without unpicking any grant; `isDeleted`
|
|
178
205
|
* on the user themselves takes the whole answer to empty.
|
|
179
206
|
*/
|
|
180
|
-
async resolveAuthzContext(
|
|
207
|
+
async resolveAuthzContext(
|
|
208
|
+
userId: string,
|
|
209
|
+
): Promise<{ roles: string[]; permissions: string[] }> {
|
|
181
210
|
const userIdBig = toId(userId);
|
|
182
211
|
const [user] = await this.db
|
|
183
212
|
.select({ id: users.id })
|
|
@@ -192,21 +221,44 @@ export class RbacRepository {
|
|
|
192
221
|
this.db
|
|
193
222
|
.select({ roleSlug: roles.slug, permissionSlug: permissions.slug })
|
|
194
223
|
.from(roleUser)
|
|
195
|
-
.innerJoin(
|
|
224
|
+
.innerJoin(
|
|
225
|
+
roles,
|
|
226
|
+
and(
|
|
227
|
+
eq(roles.id, roleUser.roleId),
|
|
228
|
+
eq(roles.isActive, true),
|
|
229
|
+
eq(roles.isDeleted, false),
|
|
230
|
+
),
|
|
231
|
+
)
|
|
196
232
|
.leftJoin(permissionRole, eq(permissionRole.roleId, roles.id))
|
|
197
|
-
.leftJoin(
|
|
233
|
+
.leftJoin(
|
|
234
|
+
permissions,
|
|
235
|
+
and(
|
|
236
|
+
eq(permissions.id, permissionRole.permissionId),
|
|
237
|
+
eq(permissions.isActive, true),
|
|
238
|
+
eq(permissions.isDeleted, false),
|
|
239
|
+
),
|
|
240
|
+
)
|
|
198
241
|
.where(eq(roleUser.userId, userIdBig)),
|
|
199
242
|
this.db
|
|
200
243
|
.select({ slug: permissions.slug })
|
|
201
244
|
.from(permissionUser)
|
|
202
|
-
.innerJoin(
|
|
245
|
+
.innerJoin(
|
|
246
|
+
permissions,
|
|
247
|
+
and(
|
|
248
|
+
eq(permissions.id, permissionUser.permissionId),
|
|
249
|
+
eq(permissions.isActive, true),
|
|
250
|
+
eq(permissions.isDeleted, false),
|
|
251
|
+
),
|
|
252
|
+
)
|
|
203
253
|
.where(eq(permissionUser.userId, userIdBig)),
|
|
204
254
|
]);
|
|
205
255
|
|
|
206
256
|
return {
|
|
207
257
|
roles: [...new Set(roleRows.map((row) => row.roleSlug))].sort(),
|
|
208
258
|
permissions: resolvePermissions(
|
|
209
|
-
roleRows
|
|
259
|
+
roleRows
|
|
260
|
+
.map((row) => row.permissionSlug)
|
|
261
|
+
.filter((slug): slug is string => slug !== null),
|
|
210
262
|
directRows.map((row) => row.slug),
|
|
211
263
|
),
|
|
212
264
|
};
|
|
@@ -222,7 +274,8 @@ export class RbacRepository {
|
|
|
222
274
|
const limit = normalizeLimit(filter.limit);
|
|
223
275
|
|
|
224
276
|
const conditions: SQL[] = [eq(users.isDeleted, false)];
|
|
225
|
-
if (filter.search)
|
|
277
|
+
if (filter.search)
|
|
278
|
+
conditions.push(ilike(users.email, `%${filter.search}%`));
|
|
226
279
|
const where = and(...conditions)!;
|
|
227
280
|
|
|
228
281
|
const rows = await this.db
|
|
@@ -232,7 +285,10 @@ export class RbacRepository {
|
|
|
232
285
|
.orderBy(desc(users.createdAt), desc(users.id))
|
|
233
286
|
.limit(limit)
|
|
234
287
|
.offset((page - 1) * limit);
|
|
235
|
-
const [{ value: total }] = await this.db
|
|
288
|
+
const [{ value: total }] = await this.db
|
|
289
|
+
.select({ value: count() })
|
|
290
|
+
.from(users)
|
|
291
|
+
.where(where);
|
|
236
292
|
|
|
237
293
|
// One read for the page's role assignments rather than one per user.
|
|
238
294
|
const assignments = rows.length
|
|
@@ -254,7 +310,10 @@ export class RbacRepository {
|
|
|
254
310
|
}
|
|
255
311
|
|
|
256
312
|
return {
|
|
257
|
-
items: rows.map((row) => ({
|
|
313
|
+
items: rows.map((row) => ({
|
|
314
|
+
...row,
|
|
315
|
+
roles: (rolesByUser.get(row.id.toString()) ?? []).sort(),
|
|
316
|
+
})),
|
|
258
317
|
meta: buildPageMeta(page, limit, total),
|
|
259
318
|
};
|
|
260
319
|
}
|
|
@@ -269,7 +328,11 @@ export class RbacRepository {
|
|
|
269
328
|
.limit(1);
|
|
270
329
|
if (!row) throw new HttpError(404, `user "${userId}" not found`);
|
|
271
330
|
|
|
272
|
-
const roleRows = await this.db
|
|
331
|
+
const roleRows = await this.db
|
|
332
|
+
.select({ slug: roles.slug })
|
|
333
|
+
.from(roleUser)
|
|
334
|
+
.innerJoin(roles, eq(roles.id, roleUser.roleId))
|
|
335
|
+
.where(eq(roleUser.userId, userIdBig));
|
|
273
336
|
return toUserSummary({ ...row, roles: roleRows.map((r) => r.slug).sort() });
|
|
274
337
|
}
|
|
275
338
|
|
|
@@ -290,7 +353,11 @@ export class RbacRepository {
|
|
|
290
353
|
},
|
|
291
354
|
actorUserId: string | null,
|
|
292
355
|
): Promise<UserSummary> {
|
|
293
|
-
const [existing] = await this.db
|
|
356
|
+
const [existing] = await this.db
|
|
357
|
+
.select({ id: users.id })
|
|
358
|
+
.from(users)
|
|
359
|
+
.where(eq(users.email, input.email))
|
|
360
|
+
.limit(1);
|
|
294
361
|
if (existing) throw new HttpError(409, "email already registered");
|
|
295
362
|
|
|
296
363
|
const [user] = await this.db
|
|
@@ -311,7 +378,13 @@ export class RbacRepository {
|
|
|
311
378
|
const granted = await this.db
|
|
312
379
|
.select({ id: roles.id })
|
|
313
380
|
.from(roles)
|
|
314
|
-
.where(
|
|
381
|
+
.where(
|
|
382
|
+
and(
|
|
383
|
+
inArray(roles.slug, input.roles),
|
|
384
|
+
eq(roles.isActive, true),
|
|
385
|
+
eq(roles.isDeleted, false),
|
|
386
|
+
),
|
|
387
|
+
);
|
|
315
388
|
if (granted.length) {
|
|
316
389
|
await this.db
|
|
317
390
|
.insert(roleUser)
|
|
@@ -332,7 +405,14 @@ export class RbacRepository {
|
|
|
332
405
|
*/
|
|
333
406
|
async updateUser(
|
|
334
407
|
userId: string,
|
|
335
|
-
input: {
|
|
408
|
+
input: {
|
|
409
|
+
firstName?: string | null;
|
|
410
|
+
lastName?: string | null;
|
|
411
|
+
displayName?: string | null;
|
|
412
|
+
phone?: string | null;
|
|
413
|
+
username?: string | null;
|
|
414
|
+
photo?: string | null;
|
|
415
|
+
},
|
|
336
416
|
actorUserId: string | null,
|
|
337
417
|
): Promise<UserSummary> {
|
|
338
418
|
const userIdBig = toId(userId);
|
|
@@ -355,7 +435,11 @@ export class RbacRepository {
|
|
|
355
435
|
* pattern — the row survives for audit purposes, it just stops appearing in listings and can no
|
|
356
436
|
* longer authenticate (mirrors how `block` already disables login without removing the row).
|
|
357
437
|
*/
|
|
358
|
-
async deleteUser(
|
|
438
|
+
async deleteUser(
|
|
439
|
+
userId: string,
|
|
440
|
+
actorUserId: string | null,
|
|
441
|
+
reason?: string,
|
|
442
|
+
): Promise<void> {
|
|
359
443
|
const userIdBig = toId(userId);
|
|
360
444
|
const [existing] = await this.db
|
|
361
445
|
.select({ id: users.id })
|
|
@@ -366,14 +450,24 @@ export class RbacRepository {
|
|
|
366
450
|
|
|
367
451
|
await this.db
|
|
368
452
|
.update(users)
|
|
369
|
-
.set({
|
|
453
|
+
.set({
|
|
454
|
+
isDeleted: true,
|
|
455
|
+
deletedAt: new Date(),
|
|
456
|
+
deletedBy: toIdOrNull(actorUserId),
|
|
457
|
+
deletedReason: reason ?? null,
|
|
458
|
+
})
|
|
370
459
|
.where(eq(users.id, userIdBig));
|
|
371
460
|
await this.cache.invalidateSubject(userId);
|
|
372
461
|
}
|
|
373
462
|
|
|
374
463
|
async updateRole(
|
|
375
464
|
roleId: string,
|
|
376
|
-
input: {
|
|
465
|
+
input: {
|
|
466
|
+
name?: string;
|
|
467
|
+
displayName?: string;
|
|
468
|
+
description?: string | null;
|
|
469
|
+
isActive?: boolean;
|
|
470
|
+
},
|
|
377
471
|
actorUserId: string | null,
|
|
378
472
|
): Promise<RoleSummary> {
|
|
379
473
|
const roleIdBig = toId(roleId);
|
|
@@ -399,7 +493,11 @@ export class RbacRepository {
|
|
|
399
493
|
* deleted role simply stops being returned by `listRoles`/`resolveAuthzContext`, rather than
|
|
400
494
|
* cascading a delete that would silently strip a user's other roles' foreign keys.
|
|
401
495
|
*/
|
|
402
|
-
async deleteRole(
|
|
496
|
+
async deleteRole(
|
|
497
|
+
roleId: string,
|
|
498
|
+
actorUserId: string | null,
|
|
499
|
+
reason?: string,
|
|
500
|
+
): Promise<void> {
|
|
403
501
|
const roleIdBig = toId(roleId);
|
|
404
502
|
const [existing] = await this.db
|
|
405
503
|
.select({ id: roles.id })
|
|
@@ -410,7 +508,12 @@ export class RbacRepository {
|
|
|
410
508
|
|
|
411
509
|
await this.db
|
|
412
510
|
.update(roles)
|
|
413
|
-
.set({
|
|
511
|
+
.set({
|
|
512
|
+
isDeleted: true,
|
|
513
|
+
deletedAt: new Date(),
|
|
514
|
+
deletedBy: toIdOrNull(actorUserId),
|
|
515
|
+
deletedReason: reason ?? null,
|
|
516
|
+
})
|
|
414
517
|
.where(eq(roles.id, roleIdBig));
|
|
415
518
|
await this.cache.invalidatePolicy();
|
|
416
519
|
}
|
|
@@ -423,7 +526,12 @@ export class RbacRepository {
|
|
|
423
526
|
.select(PERMISSION_COLUMNS)
|
|
424
527
|
.from(permissions)
|
|
425
528
|
.where(eq(permissions.isDeleted, false))
|
|
426
|
-
.orderBy(
|
|
529
|
+
.orderBy(
|
|
530
|
+
asc(permissions.groupOrder),
|
|
531
|
+
asc(permissions.group),
|
|
532
|
+
asc(permissions.order),
|
|
533
|
+
asc(permissions.slug),
|
|
534
|
+
);
|
|
427
535
|
return rows.map(asPermissionSummary);
|
|
428
536
|
}
|
|
429
537
|
|
|
@@ -436,7 +544,10 @@ export class RbacRepository {
|
|
|
436
544
|
* Upserted on `slug`, which stays the stable identifier grants and revocations use, so renaming
|
|
437
545
|
* `name`/`displayName` never breaks a grant.
|
|
438
546
|
*/
|
|
439
|
-
async upsertPermission(
|
|
547
|
+
async upsertPermission(
|
|
548
|
+
input: PermissionInput,
|
|
549
|
+
actorUserId: string | null,
|
|
550
|
+
): Promise<PermissionSummary> {
|
|
440
551
|
const actorId = toIdOrNull(actorUserId);
|
|
441
552
|
// Only the fields the caller sent are written; an absent one leaves its column alone, so a
|
|
442
553
|
// partial edit cannot blank out metadata it did not mention.
|
|
@@ -467,7 +578,9 @@ export class RbacRepository {
|
|
|
467
578
|
// returns none — so the no-op case rewrites the slug with itself.
|
|
468
579
|
.onConflictDoUpdate({
|
|
469
580
|
target: permissions.slug,
|
|
470
|
-
set: Object.keys(changed).length
|
|
581
|
+
set: Object.keys(changed).length
|
|
582
|
+
? { ...changed, updatedBy: actorId }
|
|
583
|
+
: { slug: input.slug, updatedBy: actorId },
|
|
471
584
|
})
|
|
472
585
|
.returning(PERMISSION_COLUMNS);
|
|
473
586
|
|
|
@@ -477,11 +590,23 @@ export class RbacRepository {
|
|
|
477
590
|
}
|
|
478
591
|
|
|
479
592
|
async listRoles(): Promise<RoleSummary[]> {
|
|
480
|
-
const rows = await this.db
|
|
593
|
+
const rows = await this.db
|
|
594
|
+
.select(ROLE_COLUMNS)
|
|
595
|
+
.from(roles)
|
|
596
|
+
.where(eq(roles.isDeleted, false))
|
|
597
|
+
.orderBy(asc(roles.order), asc(roles.slug));
|
|
481
598
|
return rows.map(asRoleSummary);
|
|
482
599
|
}
|
|
483
600
|
|
|
484
|
-
async createRole(
|
|
601
|
+
async createRole(
|
|
602
|
+
input: {
|
|
603
|
+
slug: string;
|
|
604
|
+
name?: string;
|
|
605
|
+
displayName?: string;
|
|
606
|
+
description?: string | null;
|
|
607
|
+
},
|
|
608
|
+
actorUserId: string | null,
|
|
609
|
+
): Promise<RoleSummary> {
|
|
485
610
|
const actorId = toIdOrNull(actorUserId);
|
|
486
611
|
const [role] = await this.db
|
|
487
612
|
.insert(roles)
|
|
@@ -503,7 +628,11 @@ export class RbacRepository {
|
|
|
503
628
|
* closed-catalog property the routes always had — `@CheckAbility` only accepts slugs from
|
|
504
629
|
* `rbac.defaults.ts`, so an invented slug cannot gate anything shipped by this library.
|
|
505
630
|
*/
|
|
506
|
-
async attachPermissionToRole(
|
|
631
|
+
async attachPermissionToRole(
|
|
632
|
+
roleId: string,
|
|
633
|
+
permissionSlug: string,
|
|
634
|
+
actorUserId: string | null,
|
|
635
|
+
): Promise<void> {
|
|
507
636
|
const roleIdBig = toId(roleId);
|
|
508
637
|
const [role] = await this.db
|
|
509
638
|
.select({ id: roles.id })
|
|
@@ -516,27 +645,46 @@ export class RbacRepository {
|
|
|
516
645
|
await this.db
|
|
517
646
|
.insert(permissionRole)
|
|
518
647
|
.values({ permissionId: permission.id, roleId: roleIdBig })
|
|
519
|
-
.onConflictDoNothing({
|
|
648
|
+
.onConflictDoNothing({
|
|
649
|
+
target: [permissionRole.permissionId, permissionRole.roleId],
|
|
650
|
+
});
|
|
520
651
|
await this.cache.invalidatePolicy(); // everyone holding this role is affected
|
|
521
652
|
}
|
|
522
653
|
|
|
523
654
|
async assignRoleToUser(userId: string, roleSlug: string): Promise<void> {
|
|
524
655
|
const userIdBig = toId(userId);
|
|
525
|
-
const [role] = await this.db
|
|
656
|
+
const [role] = await this.db
|
|
657
|
+
.select({ id: roles.id })
|
|
658
|
+
.from(roles)
|
|
659
|
+
.where(eq(roles.slug, roleSlug))
|
|
660
|
+
.limit(1);
|
|
526
661
|
if (!role) throw new HttpError(404, `role "${roleSlug}" not found`);
|
|
527
662
|
|
|
528
|
-
const [user] = await this.db
|
|
663
|
+
const [user] = await this.db
|
|
664
|
+
.select({ id: users.id })
|
|
665
|
+
.from(users)
|
|
666
|
+
.where(eq(users.id, userIdBig))
|
|
667
|
+
.limit(1);
|
|
529
668
|
if (!user) throw new HttpError(404, `user "${userId}" not found`);
|
|
530
669
|
|
|
531
|
-
await this.db
|
|
670
|
+
await this.db
|
|
671
|
+
.insert(roleUser)
|
|
672
|
+
.values({ userId: userIdBig, roleId: role.id })
|
|
673
|
+
.onConflictDoNothing({ target: [roleUser.userId, roleUser.roleId] });
|
|
532
674
|
await this.cache.invalidateSubject(userId);
|
|
533
675
|
}
|
|
534
676
|
|
|
535
677
|
async revokeRoleFromUser(userId: string, roleSlug: string): Promise<void> {
|
|
536
678
|
const userIdBig = toId(userId);
|
|
537
|
-
const [role] = await this.db
|
|
679
|
+
const [role] = await this.db
|
|
680
|
+
.select({ id: roles.id })
|
|
681
|
+
.from(roles)
|
|
682
|
+
.where(eq(roles.slug, roleSlug))
|
|
683
|
+
.limit(1);
|
|
538
684
|
if (!role) return;
|
|
539
|
-
await this.db
|
|
685
|
+
await this.db
|
|
686
|
+
.delete(roleUser)
|
|
687
|
+
.where(and(eq(roleUser.userId, userIdBig), eq(roleUser.roleId, role.id)));
|
|
540
688
|
await this.cache.invalidateSubject(userId);
|
|
541
689
|
}
|
|
542
690
|
|
|
@@ -552,7 +700,13 @@ export class RbacRepository {
|
|
|
552
700
|
const defaults = await this.db
|
|
553
701
|
.select({ id: roles.id })
|
|
554
702
|
.from(roles)
|
|
555
|
-
.where(
|
|
703
|
+
.where(
|
|
704
|
+
and(
|
|
705
|
+
eq(roles.isDefault, true),
|
|
706
|
+
eq(roles.isActive, true),
|
|
707
|
+
eq(roles.isDeleted, false),
|
|
708
|
+
),
|
|
709
|
+
);
|
|
556
710
|
if (!defaults.length) return;
|
|
557
711
|
await this.db
|
|
558
712
|
.insert(roleUser)
|
|
@@ -562,25 +716,48 @@ export class RbacRepository {
|
|
|
562
716
|
}
|
|
563
717
|
|
|
564
718
|
async hasAnyRole(userId: bigint): Promise<boolean> {
|
|
565
|
-
const [row] = await this.db
|
|
719
|
+
const [row] = await this.db
|
|
720
|
+
.select({ value: count() })
|
|
721
|
+
.from(roleUser)
|
|
722
|
+
.where(eq(roleUser.userId, userId));
|
|
566
723
|
return (row?.value ?? 0) > 0;
|
|
567
724
|
}
|
|
568
725
|
|
|
569
|
-
async grantPermissionToUser(
|
|
726
|
+
async grantPermissionToUser(
|
|
727
|
+
userId: string,
|
|
728
|
+
permissionSlug: string,
|
|
729
|
+
actorUserId: string | null,
|
|
730
|
+
): Promise<void> {
|
|
570
731
|
const userIdBig = toId(userId);
|
|
571
732
|
const permission = await this.ensurePermission(permissionSlug, actorUserId);
|
|
572
733
|
await this.db
|
|
573
734
|
.insert(permissionUser)
|
|
574
735
|
.values({ userId: userIdBig, permissionId: permission.id })
|
|
575
|
-
.onConflictDoNothing({
|
|
736
|
+
.onConflictDoNothing({
|
|
737
|
+
target: [permissionUser.userId, permissionUser.permissionId],
|
|
738
|
+
});
|
|
576
739
|
await this.cache.invalidateSubject(userId);
|
|
577
740
|
}
|
|
578
741
|
|
|
579
|
-
async revokePermissionFromUser(
|
|
742
|
+
async revokePermissionFromUser(
|
|
743
|
+
userId: string,
|
|
744
|
+
permissionSlug: string,
|
|
745
|
+
): Promise<void> {
|
|
580
746
|
const userIdBig = toId(userId);
|
|
581
|
-
const [permission] = await this.db
|
|
747
|
+
const [permission] = await this.db
|
|
748
|
+
.select({ id: permissions.id })
|
|
749
|
+
.from(permissions)
|
|
750
|
+
.where(eq(permissions.slug, permissionSlug))
|
|
751
|
+
.limit(1);
|
|
582
752
|
if (!permission) return;
|
|
583
|
-
await this.db
|
|
753
|
+
await this.db
|
|
754
|
+
.delete(permissionUser)
|
|
755
|
+
.where(
|
|
756
|
+
and(
|
|
757
|
+
eq(permissionUser.userId, userIdBig),
|
|
758
|
+
eq(permissionUser.permissionId, permission.id),
|
|
759
|
+
),
|
|
760
|
+
);
|
|
584
761
|
await this.cache.invalidateSubject(userId);
|
|
585
762
|
}
|
|
586
763
|
|
|
@@ -593,13 +770,27 @@ export class RbacRepository {
|
|
|
593
770
|
* Creates the row for a slug granted before anyone defined it; leaves an existing definition
|
|
594
771
|
* alone. Returns bigint directly: both callers feed the id straight into another database call.
|
|
595
772
|
*/
|
|
596
|
-
private async ensurePermission(
|
|
597
|
-
|
|
773
|
+
private async ensurePermission(
|
|
774
|
+
slug: string,
|
|
775
|
+
actorUserId: string | null,
|
|
776
|
+
): Promise<{ id: bigint }> {
|
|
777
|
+
const [existing] = await this.db
|
|
778
|
+
.select({ id: permissions.id })
|
|
779
|
+
.from(permissions)
|
|
780
|
+
.where(eq(permissions.slug, slug))
|
|
781
|
+
.limit(1);
|
|
598
782
|
if (existing) return existing;
|
|
599
783
|
const actorId = toIdOrNull(actorUserId);
|
|
600
784
|
const [created] = await this.db
|
|
601
785
|
.insert(permissions)
|
|
602
|
-
.values({
|
|
786
|
+
.values({
|
|
787
|
+
slug,
|
|
788
|
+
name: slug,
|
|
789
|
+
displayName: slug,
|
|
790
|
+
group: "Custom",
|
|
791
|
+
createdBy: actorId,
|
|
792
|
+
updatedBy: actorId,
|
|
793
|
+
})
|
|
603
794
|
.returning({ id: permissions.id });
|
|
604
795
|
return created;
|
|
605
796
|
}
|