@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
@@ -2,27 +2,33 @@ import { PrismaPg } from "@prisma/adapter-pg";
2
2
  import express, { type Express } from "express";
3
3
  import swaggerUi from "swagger-ui-express";
4
4
  import type { RateLimitDeps } from "@/lib/auth/core/rate-limit.js";
5
- import { createAdminRouter } from "./admin.router.js";
6
- import { AuditLogRepository } from "./audit-log.repository.js";
7
- import { authCoreErrorMiddleware } from "./auth-core-error.middleware.js";
5
+ import { createAdminRouter } from "./routers/admin.router.js";
6
+ import { AuditLogRepository } from "./repositories/audit-log.repository.js";
7
+ import { authCoreErrorMiddleware } from "./middleware/auth-core-error.middleware.js";
8
8
  import { AuthConfig, defaultAuthConfig } from "./auth.config.js";
9
- import { createAuthMiddleware } from "./auth.middleware.js";
10
- import { createAuthRouter } from "./auth.router.js";
11
- import { AuthService } from "./auth.service.js";
12
- import { createAuthzMiddleware, createWorkspaceMiddleware } from "./authz.middleware.js";
9
+ import { createAuthMiddleware } from "./middleware/auth.middleware.js";
10
+ import { createAuthRouter } from "./routers/auth.router.js";
11
+ import { AuthService } from "./services/auth.service.js";
12
+ import {
13
+ createAuthzMiddleware,
14
+ createWorkspaceMiddleware,
15
+ } from "./middleware/authz.middleware.js";
13
16
  import { PrismaClient } from "../generated/prisma/client.js";
14
17
  import { KeyProviderService } from "./key-provider.js";
15
- import { OAuthRepository } from "./oauth.repository.js";
18
+ import { OAuthRepository } from "./repositories/oauth.repository.js";
16
19
  import { openApiSpec } from "./openapi-spec.js";
17
- import { PasswordResetRepository } from "./password-reset.repository.js";
18
- import { InMemoryPermissionCacheStore, PermissionCache } from "./permission-cache.js";
20
+ import { PasswordResetRepository } from "./repositories/password-reset.repository.js";
21
+ import {
22
+ InMemoryPermissionCacheStore,
23
+ PermissionCache,
24
+ } from "./permission-cache.js";
19
25
  import { InMemoryRateLimitStore } from "./rate-limit.store.js";
20
- import { RbacRepository } from "./rbac.repository.js";
21
- import { responseEnvelope } from "./response-envelope.middleware.js";
22
- import { SessionRepository } from "./session.repository.js";
23
- import { TwoFactorRepository } from "./two-factor.repository.js";
24
- import { createWorkspaceRouter } from "./workspace.router.js";
25
- import { WorkspaceRepository } from "./workspace.repository.js";
26
+ import { RbacRepository } from "./repositories/rbac.repository.js";
27
+ import { responseEnvelope } from "./middleware/response-envelope.middleware.js";
28
+ import { SessionRepository } from "./repositories/session.repository.js";
29
+ import { TwoFactorRepository } from "./repositories/two-factor.repository.js";
30
+ import { createWorkspaceRouter } from "./routers/workspace.router.js";
31
+ import { WorkspaceRepository } from "./repositories/workspace.repository.js";
26
32
 
