@simple-auth-kit/cli 1.5.0 → 1.6.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 (176) hide show
  1. package/lib/copy.ts +1 -1
  2. package/package.json +1 -1
  3. package/registry/combos/express-drizzle/shared/src/{ability.ts → ability/ability.ts} +8 -2
  4. package/registry/combos/express-drizzle/shared/src/{permission-cache.ts → cache/permission-cache.ts} +25 -9
  5. package/registry/combos/express-drizzle/shared/src/{rate-limit.store.ts → cache/rate-limit.store.ts} +4 -1
  6. package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/config}/auth.config.ts +1 -1
  7. package/registry/combos/express-drizzle/shared/src/{db.ts → config/db.ts} +1 -1
  8. package/registry/combos/express-drizzle/shared/src/{key-provider.ts → config/key-provider.ts} +6 -2
  9. package/registry/combos/express-drizzle/shared/src/{id.helper.ts → helpers/id.helper.ts} +6 -2
  10. package/registry/combos/{nestjs-drizzle/shared/src → express-drizzle/shared/src/helpers}/pagination.ts +17 -3
  11. package/registry/combos/express-drizzle/shared/src/middleware/auth-core-error.middleware.ts +6 -8
  12. package/registry/combos/express-drizzle/shared/src/middleware/auth.middleware.ts +6 -8
  13. package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/openapi}/openapi-fragment.ts +13 -4
  14. package/registry/combos/express-drizzle/shared/src/{openapi-spec.ts → openapi/openapi-spec.ts} +251 -52
  15. package/registry/combos/express-drizzle/shared/src/repositories/oauth.repository.ts +2 -2
  16. package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +2 -4
  17. package/registry/combos/express-drizzle/shared/src/repositories/session.repository.ts +3 -3
  18. package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
  19. package/registry/combos/express-drizzle/shared/src/route-tiers.ts +52 -11
  20. package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +2 -2
  21. package/registry/combos/express-drizzle/test/bootstrap.ts +4 -2
  22. package/registry/combos/express-drizzle/test/prove-cycle.ts +1095 -308
  23. package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +5 -5
  24. package/registry/combos/express-drizzle/variants/base/src/index.ts +8 -0
  25. package/registry/combos/express-drizzle/variants/base/src/middleware/authz.middleware.ts +2 -2
  26. package/registry/combos/express-drizzle/variants/base/src/openapi/openapi-admin.ts +1082 -0
  27. package/registry/combos/express-drizzle/variants/base/src/rbac.defaults.ts +60 -17
  28. package/registry/combos/express-drizzle/variants/base/src/repositories/audit-log.repository.ts +2 -2
  29. package/registry/combos/express-drizzle/variants/base/src/repositories/rbac.repository.ts +5 -5
  30. package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +18 -22
  31. package/registry/combos/express-drizzle/variants/base/src/seed.ts +76 -17
  32. package/registry/combos/express-drizzle/variants/base/src/services/auth.service.ts +7 -9
  33. package/registry/combos/express-drizzle/variants/base/test/variant-hooks.ts +167 -45
  34. package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +5 -5
  35. package/registry/combos/express-drizzle/variants/workspaces/src/index.ts +8 -0
  36. package/registry/combos/express-drizzle/variants/workspaces/src/middleware/authz.middleware.ts +3 -3
  37. package/registry/combos/express-drizzle/variants/workspaces/src/openapi/openapi-admin.ts +1176 -0
  38. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +1 -1
  39. package/registry/combos/express-drizzle/variants/workspaces/src/rbac.defaults.ts +66 -19
  40. package/registry/combos/express-drizzle/variants/workspaces/src/repositories/audit-log.repository.ts +2 -2
  41. package/registry/combos/express-drizzle/variants/workspaces/src/repositories/rbac.repository.ts +5 -5
  42. package/registry/combos/express-drizzle/variants/workspaces/src/repositories/workspace.repository.ts +9 -11
  43. package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +18 -22
  44. package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +1 -1
  45. package/registry/combos/express-drizzle/variants/workspaces/src/seed.ts +168 -35
  46. package/registry/combos/express-drizzle/variants/workspaces/src/services/auth.service.ts +7 -9
  47. package/registry/combos/express-drizzle/variants/workspaces/test/variant-hooks.ts +539 -133
  48. package/registry/combos/express-prisma/shared/src/{ability.ts → ability/ability.ts} +8 -2
  49. package/registry/combos/express-prisma/shared/src/{permission-cache.ts → cache/permission-cache.ts} +25 -9
  50. package/registry/combos/express-prisma/shared/src/{rate-limit.store.ts → cache/rate-limit.store.ts} +4 -1
  51. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/config}/auth.config.ts +1 -1
  52. package/registry/combos/express-prisma/shared/src/{key-provider.ts → config/key-provider.ts} +6 -2
  53. package/registry/combos/{nestjs-prisma/shared/src → express-prisma/shared/src/helpers}/id.helper.ts +6 -2
  54. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/helpers}/pagination.ts +17 -3
  55. package/registry/combos/express-prisma/shared/src/middleware/auth-core-error.middleware.ts +6 -8
  56. package/registry/combos/express-prisma/shared/src/middleware/auth.middleware.ts +6 -8
  57. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/openapi}/openapi-fragment.ts +13 -4
  58. package/registry/combos/express-prisma/shared/src/{openapi-spec.ts → openapi/openapi-spec.ts} +313 -55
  59. package/registry/combos/express-prisma/shared/src/repositories/oauth.repository.ts +2 -2
  60. package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +2 -4
  61. package/registry/combos/express-prisma/shared/src/repositories/session.repository.ts +2 -2
  62. package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
  63. package/registry/combos/express-prisma/shared/src/route-tiers.ts +52 -11
  64. package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +2 -2
  65. package/registry/combos/express-prisma/test/bootstrap.ts +7 -3
  66. package/registry/combos/express-prisma/test/prove-cycle.ts +1095 -308
  67. package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +5 -5
  68. package/registry/combos/express-prisma/variants/base/src/index.ts +8 -0
  69. package/registry/combos/express-prisma/variants/base/src/middleware/authz.middleware.ts +2 -2
  70. package/registry/combos/express-prisma/variants/base/src/openapi/openapi-admin.ts +1102 -0
  71. package/registry/combos/express-prisma/variants/base/src/repositories/audit-log.repository.ts +2 -2
  72. package/registry/combos/express-prisma/variants/base/src/repositories/rbac.repository.ts +4 -4
  73. package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +18 -22
  74. package/registry/combos/express-prisma/variants/base/src/services/auth.service.ts +6 -8
  75. package/registry/combos/express-prisma/variants/base/test/variant-hooks.ts +176 -47
  76. package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +5 -5
  77. package/registry/combos/express-prisma/variants/workspaces/src/index.ts +8 -0
  78. package/registry/combos/express-prisma/variants/workspaces/src/middleware/authz.middleware.ts +3 -3
  79. package/registry/combos/express-prisma/variants/workspaces/src/openapi/openapi-admin.ts +1218 -0
  80. package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +1 -1
  81. package/registry/combos/express-prisma/variants/workspaces/src/repositories/audit-log.repository.ts +2 -2
  82. package/registry/combos/express-prisma/variants/workspaces/src/repositories/rbac.repository.ts +4 -4
  83. package/registry/combos/express-prisma/variants/workspaces/src/repositories/workspace.repository.ts +2 -2
  84. package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +18 -22
  85. package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +1 -1
  86. package/registry/combos/express-prisma/variants/workspaces/src/services/auth.service.ts +6 -8
  87. package/registry/combos/express-prisma/variants/workspaces/test/variant-hooks.ts +538 -134
  88. package/registry/combos/nestjs-drizzle/shared/src/{guards → ability}/ability.guard.ts +1 -1
  89. package/registry/combos/nestjs-drizzle/shared/src/{ability.ts → ability/ability.ts} +8 -2
  90. package/registry/combos/nestjs-drizzle/shared/src/{permission-cache.ts → cache/permission-cache.ts} +27 -10
  91. package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/cache}/rate-limit.store.ts +4 -1
  92. package/registry/combos/nestjs-drizzle/shared/src/{auth.config.ts → config/auth.config.ts} +1 -1
  93. package/registry/combos/nestjs-drizzle/shared/src/{db.ts → config/db.ts} +1 -1
  94. package/registry/combos/nestjs-drizzle/shared/src/{key-provider.ts → config/key-provider.ts} +6 -2
  95. package/registry/combos/nestjs-drizzle/shared/src/controllers/auth.controller.ts +1 -1
  96. package/registry/combos/nestjs-drizzle/shared/src/guards/auth.guard.ts +1 -1
  97. package/registry/combos/nestjs-drizzle/shared/src/{id.helper.ts → helpers/id.helper.ts} +6 -2
  98. package/registry/combos/{express-prisma/shared/src → nestjs-drizzle/shared/src/helpers}/pagination.ts +17 -3
  99. package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +2 -2
  100. package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +8 -12
  101. package/registry/combos/nestjs-drizzle/shared/src/repositories/session.repository.ts +3 -3
  102. package/registry/combos/nestjs-drizzle/shared/src/repositories/two-factor.repository.ts +1 -1
  103. package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
  104. package/registry/combos/nestjs-drizzle/test/bootstrap.ts +1 -1
  105. package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1 -1
  106. package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +10 -6
  107. package/registry/combos/nestjs-drizzle/variants/base/src/controllers/admin.controller.ts +1 -1
  108. package/registry/combos/nestjs-drizzle/variants/base/src/guards/authz.guard.ts +2 -2
  109. package/registry/combos/nestjs-drizzle/variants/base/src/index.ts +9 -0
  110. package/registry/combos/nestjs-drizzle/variants/base/src/rbac.defaults.ts +58 -16
  111. package/registry/combos/nestjs-drizzle/variants/base/src/repositories/audit-log.repository.ts +3 -3
  112. package/registry/combos/nestjs-drizzle/variants/base/src/repositories/rbac.repository.ts +4 -4
  113. package/registry/combos/nestjs-drizzle/variants/base/src/seed.ts +76 -17
  114. package/registry/combos/nestjs-drizzle/variants/base/src/services/auth.service.ts +7 -9
  115. package/registry/combos/nestjs-drizzle/variants/base/test/variant-hooks.ts +167 -45
  116. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +10 -6
  117. package/registry/combos/nestjs-drizzle/variants/workspaces/src/controllers/admin.controller.ts +1 -1
  118. package/registry/combos/nestjs-drizzle/variants/workspaces/src/controllers/workspace.controller.ts +1 -1
  119. package/registry/combos/nestjs-drizzle/variants/workspaces/src/guards/authz.guard.ts +2 -2
  120. package/registry/combos/nestjs-drizzle/variants/workspaces/src/index.ts +9 -0
  121. package/registry/combos/nestjs-drizzle/variants/workspaces/src/rbac.defaults.ts +64 -18
  122. package/registry/combos/nestjs-drizzle/variants/workspaces/src/repositories/audit-log.repository.ts +3 -3
  123. package/registry/combos/nestjs-drizzle/variants/workspaces/src/repositories/rbac.repository.ts +4 -4
  124. package/registry/combos/nestjs-drizzle/variants/workspaces/src/repositories/workspace.repository.ts +8 -10
  125. package/registry/combos/nestjs-drizzle/variants/workspaces/src/seed.ts +171 -36
  126. package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +7 -9
  127. package/registry/combos/nestjs-drizzle/variants/workspaces/test/variant-hooks.ts +539 -133
  128. package/registry/combos/nestjs-prisma/shared/src/{guards → ability}/ability.guard.ts +1 -1
  129. package/registry/combos/nestjs-prisma/shared/src/{ability.ts → ability/ability.ts} +8 -2
  130. package/registry/combos/nestjs-prisma/shared/src/{permission-cache.ts → cache/permission-cache.ts} +27 -10
  131. package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/cache}/rate-limit.store.ts +4 -1
  132. package/registry/combos/nestjs-prisma/shared/src/{auth.config.ts → config/auth.config.ts} +1 -1
  133. package/registry/combos/nestjs-prisma/shared/src/{key-provider.ts → config/key-provider.ts} +6 -2
  134. package/registry/combos/nestjs-prisma/shared/src/controllers/auth.controller.ts +1 -1
  135. package/registry/combos/nestjs-prisma/shared/src/guards/auth.guard.ts +1 -1
  136. package/registry/combos/{express-prisma/shared/src → nestjs-prisma/shared/src/helpers}/id.helper.ts +6 -2
  137. package/registry/combos/nestjs-prisma/shared/src/{pagination.ts → helpers/pagination.ts} +17 -3
  138. package/registry/combos/nestjs-prisma/shared/src/repositories/oauth.repository.ts +1 -1
  139. package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +2 -4
  140. package/registry/combos/nestjs-prisma/shared/src/repositories/session.repository.ts +2 -2
  141. package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
  142. package/registry/combos/nestjs-prisma/test/bootstrap.ts +1 -1
  143. package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1 -1
  144. package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +9 -5
  145. package/registry/combos/nestjs-prisma/variants/base/src/controllers/admin.controller.ts +1 -1
  146. package/registry/combos/nestjs-prisma/variants/base/src/gateways/audit-log.gateway.ts +1 -1
  147. package/registry/combos/nestjs-prisma/variants/base/src/guards/authz.guard.ts +2 -2
  148. package/registry/combos/nestjs-prisma/variants/base/src/index.ts +9 -0
  149. package/registry/combos/nestjs-prisma/variants/base/src/{docs.ts → openapi/docs.ts} +23 -6
  150. package/registry/combos/nestjs-prisma/variants/base/src/repositories/audit-log.repository.ts +2 -2
  151. package/registry/combos/nestjs-prisma/variants/base/src/repositories/country.repository.ts +2 -2
  152. package/registry/combos/nestjs-prisma/variants/base/src/repositories/customer.repository.ts +6 -5
  153. package/registry/combos/nestjs-prisma/variants/base/src/repositories/language.repository.ts +2 -2
  154. package/registry/combos/nestjs-prisma/variants/base/src/repositories/rbac.repository.ts +3 -3
  155. package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +6 -8
  156. package/registry/combos/nestjs-prisma/variants/base/test/variant-hooks.ts +176 -47
  157. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +9 -5
  158. package/registry/combos/nestjs-prisma/variants/workspaces/src/controllers/admin.controller.ts +1 -1
  159. package/registry/combos/nestjs-prisma/variants/workspaces/src/controllers/workspace.controller.ts +1 -1
  160. package/registry/combos/nestjs-prisma/variants/workspaces/src/guards/authz.guard.ts +2 -2
  161. package/registry/combos/nestjs-prisma/variants/workspaces/src/index.ts +9 -0
  162. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/audit-log.repository.ts +2 -2
  163. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/country.repository.ts +2 -2
  164. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/customer.repository.ts +6 -5
  165. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +2 -2
  166. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/rbac.repository.ts +3 -3
  167. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/workspace.repository.ts +1 -1
  168. package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +6 -8
  169. package/registry/combos/nestjs-prisma/variants/workspaces/test/variant-hooks.ts +538 -134
  170. package/simple-auth-kit.ts +13 -9
  171. package/registry/combos/express-drizzle/variants/base/src/openapi-admin.ts +0 -565
  172. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-admin.ts +0 -596
  173. package/registry/combos/express-prisma/variants/base/src/openapi-admin.ts +0 -569
  174. package/registry/combos/express-prisma/variants/workspaces/src/openapi-admin.ts +0 -601
  175. /package/registry/combos/express-drizzle/shared/src/{http-error.ts → errors/http-error.ts} +0 -0
  176. /package/registry/combos/express-prisma/shared/src/{http-error.ts → errors/http-error.ts} +0 -0
