@simple-auth-kit/cli 1.4.2 → 1.5.0
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.
- package/package.json +1 -1
- package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
- package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
- package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
- package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
- package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
- package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
- package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
- package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
- package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
- package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
- package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
- package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
- package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
- package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
- package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
- package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
- package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +57 -22
- package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +67 -19
- package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
- package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
- package/registry/combos/express-prisma/variants/base/src/seed.ts +78 -20
- package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
- package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +81 -26
- package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +73 -21
- package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
- package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
- package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
- package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +109 -29
- package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
- package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
- package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
- package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
- package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
- package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
- package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
- package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
- package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
- package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +59 -28
- package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
- package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +98 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
- package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +78 -20
- package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +55 -27
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +104 -29
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
- package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +109 -29
- package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
- package/registry.json +6 -4
- package/simple-auth-kit.ts +582 -124
- package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
- package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
- package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
- package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
- package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
- package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
- package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
- package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
- package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
- package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
- 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 {
|
|
13
|
-
|
|
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";
|
|
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 {
|
|
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({
|
|
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 =
|
|
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(
|
|
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(
|
|
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({
|
|
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(
|
|
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(
|
|
93
|
-
|
|
94
|
-
|
|
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 "
|
|
3
|
-
import { HttpError } from "
|
|
4
|
-
import type { PermissionCache } from "
|
|
5
|
-
import {
|
|
6
|
-
|
|
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)
|
|
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>(
|
|
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(
|
|
69
|
-
|
|
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(
|
|
85
|
-
|
|
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)
|
|
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 {
|
|
5
|
-
|
|
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:
|
|
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: [
|
|
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: {
|
|
36
|
-
|
|
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: {
|
|
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: {
|
|
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: {
|
|
116
|
+
content: {
|
|
117
|
+
"application/json": {
|
|
118
|
+
schema: { $ref: "#/components/schemas/CreateWorkspaceRequest" },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
88
121
|
},
|
|
89
122
|
responses: {
|
|
90
|
-
"201": {
|
|
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:
|
|
98
|
-
|
|
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: {
|
|
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:
|
|
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: {
|
|
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(
|
|
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: {
|
|
192
|
+
content: {
|
|
193
|
+
"application/json": {
|
|
194
|
+
schema: { $ref: "#/components/schemas/AddMemberRequest" },
|
|
195
|
+
},
|
|
196
|
+
},
|
|
134
197
|
},
|
|
135
198
|
responses: {
|
|
136
|
-
"201": {
|
|
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(
|
|
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: [
|
|
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: {
|
|
237
|
+
content: {
|
|
238
|
+
"application/json": {
|
|
239
|
+
schema: { $ref: "#/components/schemas/SetMemberRolesRequest" },
|
|
240
|
+
},
|
|
241
|
+
},
|
|
157
242
|
},
|
|
158
243
|
responses: {
|
|
159
|
-
"200": {
|
|
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(
|
|
163
|
-
|
|
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:
|
|
269
|
+
description:
|
|
270
|
+
requiresPermission("members:manage") +
|
|
271
|
+
" Their direct permission grants go with the membership.",
|
|
172
272
|
security: [{ bearerAuth: [] }],
|
|
173
|
-
parameters: [
|
|
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": {
|
|
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(
|
|
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
|
},
|