27
33
  export interface CreateAuthAppOptions {
28
34
  /** Overrides merged on top of `defaultAuthConfig`, same shape as the reference combo's `AuthModule.forRoot(config)`. */
@@ -41,7 +47,9 @@ export interface CreateAuthAppOptions {
41
47
  export function createAuthApp(options: CreateAuthAppOptions = {}): Express {
42
48
  const config: AuthConfig = { ...defaultAuthConfig, ...options.config };
43
49
 
44
- const adapter = new PrismaPg({ connectionString: process.env["DATABASE_URL"] });
50
+ const adapter = new PrismaPg({
51
+ connectionString: process.env["DATABASE_URL"],
52
+ });
45
53
  const prisma = new PrismaClient({ adapter });
46
54
 
47
55
  const auditLog = new AuditLogRepository(prisma);
@@ -49,23 +57,41 @@ export function createAuthApp(options: CreateAuthAppOptions = {}): Express {
49
57
  const keys = new KeyProviderService();
50
58
  // Swap the store for a Redis-backed one by passing `rateLimitStore` in `config` — nothing in
51
59
  // this library's source changes.
52
- const rateLimit: RateLimitDeps = config.rateLimitStore ?? new InMemoryRateLimitStore();
60
+ const rateLimit: RateLimitDeps =
61
+ config.rateLimitStore ?? new InMemoryRateLimitStore();
53
62
  // The cache seam. Swap the store for a Redis-backed one by passing `permissionCacheStore` in
54
63
  // `config` — nothing in this library's source changes. Keys are namespaced simpleauthkit:authz:*.
55
- const permissionCache = new PermissionCache(config.permissionCacheStore ?? new InMemoryPermissionCacheStore(), config);
64
+ const permissionCache = new PermissionCache(
65
+ config.permissionCacheStore ?? new InMemoryPermissionCacheStore(),
66
+ config,
67
+ );
56
68
  const rbac = new RbacRepository(prisma, permissionCache);
57
69
  const workspaces = new WorkspaceRepository(prisma, rbac);
58
70
  const twoFactor = new TwoFactorRepository(prisma);
59
71
  const oauth = new OAuthRepository(prisma);
60
72
  const passwordReset = new PasswordResetRepository(prisma);
61
- const authService = new AuthService(prisma, sessions, keys, rateLimit, auditLog, rbac, twoFactor, oauth, passwordReset, config);
73
+ const authService = new AuthService(
74
+ prisma,
75
+ sessions,
76
+ keys,
77
+ rateLimit,
78
+ auditLog,
79
+ rbac,
80
+ twoFactor,
81
+ oauth,
82
+ passwordReset,
83
+ config,
84
+ );
62
85
 
63
86
  const authentication = createAuthMiddleware({ keys, sessions });
64
87
  // Roles belong to a workspace membership, resolved from the database on the request that names
65
88
  // the workspace. `authorization` tolerates a request that names none (GET /auth/me answers with
66
89
  // empty roles); `workspaceScope` does not. See authz.middleware.ts.
67
90
  const authorization = createAuthzMiddleware({ rbac, cache: permissionCache });
68
- const workspaceScope = createWorkspaceMiddleware({ rbac, cache: permissionCache });
91
+ const workspaceScope = createWorkspaceMiddleware({
92
+ rbac,
93
+ cache: permissionCache,
94
+ });
69
95
 
70
96
  if (!config.permissionCacheStore || !config.rateLimitStore) {
71
97
  console.warn(
@@ -84,14 +110,43 @@ export function createAuthApp(options: CreateAuthAppOptions = {}): Express {
84
110
  // slash) a plain 200 instead of swagger-ui-express's default 301 to "/docs/" — the
85
111
  // underlying static asset middleware would otherwise treat the mount root as a directory
86
112
  // listing and redirect.
87
- app.use("/docs", swaggerUi.serveWithOptions({ redirect: false }), swaggerUi.setup(openApiSpec));
113
+ app.use(
114
+ "/docs",
115
+ swaggerUi.serveWithOptions({ redirect: false }),
116
+ swaggerUi.setup(openApiSpec),
117
+ );
88
118
  app.get("/docs-json", (_req, res) => res.json(openApiSpec));
89
119
  app.use(responseEnvelope());
90
120
  // Mounted before /auth so `/auth/admin/*` reaches the admin router rather than falling
91
121
  // through to the identity router's 404.
92
- app.use("/auth/admin", createAdminRouter({ auth: authService, workspaces, authentication, workspaceScope }));
93
- app.use("/auth", createAuthRouter({ auth: authService, config, authentication, authorization }));
94
- app.use("/workspaces", createWorkspaceRouter({ workspaces, rbac, authentication, authorization, workspaceScope }));
122
+ app.use(
123
+ "/auth/admin",
124
+ createAdminRouter({
125
+ auth: authService,
126
+ workspaces,
127
+ authentication,
128
+ workspaceScope,
129
+ }),
130
+ );
131
+ app.use(
132
+ "/auth",
133
+ createAuthRouter({
134
+ auth: authService,
135
+ config,
136
+ authentication,
137
+ authorization,
138
+ }),
139
+ );
140
+ app.use(
141
+ "/workspaces",
142
+ createWorkspaceRouter({
143
+ workspaces,
144
+ rbac,
145
+ authentication,
146
+ authorization,
147
+ workspaceScope,
148
+ }),
149
+ );
95
150
  // Mounted last so it catches errors forwarded via `next(err)` from every route above.
96
151
  app.use(authCoreErrorMiddleware);
97
152
 
@@ -1,9 +1,12 @@
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";
5
- import { memberCacheKey, type RbacRepository } from "./rbac.repository.js";
6
- import "./request-context.js";
2
+ import { defineAbilitiesFor } from "../ability.js";
3
+ import { HttpError } from "../http-error.js";
4
+ import type { PermissionCache } from "../permission-cache.js";
5
+ import {
6
+ memberCacheKey,
7
+ type RbacRepository,
8
+ } from "../repositories/rbac.repository.js";
9
+ import "../request-context.js";
7
10
 
8
11
  export const WORKSPACE_HEADER = "x-workspace-id";
9
12
 
@@ -45,10 +48,14 @@ async function resolve(deps: AuthzMiddlewareDeps, req: Request): Promise<void> {
45
48
  if (req.authz) return;
46
49
 
47
50
  const workspaceId = req.headers[WORKSPACE_HEADER];
48
- if (!req.auth || typeof workspaceId !== "string" || workspaceId.length === 0) return;
51
+ if (!req.auth || typeof workspaceId !== "string" || workspaceId.length === 0)
52
+ return;
49
53
 
50
54
  const userId = req.auth.sub;
51
- const authz = await deps.cache.resolve<AuthzContext>(memberCacheKey(userId, workspaceId), () => deps.rbac.resolveAuthzContext(userId, workspaceId));
55
+ const authz = await deps.cache.resolve<AuthzContext>(
56
+ memberCacheKey(userId, workspaceId),
57
+ () => deps.rbac.resolveAuthzContext(userId, workspaceId),
58
+ );
52
59
  // Deliberately the same answer for "no such workspace" and "not your workspace": a caller
53
60
  // outside a workspace must not be able to probe whether it exists.
54
61
  if (!authz) throw new HttpError(403, "not a member of this workspace");
@@ -65,8 +72,14 @@ async function resolve(deps: AuthzMiddlewareDeps, req: Request): Promise<void> {
65
72
  *
66
73
  * Replaces the reference combo's `AuthzGuard`.
67
74
  */
68
- export function createAuthzMiddleware(deps: AuthzMiddlewareDeps): RequestHandler {
69
- return async function authzMiddleware(req: Request, _res: Response, next: NextFunction): Promise<void> {
75
+ export function createAuthzMiddleware(
76
+ deps: AuthzMiddlewareDeps,
77
+ ): RequestHandler {
78
+ return async function authzMiddleware(
79
+ req: Request,
80
+ _res: Response,
81
+ next: NextFunction,
82
+ ): Promise<void> {
70
83
  try {
71
84
  await resolve(deps, req);
72
85
  next();
@@ -81,11 +94,18 @@ export function createAuthzMiddleware(deps: AuthzMiddlewareDeps): RequestHandler
81
94
  *
82
95
  * Replaces the reference combo's `WorkspaceGuard`.
83
96
  */
84
- export function createWorkspaceMiddleware(deps: AuthzMiddlewareDeps): RequestHandler {
85
- return async function workspaceMiddleware(req: Request, _res: Response, next: NextFunction): Promise<void> {
97
+ export function createWorkspaceMiddleware(
98
+ deps: AuthzMiddlewareDeps,
99
+ ): RequestHandler {
100
+ return async function workspaceMiddleware(
101
+ req: Request,
102
+ _res: Response,
103
+ next: NextFunction,
104
+ ): Promise<void> {
86
105
  try {
87
106
  await resolve(deps, req);
88
- if (!req.authz) throw new HttpError(403, `${WORKSPACE_HEADER} header is required`);
107
+ if (!req.authz)
108
+ throw new HttpError(403, `${WORKSPACE_HEADER} header is required`);
89
109
  next();
90
110
  } catch (err) {
91
111
  next(err);
@@ -1,8 +1,13 @@
1
1
  // The workspace-membership half of the OpenAPI document. Mirrors the reference combo's
2
2
  // `dto/workspace.dto.ts`; merged into the administration fragment by openapi-admin.ts, which is
3
3
  // what openapi-spec.ts assembles into the document.
4
- import { errorResponse, missingPermission, requiresPermission, type OpenApiFragment } from "./openapi-fragment.js";
5
- import { WORKSPACE_HEADER } from "./authz.middleware.js";
4
+ import {
5
+ errorResponse,
6
+ missingPermission,
7
+ requiresPermission,
8
+ type OpenApiFragment,
9
+ } from "./openapi-fragment.js";
10
+ import { WORKSPACE_HEADER } from "./middleware/authz.middleware.js";
6
11
 
7
12
  /** Every route that acts *inside* a workspace names it with this header, never a path segment. */
8
13
  export const workspaceHeaderParameter = {
@@ -10,7 +15,8 @@ export const workspaceHeaderParameter = {
10
15
  in: "header",
11
16
  required: true,
12
17
  schema: { type: "string" },
13
- description: "The workspace this request acts in. The caller must be a member of it.",
18
+ description:
19
+ "The workspace this request acts in. The caller must be a member of it.",
14
20
  };
15
21
 
16
22
  export const workspaceSpec: OpenApiFragment = {
@@ -18,7 +24,14 @@ export const workspaceSpec: OpenApiFragment = {
18
24
 
19
25
  // `GET /auth/me` works with or without it: named, it answers with that workspace's roles;
20
26
  // omitted, with none.
21
- scopeParameters: [{ ...workspaceHeaderParameter, required: false, description: "Answer for this workspace. Omitted, `roles` and `permissions` come back empty." }],
27
+ scopeParameters: [
28
+ {
29
+ ...workspaceHeaderParameter,
30
+ required: false,
31
+ description:
32
+ "Answer for this workspace. Omitted, `roles` and `permissions` come back empty.",
33
+ },
34
+ ],
22
35
 
23
36
  schemas: {
24
37
  CreateWorkspaceRequest: {
@@ -32,15 +45,27 @@ export const workspaceSpec: OpenApiFragment = {
32
45
  type: "object",
33
46
  required: ["email"],
34
47
  properties: {
35
- email: { type: "string", description: "Must already be a registered user — this library has no invite flow" },
36
- roles: { type: "array", items: { type: "string" }, description: 'Defaults to ["member"]' },
48
+ email: {
49
+ type: "string",
50
+ description:
51
+ "Must already be a registered user — this library has no invite flow",
52
+ },
53
+ roles: {
54
+ type: "array",
55
+ items: { type: "string" },
56
+ description: 'Defaults to ["member"]',
57
+ },
37
58
  },
38
59
  },
39
60
  SetMemberRolesRequest: {
40
61
  type: "object",
41
62
  required: ["roles"],
42
63
  properties: {
43
- roles: { type: "array", items: { type: "string" }, description: "Replaces the member's whole role set" },
64
+ roles: {
65
+ type: "array",
66
+ items: { type: "string" },
67
+ description: "Replaces the member's whole role set",
68
+ },
44
69
  },
45
70
  },
46
71
  WorkspaceSummary: {
@@ -50,7 +75,11 @@ export const workspaceSpec: OpenApiFragment = {
50
75
  id: { type: "string" },
51
76
  name: { type: "string" },
52
77
  createdAt: { type: "string", format: "date-time" },
53
- roles: { type: "array", items: { type: "string" }, description: "The calling user's roles in this workspace" },
78
+ roles: {
79
+ type: "array",
80
+ items: { type: "string" },
81
+ description: "The calling user's roles in this workspace",
82
+ },
54
83
  },
55
84
  },
56
85
  MembershipSummary: {
@@ -84,23 +113,43 @@ export const workspaceSpec: OpenApiFragment = {
84
113
  security: [{ bearerAuth: [] }],
85
114
  requestBody: {
86
115
  required: true,
87
- content: { "application/json": { schema: { $ref: "#/components/schemas/CreateWorkspaceRequest" } } },
116
+ content: {
117
+ "application/json": {
118
+ schema: { $ref: "#/components/schemas/CreateWorkspaceRequest" },
119
+ },
120
+ },
88
121
  },
89
122
  responses: {
90
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/WorkspaceSummary" } } } },
123
+ "201": {
124
+ description: "Created",
125
+ content: {
126
+ "application/json": {
127
+ schema: { $ref: "#/components/schemas/WorkspaceSummary" },
128
+ },
129
+ },
130
+ },
91
131
  "400": errorResponse("Missing required field"),
92
132
  "401": errorResponse("Missing or invalid access token"),
93
133
  },
94
134
  },
95
135
  get: {
96
136
  tags: ["workspaces"],
97
- summary: "List the workspaces the current user belongs to, with their roles in each",
98
- description: "Deliberately not permission-gated: it acts outside every workspace and only ever answers with the caller's own memberships.",
137
+ summary:
138
+ "List the workspaces the current user belongs to, with their roles in each",
139
+ description:
140
+ "Deliberately not permission-gated: it acts outside every workspace and only ever answers with the caller's own memberships.",
99
141
  security: [{ bearerAuth: [] }],
100
142
  responses: {
101
143
  "200": {
102
144
  description: "OK",
103
- content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/WorkspaceSummary" } } } },
145
+ content: {
146
+ "application/json": {
147
+ schema: {
148
+ type: "array",
149
+ items: { $ref: "#/components/schemas/WorkspaceSummary" },
150
+ },
151
+ },
152
+ },
104
153
  },
105
154
  "401": errorResponse("Missing or invalid access token"),
106
155
  },
@@ -110,16 +159,26 @@ export const workspaceSpec: OpenApiFragment = {
110
159
  get: {
111
160
  tags: ["workspaces"],
112
161
  summary: "List the members of the named workspace",
113
- description: "Gated on membership of the named workspace rather than on a permission — seeing who else is in a room you are in is not an administrative capability.",
162
+ description:
163
+ "Gated on membership of the named workspace rather than on a permission — seeing who else is in a room you are in is not an administrative capability.",
114
164
  security: [{ bearerAuth: [] }],
115
165
  parameters: [workspaceHeaderParameter],
116
166
  responses: {
117
167
  "200": {
118
168
  description: "OK",
119
- content: { "application/json": { schema: { type: "array", items: { $ref: "#/components/schemas/MembershipSummary" } } } },
169
+ content: {
170
+ "application/json": {
171
+ schema: {
172
+ type: "array",
173
+ items: { $ref: "#/components/schemas/MembershipSummary" },
174
+ },
175
+ },
176
+ },
120
177
  },
121
178
  "401": errorResponse("Missing or invalid access token"),
122
- "403": errorResponse("Not a member of this workspace, or no workspace named"),
179
+ "403": errorResponse(
180
+ "Not a member of this workspace, or no workspace named",
181
+ ),
123
182
  },
124
183
  },
125
184
  post: {
@@ -130,13 +189,27 @@ export const workspaceSpec: OpenApiFragment = {
130
189
  parameters: [workspaceHeaderParameter],
131
190
  requestBody: {
132
191
  required: true,
133
- content: { "application/json": { schema: { $ref: "#/components/schemas/AddMemberRequest" } } },
192
+ content: {
193
+ "application/json": {
194
+ schema: { $ref: "#/components/schemas/AddMemberRequest" },
195
+ },
196
+ },
134
197
  },
135
198
  responses: {
136
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/MembershipSummary" } } } },
199
+ "201": {
200
+ description: "Created",
201
+ content: {
202
+ "application/json": {
203
+ schema: { $ref: "#/components/schemas/MembershipSummary" },
204
+ },
205
+ },
206
+ },
137
207
  "400": errorResponse("Missing required field"),
138
208
  "401": errorResponse("Missing or invalid access token"),
139
- "403": missingPermission("members:manage", "the request named no workspace you belong to"),
209
+ "403": missingPermission(
210
+ "members:manage",
211
+ "the request named no workspace you belong to",
212
+ ),
140
213
  "404": errorResponse("No user with that email"),
141
214
  "409": errorResponse("Already a member of this workspace"),
142
215
  },
@@ -150,17 +223,42 @@ export const workspaceSpec: OpenApiFragment = {
150
223
  requiresPermission("roles:assign") +
151
224
  " It deliberately shares that key with `POST /auth/admin/users/{userId}/roles` rather than minting one of its own: it is the same capability reached by a different path.",
152
225
  security: [{ bearerAuth: [] }],
153
- parameters: [workspaceHeaderParameter, { name: "memberId", in: "path", required: true, schema: { type: "string" } }],
226
+ parameters: [
227
+ workspaceHeaderParameter,
228
+ {
229
+ name: "memberId",
230
+ in: "path",
231
+ required: true,
232
+ schema: { type: "string" },
233
+ },
234
+ ],
154
235
  requestBody: {
155
236
  required: true,
156
- content: { "application/json": { schema: { $ref: "#/components/schemas/SetMemberRolesRequest" } } },
237
+ content: {
238
+ "application/json": {
239
+ schema: { $ref: "#/components/schemas/SetMemberRolesRequest" },
240
+ },
241
+ },
157
242
  },
158
243
  responses: {
159
- "200": { description: "OK", content: { "application/json": { schema: { $ref: "#/components/schemas/MemberRolesResponse" } } } },
244
+ "200": {
245
+ description: "OK",
246
+ content: {
247
+ "application/json": {
248
+ schema: { $ref: "#/components/schemas/MemberRolesResponse" },
249
+ },
250
+ },
251
+ },
160
252
  "400": errorResponse("Missing required field"),
161
253
  "401": errorResponse("Missing or invalid access token"),
162
- "403": missingPermission("roles:assign", "cannot change your own roles", "the request named no workspace you belong to"),
163
- "404": errorResponse("Member not found in this workspace, or a role is not defined in it"),
254
+ "403": missingPermission(
255
+ "roles:assign",
256
+ "cannot change your own roles",
257
+ "the request named no workspace you belong to",
258
+ ),
259
+ "404": errorResponse(
260
+ "Member not found in this workspace, or a role is not defined in it",
261
+ ),
164
262
  },
165
263
  },
166
264
  },
@@ -168,13 +266,34 @@ export const workspaceSpec: OpenApiFragment = {
168
266
  delete: {
169
267
  tags: ["workspaces"],
170
268
  summary: "[admin] Remove a member from the named workspace",
171
- description: requiresPermission("members:manage") + " Their direct permission grants go with the membership.",
269
+ description:
270
+ requiresPermission("members:manage") +
271
+ " Their direct permission grants go with the membership.",
172
272
  security: [{ bearerAuth: [] }],
173
- parameters: [workspaceHeaderParameter, { name: "memberId", in: "path", required: true, schema: { type: "string" } }],
273
+ parameters: [
274
+ workspaceHeaderParameter,
275
+ {
276
+ name: "memberId",
277
+ in: "path",
278
+ required: true,
279
+ schema: { type: "string" },
280
+ },
281
+ ],
174
282
  responses: {
175
- "200": { description: "OK", content: { "application/json": { schema: { $ref: "#/components/schemas/OkResponse" } } } },
283
+ "200": {
284
+ description: "OK",
285
+ content: {
286
+ "application/json": {
287
+ schema: { $ref: "#/components/schemas/OkResponse" },
288
+ },
289
+ },
290
+ },
176
291
  "401": errorResponse("Missing or invalid access token"),
177
- "403": missingPermission("members:manage", "cannot remove yourself from a workspace you administer", "the request named no workspace you belong to"),
292
+ "403": missingPermission(
293
+ "members:manage",
294
+ "cannot remove yourself from a workspace you administer",
295
+ "the request named no workspace you belong to",
296
+ ),
178
297
  "404": errorResponse("Member not found in this workspace"),
179
298
  },
180
299
  },