@@ -29,7 +29,7 @@
29
29
  // same transaction that creates it, so its creator is never locked out of a workspace they just
30
30
  // made).
31
31
  import { and, eq, inArray } from "drizzle-orm";
32
- import type { Database } from "./db.js";
32
+ import type { Database } from "./config/db.js";
33
33
  import { permissionRole, permissions, roles } from "./schema.js";
34
34
 
35
35
  /** The display metadata a seeded permission carries. `group`/`order` exist so an admin console can render a stable matrix. */
@@ -55,11 +55,22 @@ export interface PermissionSeed {
55
55
  * evaluate.
56
56
  */
57
57
  export const PERMISSION_CATALOG = {
58
- "users:read": { displayName: "List members", description: "GET /auth/admin/users", group: "Users", order: 1 },
59
- "users:block": { displayName: "Block and unblock members", description: "POST /auth/admin/users/:userId/block, .../unblock", group: "Users", order: 2 },
58
+ "users:read": {
59
+ displayName: "List members",
60
+ description: "GET /auth/admin/users",
61
+ group: "Users",
62
+ order: 1,
63
+ },
64
+ "users:block": {
65
+ displayName: "Block and unblock members",
66
+ description: "POST /auth/admin/users/:userId/block, .../unblock",
67
+ group: "Users",
68
+ order: 2,
69
+ },
60
70
  "users:manage": {
61
71
  displayName: "Edit and delete members",
62
- description: "Edit a member's profile or delete their account — PATCH /auth/admin/users/:userId, DELETE /auth/admin/users/:userId",
72
+ description:
73
+ "Edit a member's profile or delete their account — PATCH /auth/admin/users/:userId, DELETE /auth/admin/users/:userId",
63
74
  group: "Users",
64
75
  order: 3,
65
76
  },
@@ -72,30 +83,44 @@ export const PERMISSION_CATALOG = {
72
83
  },
73
84
  "roles:assign": {
74
85
  displayName: "Assign roles",
75
- description: "Assign and revoke a member's roles — POST /auth/admin/users/:userId/roles, .../roles/:roleSlug/revoke, PUT /workspaces/members/:memberId/roles",
86
+ description:
87
+ "Assign and revoke a member's roles — POST /auth/admin/users/:userId/roles, .../roles/:roleSlug/revoke, PUT /workspaces/members/:memberId/roles",
76
88
  group: "Roles",
77
89
  order: 2,
78
90
  },
79
- "permissions:read": { displayName: "Read the permission catalog", description: "GET /auth/admin/permissions", group: "Permissions", order: 1 },
91
+ "permissions:read": {
92
+ displayName: "Read the permission catalog",
93
+ description: "GET /auth/admin/permissions",
94
+ group: "Permissions",
95
+ order: 1,
96
+ },
80
97
  "permissions:define": {
81
98
  displayName: "Define permissions",
82
- description: "Create a permission, rename it, or deactivate it — POST /auth/admin/permissions. The authority that defines all the others.",
99
+ description:
100
+ "Create a permission, rename it, or deactivate it — POST /auth/admin/permissions. The authority that defines all the others.",
83
101
  group: "Permissions",
84
102
  order: 2,
85
103
  },
86
104
  "permissions:grant": {
87
105
  displayName: "Grant permissions directly",
88
- description: "Grant and revoke a permission straight to a member, bypassing roles — POST /auth/admin/users/:userId/permissions, .../permissions/:slug/revoke",
106
+ description:
107
+ "Grant and revoke a permission straight to a member, bypassing roles — POST /auth/admin/users/:userId/permissions, .../permissions/:slug/revoke",
89
108
  group: "Permissions",
90
109
  order: 3,
91
110
  },
92
111
  "members:manage": {
93
112
  displayName: "Add and remove members",
94
- description: "POST /workspaces/members, DELETE /workspaces/members/:memberId",
113
+ description:
114
+ "POST /workspaces/members, DELETE /workspaces/members/:memberId",
95
115
  group: "Members",
96
116
  order: 1,
97
117
  },
98
- "audit-log:read": { displayName: "Read the audit log", description: "GET /auth/admin/audit-log", group: "Audit", order: 1 },
118
+ "audit-log:read": {
119
+ displayName: "Read the audit log",
120
+ description: "GET /auth/admin/audit-log",
121
+ group: "Audit",
122
+ order: 1,
123
+ },
99
124
  } as const satisfies Record<string, PermissionSeed>;
100
125
 
101
126
  /**
@@ -107,10 +132,14 @@ export const PERMISSION_CATALOG = {
107
132
  */
108
133
  export type PermissionSlug = keyof typeof PERMISSION_CATALOG;
109
134
 
110
- export const PERMISSION_SLUGS = Object.keys(PERMISSION_CATALOG) as PermissionSlug[];
135
+ export const PERMISSION_SLUGS = Object.keys(
136
+ PERMISSION_CATALOG,
137
+ ) as PermissionSlug[];
111
138
 
112
139
  /** Groups, in the order an admin console should render them. Derived from the catalog so it cannot drift from it. */
113
- export const PERMISSION_GROUP_ORDER: string[] = [...new Set(PERMISSION_SLUGS.map((slug) => PERMISSION_CATALOG[slug].group))];
140
+ export const PERMISSION_GROUP_ORDER: string[] = [
141
+ ...new Set(PERMISSION_SLUGS.map((slug) => PERMISSION_CATALOG[slug].group)),
142
+ ];
114
143
 
115
144
  /** The display metadata a seeded role carries, plus what it starts out granting. */
116
145
  export interface RoleSeed {
@@ -154,7 +183,8 @@ export const DEFAULT_ROLES: readonly RoleSeed[] = [
154
183
  {
155
184
  slug: "member",
156
185
  displayName: "Member",
157
- description: "The default for a new membership. Carries no administrative permission.",
186
+ description:
187
+ "The default for a new membership. Carries no administrative permission.",
158
188
  isDefault: true,
159
189
  order: 2,
160
190
  permissions: [],
@@ -191,7 +221,10 @@ export type RbacWriter = Pick<Database, "insert" | "select">;
191
221
  */
192
222
  // Takes bigint directly: both callers (`WorkspaceRepository.create`, `seed.ts`) already have the
193
223
  // workspace row's id in hand from their own database call.
194
- export async function provisionDefaultRoles(db: RbacWriter, workspaceId: bigint): Promise<void> {
224
+ export async function provisionDefaultRoles(
225
+ db: RbacWriter,
226
+ workspaceId: bigint,
227
+ ): Promise<void> {
195
228
  // Insert-then-read rather than `.returning()`: ON CONFLICT DO NOTHING returns no row for the
196
229
  // slugs that already existed, and the ids of *those* are exactly the ones a re-run needs.
197
230
  await db
@@ -203,12 +236,17 @@ export async function provisionDefaultRoles(db: RbacWriter, workspaceId: bigint)
203
236
  displayName: PERMISSION_CATALOG[slug].displayName,
204
237
  description: PERMISSION_CATALOG[slug].description,
205
238
  group: PERMISSION_CATALOG[slug].group,
206
- groupOrder: PERMISSION_GROUP_ORDER.indexOf(PERMISSION_CATALOG[slug].group),
239
+ groupOrder: PERMISSION_GROUP_ORDER.indexOf(
240
+ PERMISSION_CATALOG[slug].group,
241
+ ),
207
242
  order: PERMISSION_CATALOG[slug].order,
208
243
  })),
209
244
  )
210
245
  .onConflictDoNothing({ target: permissions.slug });
211
- const permissionRows = await db.select({ id: permissions.id, slug: permissions.slug }).from(permissions).where(inArray(permissions.slug, PERMISSION_SLUGS));
246
+ const permissionRows = await db
247
+ .select({ id: permissions.id, slug: permissions.slug })
248
+ .from(permissions)
249
+ .where(inArray(permissions.slug, PERMISSION_SLUGS));
212
250
  const permissionId = new Map(permissionRows.map((p) => [p.slug, p.id]));
213
251
 
214
252
  await db
@@ -240,9 +278,17 @@ export async function provisionDefaultRoles(db: RbacWriter, workspaceId: bigint)
240
278
  const seedBySlug = new Map(DEFAULT_ROLES.map((role) => [role.slug, role]));
241
279
 
242
280
  const pairs = roleRows.flatMap((role) =>
243
- (seedBySlug.get(role.slug)?.permissions ?? []).map((slug) => ({ roleId: role.id, permissionId: permissionId.get(slug)! })),
281
+ (seedBySlug.get(role.slug)?.permissions ?? []).map((slug) => ({
282
+ roleId: role.id,
283
+ permissionId: permissionId.get(slug)!,
284
+ })),
244
285
  );
245
286
  if (pairs.length) {
246
- await db.insert(permissionRole).values(pairs).onConflictDoNothing({ target: [permissionRole.permissionId, permissionRole.roleId] });
287
+ await db
288
+ .insert(permissionRole)
289
+ .values(pairs)
290
+ .onConflictDoNothing({
291
+ target: [permissionRole.permissionId, permissionRole.roleId],
292
+ });
247
293
  }
248
294
  }
@@ -1,15 +1,15 @@
1
1
  import { Inject, Injectable } from "@nestjs/common";
2
2
  import { and, count, desc, eq, gte, lte, type SQL } from "drizzle-orm";
3
3
  import type { AuditEvent } from "@/lib/auth/core/types.js";
4
- import { DRIZZLE_DB, type Database } from "../db.js";
4
+ import { DRIZZLE_DB, type Database } from "../config/db.js";
5
5
  import {
6
6
  buildPageMeta,
7
7
  normalizeLimit,
8
8
  normalizePage,
9
9
  type Paginated,
10
- } from "../pagination.js";
10
+ } from "../helpers/pagination.js";
11
11
  import { auditLogs } from "../schema.js";
