@simple-auth-kit/cli 1.4.3 → 1.5.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.
Files changed (134) hide show
  1. package/lib/copy.ts +1 -1
  2. package/package.json +1 -1
  3. package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  4. package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
  5. package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  6. package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
  7. package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  8. package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  9. package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
  10. package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
  11. package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
  12. package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
  13. package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  14. package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
  15. package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
  16. package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
  17. package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
  18. package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
  19. package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  20. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
  21. package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
  22. package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
  23. package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
  24. package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
  25. package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
  26. package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
  27. package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  28. package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
  29. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  30. package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
  31. package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  32. package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  33. package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  34. package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
  35. package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
  36. package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +56 -21
  37. package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  38. package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +66 -18
  39. package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  40. package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
  41. package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
  42. package/registry/combos/express-prisma/variants/base/src/seed.ts +77 -19
  43. package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
  44. package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +80 -25
  45. package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  46. package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
  47. package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +72 -20
  48. package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  49. package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
  50. package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
  51. package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
  52. package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
  53. package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +108 -28
  54. package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
  55. package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  56. package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  57. package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  58. package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  59. package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
  60. package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
  61. package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  62. package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  63. package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
  64. package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
  65. package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
  66. package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
  67. package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
  68. package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  69. package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
  70. package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
  71. package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
  72. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
  73. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
  74. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  75. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  76. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
  77. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
  78. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
  79. package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
  80. package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  81. package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  82. package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  83. package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  84. package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
  85. package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
  86. package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  87. package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  88. package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  89. package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
  90. package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
  91. package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +58 -27
  92. package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
  93. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
  94. package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  95. package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +97 -26
  96. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  97. package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
  98. package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
  99. package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
  100. package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
  101. package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +77 -19
  102. package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
  103. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +54 -26
  104. package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
  105. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  106. package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  107. package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +103 -28
  108. package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  109. package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
  110. package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
  111. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
  112. package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
  113. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
  114. package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +108 -28
  115. package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
  116. package/registry.json +4 -4
  117. package/simple-auth-kit.ts +42 -11
  118. package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
  119. package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
  120. package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
  121. package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
  122. package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
  123. package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
  124. package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
  125. package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
  126. package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
  127. package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
  128. package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
  129. package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
  130. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
  131. package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
  132. package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
  133. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
  134. package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
@@ -1,11 +1,37 @@
1
- import { ConflictException, Inject, Injectable, NotFoundException } from "@nestjs/common";
2
- import { and, asc, count, desc, eq, ilike, inArray, type SQL } from "drizzle-orm";
1
+ import {
2
+ ConflictException,
3
+ Inject,
4
+ Injectable,
5
+ NotFoundException,
6
+ } from "@nestjs/common";
7
+ import {
8
+ and,
9
+ asc,
10
+ count,
11
+ desc,
12
+ eq,
13
+ ilike,
14
+ inArray,
15
+ type SQL,
16
+ } from "drizzle-orm";
3
17
  import { resolvePermissions } from "@/lib/auth/core/rbac.js";
4
- import { DRIZZLE_DB, type Database } from "./db.js";
5
- import { buildPageMeta, normalizeLimit, normalizePage, type Paginated } from "./pagination.js";
6
- import { PermissionCache } from "./permission-cache.js";
7
- import { permissionRole, permissionUser, permissions, roleUser, roles, users } from "./schema.js";
8
- import { toId, toIdOrNull } from "./id.helper.js";
18
+ import { DRIZZLE_DB, type Database } from "../db.js";
19
+ import {
20
+ buildPageMeta,
21
+ normalizeLimit,
22
+ normalizePage,
23
+ type Paginated,
24
+ } from "../pagination.js";
25
+ import { PermissionCache } from "../permission-cache.js";
26
+ import {
27
+ permissionRole,
28
+ permissionUser,
29
+ permissions,
30
+ roleUser,
31
+ roles,
32
+ users,
33
+ } from "../schema.js";
34
+ import { toId, toIdOrNull } from "../id.helper.js";
9
35
 
