@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,32 @@
1
- import { and, asc, count, desc, eq, ilike, inArray, type SQL } from "drizzle-orm";
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 "./db.js";
4
- import { buildPageMeta, normalizeLimit, normalizePage, type Paginated } from "./pagination.js";
5
- import { PermissionCache } from "./permission-cache.js";
6
- import { permissionRole, permissionUser, permissions, roleUser, roles, users } from "./schema.js";
7
- import { HttpError } from "./http-error.js";
8
- import { toId, toIdOrNull } from "./id.helper.js";
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 => ({ ...row, id: row.id.toString() });
173
+ const asPermissionSummary = (row: PermissionRow): PermissionSummary => ({
174
+ ...row,
175
+ id: row.id.toString(),
176
+ });
153
177
 
154
- const asRoleSummary = (row: RoleRow): RoleSummary => ({ ...row, id: row.id.toString() });
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(userId: string): Promise<{ roles: string[]; permissions: string[] }> {
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(roles, and(eq(roles.id, roleUser.roleId), eq(roles.isActive, true), eq(roles.isDeleted, false)))
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(permissions, and(eq(permissions.id, permissionRole.permissionId), eq(permissions.isActive, true), eq(permissions.isDeleted, false)))
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(permissions, and(eq(permissions.id, permissionUser.permissionId), eq(permissions.isActive, true), eq(permissions.isDeleted, false)))
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.map((row) => row.permissionSlug).filter((slug): slug is string => slug !== null),
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) conditions.push(ilike(users.email, `%${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.select({ value: count() }).from(users).where(where);
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) => ({ ...row, roles: (rolesByUser.get(row.id.toString()) ?? []).sort() })),
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.select({ slug: roles.slug }).from(roleUser).innerJoin(roles, eq(roles.id, roleUser.roleId)).where(eq(roleUser.userId, userIdBig));
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.select({ id: users.id }).from(users).where(eq(users.email, input.email)).limit(1);
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(and(inArray(roles.slug, input.roles), eq(roles.isActive, true), eq(roles.isDeleted, false)));
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: { firstName?: string | null; lastName?: string | null; displayName?: string | null; phone?: string | null; username?: string | null; photo?: string | null },
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(userId: string, actorUserId: string | null, reason?: string): Promise<void> {
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({ isDeleted: true, deletedAt: new Date(), deletedBy: toIdOrNull(actorUserId), deletedReason: reason ?? null })
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: { name?: string; displayName?: string; description?: string | null; isActive?: boolean },
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(roleId: string, actorUserId: string | null, reason?: string): Promise<void> {
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({ isDeleted: true, deletedAt: new Date(), deletedBy: toIdOrNull(actorUserId), deletedReason: reason ?? null })
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(asc(permissions.groupOrder), asc(permissions.group), asc(permissions.order), asc(permissions.slug));
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(input: PermissionInput, actorUserId: string | null): Promise<PermissionSummary> {
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 ? { ...changed, updatedBy: actorId } : { slug: input.slug, updatedBy: actorId },
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.select(ROLE_COLUMNS).from(roles).where(eq(roles.isDeleted, false)).orderBy(asc(roles.order), asc(roles.slug));
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(input: { slug: string; name?: string; displayName?: string; description?: string | null }, actorUserId: string | null): Promise<RoleSummary> {
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(roleId: string, permissionSlug: string, actorUserId: string | null): Promise<void> {
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({ target: [permissionRole.permissionId, permissionRole.roleId] });
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.select({ id: roles.id }).from(roles).where(eq(roles.slug, roleSlug)).limit(1);
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.select({ id: users.id }).from(users).where(eq(users.id, userIdBig)).limit(1);
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.insert(roleUser).values({ userId: userIdBig, roleId: role.id }).onConflictDoNothing({ target: [roleUser.userId, roleUser.roleId] });
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.select({ id: roles.id }).from(roles).where(eq(roles.slug, roleSlug)).limit(1);
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.delete(roleUser).where(and(eq(roleUser.userId, userIdBig), eq(roleUser.roleId, role.id)));
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(and(eq(roles.isDefault, true), eq(roles.isActive, true), eq(roles.isDeleted, false)));
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.select({ value: count() }).from(roleUser).where(eq(roleUser.userId, userId));
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(userId: string, permissionSlug: string, actorUserId: string | null): Promise<void> {
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({ target: [permissionUser.userId, permissionUser.permissionId] });
736
+ .onConflictDoNothing({
737
+ target: [permissionUser.userId, permissionUser.permissionId],
738
+ });
576
739
  await this.cache.invalidateSubject(userId);
577
740
  }
578
741
 
579
- async revokePermissionFromUser(userId: string, permissionSlug: string): Promise<void> {
742
+ async revokePermissionFromUser(
743
+ userId: string,
744
+ permissionSlug: string,
745
+ ): Promise<void> {
580
746
  const userIdBig = toId(userId);
581
- const [permission] = await this.db.select({ id: permissions.id }).from(permissions).where(eq(permissions.slug, permissionSlug)).limit(1);
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.delete(permissionUser).where(and(eq(permissionUser.userId, userIdBig), eq(permissionUser.permissionId, permission.id)));
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(slug: string, actorUserId: string | null): Promise<{ id: bigint }> {
597
- const [existing] = await this.db.select({ id: permissions.id }).from(permissions).where(eq(permissions.slug, slug)).limit(1);
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({ slug, name: slug, displayName: slug, group: "Custom", createdBy: actorId, updatedBy: actorId })
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
  }