12
- import { toIdOrNull, toIdOrUndefined } from "../id.helper.js";
12
+ import { toIdOrNull, toIdOrUndefined } from "../helpers/id.helper.js";
13
13
 
14
14
  export interface AuditLogEntry {
15
15
  id: string;
@@ -11,14 +11,14 @@ import {
11
11
  } from "drizzle-orm";
12
12
  import { resolvePermissions } from "@/lib/auth/core/rbac.js";
13
13
  import type { AuthzContext } from "../guards/authz.guard.js";
14
- import { DRIZZLE_DB, type Database } from "../db.js";
14
+ import { DRIZZLE_DB, type Database } from "../config/db.js";
15
15
  import {
16
16
  buildPageMeta,
17
17
  normalizeLimit,
18
18
  normalizePage,
19
19
  type Paginated,
20
- } from "../pagination.js";
21
- import { PermissionCache } from "../permission-cache.js";
20
+ } from "../helpers/pagination.js";
21
+ import { PermissionCache } from "../cache/permission-cache.js";
22
22
  import {
23
23
  permissionMember,
24
24
  permissionRole,
@@ -28,7 +28,7 @@ import {
28
28
  users,
29
29
  workspaceMembers,
30
30
  } from "../schema.js";
31
- import { toId, toIdOrNull } from "../id.helper.js";
31
+ import { toId, toIdOrNull } from "../helpers/id.helper.js";
32
32
 
33
33
  export interface MemberSummary {
34
34
  memberId: string;
@@ -5,7 +5,7 @@ import {
5
5
  NotFoundException,
6
6
  } from "@nestjs/common";
7
7
  import { and, asc, eq, inArray } from "drizzle-orm";
8
- import { DRIZZLE_DB, type Database } from "../db.js";
8
+ import { DRIZZLE_DB, type Database } from "../config/db.js";
9
9
  import {
10
10
  provisionDefaultRoles,
11
11
  WORKSPACE_CREATOR_ROLES,
@@ -18,7 +18,7 @@ import {
18
18
  workspaceMembers,
19
19
  workspaces,
20
20
  } from "../schema.js";
21
- import { toId, toIdOrNull } from "../id.helper.js";
21
+ import { toId, toIdOrNull } from "../helpers/id.helper.js";
22
22
 
23
23
  export interface WorkspaceSummary {
24
24
  id: string;
@@ -77,14 +77,12 @@ export class WorkspaceRepository {
77
77
  .values({ workspaceId: created.id, userId: userIdBig })
78
78
  .returning({ id: workspaceMembers.id });
79
79
  if (creatorRoles.length) {
80
- await tx
81
- .insert(roleMember)
82
- .values(
83
- creatorRoles.map((role) => ({
84
- memberId: member.id,
85
- roleId: role.id,
86
- })),
87
- );
80
+ await tx.insert(roleMember).values(
81
+ creatorRoles.map((role) => ({
82
+ memberId: member.id,
83
+ roleId: role.id,
84
+ })),
85
+ );
88
86
  }
89
87
  return created;
90
88
  });
@@ -31,23 +31,46 @@ import { and, asc, eq, inArray, notInArray } from "drizzle-orm";
31
31
  import { drizzle } from "drizzle-orm/node-postgres";
32
32
  import { Pool } from "pg";
33
33
  import { hashPassword } from "@/lib/auth/core/crypto.js";
34
- import type { Database } from "./db.js";
35
- import { DEFAULT_ROLES, PERMISSION_SLUGS, SEED_SUPERADMIN_ROLES, WORKSPACE_CREATOR_ROLES, provisionDefaultRoles } from "./rbac.defaults.js";
34
+ import type { Database } from "./config/db.js";
35
+ import {
36
+ DEFAULT_ROLES,
37
+ PERMISSION_SLUGS,
38
+ SEED_SUPERADMIN_ROLES,
39
+ WORKSPACE_CREATOR_ROLES,
40
+ provisionDefaultRoles,
41
+ } from "./rbac.defaults.js";
36
42
  import * as schema from "./schema.js";
37
- import { permissions, roleMember, roles, users, workspaceMembers, workspaces } from "./schema.js";
43
+ import {
44
+ permissions,
45
+ roleMember,
46
+ roles,
47
+ users,
48
+ workspaceMembers,
49
+ workspaces,
50
+ } from "./schema.js";
38
51
 
39
52
  const DEFAULT_WORKSPACE_NAME = "Default workspace";
40
53
 
41
54
  function requireEnv(name: string): string {
42
55
  const value = process.env[name];
43
- if (!value) throw new Error(`${name} is not set — the seeder cannot reach the database without it`);
56
+ if (!value)
57
+ throw new Error(
58
+ `${name} is not set — the seeder cannot reach the database without it`,
59
+ );
44
60
  return value;
45
61
  }
46
62
 
47
63
  /** Workspace names are not unique in the schema, so re-running adopts the one it made last time rather than making another. */
48
- async function seedWorkspace(db: Database): Promise<{ id: bigint; name: string }> {
64
+ async function seedWorkspace(
65
+ db: Database,
66
+ ): Promise<{ id: bigint; name: string }> {
49
67
  const name = process.env["SEED_WORKSPACE_NAME"] || DEFAULT_WORKSPACE_NAME;
50
- const [existing] = await db.select().from(workspaces).where(eq(workspaces.name, name)).orderBy(asc(workspaces.createdAt)).limit(1);
68
+ const [existing] = await db
69
+ .select()
70
+ .from(workspaces)
71
+ .where(eq(workspaces.name, name))
72
+ .orderBy(asc(workspaces.createdAt))
73
+ .limit(1);
51
74
  if (existing) {
52
75
  console.log(`workspace: "${name}" already exists (${existing.id})`);
53
76
  return existing;
@@ -58,92 +81,204 @@ async function seedWorkspace(db: Database): Promise<{ id: bigint; name: string }
58
81
  }
59
82
 
60
83
  /** The catalog and this workspace's roles come from rbac.defaults.ts; this only reports what it wrote. */
61
- async function seedRbacDefaults(db: Database, workspaceId: bigint): Promise<void> {
84
+ async function seedRbacDefaults(
85
+ db: Database,
86
+ workspaceId: bigint,
87
+ ): Promise<void> {
62
88
  await provisionDefaultRoles(db, workspaceId);
63
89
  console.log(`permissions: ${PERMISSION_SLUGS.length} slug(s) in the catalog`);
64
- for (const role of DEFAULT_ROLES) console.log(`role "${role.slug}": ${role.permissions.length} permission(s)${role.isDefault ? " (new-member default)" : ""}`);
90
+ for (const role of DEFAULT_ROLES)
91
+ console.log(
92
+ `role "${role.slug}": ${role.permissions.length} permission(s)${role.isDefault ? " (new-member default)" : ""}`,
93
+ );
65
94
 
66
95
  // Slugs that exist in the database but not in this build's catalog. They are not an error —
67
96
  // `POST /auth/admin/permissions` exists so a deployment can define its own, and a grant can
68
97
  // create one — but a slug no route names grants nothing, so a typo would otherwise be a
69
98
  // permission that mysteriously never works. Printing them is what makes it visible.
70
- const unknown = await db.select({ slug: permissions.slug }).from(permissions).where(notInArray(permissions.slug, PERMISSION_SLUGS));
71
- if (unknown.length) console.log(`permissions: ${unknown.length} slug(s) outside this build's catalog (no route names them): ${unknown.map((r) => r.slug).join(", ")}`);
72
- const inactive = await db.select({ slug: permissions.slug }).from(permissions).where(eq(permissions.isActive, false));
73
- if (inactive.length) console.log(`permissions: ${inactive.length} deactivated, granting nothing: ${inactive.map((r) => r.slug).join(", ")}`);
99
+ const unknown = await db
100
+ .select({ slug: permissions.slug })
101
+ .from(permissions)
102
+ .where(notInArray(permissions.slug, PERMISSION_SLUGS));
103
+ if (unknown.length)
104
+ console.log(
105
+ `permissions: ${unknown.length} slug(s) outside this build's catalog (no route names them): ${unknown.map((r) => r.slug).join(", ")}`,
106
+ );
107
+ const inactive = await db
108
+ .select({ slug: permissions.slug })
109
+ .from(permissions)
110
+ .where(eq(permissions.isActive, false));
111
+ if (inactive.length)
112
+ console.log(
113
+ `permissions: ${inactive.length} deactivated, granting nothing: ${inactive.map((r) => r.slug).join(", ")}`,
114
+ );
74
115
  }
75
116
 
76
117
  /** The user is the authentication principal; the membership below is the authorization one. */
77
- async function seedAdminUser(db: Database, workspace: { id: bigint; name: string }): Promise<void> {
118
+ async function seedAdminUser(
119
+ db: Database,
120
+ workspace: { id: bigint; name: string },
121
+ ): Promise<void> {
78
122
  const email = process.env["SEED_ADMIN_EMAIL"];
79
123
  const password = process.env["SEED_ADMIN_PASSWORD"];
80
124
  if (!email || !password) {
81
- console.log(`admin: skipped — set SEED_ADMIN_EMAIL and SEED_ADMIN_PASSWORD to create one (there is no default password)`);
82
- console.log(`admin: "${workspace.name}" has no members until you re-run with them set`);
125
+ console.log(
126
+ `admin: skipped set SEED_ADMIN_EMAIL and SEED_ADMIN_PASSWORD to create one (there is no default password)`,
127
+ );
128
+ console.log(
129
+ `admin: "${workspace.name}" has no members until you re-run with them set`,
130
+ );
83
131
  return;
84
132
  }
85
133
 
86
134
  // Their password may have been changed since; rewriting it here would silently reset it.
87
- const [existing] = await db.select({ id: users.id }).from(users).where(eq(users.email, email)).limit(1);
88
- const user = existing ?? (await db.insert(users).values({ email, passwordHash: await hashPassword(password) }).returning({ id: users.id }))[0];
89
- console.log(existing ? `admin: ${email} already exists — password left unchanged` : `admin: created ${email}`);
135
+ const [existing] = await db
136
+ .select({ id: users.id })
137
+ .from(users)
138
+ .where(eq(users.email, email))
139
+ .limit(1);
140
+ const user =
141
+ existing ??
142
+ (
143
+ await db
144
+ .insert(users)
145
+ .values({ email, passwordHash: await hashPassword(password) })
146
+ .returning({ id: users.id })
147
+ )[0];
148
+ console.log(
149
+ existing
150
+ ? `admin: ${email} already exists — password left unchanged`
151
+ : `admin: created ${email}`,
152
+ );
90
153
 
91
154
  const [existingMembership] = await db
92
155
  .select({ id: workspaceMembers.id })
93
156
  .from(workspaceMembers)
94
- .where(and(eq(workspaceMembers.userId, user.id), eq(workspaceMembers.workspaceId, workspace.id)))
157
+ .where(
158
+ and(
159
+ eq(workspaceMembers.userId, user.id),
160
+ eq(workspaceMembers.workspaceId, workspace.id),
161
+ ),
162
+ )
95
163
  .limit(1);
96
164
  const membership =
97
- existingMembership ?? (await db.insert(workspaceMembers).values({ userId: user.id, workspaceId: workspace.id }).returning({ id: workspaceMembers.id }))[0];
165
+ existingMembership ??
166
+ (
167
+ await db
168
+ .insert(workspaceMembers)
169
+ .values({ userId: user.id, workspaceId: workspace.id })
170
+ .returning({ id: workspaceMembers.id })
171
+ )[0];
98
172
 
99
173
  const adminRoles = await db
100
174
  .select({ id: roles.id, slug: roles.slug })
101
175
  .from(roles)
102
- .where(and(eq(roles.workspaceId, workspace.id), inArray(roles.slug, WORKSPACE_CREATOR_ROLES)));
176
+ .where(
177
+ and(
178
+ eq(roles.workspaceId, workspace.id),
179
+ inArray(roles.slug, WORKSPACE_CREATOR_ROLES),
180
+ ),
181
+ );
103
182
  if (adminRoles.length) {
104
183
  await db
105
184
  .insert(roleMember)
106
- .values(adminRoles.map((role) => ({ memberId: membership.id, roleId: role.id })))
107
- .onConflictDoNothing({ target: [roleMember.memberId, roleMember.roleId] });
185
+ .values(
186
+ adminRoles.map((role) => ({
187
+ memberId: membership.id,
188
+ roleId: role.id,
189
+ })),
190
+ )
191
+ .onConflictDoNothing({
192
+ target: [roleMember.memberId, roleMember.roleId],
193
+ });
108
194
  }
109
- console.log(`admin: member of "${workspace.name}" with roles ${adminRoles.map((r) => r.slug).join(", ")}`);
195
+ console.log(
196
+ `admin: member of "${workspace.name}" with roles ${adminRoles.map((r) => r.slug).join(", ")}`,
197
+ );
110
198
  }
111
199
 
112
200
  /** The user is the authentication principal; the membership below is the authorization one. */
113
- async function seedSuperAdminUser(db: Database, workspace: { id: bigint; name: string }): Promise<void> {
201
+ async function seedSuperAdminUser(
202
+ db: Database,
203
+ workspace: { id: bigint; name: string },
204
+ ): Promise<void> {
114
205
  const email = process.env["SEED_SUPERADMIN_EMAIL"];
115
206
  const password = process.env["SEED_SUPERADMIN_PASSWORD"];
116
207
  if (!email || !password) {
117
- console.log("super admin: skipped — set SEED_SUPERADMIN_EMAIL and SEED_SUPERADMIN_PASSWORD to create one (there is no default password)");
118
- console.log(`super admin: "${workspace.name}" has no members until you re-run with them set`);
208
+ console.log(
209
+ "super admin: skipped set SEED_SUPERADMIN_EMAIL and SEED_SUPERADMIN_PASSWORD to create one (there is no default password)",
210
+ );
211
+ console.log(
212
+ `super admin: "${workspace.name}" has no members until you re-run with them set`,
213
+ );
119
214
  return;
120
215
  }
121
216
  const username = process.env["SEED_SUPERADMIN_USERNAME"] || undefined;
122
217
 
123
218
  // Their password may have been changed since; rewriting it here would silently reset it.
124
- const [existing] = await db.select({ id: users.id }).from(users).where(eq(users.email, email)).limit(1);
125
- const user = existing ?? (await db.insert(users).values({ email, username, passwordHash: await hashPassword(password) }).returning({ id: users.id }))[0];
126
- console.log(existing ? `super admin: ${email} already exists — password left unchanged` : `super admin: created ${email}${username ? ` (username ${username})` : ""}`);
219
+ const [existing] = await db
220
+ .select({ id: users.id })
221
+ .from(users)
222
+ .where(eq(users.email, email))
223
+ .limit(1);
224
+ const user =
225
+ existing ??
226
+ (
227
+ await db
228
+ .insert(users)
229
+ .values({ email, username, passwordHash: await hashPassword(password) })
230
+ .returning({ id: users.id })
231
+ )[0];
232
+ console.log(
233
+ existing
234
+ ? `super admin: ${email} already exists — password left unchanged`
235
+ : `super admin: created ${email}${username ? ` (username ${username})` : ""}`,
236
+ );
127
237
 
128
238
  const [existingMembership] = await db
129
239
  .select({ id: workspaceMembers.id })
130
240
  .from(workspaceMembers)
131
- .where(and(eq(workspaceMembers.userId, user.id), eq(workspaceMembers.workspaceId, workspace.id)))
241
+ .where(
242
+ and(
243
+ eq(workspaceMembers.userId, user.id),
244
+ eq(workspaceMembers.workspaceId, workspace.id),
245
+ ),
246
+ )
132
247
  .limit(1);
133
248
  const membership =
134
- existingMembership ?? (await db.insert(workspaceMembers).values({ userId: user.id, workspaceId: workspace.id }).returning({ id: workspaceMembers.id }))[0];
249
+ existingMembership ??
250
+ (
251
+ await db
252
+ .insert(workspaceMembers)
253
+ .values({ userId: user.id, workspaceId: workspace.id })
254
+ .returning({ id: workspaceMembers.id })
255
+ )[0];
135
256
 
136
257
  const superAdminRoles = await db
137
258
  .select({ id: roles.id, slug: roles.slug })
138
259
  .from(roles)
139
- .where(and(eq(roles.workspaceId, workspace.id), inArray(roles.slug, SEED_SUPERADMIN_ROLES)));
260
+ .where(
261
+ and(
262
+ eq(roles.workspaceId, workspace.id),
263
+ inArray(roles.slug, SEED_SUPERADMIN_ROLES),
264
+ ),
265
+ );
140
266
  if (superAdminRoles.length) {
141
267
  await db
142
268
  .insert(roleMember)
143
- .values(superAdminRoles.map((role) => ({ memberId: membership.id, roleId: role.id })))
144
- .onConflictDoNothing({ target: [roleMember.memberId, roleMember.roleId] });
269
+ .values(
270
+ superAdminRoles.map((role) => ({
271
+ memberId: membership.id,
272
+ roleId: role.id,
273
+ })),
274
+ )
275
+ .onConflictDoNothing({
276
+ target: [roleMember.memberId, roleMember.roleId],
277
+ });
145
278
  }
146
- console.log(`super admin: member of "${workspace.name}" with roles ${superAdminRoles.map((r) => r.slug).join(", ")}`);
279
+ console.log(
280
+ `super admin: member of "${workspace.name}" with roles ${superAdminRoles.map((r) => r.slug).join(", ")}`,
281
+ );
147
282
  }
148
283
 
149
284
  async function main(): Promise<void> {
@@ -52,19 +52,19 @@ import {
52
52
  generateTotpSecret,
53
53
  verifyTotpCode,
54
54
  } from "@/lib/auth/core/two-factor.js";
55
- import { AUTH_CONFIG, AuthConfig } from "../auth.config.js";
55
+ import { AUTH_CONFIG, AuthConfig } from "../config/auth.config.js";
56
56
  import {
57
57
  AuditLogEntry,
58
58
  AuditLogListFilter,
59
59
  AuditLogRepository,
60
60
  toAuditLogEntry,
61
61
  } from "../repositories/audit-log.repository.js";
62
- import type { Paginated } from "../pagination.js";
63
- import { DRIZZLE_DB, type Database } from "../db.js";
64
- import { KeyProviderService } from "../key-provider.js";
62
+ import type { Paginated } from "../helpers/pagination.js";
63
+ import { DRIZZLE_DB, type Database } from "../config/db.js";
64
+ import { KeyProviderService } from "../config/key-provider.js";
65
65
  import { OAuthRepository } from "../repositories/oauth.repository.js";
66
66
  import { PasswordResetRepository } from "../repositories/password-reset.repository.js";
67
- import { RATE_LIMIT_STORE } from "../rate-limit.store.js";
67
+ import { RATE_LIMIT_STORE } from "../cache/rate-limit.store.js";
68
68
  import type { Revoker } from "@/lib/auth/core/types.js";
69
69
  import type { AuthzContext } from "../guards/authz.guard.js";
70
70
  import {
@@ -80,7 +80,7 @@ import {
80
80
  import { sessions, users } from "../schema.js";
81
81
  import { SessionRepository } from "../repositories/session.repository.js";
82
82
  import { TwoFactorRepository } from "../repositories/two-factor.repository.js";
83
- import { toId, toIdOrNull } from "../id.helper.js";
83
+ import { toId, toIdOrNull } from "../helpers/id.helper.js";
84
84
 
85
85
  export interface AuthTokens {
86
86
  accessToken: string;
@@ -488,9 +488,7 @@ export class AuthService {
488
488
  return { twoFactorEnabled: user.twoFactorEnabled };
489
489
  }
490
490
 
491
- async listActiveSessions(
492
- userId: string,
493
- ): Promise<
491
+ async listActiveSessions(userId: string): Promise<
494
492
  Array<{
495
493
  id: string;
496
494
  createdAt: string;