@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
@@ -5,8 +5,8 @@ import {
5
5
  normalizeLimit,
6
6
  normalizePage,
7
7
  type Paginated,
8
- } from "../pagination.js";
9
- import { toIdOrNull, toIdOrUndefined } from "../id.helper.js";
8
+ } from "../helpers/pagination.js";
9
+ import { toIdOrNull, toIdOrUndefined } from "../helpers/id.helper.js";
10
10
 
11
11
  export interface AuditLogEntry {
12
12
  id: string;
@@ -1,14 +1,14 @@
1
1
  import { resolvePermissions } from "@/lib/auth/core/rbac.js";
2
2
  import { Prisma, PrismaClient } from "../../generated/prisma/client.js";
3
- import { HttpError } from "../http-error.js";
3
+ import { HttpError } from "../errors/http-error.js";
4
4
  import {
5
5
  buildPageMeta,
6
6
  normalizeLimit,
7
7
  normalizePage,
8
8
  type Paginated,
9
- } from "../pagination.js";
10
- import { PermissionCache } from "../permission-cache.js";
11
- import { toId, toIdOrNull } from "../id.helper.js";
9
+ } from "../helpers/pagination.js";
10
+ import { PermissionCache } from "../cache/permission-cache.js";
11
+ import { toId, toIdOrNull } from "../helpers/id.helper.js";
12
12
 