10
36
  export interface UserSummary {
11
37
  id: string;
@@ -148,9 +174,15 @@ interface RoleRow {
148
174
  isActive: boolean;
149
175
  }
150
176
 
151
- const asPermissionSummary = (row: PermissionRow): PermissionSummary => ({ ...row, id: row.id.toString() });
177
+ const asPermissionSummary = (row: PermissionRow): PermissionSummary => ({
178
+ ...row,
179
+ id: row.id.toString(),
180
+ });
152
181
 
153
- const asRoleSummary = (row: RoleRow): RoleSummary => ({ ...row, id: row.id.toString() });
182
+ const asRoleSummary = (row: RoleRow): RoleSummary => ({
183
+ ...row,
184
+ id: row.id.toString(),
185
+ });
154
186
 
155
187
  @Injectable()
156
188
  export class RbacRepository {
@@ -177,7 +209,9 @@ export class RbacRepository {
177
209
  * makes deactivating either one enforcement-changing without unpicking any grant; `isDeleted`
178
210
  * on the user themselves takes the whole answer to empty.
179
211
  */
180
- async resolveAuthzContext(userId: string): Promise<{ roles: string[]; permissions: string[] }> {
212
+ async resolveAuthzContext(
213
+ userId: string,
214
+ ): Promise<{ roles: string[]; permissions: string[] }> {
181
215
  const userIdBig = toId(userId);
182
216
  const [user] = await this.db
183
217
  .select({ id: users.id })
@@ -192,21 +226,44 @@ export class RbacRepository {
192
226
  this.db
193
227
  .select({ roleSlug: roles.slug, permissionSlug: permissions.slug })
194
228
  .from(roleUser)
195
- .innerJoin(roles, and(eq(roles.id, roleUser.roleId), eq(roles.isActive, true), eq(roles.isDeleted, false)))
229
+ .innerJoin(
230
+ roles,
231
+ and(
232
+ eq(roles.id, roleUser.roleId),
233
+ eq(roles.isActive, true),
234
+ eq(roles.isDeleted, false),
235
+ ),
236
+ )
196
237
  .leftJoin(permissionRole, eq(permissionRole.roleId, roles.id))
197
- .leftJoin(permissions, and(eq(permissions.id, permissionRole.permissionId), eq(permissions.isActive, true), eq(permissions.isDeleted, false)))
238
+ .leftJoin(
239
+ permissions,
240
+ and(
241
+ eq(permissions.id, permissionRole.permissionId),
242
+ eq(permissions.isActive, true),
243
+ eq(permissions.isDeleted, false),
244
+ ),
245
+ )
198
246
  .where(eq(roleUser.userId, userIdBig)),
199
247
  this.db
200
248
  .select({ slug: permissions.slug })
201
249
  .from(permissionUser)
202
- .innerJoin(permissions, and(eq(permissions.id, permissionUser.permissionId), eq(permissions.isActive, true), eq(permissions.isDeleted, false)))
250
+ .innerJoin(
251
+ permissions,
252
+ and(
253
+ eq(permissions.id, permissionUser.permissionId),
254
+ eq(permissions.isActive, true),
255
+ eq(permissions.isDeleted, false),
256
+ ),
257
+ )
203
258
  .where(eq(permissionUser.userId, userIdBig)),
204
259
  ]);
205
260
 
206
261
  return {
207
262
  roles: [...new Set(roleRows.map((row) => row.roleSlug))].sort(),
208
263
  permissions: resolvePermissions(
209
- roleRows.map((row) => row.permissionSlug).filter((slug): slug is string => slug !== null),
264
+ roleRows
265
+ .map((row) => row.permissionSlug)
266
+ .filter((slug): slug is string => slug !== null),
210
267
  directRows.map((row) => row.slug),
211
268
  ),
212
269
  };
@@ -222,7 +279,8 @@ export class RbacRepository {
222
279
  const limit = normalizeLimit(filter.limit);
223
280
 
224
281
  const conditions: SQL[] = [eq(users.isDeleted, false)];
225
- if (filter.search) conditions.push(ilike(users.email, `%${filter.search}%`));
282
+ if (filter.search)
283
+ conditions.push(ilike(users.email, `%${filter.search}%`));
226
284
  const where = and(...conditions)!;
227
285
 
228
286
  const rows = await this.db
@@ -232,7 +290,10 @@ export class RbacRepository {
232
290
  .orderBy(desc(users.createdAt), desc(users.id))
233
291
  .limit(limit)
234
292
  .offset((page - 1) * limit);
235
- const [{ value: total }] = await this.db.select({ value: count() }).from(users).where(where);
293
+ const [{ value: total }] = await this.db
294
+ .select({ value: count() })
295
+ .from(users)
296
+ .where(where);
236
297
 
237
298
  // One read for the page's role assignments rather than one per user.
238
299
  const assignments = rows.length
@@ -254,7 +315,10 @@ export class RbacRepository {
254
315
  }
255
316
 
256
317
  return {
257
- items: rows.map((row) => ({ ...row, roles: (rolesByUser.get(row.id.toString()) ?? []).sort() })),
318
+ items: rows.map((row) => ({
319
+ ...row,
320
+ roles: (rolesByUser.get(row.id.toString()) ?? []).sort(),
321
+ })),
258
322
  meta: buildPageMeta(page, limit, total),
259
323
  };
260
324
  }
@@ -269,9 +333,16 @@ export class RbacRepository {
269
333
  .limit(1);
270
334
  if (!row) throw new NotFoundException(`user "${userId}" not found`);
271
335
 
272
- const assignments = await this.db.select({ slug: roles.slug }).from(roleUser).innerJoin(roles, eq(roles.id, roleUser.roleId)).where(eq(roleUser.userId, userIdBig));
336
+ const assignments = await this.db
337
+ .select({ slug: roles.slug })
338
+ .from(roleUser)
339
+ .innerJoin(roles, eq(roles.id, roleUser.roleId))
340
+ .where(eq(roleUser.userId, userIdBig));
273
341
 
274
- return toUserSummary({ ...row, roles: assignments.map((a) => a.slug).sort() });
342
+ return toUserSummary({
343
+ ...row,
344
+ roles: assignments.map((a) => a.slug).sort(),
345
+ });
275
346
  }
276
347
 
277
348
  /**
@@ -295,7 +366,11 @@ export class RbacRepository {
295
366
  },
296
367
  actorUserId: string | null,
297
368
  ): Promise<UserSummary> {
298
- const [existing] = await this.db.select({ id: users.id }).from(users).where(eq(users.email, input.email)).limit(1);
369
+ const [existing] = await this.db
370
+ .select({ id: users.id })
371
+ .from(users)
372
+ .where(eq(users.email, input.email))
373
+ .limit(1);
299
374
  if (existing) throw new ConflictException("email already registered");
300
375
 
301
376
  const [user] = await this.db
@@ -320,11 +395,19 @@ export class RbacRepository {
320
395
  const grantedRoles = await this.db
321
396
  .select({ id: roles.id })
322
397
  .from(roles)
323
- .where(and(inArray(roles.slug, input.roles), eq(roles.isActive, true), eq(roles.isDeleted, false)));
398
+ .where(
399
+ and(
400
+ inArray(roles.slug, input.roles),
401
+ eq(roles.isActive, true),
402
+ eq(roles.isDeleted, false),
403
+ ),
404
+ );
324
405
  if (grantedRoles.length > 0) {
325
406
  await this.db
326
407
  .insert(roleUser)
327
- .values(grantedRoles.map((role) => ({ userId: user.id, roleId: role.id })))
408
+ .values(
409
+ grantedRoles.map((role) => ({ userId: user.id, roleId: role.id })),
410
+ )
328
411
  .onConflictDoNothing({ target: [roleUser.userId, roleUser.roleId] });
329
412
  await this.cache.invalidateSubject(user.id.toString());
330
413
  }
@@ -363,7 +446,9 @@ export class RbacRepository {
363
446
  if (!existing) throw new NotFoundException(`user "${userId}" not found`);
364
447
 
365
448
  // Only the fields the caller sent are written; an absent one leaves its column alone.
366
- const changed = Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
449
+ const changed = Object.fromEntries(
450
+ Object.entries(input).filter(([, value]) => value !== undefined),
451
+ );
367
452
  await this.db
368
453
  .update(users)
369
454
  .set({ ...changed, updatedBy: toIdOrNull(actorUserId) })
@@ -376,7 +461,11 @@ export class RbacRepository {
376
461
  * pattern — the row survives for audit purposes, it just stops appearing in listings and can no
377
462
  * longer authenticate (mirrors how `block` already disables login without removing the row).
378
463
  */
379
- async deleteUser(userId: string, actorUserId: string | null, reason?: string): Promise<void> {
464
+ async deleteUser(
465
+ userId: string,
466
+ actorUserId: string | null,
467
+ reason?: string,
468
+ ): Promise<void> {
380
469
  const userIdBig = toId(userId);
381
470
  const [existing] = await this.db
382
471
  .select({ id: users.id })
@@ -387,7 +476,12 @@ export class RbacRepository {
387
476
 
388
477
  await this.db
389
478
  .update(users)
390
- .set({ isDeleted: true, deletedAt: new Date(), deletedBy: toIdOrNull(actorUserId), deletedReason: reason ?? null })
479
+ .set({
480
+ isDeleted: true,
481
+ deletedAt: new Date(),
482
+ deletedBy: toIdOrNull(actorUserId),
483
+ deletedReason: reason ?? null,
484
+ })
391
485
  .where(eq(users.id, userIdBig));
392
486
  await this.cache.invalidateSubject(userId);
393
487
  }
@@ -400,7 +494,12 @@ export class RbacRepository {
400
494
  .select(PERMISSION_COLUMNS)
401
495
  .from(permissions)
402
496
  .where(eq(permissions.isDeleted, false))
403
- .orderBy(asc(permissions.groupOrder), asc(permissions.group), asc(permissions.order), asc(permissions.slug));
497
+ .orderBy(
498
+ asc(permissions.groupOrder),
499
+ asc(permissions.group),
500
+ asc(permissions.order),
501
+ asc(permissions.slug),
502
+ );
404
503
  return rows.map(asPermissionSummary);
405
504
  }
406
505
 
@@ -413,7 +512,10 @@ export class RbacRepository {
413
512
  * Upserted on `slug`, which stays the stable identifier grants and revocations use, so renaming
414
513
  * `name`/`displayName` never breaks a grant.
415
514
  */
416
- async upsertPermission(input: PermissionInput, actorUserId: string | null): Promise<PermissionSummary> {
515
+ async upsertPermission(
516
+ input: PermissionInput,
517
+ actorUserId: string | null,
518
+ ): Promise<PermissionSummary> {
417
519
  const actorId = toIdOrNull(actorUserId);
418
520
  // Only the fields the caller sent are written; an absent one leaves its column alone, so a
419
521
  // partial edit cannot blank out metadata it did not mention.
@@ -444,7 +546,9 @@ export class RbacRepository {
444
546
  // returns none — so the no-op case rewrites the slug with itself.
445
547
  .onConflictDoUpdate({
446
548
  target: permissions.slug,
447
- set: Object.keys(changed).length ? { ...changed, updatedBy: actorId } : { slug: input.slug, updatedBy: actorId },
549
+ set: Object.keys(changed).length
550
+ ? { ...changed, updatedBy: actorId }
551
+ : { slug: input.slug, updatedBy: actorId },
448
552
  })
449
553
  .returning(PERMISSION_COLUMNS);
450
554
 
@@ -454,12 +558,23 @@ export class RbacRepository {
454
558
  }
455
559
 
456
560
  async listRoles(): Promise<RoleSummary[]> {
457
- const rows = await this.db.select(ROLE_COLUMNS).from(roles).where(eq(roles.isDeleted, false)).orderBy(asc(roles.order), asc(roles.slug));
561
+ const rows = await this.db
562
+ .select(ROLE_COLUMNS)
563
+ .from(roles)
564
+ .where(eq(roles.isDeleted, false))
565
+ .orderBy(asc(roles.order), asc(roles.slug));
458
566
  return rows.map(asRoleSummary);
459
567
  }
460
568
 
461
569
  async createRole(
462
- input: { slug: string; name?: string; displayName?: string; description?: string | null; isDefault?: boolean; isActive?: boolean },
570
+ input: {
571
+ slug: string;
572
+ name?: string;
573
+ displayName?: string;
574
+ description?: string | null;
575
+ isDefault?: boolean;
576
+ isActive?: boolean;
577
+ },
463
578
  actorUserId: string | null,
464
579
  ): Promise<RoleSummary> {
465
580
  const actorId = toIdOrNull(actorUserId);
@@ -481,7 +596,13 @@ export class RbacRepository {
481
596
 
482
597
  async updateRole(
483
598
  roleId: string,
484
- input: { name?: string; displayName?: string; description?: string | null; isDefault?: boolean; isActive?: boolean },
599
+ input: {
600
+ name?: string;
601
+ displayName?: string;
602
+ description?: string | null;
603
+ isDefault?: boolean;
604
+ isActive?: boolean;
605
+ },
485
606
  actorUserId: string | null,
486
607
  ): Promise<RoleSummary> {
487
608
  const roleIdBig = toId(roleId);
@@ -492,7 +613,9 @@ export class RbacRepository {
492
613
  .limit(1);
493
614
  if (!existing) throw new NotFoundException("role not found");
494
615
 
495
- const changed = Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
616
+ const changed = Object.fromEntries(
617
+ Object.entries(input).filter(([, value]) => value !== undefined),
618
+ );
496
619
  const [role] = await this.db
497
620
  .update(roles)
498
621
  .set({ ...changed, updatedBy: toIdOrNull(actorUserId) })
@@ -508,7 +631,11 @@ export class RbacRepository {
508
631
  * deleted role simply stops being returned by `listRoles`/`resolveAuthzContext`, rather than
509
632
  * cascading a delete that would silently strip a user's other roles' foreign keys.
510
633
  */
511
- async deleteRole(roleId: string, actorUserId: string | null, reason?: string): Promise<void> {
634
+ async deleteRole(
635
+ roleId: string,
636
+ actorUserId: string | null,
637
+ reason?: string,
638
+ ): Promise<void> {
512
639
  const roleIdBig = toId(roleId);
513
640
  const [existing] = await this.db
514
641
  .select({ id: roles.id })
@@ -519,7 +646,12 @@ export class RbacRepository {
519
646
 
520
647
  await this.db
521
648
  .update(roles)
522
- .set({ isDeleted: true, deletedAt: new Date(), deletedBy: toIdOrNull(actorUserId), deletedReason: reason ?? null })
649
+ .set({
650
+ isDeleted: true,
651
+ deletedAt: new Date(),
652
+ deletedBy: toIdOrNull(actorUserId),
653
+ deletedReason: reason ?? null,
654
+ })
523
655
  .where(eq(roles.id, roleIdBig));
524
656
  await this.cache.invalidatePolicy();
525
657
  }
@@ -530,7 +662,11 @@ export class RbacRepository {
530
662
  * closed-catalog property the routes always had — `@CheckAbility` only accepts slugs from
531
663
  * `rbac.defaults.ts`, so an invented slug cannot gate anything shipped by this library.
532
664
  */
533
- async attachPermissionToRole(roleId: string, permissionSlug: string, actorUserId: string | null): Promise<void> {
665
+ async attachPermissionToRole(
666
+ roleId: string,
667
+ permissionSlug: string,
668
+ actorUserId: string | null,
669
+ ): Promise<void> {
534
670
  const roleIdBig = toId(roleId);
535
671
  const [role] = await this.db
536
672
  .select({ id: roles.id })
@@ -543,27 +679,46 @@ export class RbacRepository {
543
679
  await this.db
544
680
  .insert(permissionRole)
545
681
  .values({ permissionId: permission.id, roleId: roleIdBig })
546
- .onConflictDoNothing({ target: [permissionRole.permissionId, permissionRole.roleId] });
682
+ .onConflictDoNothing({
683
+ target: [permissionRole.permissionId, permissionRole.roleId],
684
+ });
547
685
  await this.cache.invalidatePolicy(); // everyone holding this role is affected
548
686
  }
549
687
 
550
688
  async assignRoleToUser(userId: string, roleSlug: string): Promise<void> {
551
689
  const userIdBig = toId(userId);
552
- const [role] = await this.db.select({ id: roles.id }).from(roles).where(eq(roles.slug, roleSlug)).limit(1);
690
+ const [role] = await this.db
691
+ .select({ id: roles.id })
692
+ .from(roles)
693
+ .where(eq(roles.slug, roleSlug))
694
+ .limit(1);
553
695
  if (!role) throw new NotFoundException(`role "${roleSlug}" not found`);
554
696
 
555
- const [user] = await this.db.select({ id: users.id }).from(users).where(eq(users.id, userIdBig)).limit(1);
697
+ const [user] = await this.db
698
+ .select({ id: users.id })
699
+ .from(users)
700
+ .where(eq(users.id, userIdBig))
701
+ .limit(1);
556
702
  if (!user) throw new NotFoundException(`user "${userId}" not found`);
557
703
 
558
- await this.db.insert(roleUser).values({ userId: userIdBig, roleId: role.id }).onConflictDoNothing({ target: [roleUser.userId, roleUser.roleId] });
704
+ await this.db
705
+ .insert(roleUser)
706
+ .values({ userId: userIdBig, roleId: role.id })
707
+ .onConflictDoNothing({ target: [roleUser.userId, roleUser.roleId] });
559
708
  await this.cache.invalidateSubject(userId);
560
709
  }
561
710
 
562
711
  async revokeRoleFromUser(userId: string, roleSlug: string): Promise<void> {
563
712
  const userIdBig = toId(userId);
564
- const [role] = await this.db.select({ id: roles.id }).from(roles).where(eq(roles.slug, roleSlug)).limit(1);
713
+ const [role] = await this.db
714
+ .select({ id: roles.id })
715
+ .from(roles)
716
+ .where(eq(roles.slug, roleSlug))
717
+ .limit(1);
565
718
  if (!role) return;
566
- await this.db.delete(roleUser).where(and(eq(roleUser.userId, userIdBig), eq(roleUser.roleId, role.id)));
719
+ await this.db
720
+ .delete(roleUser)
721
+ .where(and(eq(roleUser.userId, userIdBig), eq(roleUser.roleId, role.id)));
567
722
  await this.cache.invalidateSubject(userId);
568
723
  }
569
724
 
@@ -579,7 +734,13 @@ export class RbacRepository {
579
734
  const defaults = await this.db
580
735
  .select({ id: roles.id })
581
736
  .from(roles)
582
- .where(and(eq(roles.isDefault, true), eq(roles.isActive, true), eq(roles.isDeleted, false)));
737
+ .where(
738
+ and(
739
+ eq(roles.isDefault, true),
740
+ eq(roles.isActive, true),
741
+ eq(roles.isDeleted, false),
742
+ ),
743
+ );
583
744
  if (!defaults.length) return;
584
745
  await this.db
585
746
  .insert(roleUser)
@@ -589,25 +750,48 @@ export class RbacRepository {
589
750
  }
590
751
 
591
752
  async hasAnyRole(userId: bigint): Promise<boolean> {
592
- const [row] = await this.db.select({ value: count() }).from(roleUser).where(eq(roleUser.userId, userId));
753
+ const [row] = await this.db
754
+ .select({ value: count() })
755
+ .from(roleUser)
756
+ .where(eq(roleUser.userId, userId));
593
757
  return (row?.value ?? 0) > 0;
594
758
  }
595
759
 
596
- async grantPermissionToUser(userId: string, permissionSlug: string, actorUserId: string | null): Promise<void> {
760
+ async grantPermissionToUser(
761
+ userId: string,
762
+ permissionSlug: string,
763
+ actorUserId: string | null,
764
+ ): Promise<void> {
597
765
  const userIdBig = toId(userId);
598
766
  const permission = await this.ensurePermission(permissionSlug, actorUserId);
599
767
  await this.db
600
768
  .insert(permissionUser)
601
769
  .values({ userId: userIdBig, permissionId: permission.id })
602
- .onConflictDoNothing({ target: [permissionUser.userId, permissionUser.permissionId] });
770
+ .onConflictDoNothing({
771
+ target: [permissionUser.userId, permissionUser.permissionId],
772
+ });
603
773
  await this.cache.invalidateSubject(userId);
604
774
  }
605
775
 
606
- async revokePermissionFromUser(userId: string, permissionSlug: string): Promise<void> {
776
+ async revokePermissionFromUser(
777
+ userId: string,
778
+ permissionSlug: string,
779
+ ): Promise<void> {
607
780
  const userIdBig = toId(userId);
608
- const [permission] = await this.db.select({ id: permissions.id }).from(permissions).where(eq(permissions.slug, permissionSlug)).limit(1);
781
+ const [permission] = await this.db
782
+ .select({ id: permissions.id })
783
+ .from(permissions)
784
+ .where(eq(permissions.slug, permissionSlug))
785
+ .limit(1);
609
786
  if (!permission) return;
610
- await this.db.delete(permissionUser).where(and(eq(permissionUser.userId, userIdBig), eq(permissionUser.permissionId, permission.id)));
787
+ await this.db
788
+ .delete(permissionUser)
789
+ .where(
790
+ and(
791
+ eq(permissionUser.userId, userIdBig),
792
+ eq(permissionUser.permissionId, permission.id),
793
+ ),
794
+ );
611
795
  await this.cache.invalidateSubject(userId);
612
796
  }
613
797
 
@@ -620,13 +804,27 @@ export class RbacRepository {
620
804
  * Creates the row for a slug granted before anyone defined it; leaves an existing definition
621
805
  * alone. Returns bigint directly: both callers feed the id straight into another database call.
622
806
  */
623
- private async ensurePermission(slug: string, actorUserId: string | null): Promise<{ id: bigint }> {
624
- const [existing] = await this.db.select({ id: permissions.id }).from(permissions).where(eq(permissions.slug, slug)).limit(1);
807
+ private async ensurePermission(
808
+ slug: string,
809
+ actorUserId: string | null,
810
+ ): Promise<{ id: bigint }> {
811
+ const [existing] = await this.db
812
+ .select({ id: permissions.id })
813
+ .from(permissions)
814
+ .where(eq(permissions.slug, slug))
815
+ .limit(1);
625
816
  if (existing) return existing;
626
817
  const actorId = toIdOrNull(actorUserId);
627
818
  const [created] = await this.db
628
819
  .insert(permissions)
629
- .values({ slug, name: slug, displayName: slug, group: "Custom", createdBy: actorId, updatedBy: actorId })
820
+ .values({
821
+ slug,
822
+ name: slug,
823
+ displayName: slug,
824
+ group: "Custom",
825
+ createdBy: actorId,
826
+ updatedBy: actorId,
827
+ })
630
828
  .returning({ id: permissions.id });
631
829
  return created;
632
830
  }