@simple-auth-kit/cli 1.5.1 → 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.
- package/package.json +1 -1
- package/registry/combos/express-drizzle/shared/src/{ability.ts → ability/ability.ts} +8 -2
- package/registry/combos/express-drizzle/shared/src/{permission-cache.ts → cache/permission-cache.ts} +25 -9
- package/registry/combos/express-drizzle/shared/src/{rate-limit.store.ts → cache/rate-limit.store.ts} +4 -1
- package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/config}/auth.config.ts +1 -1
- package/registry/combos/express-drizzle/shared/src/{db.ts → config/db.ts} +1 -1
- package/registry/combos/express-drizzle/shared/src/{key-provider.ts → config/key-provider.ts} +6 -2
- package/registry/combos/express-drizzle/shared/src/{id.helper.ts → helpers/id.helper.ts} +6 -2
- package/registry/combos/{nestjs-drizzle/shared/src → express-drizzle/shared/src/helpers}/pagination.ts +17 -3
- package/registry/combos/express-drizzle/shared/src/middleware/auth-core-error.middleware.ts +6 -8
- package/registry/combos/express-drizzle/shared/src/middleware/auth.middleware.ts +6 -8
- package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/openapi}/openapi-fragment.ts +13 -4
- package/registry/combos/express-drizzle/shared/src/{openapi-spec.ts → openapi/openapi-spec.ts} +251 -52
- package/registry/combos/express-drizzle/shared/src/repositories/oauth.repository.ts +2 -2
- package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +2 -4
- package/registry/combos/express-drizzle/shared/src/repositories/session.repository.ts +3 -3
- package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/express-drizzle/shared/src/route-tiers.ts +52 -11
- package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +2 -2
- package/registry/combos/express-drizzle/test/bootstrap.ts +4 -2
- package/registry/combos/express-drizzle/test/prove-cycle.ts +1095 -308
- package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +5 -5
- package/registry/combos/express-drizzle/variants/base/src/index.ts +8 -0
- package/registry/combos/express-drizzle/variants/base/src/middleware/authz.middleware.ts +2 -2
- package/registry/combos/express-drizzle/variants/base/src/openapi/openapi-admin.ts +1082 -0
- package/registry/combos/express-drizzle/variants/base/src/rbac.defaults.ts +60 -17
- package/registry/combos/express-drizzle/variants/base/src/repositories/audit-log.repository.ts +2 -2
- package/registry/combos/express-drizzle/variants/base/src/repositories/rbac.repository.ts +5 -5
- package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +18 -22
- package/registry/combos/express-drizzle/variants/base/src/seed.ts +76 -17
- package/registry/combos/express-drizzle/variants/base/src/services/auth.service.ts +7 -9
- package/registry/combos/express-drizzle/variants/base/test/variant-hooks.ts +167 -45
- package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +5 -5
- package/registry/combos/express-drizzle/variants/workspaces/src/index.ts +8 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/middleware/authz.middleware.ts +3 -3
- package/registry/combos/express-drizzle/variants/workspaces/src/openapi/openapi-admin.ts +1176 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +1 -1
- package/registry/combos/express-drizzle/variants/workspaces/src/rbac.defaults.ts +66 -19
- package/registry/combos/express-drizzle/variants/workspaces/src/repositories/audit-log.repository.ts +2 -2
- package/registry/combos/express-drizzle/variants/workspaces/src/repositories/rbac.repository.ts +5 -5
- package/registry/combos/express-drizzle/variants/workspaces/src/repositories/workspace.repository.ts +9 -11
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +18 -22
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +1 -1
- package/registry/combos/express-drizzle/variants/workspaces/src/seed.ts +168 -35
- package/registry/combos/express-drizzle/variants/workspaces/src/services/auth.service.ts +7 -9
- package/registry/combos/express-drizzle/variants/workspaces/test/variant-hooks.ts +539 -133
- package/registry/combos/express-prisma/shared/src/{ability.ts → ability/ability.ts} +8 -2
- package/registry/combos/express-prisma/shared/src/{permission-cache.ts → cache/permission-cache.ts} +25 -9
- package/registry/combos/express-prisma/shared/src/{rate-limit.store.ts → cache/rate-limit.store.ts} +4 -1
- package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/config}/auth.config.ts +1 -1
- package/registry/combos/express-prisma/shared/src/{key-provider.ts → config/key-provider.ts} +6 -2
- package/registry/combos/{nestjs-prisma/shared/src → express-prisma/shared/src/helpers}/id.helper.ts +6 -2
- package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/helpers}/pagination.ts +17 -3
- package/registry/combos/express-prisma/shared/src/middleware/auth-core-error.middleware.ts +6 -8
- package/registry/combos/express-prisma/shared/src/middleware/auth.middleware.ts +6 -8
- package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/openapi}/openapi-fragment.ts +13 -4
- package/registry/combos/express-prisma/shared/src/{openapi-spec.ts → openapi/openapi-spec.ts} +313 -55
- package/registry/combos/express-prisma/shared/src/repositories/oauth.repository.ts +2 -2
- package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +2 -4
- package/registry/combos/express-prisma/shared/src/repositories/session.repository.ts +2 -2
- package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/express-prisma/shared/src/route-tiers.ts +52 -11
- package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +2 -2
- package/registry/combos/express-prisma/test/bootstrap.ts +7 -3
- package/registry/combos/express-prisma/test/prove-cycle.ts +1095 -308
- package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +6 -6
- package/registry/combos/express-prisma/variants/base/src/index.ts +8 -0
- package/registry/combos/express-prisma/variants/base/src/middleware/authz.middleware.ts +2 -2
- package/registry/combos/express-prisma/variants/base/src/openapi/openapi-admin.ts +1102 -0
- package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +1 -1
- package/registry/combos/express-prisma/variants/base/src/repositories/audit-log.repository.ts +2 -2
- package/registry/combos/express-prisma/variants/base/src/repositories/rbac.repository.ts +4 -4
- package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +18 -22
- package/registry/combos/express-prisma/variants/base/src/seed.ts +1 -1
- package/registry/combos/express-prisma/variants/base/src/services/auth.service.ts +6 -8
- package/registry/combos/express-prisma/variants/base/test/variant-hooks.ts +176 -47
- package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +6 -6
- package/registry/combos/express-prisma/variants/workspaces/src/index.ts +8 -0
- package/registry/combos/express-prisma/variants/workspaces/src/middleware/authz.middleware.ts +3 -3
- package/registry/combos/express-prisma/variants/workspaces/src/openapi/openapi-admin.ts +1218 -0
- package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +1 -1
- package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +1 -1
- package/registry/combos/express-prisma/variants/workspaces/src/repositories/audit-log.repository.ts +2 -2
- package/registry/combos/express-prisma/variants/workspaces/src/repositories/rbac.repository.ts +4 -4
- package/registry/combos/express-prisma/variants/workspaces/src/repositories/workspace.repository.ts +2 -2
- package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +18 -22
- package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +1 -1
- package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +1 -1
- package/registry/combos/express-prisma/variants/workspaces/src/services/auth.service.ts +6 -8
- package/registry/combos/express-prisma/variants/workspaces/test/variant-hooks.ts +538 -134
- package/registry/combos/nestjs-drizzle/shared/src/{guards → ability}/ability.guard.ts +1 -1
- package/registry/combos/nestjs-drizzle/shared/src/{ability.ts → ability/ability.ts} +8 -2
- package/registry/combos/nestjs-drizzle/shared/src/{permission-cache.ts → cache/permission-cache.ts} +27 -10
- package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/cache}/rate-limit.store.ts +4 -1
- package/registry/combos/nestjs-drizzle/shared/src/{auth.config.ts → config/auth.config.ts} +1 -1
- package/registry/combos/nestjs-drizzle/shared/src/{db.ts → config/db.ts} +1 -1
- package/registry/combos/nestjs-drizzle/shared/src/{key-provider.ts → config/key-provider.ts} +6 -2
- package/registry/combos/nestjs-drizzle/shared/src/controllers/auth.controller.ts +1 -1
- package/registry/combos/nestjs-drizzle/shared/src/guards/auth.guard.ts +1 -1
- package/registry/combos/nestjs-drizzle/shared/src/{id.helper.ts → helpers/id.helper.ts} +6 -2
- package/registry/combos/{express-prisma/shared/src → nestjs-drizzle/shared/src/helpers}/pagination.ts +17 -3
- package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +2 -2
- package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +8 -12
- package/registry/combos/nestjs-drizzle/shared/src/repositories/session.repository.ts +3 -3
- package/registry/combos/nestjs-drizzle/shared/src/repositories/two-factor.repository.ts +1 -1
- package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-drizzle/test/bootstrap.ts +1 -1
- package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1 -1
- package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +10 -6
- package/registry/combos/nestjs-drizzle/variants/base/src/controllers/admin.controller.ts +1 -1
- package/registry/combos/nestjs-drizzle/variants/base/src/guards/authz.guard.ts +2 -2
- package/registry/combos/nestjs-drizzle/variants/base/src/index.ts +9 -0
- package/registry/combos/nestjs-drizzle/variants/base/src/rbac.defaults.ts +58 -16
- package/registry/combos/nestjs-drizzle/variants/base/src/repositories/audit-log.repository.ts +3 -3
- package/registry/combos/nestjs-drizzle/variants/base/src/repositories/rbac.repository.ts +4 -4
- package/registry/combos/nestjs-drizzle/variants/base/src/seed.ts +76 -17
- package/registry/combos/nestjs-drizzle/variants/base/src/services/auth.service.ts +7 -9
- package/registry/combos/nestjs-drizzle/variants/base/test/variant-hooks.ts +167 -45
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +10 -6
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/controllers/admin.controller.ts +1 -1
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/controllers/workspace.controller.ts +1 -1
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/guards/authz.guard.ts +2 -2
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/index.ts +9 -0
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/rbac.defaults.ts +64 -18
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/repositories/audit-log.repository.ts +3 -3
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/repositories/rbac.repository.ts +4 -4
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/repositories/workspace.repository.ts +8 -10
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/seed.ts +171 -36
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +7 -9
- package/registry/combos/nestjs-drizzle/variants/workspaces/test/variant-hooks.ts +539 -133
- package/registry/combos/nestjs-prisma/shared/src/{guards → ability}/ability.guard.ts +1 -1
- package/registry/combos/nestjs-prisma/shared/src/{ability.ts → ability/ability.ts} +8 -2
- package/registry/combos/nestjs-prisma/shared/src/{permission-cache.ts → cache/permission-cache.ts} +27 -10
- package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/cache}/rate-limit.store.ts +4 -1
- package/registry/combos/nestjs-prisma/shared/src/{auth.config.ts → config/auth.config.ts} +1 -1
- package/registry/combos/nestjs-prisma/shared/src/{key-provider.ts → config/key-provider.ts} +6 -2
- package/registry/combos/nestjs-prisma/shared/src/controllers/auth.controller.ts +1 -1
- package/registry/combos/nestjs-prisma/shared/src/guards/auth.guard.ts +1 -1
- package/registry/combos/{express-prisma/shared/src → nestjs-prisma/shared/src/helpers}/id.helper.ts +6 -2
- package/registry/combos/nestjs-prisma/shared/src/{pagination.ts → helpers/pagination.ts} +17 -3
- package/registry/combos/nestjs-prisma/shared/src/repositories/oauth.repository.ts +1 -1
- package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +2 -4
- package/registry/combos/nestjs-prisma/shared/src/repositories/session.repository.ts +2 -2
- package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-prisma/test/bootstrap.ts +1 -1
- package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1 -1
- package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +10 -6
- package/registry/combos/nestjs-prisma/variants/base/src/controllers/admin.controller.ts +1 -1
- package/registry/combos/nestjs-prisma/variants/base/src/gateways/audit-log.gateway.ts +1 -1
- package/registry/combos/nestjs-prisma/variants/base/src/guards/authz.guard.ts +2 -2
- package/registry/combos/nestjs-prisma/variants/base/src/index.ts +9 -0
- package/registry/combos/nestjs-prisma/variants/base/src/{docs.ts → openapi/docs.ts} +23 -6
- package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +1 -1
- package/registry/combos/nestjs-prisma/variants/base/src/repositories/audit-log.repository.ts +2 -2
- package/registry/combos/nestjs-prisma/variants/base/src/repositories/country.repository.ts +2 -2
- package/registry/combos/nestjs-prisma/variants/base/src/repositories/customer.repository.ts +6 -5
- package/registry/combos/nestjs-prisma/variants/base/src/repositories/language.repository.ts +2 -2
- package/registry/combos/nestjs-prisma/variants/base/src/repositories/rbac.repository.ts +3 -3
- package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +1 -1
- package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +6 -8
- package/registry/combos/nestjs-prisma/variants/base/test/variant-hooks.ts +176 -47
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +10 -6
- package/registry/combos/nestjs-prisma/variants/workspaces/src/controllers/admin.controller.ts +1 -1
- package/registry/combos/nestjs-prisma/variants/workspaces/src/controllers/workspace.controller.ts +1 -1
- package/registry/combos/nestjs-prisma/variants/workspaces/src/guards/authz.guard.ts +2 -2
- package/registry/combos/nestjs-prisma/variants/workspaces/src/index.ts +9 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +1 -1
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/audit-log.repository.ts +2 -2
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/country.repository.ts +2 -2
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/customer.repository.ts +6 -5
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +2 -2
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/rbac.repository.ts +3 -3
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/workspace.repository.ts +1 -1
- package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +1 -1
- package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +6 -8
- package/registry/combos/nestjs-prisma/variants/workspaces/test/variant-hooks.ts +538 -134
- package/registry/combos/express-drizzle/variants/base/src/openapi-admin.ts +0 -565
- package/registry/combos/express-drizzle/variants/workspaces/src/openapi-admin.ts +0 -596
- package/registry/combos/express-prisma/variants/base/src/openapi-admin.ts +0 -569
- package/registry/combos/express-prisma/variants/workspaces/src/openapi-admin.ts +0 -601
- /package/registry/combos/express-drizzle/shared/src/{http-error.ts → errors/http-error.ts} +0 -0
- /package/registry/combos/express-prisma/shared/src/{http-error.ts → errors/http-error.ts} +0 -0
|
@@ -7,14 +7,24 @@ import "dotenv/config";
|
|
|
7
7
|
import { eq, inArray } from "drizzle-orm";
|
|
8
8
|
import { drizzle } from "drizzle-orm/node-postgres";
|
|
9
9
|
import { Pool } from "pg";
|
|
10
|
-
import type { Database } from "../src/db.js";
|
|
11
|
-
import { toId } from "../src/id.helper.js";
|
|
12
|
-
import { POLICY_VERSION_KEY } from "../src/permission-cache.js";
|
|
13
|
-
import {
|
|
10
|
+
import type { Database } from "../src/config/db.js";
|
|
11
|
+
import { toId } from "../src/helpers/id.helper.js";
|
|
12
|
+
import { POLICY_VERSION_KEY } from "../src/cache/permission-cache.js";
|
|
13
|
+
import {
|
|
14
|
+
provisionDefaultRoles,
|
|
15
|
+
SEED_ADMIN_ROLES,
|
|
16
|
+
} from "../src/rbac.defaults.js";
|
|
14
17
|
import * as schema from "../src/schema.js";
|
|
15
18
|
import { permissions, roleUser, roles } from "../src/schema.js";
|
|
16
19
|
import { permissionCacheStore } from "./bootstrap.js";
|
|
17
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
renewingToken,
|
|
22
|
+
type AdminSession,
|
|
23
|
+
type AuthTokens,
|
|
24
|
+
type Principal,
|
|
25
|
+
type ProofContext,
|
|
26
|
+
type VariantHooks,
|
|
27
|
+
} from "./harness.js";
|
|
18
28
|
|
|
19
29
|
/** One short-lived pool per call, closed on the way out, so the proof process has no lingering handle to wait on. */
|
|
20
30
|
async function withDb<T>(fn: (db: Database) => Promise<T>): Promise<T> {
|
|
@@ -32,7 +42,10 @@ let adminCredentials: { identifier: string; password: string } | undefined;
|
|
|
32
42
|
export const hooks: VariantHooks = {
|
|
33
43
|
variant: "base",
|
|
34
44
|
|
|
35
|
-
async makeAdmin(
|
|
45
|
+
async makeAdmin(
|
|
46
|
+
ctx: ProofContext,
|
|
47
|
+
principal: Principal,
|
|
48
|
+
): Promise<AdminSession> {
|
|
36
49
|
// The role slug on its own confers nothing — no guard checks for the string "admin". Authority
|
|
37
50
|
// comes from the permission_role rows behind it, and in this variant the seeder is the only
|
|
38
51
|
// thing that writes them, so the hook does what `npm run seed` does: calls the same
|
|
@@ -46,24 +59,36 @@ export const hooks: VariantHooks = {
|
|
|
46
59
|
.where(inArray(roles.slug, SEED_ADMIN_ROLES));
|
|
47
60
|
await db
|
|
48
61
|
.insert(roleUser)
|
|
49
|
-
.values(
|
|
62
|
+
.values(
|
|
63
|
+
adminRoles.map((role) => ({
|
|
64
|
+
userId: toId(principal.userId),
|
|
65
|
+
roleId: role.id,
|
|
66
|
+
})),
|
|
67
|
+
)
|
|
50
68
|
.onConflictDoNothing({ target: [roleUser.userId, roleUser.roleId] });
|
|
51
69
|
});
|
|
52
70
|
|
|
53
|
-
adminCredentials = {
|
|
71
|
+
adminCredentials = {
|
|
72
|
+
identifier: principal.email,
|
|
73
|
+
password: principal.password,
|
|
74
|
+
};
|
|
54
75
|
// Those writes went straight to the database, behind the running app's back, so nothing
|
|
55
76
|
// bumped a version counter and the caller's already-cached (empty) permissions would still be
|
|
56
77
|
// served. Bumping the policy counter is exactly what an out-of-band writer is supposed to do —
|
|
57
78
|
// see permission-cache.ts. Logging in would not have helped: nothing about authorization is
|
|
58
79
|
// in the token.
|
|
59
80
|
await permissionCacheStore.bump(POLICY_VERSION_KEY);
|
|
60
|
-
const login = await ctx.call("POST", "/auth/login", {
|
|
81
|
+
const login = await ctx.call("POST", "/auth/login", {
|
|
82
|
+
body: { identifier: principal.email, password: principal.password },
|
|
83
|
+
});
|
|
61
84
|
const token = (login.body as AuthTokens).accessToken;
|
|
62
85
|
ctx.assert(!!token, `the seeded admin can log in (got ${login.status})`);
|
|
63
86
|
return {
|
|
64
87
|
token,
|
|
65
88
|
freshToken: renewingToken(async () => {
|
|
66
|
-
const res = await ctx.call("POST", "/auth/login", {
|
|
89
|
+
const res = await ctx.call("POST", "/auth/login", {
|
|
90
|
+
body: adminCredentials!,
|
|
91
|
+
});
|
|
67
92
|
return (res.body as AuthTokens).accessToken;
|
|
68
93
|
}),
|
|
69
94
|
};
|
|
@@ -73,16 +98,37 @@ export const hooks: VariantHooks = {
|
|
|
73
98
|
// Nothing to do: admin endpoints operate on the whole deployment.
|
|
74
99
|
},
|
|
75
100
|
|
|
76
|
-
async proveVariantProperties(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
101
|
+
async proveVariantProperties(
|
|
102
|
+
ctx: ProofContext,
|
|
103
|
+
admin: AdminSession,
|
|
104
|
+
): Promise<void> {
|
|
105
|
+
const listed = await ctx.call("GET", "/auth/admin/users", {
|
|
106
|
+
token: await admin.freshToken(),
|
|
107
|
+
});
|
|
108
|
+
ctx.assert(
|
|
109
|
+
listed.status === 200,
|
|
110
|
+
`admin can list every user in the deployment (got ${listed.status})`,
|
|
111
|
+
);
|
|
112
|
+
ctx.assert(
|
|
113
|
+
Array.isArray(listed.body?.items) && listed.body.items.length > 0,
|
|
114
|
+
"the user list is non-empty",
|
|
115
|
+
);
|
|
80
116
|
|
|
81
|
-
const workspaces = await ctx.call("GET", "/workspaces", {
|
|
82
|
-
|
|
117
|
+
const workspaces = await ctx.call("GET", "/workspaces", {
|
|
118
|
+
token: await admin.freshToken(),
|
|
119
|
+
});
|
|
120
|
+
ctx.assert(
|
|
121
|
+
workspaces.status === 404,
|
|
122
|
+
`no workspace endpoints exist in this variant (got ${workspaces.status})`,
|
|
123
|
+
);
|
|
83
124
|
|
|
84
|
-
const me = await ctx.call("GET", "/auth/me", {
|
|
85
|
-
|
|
125
|
+
const me = await ctx.call("GET", "/auth/me", {
|
|
126
|
+
token: await admin.freshToken(),
|
|
127
|
+
});
|
|
128
|
+
ctx.assert(
|
|
129
|
+
me.body?.roles?.includes("admin"),
|
|
130
|
+
"roles are global: they apply to every request this user makes",
|
|
131
|
+
);
|
|
86
132
|
|
|
87
133
|
// --- authorization is not in the token, so a change lands on the next *request* ---
|
|
88
134
|
//
|
|
@@ -96,24 +142,55 @@ export const hooks: VariantHooks = {
|
|
|
96
142
|
const email = ctx.uniqueEmail("immediate");
|
|
97
143
|
const password = "immediate-pw-12345";
|
|
98
144
|
const tokens = await ctx.signup(email, password);
|
|
99
|
-
const userId = (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
ctx.assert(beforeGrant.status === 403, `a user with no grant is refused (got ${beforeGrant.status})`);
|
|
145
|
+
const userId = (
|
|
146
|
+
await ctx.call("GET", "/auth/me", { token: tokens.accessToken })
|
|
147
|
+
).body.sub as string;
|
|
103
148
|
|
|
104
|
-
const
|
|
105
|
-
|
|
149
|
+
const beforeGrant = await ctx.call("GET", "/auth/admin/users", {
|
|
150
|
+
token: tokens.accessToken,
|
|
151
|
+
});
|
|
152
|
+
ctx.assert(
|
|
153
|
+
beforeGrant.status === 403,
|
|
154
|
+
`a user with no grant is refused (got ${beforeGrant.status})`,
|
|
155
|
+
);
|
|
106
156
|
|
|
107
|
-
const
|
|
108
|
-
|
|
157
|
+
const granted = await ctx.call(
|
|
158
|
+
"POST",
|
|
159
|
+
`/auth/admin/users/${userId}/permissions`,
|
|
160
|
+
{ token: await admin.freshToken(), body: { permission: "users:read" } },
|
|
161
|
+
);
|
|
162
|
+
ctx.assert(
|
|
163
|
+
granted.status === 200 || granted.status === 201,
|
|
164
|
+
`admin can grant a permission directly (got ${granted.status})`,
|
|
165
|
+
);
|
|
109
166
|
|
|
110
|
-
const
|
|
111
|
-
token:
|
|
167
|
+
const afterGrant = await ctx.call("GET", "/auth/admin/users", {
|
|
168
|
+
token: tokens.accessToken,
|
|
112
169
|
});
|
|
113
|
-
ctx.assert(
|
|
170
|
+
ctx.assert(
|
|
171
|
+
afterGrant.status === 200,
|
|
172
|
+
`…and the grant is in effect on the very next request, on the token they already held (got ${afterGrant.status})`,
|
|
173
|
+
);
|
|
114
174
|
|
|
115
|
-
const
|
|
116
|
-
|
|
175
|
+
const revoked = await ctx.call(
|
|
176
|
+
"POST",
|
|
177
|
+
`/auth/admin/users/${userId}/permissions/${encodeURIComponent("users:read")}/revoke`,
|
|
178
|
+
{
|
|
179
|
+
token: await admin.freshToken(),
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
ctx.assert(
|
|
183
|
+
revoked.status === 200 || revoked.status === 201,
|
|
184
|
+
`admin can revoke the direct grant (got ${revoked.status})`,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const afterRevoke = await ctx.call("GET", "/auth/admin/users", {
|
|
188
|
+
token: tokens.accessToken,
|
|
189
|
+
});
|
|
190
|
+
ctx.assert(
|
|
191
|
+
afterRevoke.status === 403,
|
|
192
|
+
`and the revocation lands on the very next request too, on the same token (got ${afterRevoke.status})`,
|
|
193
|
+
);
|
|
117
194
|
|
|
118
195
|
await proveTheCacheIsReal(ctx, admin, tokens.accessToken);
|
|
119
196
|
await proveARawDatabaseEditChangesEnforcement(ctx, admin);
|
|
@@ -125,7 +202,11 @@ export const hooks: VariantHooks = {
|
|
|
125
202
|
* app resolves through (`test/bootstrap.ts` hands it to `AuthModule.forRoot`), so "the second
|
|
126
203
|
* request did not touch the database" is a measurement rather than a claim.
|
|
127
204
|
*/
|
|
128
|
-
async function proveTheCacheIsReal(
|
|
205
|
+
async function proveTheCacheIsReal(
|
|
206
|
+
ctx: ProofContext,
|
|
207
|
+
admin: AdminSession,
|
|
208
|
+
token: string,
|
|
209
|
+
): Promise<void> {
|
|
129
210
|
const N = 8;
|
|
130
211
|
await ctx.call("GET", "/auth/me", { token }); // warm, so the measurement is of the steady state
|
|
131
212
|
const before = { ...permissionCacheStore.stats };
|
|
@@ -137,16 +218,27 @@ async function proveTheCacheIsReal(ctx: ProofContext, admin: AdminSession, token
|
|
|
137
218
|
`${N} identical authorized requests resolve permissions 0 further times — all ${N} were cache hits (misses +${after.misses - before.misses}, hits +${after.hits - before.hits})`,
|
|
138
219
|
);
|
|
139
220
|
|
|
140
|
-
const userId = (await ctx.call("GET", "/auth/me", { token })).body
|
|
221
|
+
const userId = (await ctx.call("GET", "/auth/me", { token })).body
|
|
222
|
+
.sub as string;
|
|
141
223
|
const beforeGrant = { ...permissionCacheStore.stats };
|
|
142
|
-
await ctx.call("POST", `/auth/admin/users/${userId}/permissions`, {
|
|
224
|
+
await ctx.call("POST", `/auth/admin/users/${userId}/permissions`, {
|
|
225
|
+
token: await admin.freshToken(),
|
|
226
|
+
body: { permission: "probe:cache" },
|
|
227
|
+
});
|
|
143
228
|
const afterGrantCall = await ctx.call("GET", "/auth/me", { token });
|
|
144
229
|
ctx.assert(
|
|
145
230
|
permissionCacheStore.stats.misses > beforeGrant.misses,
|
|
146
231
|
"a grant bumps this user's version counter, so the next request misses the cache and re-resolves rather than serving a stale entry",
|
|
147
232
|
);
|
|
148
|
-
ctx.assert(
|
|
149
|
-
|
|
233
|
+
ctx.assert(
|
|
234
|
+
(afterGrantCall.body?.permissions ?? []).includes("probe:cache"),
|
|
235
|
+
"…and the freshly resolved answer contains the new permission",
|
|
236
|
+
);
|
|
237
|
+
await ctx.call(
|
|
238
|
+
"POST",
|
|
239
|
+
`/auth/admin/users/${userId}/permissions/${encodeURIComponent("probe:cache")}/revoke`,
|
|
240
|
+
{ token: await admin.freshToken() },
|
|
241
|
+
);
|
|
150
242
|
}
|
|
151
243
|
|
|
152
244
|
/**
|
|
@@ -154,26 +246,56 @@ async function proveTheCacheIsReal(ctx: ProofContext, admin: AdminSession, token
|
|
|
154
246
|
* doing the writing — this rules out any possibility that it is also doing something in memory.
|
|
155
247
|
* A `psql` session would look exactly like this.
|
|
156
248
|
*/
|
|
157
|
-
async function proveARawDatabaseEditChangesEnforcement(
|
|
249
|
+
async function proveARawDatabaseEditChangesEnforcement(
|
|
250
|
+
ctx: ProofContext,
|
|
251
|
+
admin: AdminSession,
|
|
252
|
+
): Promise<void> {
|
|
158
253
|
const email = ctx.uniqueEmail("rawedit");
|
|
159
254
|
const tokens = await ctx.signup(email, "rawedit-pw-12345");
|
|
160
|
-
const userId = (
|
|
161
|
-
|
|
162
|
-
|
|
255
|
+
const userId = (
|
|
256
|
+
await ctx.call("GET", "/auth/me", { token: tokens.accessToken })
|
|
257
|
+
).body.sub as string;
|
|
258
|
+
await ctx.call("POST", `/auth/admin/users/${userId}/permissions`, {
|
|
259
|
+
token: await admin.freshToken(),
|
|
260
|
+
body: { permission: "audit-log:read" },
|
|
261
|
+
});
|
|
262
|
+
ctx.assert(
|
|
263
|
+
(
|
|
264
|
+
await ctx.call("GET", "/auth/admin/audit-log", {
|
|
265
|
+
token: tokens.accessToken,
|
|
266
|
+
})
|
|
267
|
+
).status === 200,
|
|
268
|
+
"a direct grant opens the audit log",
|
|
269
|
+
);
|
|
163
270
|
|
|
164
271
|
const setActive = async (isActive: boolean) => {
|
|
165
272
|
// Written straight to the table, with no code change and no redeploy. The bump that follows is
|
|
166
273
|
// the out-of-band writer's half of the cache contract (permission-cache.ts) — without it the
|
|
167
274
|
// edit would still land, but only once the cached entry expired.
|
|
168
|
-
await withDb((db) =>
|
|
275
|
+
await withDb((db) =>
|
|
276
|
+
db
|
|
277
|
+
.update(permissions)
|
|
278
|
+
.set({ isActive })
|
|
279
|
+
.where(eq(permissions.slug, "audit-log:read")),
|
|
280
|
+
);
|
|
169
281
|
await permissionCacheStore.bump(POLICY_VERSION_KEY);
|
|
170
282
|
};
|
|
171
283
|
|
|
172
284
|
await setActive(false);
|
|
173
|
-
const denied = await ctx.call("GET", "/auth/admin/audit-log", {
|
|
174
|
-
|
|
285
|
+
const denied = await ctx.call("GET", "/auth/admin/audit-log", {
|
|
286
|
+
token: tokens.accessToken,
|
|
287
|
+
});
|
|
288
|
+
ctx.assert(
|
|
289
|
+
denied.status === 403,
|
|
290
|
+
`a permission deactivated by a raw database write stops opening its route (got ${denied.status})`,
|
|
291
|
+
);
|
|
175
292
|
|
|
176
293
|
await setActive(true);
|
|
177
|
-
const restored = await ctx.call("GET", "/auth/admin/audit-log", {
|
|
178
|
-
|
|
294
|
+
const restored = await ctx.call("GET", "/auth/admin/audit-log", {
|
|
295
|
+
token: tokens.accessToken,
|
|
296
|
+
});
|
|
297
|
+
ctx.assert(
|
|
298
|
+
restored.status === 200,
|
|
299
|
+
`…and reactivating it in the database opens the route again (got ${restored.status})`,
|
|
300
|
+
);
|
|
179
301
|
}
|
|
@@ -6,7 +6,7 @@ import type { RateLimitDeps } from "@/lib/auth/core/rate-limit.js";
|
|
|
6
6
|
import { createAdminRouter } from "./routers/admin.router.js";
|
|
7
7
|
import { AuditLogRepository } from "./repositories/audit-log.repository.js";
|
|
8
8
|
import { authCoreErrorMiddleware } from "./middleware/auth-core-error.middleware.js";
|
|
9
|
-
import { AuthConfig, defaultAuthConfig } from "./auth.config.js";
|
|
9
|
+
import { AuthConfig, defaultAuthConfig } from "./config/auth.config.js";
|
|
10
10
|
import { createAuthMiddleware } from "./middleware/auth.middleware.js";
|
|
11
11
|
import { createAuthRouter } from "./routers/auth.router.js";
|
|
12
12
|
import { AuthService } from "./services/auth.service.js";
|
|
@@ -14,15 +14,15 @@ import {
|
|
|
14
14
|
createAuthzMiddleware,
|
|
15
15
|
createWorkspaceMiddleware,
|
|
16
16
|
} from "./middleware/authz.middleware.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 * as schema from "./schema.js";
|
package/registry/combos/express-drizzle/variants/workspaces/src/middleware/authz.middleware.ts
CHANGED
|
@@ -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,
|