@simple-auth-kit/cli 1.4.3 → 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 +4 -4
- package/simple-auth-kit.ts +29 -2
- 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,34 @@ import { Pool } from "pg";
|
|
|
2
2
|
import { drizzle } from "drizzle-orm/node-postgres";
|
|
3
3
|
import { DynamicModule, Module } from "@nestjs/common";
|
|
4
4
|
import { APP_FILTER, APP_INTERCEPTOR } from "@nestjs/core";
|
|
5
|
-
import { AbilityGuard } from "./ability.guard.js";
|
|
6
|
-
import { AdminController } from "./admin.controller.js";
|
|
7
|
-
import { AuditLogRepository } from "./audit-log.repository.js";
|
|
8
|
-
import { AuthCoreErrorFilter } from "./auth-core-error.filter.js";
|
|
5
|
+
import { AbilityGuard } from "./guards/ability.guard.js";
|
|
6
|
+
import { AdminController } from "./controllers/admin.controller.js";
|
|
7
|
+
import { AuditLogRepository } from "./repositories/audit-log.repository.js";
|
|
8
|
+
import { AuthCoreErrorFilter } from "./filters/auth-core-error.filter.js";
|
|
9
9
|
import { AUTH_CONFIG, AuthConfig, defaultAuthConfig } from "./auth.config.js";
|
|
10
|
-
import { AuthController } from "./auth.controller.js";
|
|
11
|
-
import { AuthGuard } from "./auth.guard.js";
|
|
12
|
-
import { AuthService } from "./auth.service.js";
|
|
13
|
-
import { AuthzGuard } from "./authz.guard.js";
|
|
10
|
+
import { AuthController } from "./controllers/auth.controller.js";
|
|
11
|
+
import { AuthGuard } from "./guards/auth.guard.js";
|
|
12
|
+
import { AuthService } from "./services/auth.service.js";
|
|
13
|
+
import { AuthzGuard } from "./guards/authz.guard.js";
|
|
14
14
|
import { DRIZZLE_DB } from "./db.js";
|
|
15
15
|
import { KeyProviderService } from "./key-provider.js";
|
|
16
|
-
import { OAuthRepository } from "./oauth.repository.js";
|
|
17
|
-
import { PasswordResetRepository } from "./password-reset.repository.js";
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
import { OAuthRepository } from "./repositories/oauth.repository.js";
|
|
17
|
+
import { PasswordResetRepository } from "./repositories/password-reset.repository.js";
|
|
18
|
+
import {
|
|
19
|
+
InMemoryPermissionCacheStore,
|
|
20
|
+
PERMISSION_CACHE_STORE,
|
|
21
|
+
PermissionCache,
|
|
22
|
+
} from "./permission-cache.js";
|
|
23
|
+
import {
|
|
24
|
+
InMemoryRateLimitStore,
|
|
25
|
+
RATE_LIMIT_STORE,
|
|
26
|
+
} from "./rate-limit.store.js";
|
|
27
|
+
import { RbacRepository } from "./repositories/rbac.repository.js";
|
|
28
|
+
import { ResponseInterceptor } from "./interceptors/response.interceptor.js";
|
|
22
29
|
import { assertEveryRouteDeclaresATier } from "./route-tiers.js";
|
|
23
30
|
import * as schema from "./schema.js";
|
|
24
|
-
import { SessionRepository } from "./session.repository.js";
|
|
25
|
-
import { TwoFactorRepository } from "./two-factor.repository.js";
|
|
31
|
+
import { SessionRepository } from "./repositories/session.repository.js";
|
|
32
|
+
import { TwoFactorRepository } from "./repositories/two-factor.repository.js";
|
|
26
33
|
|
|
27
34
|
/**
|
|
28
35
|
* Every controller this library ships, in one place. It is both what the module registers and
|
|
@@ -39,7 +46,10 @@ export class AuthModule {
|
|
|
39
46
|
// stops the process here, by name, rather than shipping as an open endpoint nobody noticed.
|
|
40
47
|
// Nothing above this line allocates a connection pool or a port, so a failed boot leaves
|
|
41
48
|
// nothing behind.
|
|
42
|
-
assertEveryRouteDeclaresATier(AUTH_CONTROLLERS, {
|
|
49
|
+
assertEveryRouteDeclaresATier(AUTH_CONTROLLERS, {
|
|
50
|
+
authentication: AuthGuard,
|
|
51
|
+
ability: AbilityGuard,
|
|
52
|
+
});
|
|
43
53
|
|
|
44
54
|
const pool = new Pool({ connectionString: process.env["DATABASE_URL"] });
|
|
45
55
|
const db = drizzle(pool, { schema });
|
|
@@ -60,14 +70,21 @@ export class AuthModule {
|
|
|
60
70
|
{ provide: DRIZZLE_DB, useValue: db },
|
|
61
71
|
// The cache seam. Swap the store for a Redis-backed one by passing `permissionCacheStore`
|
|
62
72
|
// to forRoot — nothing in this library's source changes. Keys are namespaced simpleauthkit:authz:*.
|
|
63
|
-
{
|
|
73
|
+
{
|
|
74
|
+
provide: PERMISSION_CACHE_STORE,
|
|
75
|
+
useValue:
|
|
76
|
+
config.permissionCacheStore ?? new InMemoryPermissionCacheStore(),
|
|
77
|
+
},
|
|
64
78
|
PermissionCache,
|
|
65
79
|
{ provide: APP_FILTER, useClass: AuthCoreErrorFilter },
|
|
66
80
|
{ provide: APP_INTERCEPTOR, useClass: ResponseInterceptor },
|
|
67
81
|
AuditLogRepository,
|
|
68
82
|
SessionRepository,
|
|
69
83
|
KeyProviderService,
|
|
70
|
-
{
|
|
84
|
+
{
|
|
85
|
+
provide: RATE_LIMIT_STORE,
|
|
86
|
+
useValue: config.rateLimitStore ?? new InMemoryRateLimitStore(),
|
|
87
|
+
},
|
|
71
88
|
RbacRepository,
|
|
72
89
|
TwoFactorRepository,
|
|
73
90
|
OAuthRepository,
|
|
@@ -77,7 +94,14 @@ export class AuthModule {
|
|
|
77
94
|
AuthzGuard,
|
|
78
95
|
AbilityGuard,
|
|
79
96
|
],
|
|
80
|
-
exports: [
|
|
97
|
+
exports: [
|
|
98
|
+
AuthService,
|
|
99
|
+
AuthGuard,
|
|
100
|
+
AuthzGuard,
|
|
101
|
+
AbilityGuard,
|
|
102
|
+
PermissionCache,
|
|
103
|
+
DRIZZLE_DB,
|
|
104
|
+
],
|
|
81
105
|
};
|
|
82
106
|
}
|
|
83
107
|
}
|
|
@@ -1,11 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
BadRequestException,
|
|
3
|
+
Body,
|
|
4
|
+
Controller,
|
|
5
|
+
Delete,
|
|
6
|
+
ForbiddenException,
|
|
7
|
+
Get,
|
|
8
|
+
Inject,
|
|
9
|
+
Param,
|
|
10
|
+
Patch,
|
|
11
|
+
Post,
|
|
12
|
+
Query,
|
|
13
|
+
Req,
|
|
14
|
+
UseGuards,
|
|
15
|
+
} from "@nestjs/common";
|
|
16
|
+
import {
|
|
17
|
+
ApiBearerAuth,
|
|
18
|
+
ApiBody,
|
|
19
|
+
ApiOperation,
|
|
20
|
+
ApiParam,
|
|
21
|
+
ApiQuery,
|
|
22
|
+
ApiResponse,
|
|
23
|
+
ApiTags,
|
|
24
|
+
} from "@nestjs/swagger";
|
|
3
25
|
import type { Request } from "express";
|
|
4
|
-
import { AbilityGuard } from "
|
|
5
|
-
import { AuthGuard } from "
|
|
6
|
-
import { AuthService } from "
|
|
7
|
-
import { AuthzGuard } from "
|
|
8
|
-
import { CheckAbility } from "
|
|
26
|
+
import { AbilityGuard } from "../guards/ability.guard.js";
|
|
27
|
+
import { AuthGuard } from "../guards/auth.guard.js";
|
|
28
|
+
import { AuthService } from "../services/auth.service.js";
|
|
29
|
+
import { AuthzGuard } from "../guards/authz.guard.js";
|
|
30
|
+
import { CheckAbility } from "../route-tiers.js";
|
|
9
31
|
import {
|
|
10
32
|
AssignRoleDto,
|
|
11
33
|
AttachPermissionDto,
|
|
@@ -21,15 +43,21 @@ import {
|
|
|
21
43
|
RoleSummaryDto,
|
|
22
44
|
UpdateRoleDto,
|
|
23
45
|
UpdateUserDto,
|
|
24
|
-
} from "
|
|
25
|
-
import {
|
|
46
|
+
} from "../dto/auth.dto.js";
|
|
47
|
+
import {
|
|
48
|
+
AuditLogListResponseDto,
|
|
49
|
+
UserListResponseDto,
|
|
50
|
+
UserSummaryDto,
|
|
51
|
+
} from "../dto/admin.dto.js";
|
|
26
52
|
|
|
27
53
|
function requireString(value: unknown, field: string): string {
|
|
28
|
-
if (typeof value !== "string" || value.length === 0)
|
|
54
|
+
if (typeof value !== "string" || value.length === 0)
|
|
55
|
+
throw new BadRequestException(`${field} is required`);
|
|
29
56
|
return value;
|
|
30
57
|
}
|
|
31
58
|
|
|
32
|
-
const optionalString = (value: unknown): string | undefined =>
|
|
59
|
+
const optionalString = (value: unknown): string | undefined =>
|
|
60
|
+
typeof value === "string" ? value : undefined;
|
|
33
61
|
|
|
34
62
|
/**
|
|
35
63
|
* Administration of the whole deployment. Authority is a permission, never a role name:
|
|
@@ -56,17 +84,41 @@ export class AdminController {
|
|
|
56
84
|
@Get("users")
|
|
57
85
|
@CheckAbility("users:read")
|
|
58
86
|
@ApiOperation({ summary: "[admin] List users" })
|
|
59
|
-
@ApiQuery({
|
|
60
|
-
|
|
61
|
-
|
|
87
|
+
@ApiQuery({
|
|
88
|
+
name: "search",
|
|
89
|
+
required: false,
|
|
90
|
+
description: "Email substring match",
|
|
91
|
+
})
|
|
92
|
+
@ApiQuery({
|
|
93
|
+
name: "page",
|
|
94
|
+
required: false,
|
|
95
|
+
description: "1-indexed. Defaults to 1.",
|
|
96
|
+
})
|
|
97
|
+
@ApiQuery({
|
|
98
|
+
name: "limit",
|
|
99
|
+
required: false,
|
|
100
|
+
description: "Defaults to 25, capped at 100.",
|
|
101
|
+
})
|
|
62
102
|
@ApiResponse({ status: 200, type: UserListResponseDto })
|
|
63
|
-
async listUsers(
|
|
64
|
-
|
|
103
|
+
async listUsers(
|
|
104
|
+
@Query("search") search?: string,
|
|
105
|
+
@Query("page") page?: string,
|
|
106
|
+
@Query("limit") limit?: string,
|
|
107
|
+
) {
|
|
108
|
+
return this.auth.listUsers({
|
|
109
|
+
search,
|
|
110
|
+
page: page ? Number(page) : undefined,
|
|
111
|
+
limit: limit ? Number(limit) : undefined,
|
|
112
|
+
});
|
|
65
113
|
}
|
|
66
114
|
|
|
67
115
|
@Post("users")
|
|
68
116
|
@CheckAbility("users:manage")
|
|
69
|
-
@ApiOperation({
|
|
117
|
+
@ApiOperation({
|
|
118
|
+
summary: "[admin] Create a user directly",
|
|
119
|
+
description:
|
|
120
|
+
"No invitation email — the account is usable immediately with the password given here.",
|
|
121
|
+
})
|
|
70
122
|
@ApiBody({ type: CreateUserDto })
|
|
71
123
|
@ApiResponse({ status: 201, type: UserSummaryDto })
|
|
72
124
|
async createUser(@Body() body: Record<string, unknown>, @Req() req: Request) {
|
|
@@ -82,8 +134,13 @@ export class AdminController {
|
|
|
82
134
|
dob: optionalString(body.dob),
|
|
83
135
|
gender: optionalString(body.gender),
|
|
84
136
|
joinedDate: optionalString(body.joinedDate),
|
|
85
|
-
isActive:
|
|
86
|
-
|
|
137
|
+
isActive:
|
|
138
|
+
typeof body.isActive === "boolean" ? body.isActive : undefined,
|
|
139
|
+
roles: Array.isArray(body.roles)
|
|
140
|
+
? body.roles.filter(
|
|
141
|
+
(role): role is string => typeof role === "string",
|
|
142
|
+
)
|
|
143
|
+
: undefined,
|
|
87
144
|
},
|
|
88
145
|
req.auth!.sub,
|
|
89
146
|
);
|
|
@@ -100,17 +157,27 @@ export class AdminController {
|
|
|
100
157
|
|
|
101
158
|
@Patch("users/:userId")
|
|
102
159
|
@CheckAbility("users:manage")
|
|
103
|
-
@ApiOperation({
|
|
160
|
+
@ApiOperation({
|
|
161
|
+
summary: "[admin] Edit a user's profile",
|
|
162
|
+
description:
|
|
163
|
+
"Profile fields only — email is the login identifier and is not editable here.",
|
|
164
|
+
})
|
|
104
165
|
@ApiParam({ name: "userId" })
|
|
105
166
|
@ApiBody({ type: UpdateUserDto })
|
|
106
167
|
@ApiResponse({ status: 200, type: UserSummaryDto })
|
|
107
|
-
async updateUser(
|
|
168
|
+
async updateUser(
|
|
169
|
+
@Param("userId") userId: string,
|
|
170
|
+
@Body() body: Record<string, unknown>,
|
|
171
|
+
@Req() req: Request,
|
|
172
|
+
) {
|
|
108
173
|
return this.auth.updateUser(
|
|
109
174
|
userId,
|
|
110
175
|
{
|
|
111
|
-
firstName:
|
|
176
|
+
firstName:
|
|
177
|
+
body.firstName === null ? null : optionalString(body.firstName),
|
|
112
178
|
lastName: body.lastName === null ? null : optionalString(body.lastName),
|
|
113
|
-
displayName:
|
|
179
|
+
displayName:
|
|
180
|
+
body.displayName === null ? null : optionalString(body.displayName),
|
|
114
181
|
phone: body.phone === null ? null : optionalString(body.phone),
|
|
115
182
|
username: body.username === null ? null : optionalString(body.username),
|
|
116
183
|
photo: body.photo === null ? null : optionalString(body.photo),
|
|
@@ -126,14 +193,24 @@ export class AdminController {
|
|
|
126
193
|
@CheckAbility("users:manage")
|
|
127
194
|
@ApiOperation({
|
|
128
195
|
summary: "[admin] Delete a user",
|
|
129
|
-
description:
|
|
196
|
+
description:
|
|
197
|
+
"Soft-delete: the row survives for audit purposes, stops appearing in listings, and can no longer authenticate.",
|
|
130
198
|
})
|
|
131
199
|
@ApiParam({ name: "userId" })
|
|
132
200
|
@ApiBody({ type: DeleteReasonDto, required: false })
|
|
133
201
|
@ApiResponse({ status: 200, type: OkResponseDto })
|
|
134
|
-
async deleteUser(
|
|
135
|
-
|
|
136
|
-
|
|
202
|
+
async deleteUser(
|
|
203
|
+
@Param("userId") userId: string,
|
|
204
|
+
@Body() body: Record<string, unknown>,
|
|
205
|
+
@Req() req: Request,
|
|
206
|
+
) {
|
|
207
|
+
if (userId === req.auth!.sub)
|
|
208
|
+
throw new ForbiddenException("cannot delete your own account");
|
|
209
|
+
await this.auth.deleteUser(
|
|
210
|
+
userId,
|
|
211
|
+
req.auth!.sub,
|
|
212
|
+
optionalString(body?.reason),
|
|
213
|
+
);
|
|
137
214
|
return { ok: true };
|
|
138
215
|
}
|
|
139
216
|
|
|
@@ -141,11 +218,31 @@ export class AdminController {
|
|
|
141
218
|
@CheckAbility("audit-log:read")
|
|
142
219
|
@ApiOperation({ summary: "[admin] List audit log entries, newest first" })
|
|
143
220
|
@ApiQuery({ name: "userId", required: false })
|
|
144
|
-
@ApiQuery({
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
221
|
+
@ApiQuery({
|
|
222
|
+
name: "action",
|
|
223
|
+
required: false,
|
|
224
|
+
description: "AuditEvent discriminant, e.g. 'role_assigned'",
|
|
225
|
+
})
|
|
226
|
+
@ApiQuery({
|
|
227
|
+
name: "since",
|
|
228
|
+
required: false,
|
|
229
|
+
description: "ISO 8601 timestamp",
|
|
230
|
+
})
|
|
231
|
+
@ApiQuery({
|
|
232
|
+
name: "until",
|
|
233
|
+
required: false,
|
|
234
|
+
description: "ISO 8601 timestamp",
|
|
235
|
+
})
|
|
236
|
+
@ApiQuery({
|
|
237
|
+
name: "page",
|
|
238
|
+
required: false,
|
|
239
|
+
description: "1-indexed. Defaults to 1.",
|
|
240
|
+
})
|
|
241
|
+
@ApiQuery({
|
|
242
|
+
name: "limit",
|
|
243
|
+
required: false,
|
|
244
|
+
description: "Defaults to 25, capped at 100.",
|
|
245
|
+
})
|
|
149
246
|
@ApiResponse({ status: 200, type: AuditLogListResponseDto })
|
|
150
247
|
async listAuditLog(
|
|
151
248
|
@Query("userId") userId?: string,
|
|
@@ -155,14 +252,22 @@ export class AdminController {
|
|
|
155
252
|
@Query("page") page?: string,
|
|
156
253
|
@Query("limit") limit?: string,
|
|
157
254
|
) {
|
|
158
|
-
return this.auth.listAuditLog({
|
|
255
|
+
return this.auth.listAuditLog({
|
|
256
|
+
userId,
|
|
257
|
+
action,
|
|
258
|
+
since,
|
|
259
|
+
until,
|
|
260
|
+
page: page ? Number(page) : undefined,
|
|
261
|
+
limit: limit ? Number(limit) : undefined,
|
|
262
|
+
});
|
|
159
263
|
}
|
|
160
264
|
|
|
161
265
|
@Get("permissions")
|
|
162
266
|
@CheckAbility("permissions:read")
|
|
163
267
|
@ApiOperation({
|
|
164
268
|
summary: "[admin] List the permission catalog",
|
|
165
|
-
description:
|
|
269
|
+
description:
|
|
270
|
+
"Grouped and ordered for a permission matrix. This is the whole vocabulary of the deployment — there is nothing about authorization outside these rows.",
|
|
166
271
|
})
|
|
167
272
|
@ApiResponse({ status: 200, type: PermissionListResponseDto })
|
|
168
273
|
async listPermissions() {
|
|
@@ -179,17 +284,23 @@ export class AdminController {
|
|
|
179
284
|
})
|
|
180
285
|
@ApiBody({ type: DefinePermissionDto })
|
|
181
286
|
@ApiResponse({ status: 201, type: PermissionSummaryDto })
|
|
182
|
-
async definePermission(
|
|
287
|
+
async definePermission(
|
|
288
|
+
@Body() body: Record<string, unknown>,
|
|
289
|
+
@Req() req: Request,
|
|
290
|
+
) {
|
|
183
291
|
return this.auth.definePermission(
|
|
184
292
|
{
|
|
185
293
|
slug: requireString(body.slug, "slug"),
|
|
186
294
|
name: optionalString(body.name),
|
|
187
295
|
displayName: optionalString(body.displayName),
|
|
188
|
-
description:
|
|
296
|
+
description:
|
|
297
|
+
body.description === null ? null : optionalString(body.description),
|
|
189
298
|
group: optionalString(body.group),
|
|
190
|
-
groupOrder:
|
|
299
|
+
groupOrder:
|
|
300
|
+
typeof body.groupOrder === "number" ? body.groupOrder : undefined,
|
|
191
301
|
order: typeof body.order === "number" ? body.order : undefined,
|
|
192
|
-
isActive:
|
|
302
|
+
isActive:
|
|
303
|
+
typeof body.isActive === "boolean" ? body.isActive : undefined,
|
|
193
304
|
},
|
|
194
305
|
req.auth!.sub,
|
|
195
306
|
);
|
|
@@ -215,8 +326,10 @@ export class AdminController {
|
|
|
215
326
|
name: optionalString(body.name),
|
|
216
327
|
displayName: optionalString(body.displayName),
|
|
217
328
|
description: optionalString(body.description) ?? null,
|
|
218
|
-
isDefault:
|
|
219
|
-
|
|
329
|
+
isDefault:
|
|
330
|
+
typeof body.isDefault === "boolean" ? body.isDefault : undefined,
|
|
331
|
+
isActive:
|
|
332
|
+
typeof body.isActive === "boolean" ? body.isActive : undefined,
|
|
220
333
|
},
|
|
221
334
|
req.auth!.sub,
|
|
222
335
|
);
|
|
@@ -228,15 +341,22 @@ export class AdminController {
|
|
|
228
341
|
@ApiParam({ name: "roleId" })
|
|
229
342
|
@ApiBody({ type: UpdateRoleDto })
|
|
230
343
|
@ApiResponse({ status: 200, type: RoleSummaryDto })
|
|
231
|
-
async updateRole(
|
|
344
|
+
async updateRole(
|
|
345
|
+
@Param("roleId") roleId: string,
|
|
346
|
+
@Body() body: Record<string, unknown>,
|
|
347
|
+
@Req() req: Request,
|
|
348
|
+
) {
|
|
232
349
|
return this.auth.updateRole(
|
|
233
350
|
roleId,
|
|
234
351
|
{
|
|
235
352
|
name: optionalString(body.name),
|
|
236
353
|
displayName: optionalString(body.displayName),
|
|
237
|
-
description:
|
|
238
|
-
|
|
239
|
-
|
|
354
|
+
description:
|
|
355
|
+
body.description === null ? null : optionalString(body.description),
|
|
356
|
+
isDefault:
|
|
357
|
+
typeof body.isDefault === "boolean" ? body.isDefault : undefined,
|
|
358
|
+
isActive:
|
|
359
|
+
typeof body.isActive === "boolean" ? body.isActive : undefined,
|
|
240
360
|
},
|
|
241
361
|
req.auth!.sub,
|
|
242
362
|
);
|
|
@@ -246,13 +366,22 @@ export class AdminController {
|
|
|
246
366
|
@CheckAbility("roles:manage")
|
|
247
367
|
@ApiOperation({
|
|
248
368
|
summary: "[admin] Delete a role",
|
|
249
|
-
description:
|
|
369
|
+
description:
|
|
370
|
+
"Soft-delete: existing assignments are left in place rather than cascade-deleted, and the role simply stops being resolved.",
|
|
250
371
|
})
|
|
251
372
|
@ApiParam({ name: "roleId" })
|
|
252
373
|
@ApiBody({ type: DeleteReasonDto, required: false })
|
|
253
374
|
@ApiResponse({ status: 200, type: OkResponseDto })
|
|
254
|
-
async deleteRole(
|
|
255
|
-
|
|
375
|
+
async deleteRole(
|
|
376
|
+
@Param("roleId") roleId: string,
|
|
377
|
+
@Body() body: Record<string, unknown>,
|
|
378
|
+
@Req() req: Request,
|
|
379
|
+
) {
|
|
380
|
+
await this.auth.deleteRole(
|
|
381
|
+
roleId,
|
|
382
|
+
req.auth!.sub,
|
|
383
|
+
optionalString(body?.reason),
|
|
384
|
+
);
|
|
256
385
|
return { ok: true };
|
|
257
386
|
}
|
|
258
387
|
|
|
@@ -262,8 +391,16 @@ export class AdminController {
|
|
|
262
391
|
@ApiParam({ name: "roleId" })
|
|
263
392
|
@ApiBody({ type: AttachPermissionDto })
|
|
264
393
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
265
|
-
async attachPermissionToRole(
|
|
266
|
-
|
|
394
|
+
async attachPermissionToRole(
|
|
395
|
+
@Param("roleId") roleId: string,
|
|
396
|
+
@Body() body: Record<string, unknown>,
|
|
397
|
+
@Req() req: Request,
|
|
398
|
+
) {
|
|
399
|
+
await this.auth.attachPermissionToRole(
|
|
400
|
+
roleId,
|
|
401
|
+
requireString(body.permission, "permission"),
|
|
402
|
+
req.auth!.sub,
|
|
403
|
+
);
|
|
267
404
|
return { ok: true };
|
|
268
405
|
}
|
|
269
406
|
|
|
@@ -273,7 +410,10 @@ export class AdminController {
|
|
|
273
410
|
@ApiParam({ name: "userId" })
|
|
274
411
|
@ApiBody({ type: AssignRoleDto })
|
|
275
412
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
276
|
-
async assignRole(
|
|
413
|
+
async assignRole(
|
|
414
|
+
@Param("userId") userId: string,
|
|
415
|
+
@Body() body: Record<string, unknown>,
|
|
416
|
+
) {
|
|
277
417
|
await this.auth.assignRole(userId, requireString(body.role, "role"));
|
|
278
418
|
return { ok: true };
|
|
279
419
|
}
|
|
@@ -284,43 +424,66 @@ export class AdminController {
|
|
|
284
424
|
@ApiParam({ name: "userId" })
|
|
285
425
|
@ApiParam({ name: "roleSlug" })
|
|
286
426
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
287
|
-
async revokeRole(
|
|
427
|
+
async revokeRole(
|
|
428
|
+
@Param("userId") userId: string,
|
|
429
|
+
@Param("roleSlug") roleSlug: string,
|
|
430
|
+
@Req() req: Request,
|
|
431
|
+
) {
|
|
288
432
|
// Revoking your own `admin` would strip the very permission that let you call this,
|
|
289
433
|
// with no route back in. Assigning to yourself is fine — it can't lock anyone out.
|
|
290
|
-
if (userId === req.auth!.sub)
|
|
434
|
+
if (userId === req.auth!.sub)
|
|
435
|
+
throw new ForbiddenException("cannot change your own roles");
|
|
291
436
|
await this.auth.revokeRole(userId, roleSlug);
|
|
292
437
|
return { ok: true };
|
|
293
438
|
}
|
|
294
439
|
|
|
295
440
|
@Post("users/:userId/permissions")
|
|
296
441
|
@CheckAbility("permissions:grant")
|
|
297
|
-
@ApiOperation({
|
|
442
|
+
@ApiOperation({
|
|
443
|
+
summary: "[admin] Grant a permission directly to a user, bypassing roles",
|
|
444
|
+
})
|
|
298
445
|
@ApiParam({ name: "userId" })
|
|
299
446
|
@ApiBody({ type: GrantPermissionDto })
|
|
300
447
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
301
|
-
async grantPermission(
|
|
302
|
-
|
|
448
|
+
async grantPermission(
|
|
449
|
+
@Param("userId") userId: string,
|
|
450
|
+
@Body() body: Record<string, unknown>,
|
|
451
|
+
@Req() req: Request,
|
|
452
|
+
) {
|
|
453
|
+
await this.auth.grantPermission(
|
|
454
|
+
userId,
|
|
455
|
+
requireString(body.permission, "permission"),
|
|
456
|
+
req.auth!.sub,
|
|
457
|
+
);
|
|
303
458
|
return { ok: true };
|
|
304
459
|
}
|
|
305
460
|
|
|
306
461
|
@Post("users/:userId/permissions/:permissionSlug/revoke")
|
|
307
462
|
@CheckAbility("permissions:grant")
|
|
308
|
-
@ApiOperation({
|
|
463
|
+
@ApiOperation({
|
|
464
|
+
summary: "[admin] Revoke a direct permission grant from a user",
|
|
465
|
+
})
|
|
309
466
|
@ApiParam({ name: "userId" })
|
|
310
467
|
@ApiParam({ name: "permissionSlug" })
|
|
311
468
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
312
|
-
async revokePermission(
|
|
469
|
+
async revokePermission(
|
|
470
|
+
@Param("userId") userId: string,
|
|
471
|
+
@Param("permissionSlug") permissionSlug: string,
|
|
472
|
+
) {
|
|
313
473
|
await this.auth.revokePermission(userId, permissionSlug);
|
|
314
474
|
return { ok: true };
|
|
315
475
|
}
|
|
316
476
|
|
|
317
477
|
@Post("users/:userId/block")
|
|
318
478
|
@CheckAbility("users:block")
|
|
319
|
-
@ApiOperation({
|
|
479
|
+
@ApiOperation({
|
|
480
|
+
summary: "[admin] Block a user, revoking all their sessions immediately",
|
|
481
|
+
})
|
|
320
482
|
@ApiParam({ name: "userId" })
|
|
321
483
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
322
484
|
async block(@Param("userId") userId: string, @Req() req: Request) {
|
|
323
|
-
if (userId === req.auth!.sub)
|
|
485
|
+
if (userId === req.auth!.sub)
|
|
486
|
+
throw new ForbiddenException("cannot block your own account");
|
|
324
487
|
await this.auth.block(userId, { userId: req.auth!.sub, ip: req.ip });
|
|
325
488
|
return { ok: true };
|
|
326
489
|
}
|
|
@@ -338,13 +501,16 @@ export class AdminController {
|
|
|
338
501
|
@Post("users/:userId/deactivate")
|
|
339
502
|
@CheckAbility("users:block")
|
|
340
503
|
@ApiOperation({
|
|
341
|
-
summary:
|
|
342
|
-
|
|
504
|
+
summary:
|
|
505
|
+
"[admin] Deactivate a user, revoking all their sessions immediately",
|
|
506
|
+
description:
|
|
507
|
+
"Distinct from block/unblock — a routine administrative toggle, not a security action. Both independently deny login.",
|
|
343
508
|
})
|
|
344
509
|
@ApiParam({ name: "userId" })
|
|
345
510
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
346
511
|
async deactivate(@Param("userId") userId: string, @Req() req: Request) {
|
|
347
|
-
if (userId === req.auth!.sub)
|
|
512
|
+
if (userId === req.auth!.sub)
|
|
513
|
+
throw new ForbiddenException("cannot deactivate your own account");
|
|
348
514
|
await this.auth.deactivate(userId, { userId: req.auth!.sub, ip: req.ip });
|
|
349
515
|
return { ok: true };
|
|
350
516
|
}
|
package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts}
RENAMED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
CanActivate,
|
|
3
|
+
ExecutionContext,
|
|
4
|
+
Inject,
|
|
5
|
+
Injectable,
|
|
6
|
+
} from "@nestjs/common";
|
|
7
|
+
import { defineAbilitiesFor } from "../ability.js";
|
|
8
|
+
import { PermissionCache } from "../permission-cache.js";
|
|
9
|
+
import { RbacRepository } from "../repositories/rbac.repository.js";
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* Roles and permissions are global to this deployment: a user has one set, and it applies
|
|
@@ -45,7 +50,9 @@ export class AuthzGuard implements CanActivate {
|
|
|
45
50
|
const req = context.switchToHttp().getRequest();
|
|
46
51
|
if (req.auth) {
|
|
47
52
|
const userId = req.auth.sub as string;
|
|
48
|
-
req.authz = (await this.cache.resolve<AuthzContext>(userId, () =>
|
|
53
|
+
req.authz = (await this.cache.resolve<AuthzContext>(userId, () =>
|
|
54
|
+
this.rbac.resolveAuthzContext(userId),
|
|
55
|
+
)) ?? { roles: [], permissions: [] };
|
|
49
56
|
req.ability = defineAbilitiesFor(req.authz.permissions);
|
|
50
57
|
}
|
|
51
58
|
return true;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { Inject, Injectable } from "@nestjs/common";
|
|
2
2
|
import { and, count, desc, eq, gte, lte, type SQL } from "drizzle-orm";
|
|
3
3
|
import type { AuditEvent } from "@/lib/auth/core/types.js";
|
|
4
|
-
import { DRIZZLE_DB, type Database } from "
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { DRIZZLE_DB, type Database } from "../db.js";
|
|
5
|
+
import {
|
|
6
|
+
buildPageMeta,
|
|
7
|
+
normalizeLimit,
|
|
8
|
+
normalizePage,
|
|
9
|
+
type Paginated,
|
|
10
|
+
} from "../pagination.js";
|
|
11
|
+
import { auditLogs } from "../schema.js";
|
|
12
|
+
import { toIdOrNull, toIdOrUndefined } from "../id.helper.js";
|
|
8
13
|
|
|
9
14
|
export interface AuditLogEntry {
|
|
10
15
|
id: string;
|
|
@@ -46,14 +51,20 @@ export function toAuditLogEntry(row: AuditLogRow): AuditLogEntry {
|
|
|
46
51
|
/** "role_assigned" -> "Role assigned". Derived rather than passed in, so core never has to carry display text. */
|
|
47
52
|
export function humanizeAction(action: string): string {
|
|
48
53
|
const words = action.split("_");
|
|
49
|
-
return [
|
|
54
|
+
return [
|
|
55
|
+
words[0].charAt(0).toUpperCase() + words[0].slice(1),
|
|
56
|
+
...words.slice(1),
|
|
57
|
+
].join(" ");
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
@Injectable()
|
|
53
61
|
export class AuditLogRepository {
|
|
54
62
|
constructor(@Inject(DRIZZLE_DB) private readonly db: Database) {}
|
|
55
63
|
|
|
56
|
-
async append(
|
|
64
|
+
async append(
|
|
65
|
+
event: AuditEvent,
|
|
66
|
+
opts: { remarks?: string } = {},
|
|
67
|
+
): Promise<void> {
|
|
57
68
|
await this.db.insert(auditLogs).values({
|
|
58
69
|
action: event.type,
|
|
59
70
|
name: humanizeAction(event.type),
|
|
@@ -70,10 +81,13 @@ export class AuditLogRepository {
|
|
|
70
81
|
|
|
71
82
|
const conditions: SQL[] = [];
|
|
72
83
|
const userIdBig = toIdOrUndefined(filter.userId);
|
|
73
|
-
if (userIdBig !== undefined)
|
|
84
|
+
if (userIdBig !== undefined)
|
|
85
|
+
conditions.push(eq(auditLogs.userId, userIdBig));
|
|
74
86
|
if (filter.action) conditions.push(eq(auditLogs.action, filter.action));
|
|
75
|
-
if (filter.since)
|
|
76
|
-
|
|
87
|
+
if (filter.since)
|
|
88
|
+
conditions.push(gte(auditLogs.createdAt, new Date(filter.since)));
|
|
89
|
+
if (filter.until)
|
|
90
|
+
conditions.push(lte(auditLogs.createdAt, new Date(filter.until)));
|
|
77
91
|
const where = conditions.length ? and(...conditions) : undefined;
|
|
78
92
|
|
|
79
93
|
const rows = await this.db
|
|
@@ -83,7 +97,10 @@ export class AuditLogRepository {
|
|
|
83
97
|
.orderBy(desc(auditLogs.createdAt), desc(auditLogs.id))
|
|
84
98
|
.limit(limit)
|
|
85
99
|
.offset((page - 1) * limit);
|
|
86
|
-
const [{ value: total }] = await this.db
|
|
100
|
+
const [{ value: total }] = await this.db
|
|
101
|
+
.select({ value: count() })
|
|
102
|
+
.from(auditLogs)
|
|
103
|
+
.where(where);
|
|
87
104
|
|
|
88
105
|
return { items: rows, meta: buildPageMeta(page, limit, total) };
|
|
89
106
|
}
|