13
13
  export interface UserSummary {
14
14
  id: string;
@@ -1,6 +1,6 @@
1
1
  import type { NextFunction, Request, RequestHandler, Response } from "express";
2
2
  import { AuthService } from "../services/auth.service.js";
3
- import { HttpError } from "../http-error.js";
3
+ import { HttpError } from "../errors/http-error.js";
4
4
  import {
5
5
  ability,
6
6
  createTieredRouter,
@@ -54,15 +54,13 @@ export function createAdminRouter(deps: AdminRouterDeps): RequestHandler {
54
54
  string,
55
55
  string | undefined
56
56
  >;
57
- res
58
- .status(200)
59
- .json(
60
- await auth.listUsers({
61
- search,
62
- page: page ? Number(page) : undefined,
63
- limit: limit ? Number(limit) : undefined,
64
- }),
65
- );
57
+ res.status(200).json(
58
+ await auth.listUsers({
59
+ search,
60
+ page: page ? Number(page) : undefined,
61
+ limit: limit ? Number(limit) : undefined,
62
+ }),
63
+ );
66
64
  } catch (err) {
67
65
  next(err);
68
66
  }
@@ -185,18 +183,16 @@ export function createAdminRouter(deps: AdminRouterDeps): RequestHandler {
185
183
  try {
186
184
  const { userId, action, since, until, page, limit } =
187
185
  req.query as Record<string, string | undefined>;
188
- res
189
- .status(200)
190
- .json(
191
- await auth.listAuditLog({
192
- userId,
193
- action,
194
- since,
195
- until,
196
- page: page ? Number(page) : undefined,
197
- limit: limit ? Number(limit) : undefined,
198
- }),
199
- );
186
+ res.status(200).json(
187
+ await auth.listAuditLog({
188
+ userId,
189
+ action,
190
+ since,
191
+ until,
192
+ page: page ? Number(page) : undefined,
193
+ limit: limit ? Number(limit) : undefined,
194
+ }),
195
+ );
200
196
  } catch (err) {
201
197
  next(err);
202
198
  }
@@ -42,17 +42,17 @@ import {
42
42
  verifyTotpCode,
43
43
  } from "@/lib/auth/core/two-factor.js";
44
44
  import type { Revoker } from "@/lib/auth/core/types.js";
45
- import type { AuthConfig } from "../auth.config.js";
46
- import { HttpError } from "../http-error.js";
45
+ import type { AuthConfig } from "../config/auth.config.js";
46
+ import { HttpError } from "../errors/http-error.js";
47
47
  import {
48
48
  AuditLogEntry,
49
49
  AuditLogListFilter,
50
50
  AuditLogRepository,
51
51
  toAuditLogEntry,
52
52
  } from "../repositories/audit-log.repository.js";
53
- import type { Paginated } from "../pagination.js";
53
+ import type { Paginated } from "../helpers/pagination.js";
54
54
  import { PrismaClient } from "../../generated/prisma/client.js";
55
- import { KeyProviderService } from "../key-provider.js";
55
+ import { KeyProviderService } from "../config/key-provider.js";
56
56
  import { OAuthRepository } from "../repositories/oauth.repository.js";
57
57
  import { PasswordResetRepository } from "../repositories/password-reset.repository.js";
58
58
  import {
@@ -67,7 +67,7 @@ import {
67
67
  } from "../repositories/rbac.repository.js";
68
68
  import { SessionRepository } from "../repositories/session.repository.js";
69
69
  import { TwoFactorRepository } from "../repositories/two-factor.repository.js";
70
- import { toId, toIdOrNull } from "../id.helper.js";
70
+ import { toId, toIdOrNull } from "../helpers/id.helper.js";
71
71
 
72
72
  export interface AuthTokens {
73
73
  accessToken: string;
@@ -454,9 +454,7 @@ export class AuthService {
454
454
  return { twoFactorEnabled: user.twoFactorEnabled };
455
455
  }
456
456
 
457
- async listActiveSessions(
458
- userId: string,
459
- ): Promise<
457
+ async listActiveSessions(userId: string): Promise<
460
458
  Array<{
461
459
  id: string;
462
460
  createdAt: string;
@@ -6,13 +6,25 @@
6
6
  import "dotenv/config";
7
7
  import { PrismaPg } from "@prisma/adapter-pg";
8
8
  import { PrismaClient } from "../generated/prisma/client.js";
9
- import { toId } from "../src/id.helper.js";
10
- import { provisionDefaultRoles, SEED_ADMIN_ROLES } from "../src/rbac.defaults.js";
11
- import { POLICY_VERSION_KEY } from "../src/permission-cache.js";
9
+ import { toId } from "../src/helpers/id.helper.js";
10
+ import {
11
+ provisionDefaultRoles,
12
+ SEED_ADMIN_ROLES,
13
+ } from "../src/rbac.defaults.js";
14
+ import { POLICY_VERSION_KEY } from "../src/cache/permission-cache.js";
12
15
  import { permissionCacheStore } from "./bootstrap.js";
13
- import { renewingToken, type AdminSession, type AuthTokens, type Principal, type ProofContext, type VariantHooks } from "./harness.js";
16
+ import {
17
+ renewingToken,
18
+ type AdminSession,
19
+ type AuthTokens,
20
+ type Principal,
21
+ type ProofContext,
22
+ type VariantHooks,
23
+ } from "./harness.js";
14
24
 
15
- const prisma = new PrismaClient({ adapter: new PrismaPg({ connectionString: process.env["DATABASE_URL"] }) });
25
+ const prisma = new PrismaClient({
26
+ adapter: new PrismaPg({ connectionString: process.env["DATABASE_URL"] }),
27
+ });
16
28
 
17
29
  /** Remembered by `makeAdmin` so `freshToken()` can mint a new one whenever a section needs it. */
18
30
  let adminCredentials: { identifier: string; password: string } | undefined;
@@ -20,30 +32,49 @@ let adminCredentials: { identifier: string; password: string } | undefined;
20
32
  export const hooks: VariantHooks = {
21
33
  variant: "base",
22
34
 
23
- async makeAdmin(ctx: ProofContext, principal: Principal): Promise<AdminSession> {
35
+ async makeAdmin(
36
+ ctx: ProofContext,
37
+ principal: Principal,
38
+ ): Promise<AdminSession> {
24
39
  // The role slug on its own confers nothing — no guard checks for the string "admin". Authority
25
40
  // comes from the PermissionRole rows behind it, and in this variant the seeder is the only
26
41
  // thing that writes them, so the hook does what `npm run seed` does: calls the same
27
42
  // provisioning function, from the same definition the routes are typed against. Idempotent,
28
43
  // so it is safe against a database that has already been seeded.
29
44
  await provisionDefaultRoles(prisma);
30
- const roles = await prisma.role.findMany({ where: { slug: { in: SEED_ADMIN_ROLES } }, select: { id: true } });
31
- await prisma.roleUser.createMany({ data: roles.map((role) => ({ userId: toId(principal.userId), roleId: role.id })), skipDuplicates: true });
45
+ const roles = await prisma.role.findMany({
46
+ where: { slug: { in: SEED_ADMIN_ROLES } },
47
+ select: { id: true },
48
+ });
49
+ await prisma.roleUser.createMany({
50
+ data: roles.map((role) => ({
51
+ userId: toId(principal.userId),
52
+ roleId: role.id,
53
+ })),
54
+ skipDuplicates: true,
55
+ });
32
56
 
33
- adminCredentials = { identifier: principal.email, password: principal.password };
57
+ adminCredentials = {
58
+ identifier: principal.email,
59
+ password: principal.password,
60
+ };
34
61
  // Those writes went straight to the database, behind the running app's back, so nothing
35
62
  // bumped a version counter and the caller's already-cached (empty) permissions would still be
36
63
  // served. Bumping the policy counter is exactly what an out-of-band writer is supposed to do —
37
64
  // see permission-cache.ts. Logging in would not have helped: nothing about authorization is
38
65
  // in the token.
39
66
  await permissionCacheStore.bump(POLICY_VERSION_KEY);
40
- const login = await ctx.call("POST", "/auth/login", { body: { identifier: principal.email, password: principal.password } });
67
+ const login = await ctx.call("POST", "/auth/login", {
68
+ body: { identifier: principal.email, password: principal.password },
69
+ });
41
70
  const token = (login.body as AuthTokens).accessToken;
42
71
  ctx.assert(!!token, `the seeded admin can log in (got ${login.status})`);
43
72
  return {
44
73
  token,
45
74
  freshToken: renewingToken(async () => {
46
- const res = await ctx.call("POST", "/auth/login", { body: adminCredentials! });
75
+ const res = await ctx.call("POST", "/auth/login", {
76
+ body: adminCredentials!,
77
+ });
47
78
  return (res.body as AuthTokens).accessToken;
48
79
  }),
49
80
  };
@@ -53,16 +84,37 @@ export const hooks: VariantHooks = {
53
84
  // Nothing to do: admin endpoints operate on the whole deployment.
54
85
  },
55
86
 
56
- async proveVariantProperties(ctx: ProofContext, admin: AdminSession): Promise<void> {
57
- const listed = await ctx.call("GET", "/auth/admin/users", { token: await admin.freshToken() });
58
- ctx.assert(listed.status === 200, `admin can list every user in the deployment (got ${listed.status})`);
59
- ctx.assert(Array.isArray(listed.body?.items) && listed.body.items.length > 0, "the user list is non-empty");
87
+ async proveVariantProperties(
88
+ ctx: ProofContext,
89
+ admin: AdminSession,
90
+ ): Promise<void> {
91
+ const listed = await ctx.call("GET", "/auth/admin/users", {
92
+ token: await admin.freshToken(),
93
+ });
94
+ ctx.assert(
95
+ listed.status === 200,
96
+ `admin can list every user in the deployment (got ${listed.status})`,
97
+ );
98
+ ctx.assert(
99
+ Array.isArray(listed.body?.items) && listed.body.items.length > 0,
100
+ "the user list is non-empty",
101
+ );
60
102
 
61
- const workspaces = await ctx.call("GET", "/workspaces", { token: await admin.freshToken() });
62
- ctx.assert(workspaces.status === 404, `no workspace endpoints exist in this variant (got ${workspaces.status})`);
103
+ const workspaces = await ctx.call("GET", "/workspaces", {
104
+ token: await admin.freshToken(),
105
+ });
106
+ ctx.assert(
107
+ workspaces.status === 404,
108
+ `no workspace endpoints exist in this variant (got ${workspaces.status})`,
109
+ );
63
110
 
64
- const me = await ctx.call("GET", "/auth/me", { token: await admin.freshToken() });
65
- ctx.assert(me.body?.roles?.includes("admin"), "roles are global: they apply to every request this user makes");
111
+ const me = await ctx.call("GET", "/auth/me", {
112
+ token: await admin.freshToken(),
113
+ });
114
+ ctx.assert(
115
+ me.body?.roles?.includes("admin"),
116
+ "roles are global: they apply to every request this user makes",
117
+ );
66
118
 
67
119
  // --- authorization is not in the token, so a change lands on the next *request* ---
68
120
  //
@@ -76,27 +128,58 @@ export const hooks: VariantHooks = {
76
128
  const email = ctx.uniqueEmail("immediate");
77
129
  const password = "immediate-pw-12345";
78
130
  const tokens = await ctx.signup(email, password);
79
- const userId = (await ctx.call("GET", "/auth/me", { token: tokens.accessToken })).body.sub as string;
131
+ const userId = (
132
+ await ctx.call("GET", "/auth/me", { token: tokens.accessToken })
133
+ ).body.sub as string;
80
134
 
81
- const beforeGrant = await ctx.call("GET", "/auth/admin/users", { token: tokens.accessToken });
82
- ctx.assert(beforeGrant.status === 403, `a user with no grant is refused (got ${beforeGrant.status})`);
135
+ const beforeGrant = await ctx.call("GET", "/auth/admin/users", {
136
+ token: tokens.accessToken,
137
+ });
138
+ ctx.assert(
139
+ beforeGrant.status === 403,
140
+ `a user with no grant is refused (got ${beforeGrant.status})`,
141
+ );
83
142
 
84
- const granted = await ctx.call("POST", `/auth/admin/users/${userId}/permissions`, { token: await admin.freshToken(), body: { permission: "users:read" } });
85
- ctx.assert(granted.status === 200 || granted.status === 201, `admin can grant a permission directly (got ${granted.status})`);
143
+ const granted = await ctx.call(
144
+ "POST",
145
+ `/auth/admin/users/${userId}/permissions`,
146
+ { token: await admin.freshToken(), body: { permission: "users:read" } },
147
+ );
148
+ ctx.assert(
149
+ granted.status === 200 || granted.status === 201,
150
+ `admin can grant a permission directly (got ${granted.status})`,
151
+ );
86
152
 
87
- const afterGrant = await ctx.call("GET", "/auth/admin/users", { token: tokens.accessToken });
88
- ctx.assert(afterGrant.status === 200, `…and the grant is in effect on the very next request, on the token they already held (got ${afterGrant.status})`);
153
+ const afterGrant = await ctx.call("GET", "/auth/admin/users", {
154
+ token: tokens.accessToken,
155
+ });
156
+ ctx.assert(
157
+ afterGrant.status === 200,
158
+ `…and the grant is in effect on the very next request, on the token they already held (got ${afterGrant.status})`,
159
+ );
89
160
 
90
161
  // The status of this revoke used to be the one admin call in the whole run that was never
91
162
  // asserted, which is exactly why a token that had quietly expired surfaced as a confusing
92
163
  // failure of the *next* assertion instead of this one.
93
- const revoked = await ctx.call("POST", `/auth/admin/users/${userId}/permissions/${encodeURIComponent("users:read")}/revoke`, {
94
- token: await admin.freshToken(),
95
- });
96
- ctx.assert(revoked.status === 200 || revoked.status === 201, `admin can revoke the direct grant (got ${revoked.status})`);
164
+ const revoked = await ctx.call(
165
+ "POST",
166
+ `/auth/admin/users/${userId}/permissions/${encodeURIComponent("users:read")}/revoke`,
167
+ {
168
+ token: await admin.freshToken(),
169
+ },
170
+ );
171
+ ctx.assert(
172
+ revoked.status === 200 || revoked.status === 201,
173
+ `admin can revoke the direct grant (got ${revoked.status})`,
174
+ );
97
175
 
98
- const afterRevoke = await ctx.call("GET", "/auth/admin/users", { token: tokens.accessToken });
99
- ctx.assert(afterRevoke.status === 403, `and the revocation lands on the very next request too, on the same token (got ${afterRevoke.status})`);
176
+ const afterRevoke = await ctx.call("GET", "/auth/admin/users", {
177
+ token: tokens.accessToken,
178
+ });
179
+ ctx.assert(
180
+ afterRevoke.status === 403,
181
+ `and the revocation lands on the very next request too, on the same token (got ${afterRevoke.status})`,
182
+ );
100
183
 
101
184
  await proveTheCacheIsReal(ctx, admin, tokens.accessToken);
102
185
  await proveARawDatabaseEditChangesEnforcement(ctx, admin);
@@ -108,7 +191,11 @@ export const hooks: VariantHooks = {
108
191
  * app resolves through (`test/bootstrap.ts` hands it to `AuthModule.forRoot`), so "the second
109
192
  * request did not touch the database" is a measurement rather than a claim.
110
193
  */
111
- async function proveTheCacheIsReal(ctx: ProofContext, admin: AdminSession, token: string): Promise<void> {
194
+ async function proveTheCacheIsReal(
195
+ ctx: ProofContext,
196
+ admin: AdminSession,
197
+ token: string,
198
+ ): Promise<void> {
112
199
  const N = 8;
113
200
  await ctx.call("GET", "/auth/me", { token }); // warm, so the measurement is of the steady state
114
201
  const before = { ...permissionCacheStore.stats };
@@ -120,16 +207,27 @@ async function proveTheCacheIsReal(ctx: ProofContext, admin: AdminSession, token
120
207
  `${N} identical authorized requests resolve permissions 0 further times — all ${N} were cache hits (misses +${after.misses - before.misses}, hits +${after.hits - before.hits})`,
121
208
  );
122
209
 
123
- const userId = (await ctx.call("GET", "/auth/me", { token })).body.sub as string;
210
+ const userId = (await ctx.call("GET", "/auth/me", { token })).body
211
+ .sub as string;
124
212
  const beforeGrant = { ...permissionCacheStore.stats };
125
- await ctx.call("POST", `/auth/admin/users/${userId}/permissions`, { token: await admin.freshToken(), body: { permission: "probe:cache" } });
213
+ await ctx.call("POST", `/auth/admin/users/${userId}/permissions`, {
214
+ token: await admin.freshToken(),
215
+ body: { permission: "probe:cache" },
216
+ });
126
217
  const afterGrantCall = await ctx.call("GET", "/auth/me", { token });
127
218
  ctx.assert(
128
219
  permissionCacheStore.stats.misses > beforeGrant.misses,
129
220
  "a grant bumps this user's version counter, so the next request misses the cache and re-resolves rather than serving a stale entry",
130
221
  );
131
- ctx.assert((afterGrantCall.body?.permissions ?? []).includes("probe:cache"), "…and the freshly resolved answer contains the new permission");
132
- await ctx.call("POST", `/auth/admin/users/${userId}/permissions/${encodeURIComponent("probe:cache")}/revoke`, { token: await admin.freshToken() });
222
+ ctx.assert(
223
+ (afterGrantCall.body?.permissions ?? []).includes("probe:cache"),
224
+ "…and the freshly resolved answer contains the new permission",
225
+ );
226
+ await ctx.call(
227
+ "POST",
228
+ `/auth/admin/users/${userId}/permissions/${encodeURIComponent("probe:cache")}/revoke`,
229
+ { token: await admin.freshToken() },
230
+ );
133
231
  }
134
232
 
135
233
  /**
@@ -137,23 +235,54 @@ async function proveTheCacheIsReal(ctx: ProofContext, admin: AdminSession, token
137
235
  * doing the writing — this rules out any possibility that it is also doing something in memory.
138
236
  * A `psql` session would look exactly like this.
139
237
  */
140
- async function proveARawDatabaseEditChangesEnforcement(ctx: ProofContext, admin: AdminSession): Promise<void> {
238
+ async function proveARawDatabaseEditChangesEnforcement(
239
+ ctx: ProofContext,
240
+ admin: AdminSession,
241
+ ): Promise<void> {
141
242
  const email = ctx.uniqueEmail("rawedit");
142
243
  const tokens = await ctx.signup(email, "rawedit-pw-12345");
143
- const userId = (await ctx.call("GET", "/auth/me", { token: tokens.accessToken })).body.sub as string;
144
- await ctx.call("POST", `/auth/admin/users/${userId}/permissions`, { token: await admin.freshToken(), body: { permission: "audit-log:read" } });
145
- ctx.assert((await ctx.call("GET", "/auth/admin/audit-log", { token: tokens.accessToken })).status === 200, "a direct grant opens the audit log");
244
+ const userId = (
245
+ await ctx.call("GET", "/auth/me", { token: tokens.accessToken })
246
+ ).body.sub as string;
247
+ await ctx.call("POST", `/auth/admin/users/${userId}/permissions`, {
248
+ token: await admin.freshToken(),
249
+ body: { permission: "audit-log:read" },
250
+ });
251
+ ctx.assert(
252
+ (
253
+ await ctx.call("GET", "/auth/admin/audit-log", {
254
+ token: tokens.accessToken,
255
+ })
256
+ ).status === 200,
257
+ "a direct grant opens the audit log",
258
+ );
146
259
 
147
260
  // Written straight to the table, with no code change and no redeploy. The bump that follows is
148
261
  // the out-of-band writer's half of the cache contract (permission-cache.ts) — without it the
149
262
  // edit would still land, but only once the cached entry expired.
150
- await prisma.permission.update({ where: { slug: "audit-log:read" }, data: { isActive: false } });
263
+ await prisma.permission.update({
264
+ where: { slug: "audit-log:read" },
265
+ data: { isActive: false },
266
+ });
151
267
  await permissionCacheStore.bump(POLICY_VERSION_KEY);
152
- const denied = await ctx.call("GET", "/auth/admin/audit-log", { token: tokens.accessToken });
153
- ctx.assert(denied.status === 403, `a permission deactivated by a raw database write stops opening its route (got ${denied.status})`);
268
+ const denied = await ctx.call("GET", "/auth/admin/audit-log", {
269
+ token: tokens.accessToken,
270
+ });
271
+ ctx.assert(
272
+ denied.status === 403,
273
+ `a permission deactivated by a raw database write stops opening its route (got ${denied.status})`,
274
+ );
154
275
 
155
- await prisma.permission.update({ where: { slug: "audit-log:read" }, data: { isActive: true } });
276
+ await prisma.permission.update({
277
+ where: { slug: "audit-log:read" },
278
+ data: { isActive: true },
279
+ });
156
280
  await permissionCacheStore.bump(POLICY_VERSION_KEY);
157
- const restored = await ctx.call("GET", "/auth/admin/audit-log", { token: tokens.accessToken });
158
- ctx.assert(restored.status === 200, `…and reactivating it in the database opens the route again (got ${restored.status})`);
281
+ const restored = await ctx.call("GET", "/auth/admin/audit-log", {
282
+ token: tokens.accessToken,
283
+ });
284
+ ctx.assert(
285
+ restored.status === 200,
286
+ `…and reactivating it in the database opens the route again (got ${restored.status})`,
287
+ );
159
288
  }
@@ -5,7 +5,7 @@ import type { RateLimitDeps } from "@/lib/auth/core/rate-limit.js";
5
5
  import { createAdminRouter } from "./routers/admin.router.js";
6
6
  import { AuditLogRepository } from "./repositories/audit-log.repository.js";
7
7
  import { authCoreErrorMiddleware } from "./middleware/auth-core-error.middleware.js";
8
- import { AuthConfig, defaultAuthConfig } from "./auth.config.js";
8
+ import { AuthConfig, defaultAuthConfig } from "./config/auth.config.js";
9
9
  import { createAuthMiddleware } from "./middleware/auth.middleware.js";
10
10
  import { createAuthRouter } from "./routers/auth.router.js";
11
11
  import { AuthService } from "./services/auth.service.js";
@@ -14,15 +14,15 @@ import {
14
14
  createWorkspaceMiddleware,
15
15
  } from "./middleware/authz.middleware.js";
16
16
  import { PrismaClient } from "./../generated/prisma/client.js";
17
- import { KeyProviderService } from "./key-provider.js";
17
+ import { KeyProviderService } from "./config/key-provider.js";
18
18
  import { OAuthRepository } from "./repositories/oauth.repository.js";
19
- import { openApiSpec } from "./openapi-spec.js";
19
+ import { openApiSpec } from "./openapi/openapi-spec.js";
20
20
  import { PasswordResetRepository } from "./repositories/password-reset.repository.js";
21
21
  import {
22
22
  InMemoryPermissionCacheStore,
23
23
  PermissionCache,
24
- } from "./permission-cache.js";
25
- import { InMemoryRateLimitStore } from "./rate-limit.store.js";
24
+ } from "./cache/permission-cache.js";
25
+ import { InMemoryRateLimitStore } from "./cache/rate-limit.store.js";
26
26
  import { RbacRepository } from "./repositories/rbac.repository.js";
27
27
  import { responseEnvelope } from "./middleware/response-envelope.middleware.js";
28
28
  import { SessionRepository } from "./repositories/session.repository.js";
@@ -0,0 +1,8 @@
1
+ export * from "./create-auth-app.js";
2
+
3
+ export * from "./route-tiers.js";
4
+
5
+ export * from "./middleware/auth.middleware.js";
6
+ export * from "./middleware/authz.middleware.js";
7
+
8
+ export * from "./ability/ability.js";
@@ -1,7 +1,7 @@
1
1
  import type { NextFunction, Request, RequestHandler, Response } from "express";
2
- import { defineAbilitiesFor } from "../ability.js";
3
- import { HttpError } from "../http-error.js";
4
- import type { PermissionCache } from "../permission-cache.js";
2
+ import { defineAbilitiesFor } from "../ability/ability.js";
3
+ import { HttpError } from "../errors/http-error.js";
4
+ import type { PermissionCache } from "../cache/permission-cache.js";
5
5
  import {
6
6
  memberCacheKey,
7
7
  type RbacRepository,