@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,29 +2,36 @@ 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, WorkspaceGuard } 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, WorkspaceGuard } 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";
|
|
26
|
-
import { WorkspaceController } from "./workspace.controller.js";
|
|
27
|
-
import { WorkspaceRepository } from "./workspace.repository.js";
|
|
31
|
+
import { SessionRepository } from "./repositories/session.repository.js";
|
|
32
|
+
import { TwoFactorRepository } from "./repositories/two-factor.repository.js";
|
|
33
|
+
import { WorkspaceController } from "./controllers/workspace.controller.js";
|
|
34
|
+
import { WorkspaceRepository } from "./repositories/workspace.repository.js";
|
|
28
35
|
|
|
29
36
|
/**
|
|
30
37
|
* Every controller this library ships, in one place. It is both what the module registers and
|
|
@@ -41,7 +48,10 @@ export class AuthModule {
|
|
|
41
48
|
// stops the process here, by name, rather than shipping as an open endpoint nobody noticed.
|
|
42
49
|
// Nothing above this line allocates a connection pool or a port, so a failed boot leaves
|
|
43
50
|
// nothing behind.
|
|
44
|
-
assertEveryRouteDeclaresATier(AUTH_CONTROLLERS, {
|
|
51
|
+
assertEveryRouteDeclaresATier(AUTH_CONTROLLERS, {
|
|
52
|
+
authentication: AuthGuard,
|
|
53
|
+
ability: AbilityGuard,
|
|
54
|
+
});
|
|
45
55
|
|
|
46
56
|
const pool = new Pool({ connectionString: process.env["DATABASE_URL"] });
|
|
47
57
|
const db = drizzle(pool, { schema });
|
|
@@ -62,14 +72,21 @@ export class AuthModule {
|
|
|
62
72
|
{ provide: DRIZZLE_DB, useValue: db },
|
|
63
73
|
// The cache seam. Swap the store for a Redis-backed one by passing `permissionCacheStore`
|
|
64
74
|
// to forRoot — nothing in this library's source changes. Keys are namespaced simpleauthkit:authz:*.
|
|
65
|
-
{
|
|
75
|
+
{
|
|
76
|
+
provide: PERMISSION_CACHE_STORE,
|
|
77
|
+
useValue:
|
|
78
|
+
config.permissionCacheStore ?? new InMemoryPermissionCacheStore(),
|
|
79
|
+
},
|
|
66
80
|
PermissionCache,
|
|
67
81
|
{ provide: APP_FILTER, useClass: AuthCoreErrorFilter },
|
|
68
82
|
{ provide: APP_INTERCEPTOR, useClass: ResponseInterceptor },
|
|
69
83
|
AuditLogRepository,
|
|
70
84
|
SessionRepository,
|
|
71
85
|
KeyProviderService,
|
|
72
|
-
{
|
|
86
|
+
{
|
|
87
|
+
provide: RATE_LIMIT_STORE,
|
|
88
|
+
useValue: config.rateLimitStore ?? new InMemoryRateLimitStore(),
|
|
89
|
+
},
|
|
73
90
|
RbacRepository,
|
|
74
91
|
WorkspaceRepository,
|
|
75
92
|
TwoFactorRepository,
|
|
@@ -81,7 +98,16 @@ export class AuthModule {
|
|
|
81
98
|
WorkspaceGuard,
|
|
82
99
|
AbilityGuard,
|
|
83
100
|
],
|
|
84
|
-
exports: [
|
|
101
|
+
exports: [
|
|
102
|
+
AuthService,
|
|
103
|
+
AuthGuard,
|
|
104
|
+
AuthzGuard,
|
|
105
|
+
WorkspaceGuard,
|
|
106
|
+
AbilityGuard,
|
|
107
|
+
PermissionCache,
|
|
108
|
+
WorkspaceRepository,
|
|
109
|
+
DRIZZLE_DB,
|
|
110
|
+
],
|
|
85
111
|
};
|
|
86
112
|
}
|
|
87
113
|
}
|
|
@@ -1,12 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
BadRequestException,
|
|
3
|
+
Body,
|
|
4
|
+
Controller,
|
|
5
|
+
Delete,
|
|
6
|
+
ForbiddenException,
|
|
7
|
+
Get,
|
|
8
|
+
Inject,
|
|
9
|
+
Ip,
|
|
10
|
+
Param,
|
|
11
|
+
Patch,
|
|
12
|
+
Post,
|
|
13
|
+
Query,
|
|
14
|
+
Req,
|
|
15
|
+
UseGuards,
|
|
16
|
+
} from "@nestjs/common";
|
|
17
|
+
import {
|
|
18
|
+
ApiBearerAuth,
|
|
19
|
+
ApiBody,
|
|
20
|
+
ApiHeader,
|
|
21
|
+
ApiOperation,
|
|
22
|
+
ApiParam,
|
|
23
|
+
ApiQuery,
|
|
24
|
+
ApiResponse,
|
|
25
|
+
ApiTags,
|
|
26
|
+
} from "@nestjs/swagger";
|
|
3
27
|
import type { Request } from "express";
|
|
4
|
-
import { AbilityGuard } from "
|
|
5
|
-
import { AuthGuard } from "
|
|
6
|
-
import { AuthService } from "
|
|
7
|
-
import { WORKSPACE_HEADER, WorkspaceGuard } from "
|
|
8
|
-
import { CheckAbility } from "
|
|
9
|
-
import { WorkspaceRepository } from "
|
|
28
|
+
import { AbilityGuard } from "../guards/ability.guard.js";
|
|
29
|
+
import { AuthGuard } from "../guards/auth.guard.js";
|
|
30
|
+
import { AuthService } from "../services/auth.service.js";
|
|
31
|
+
import { WORKSPACE_HEADER, WorkspaceGuard } from "../guards/authz.guard.js";
|
|
32
|
+
import { CheckAbility } from "../route-tiers.js";
|
|
33
|
+
import { WorkspaceRepository } from "../repositories/workspace.repository.js";
|
|
10
34
|
import { hashPassword } from "@/lib/auth/core/crypto.js";
|
|
11
35
|
import {
|
|
12
36
|
AssignRoleDto,
|
|
@@ -23,15 +47,21 @@ import {
|
|
|
23
47
|
RoleSummaryDto,
|
|
24
48
|
UpdateRoleDto,
|
|
25
49
|
UpdateUserDto,
|
|
26
|
-
} from "
|
|
27
|
-
import {
|
|
50
|
+
} from "../dto/auth.dto.js";
|
|
51
|
+
import {
|
|
52
|
+
AuditLogListResponseDto,
|
|
53
|
+
UserListResponseDto,
|
|
54
|
+
UserSummaryDto,
|
|
55
|
+
} from "../dto/admin.dto.js";
|
|
28
56
|
|
|
29
57
|
function requireString(value: unknown, field: string): string {
|
|
30
|
-
if (typeof value !== "string" || value.length === 0)
|
|
58
|
+
if (typeof value !== "string" || value.length === 0)
|
|
59
|
+
throw new BadRequestException(`${field} is required`);
|
|
31
60
|
return value;
|
|
32
61
|
}
|
|
33
62
|
|
|
34
|
-
const optionalString = (value: unknown): string | undefined =>
|
|
63
|
+
const optionalString = (value: unknown): string | undefined =>
|
|
64
|
+
typeof value === "string" ? value : undefined;
|
|
35
65
|
|
|
36
66
|
/**
|
|
37
67
|
* Administration *of one workspace*, and of nothing else. Authority is a permission, never a role
|
|
@@ -55,35 +85,65 @@ const optionalString = (value: unknown): string | undefined => (typeof value ===
|
|
|
55
85
|
@ApiTags("auth")
|
|
56
86
|
@Controller("auth/admin")
|
|
57
87
|
@ApiBearerAuth()
|
|
58
|
-
@ApiHeader({
|
|
88
|
+
@ApiHeader({
|
|
89
|
+
name: WORKSPACE_HEADER,
|
|
90
|
+
required: true,
|
|
91
|
+
description:
|
|
92
|
+
"The workspace this request administers. The caller must be an admin member of it.",
|
|
93
|
+
})
|
|
59
94
|
@UseGuards(AuthGuard, WorkspaceGuard, AbilityGuard)
|
|
60
95
|
export class AdminController {
|
|
61
96
|
constructor(
|
|
62
97
|
@Inject(AuthService) private readonly auth: AuthService,
|
|
63
|
-
@Inject(WorkspaceRepository)
|
|
98
|
+
@Inject(WorkspaceRepository)
|
|
99
|
+
private readonly workspaces: WorkspaceRepository,
|
|
64
100
|
) {}
|
|
65
101
|
|
|
66
102
|
@Get("users")
|
|
67
103
|
@CheckAbility("users:read")
|
|
68
104
|
@ApiOperation({ summary: "[admin] List the members of this workspace" })
|
|
69
|
-
@ApiQuery({
|
|
70
|
-
|
|
71
|
-
|
|
105
|
+
@ApiQuery({
|
|
106
|
+
name: "search",
|
|
107
|
+
required: false,
|
|
108
|
+
description: "Email substring match",
|
|
109
|
+
})
|
|
110
|
+
@ApiQuery({
|
|
111
|
+
name: "page",
|
|
112
|
+
required: false,
|
|
113
|
+
description: "1-indexed. Defaults to 1.",
|
|
114
|
+
})
|
|
115
|
+
@ApiQuery({
|
|
116
|
+
name: "limit",
|
|
117
|
+
required: false,
|
|
118
|
+
description: "Defaults to 25, capped at 100.",
|
|
119
|
+
})
|
|
72
120
|
@ApiResponse({ status: 200, type: UserListResponseDto })
|
|
73
|
-
async listUsers(
|
|
74
|
-
|
|
121
|
+
async listUsers(
|
|
122
|
+
@Req() req: Request,
|
|
123
|
+
@Query("search") search?: string,
|
|
124
|
+
@Query("page") page?: string,
|
|
125
|
+
@Query("limit") limit?: string,
|
|
126
|
+
) {
|
|
127
|
+
return this.auth.listUsers(req.authz!, {
|
|
128
|
+
search,
|
|
129
|
+
page: page ? Number(page) : undefined,
|
|
130
|
+
limit: limit ? Number(limit) : undefined,
|
|
131
|
+
});
|
|
75
132
|
}
|
|
76
133
|
|
|
77
134
|
@Post("users")
|
|
78
135
|
@CheckAbility("users:manage")
|
|
79
136
|
@ApiOperation({
|
|
80
137
|
summary: "[admin] Create a user and add them to this workspace",
|
|
81
|
-
description:
|
|
138
|
+
description:
|
|
139
|
+
"No invitation email — the account is usable immediately with the password given here.",
|
|
82
140
|
})
|
|
83
141
|
@ApiBody({ type: CreateUserDto })
|
|
84
142
|
@ApiResponse({ status: 201, type: UserSummaryDto })
|
|
85
143
|
async createUser(@Body() body: Record<string, unknown>, @Req() req: Request) {
|
|
86
|
-
const passwordHash = await hashPassword(
|
|
144
|
+
const passwordHash = await hashPassword(
|
|
145
|
+
requireString(body.password, "password"),
|
|
146
|
+
);
|
|
87
147
|
const member = await this.workspaces.createMember(
|
|
88
148
|
req.authz!.workspaceId,
|
|
89
149
|
{
|
|
@@ -94,7 +154,11 @@ export class AdminController {
|
|
|
94
154
|
displayName: optionalString(body.displayName),
|
|
95
155
|
phone: optionalString(body.phone),
|
|
96
156
|
username: optionalString(body.username),
|
|
97
|
-
roles: Array.isArray(body.roles)
|
|
157
|
+
roles: Array.isArray(body.roles)
|
|
158
|
+
? body.roles.filter(
|
|
159
|
+
(role): role is string => typeof role === "string",
|
|
160
|
+
)
|
|
161
|
+
: undefined,
|
|
98
162
|
},
|
|
99
163
|
req.auth!.sub,
|
|
100
164
|
);
|
|
@@ -112,18 +176,28 @@ export class AdminController {
|
|
|
112
176
|
|
|
113
177
|
@Patch("users/:userId")
|
|
114
178
|
@CheckAbility("users:manage")
|
|
115
|
-
@ApiOperation({
|
|
179
|
+
@ApiOperation({
|
|
180
|
+
summary: "[admin] Edit a member's profile",
|
|
181
|
+
description:
|
|
182
|
+
"Profile fields only — email is the login identifier and is not editable here.",
|
|
183
|
+
})
|
|
116
184
|
@ApiParam({ name: "userId" })
|
|
117
185
|
@ApiBody({ type: UpdateUserDto })
|
|
118
186
|
@ApiResponse({ status: 200, type: UserSummaryDto })
|
|
119
|
-
async updateUser(
|
|
187
|
+
async updateUser(
|
|
188
|
+
@Param("userId") userId: string,
|
|
189
|
+
@Body() body: Record<string, unknown>,
|
|
190
|
+
@Req() req: Request,
|
|
191
|
+
) {
|
|
120
192
|
return this.auth.updateUser(
|
|
121
193
|
req.authz!,
|
|
122
194
|
userId,
|
|
123
195
|
{
|
|
124
|
-
firstName:
|
|
196
|
+
firstName:
|
|
197
|
+
body.firstName === null ? null : optionalString(body.firstName),
|
|
125
198
|
lastName: body.lastName === null ? null : optionalString(body.lastName),
|
|
126
|
-
displayName:
|
|
199
|
+
displayName:
|
|
200
|
+
body.displayName === null ? null : optionalString(body.displayName),
|
|
127
201
|
phone: body.phone === null ? null : optionalString(body.phone),
|
|
128
202
|
username: body.username === null ? null : optionalString(body.username),
|
|
129
203
|
photo: body.photo === null ? null : optionalString(body.photo),
|
|
@@ -143,21 +217,53 @@ export class AdminController {
|
|
|
143
217
|
@ApiParam({ name: "userId" })
|
|
144
218
|
@ApiBody({ type: DeleteReasonDto, required: false })
|
|
145
219
|
@ApiResponse({ status: 200, type: OkResponseDto })
|
|
146
|
-
async deleteUser(
|
|
147
|
-
|
|
148
|
-
|
|
220
|
+
async deleteUser(
|
|
221
|
+
@Param("userId") userId: string,
|
|
222
|
+
@Body() body: Record<string, unknown>,
|
|
223
|
+
@Req() req: Request,
|
|
224
|
+
) {
|
|
225
|
+
if (userId === req.auth!.sub)
|
|
226
|
+
throw new ForbiddenException("cannot delete your own account");
|
|
227
|
+
await this.auth.deleteUser(
|
|
228
|
+
req.authz!,
|
|
229
|
+
userId,
|
|
230
|
+
req.auth!.sub,
|
|
231
|
+
optionalString(body?.reason),
|
|
232
|
+
);
|
|
149
233
|
return { ok: true };
|
|
150
234
|
}
|
|
151
235
|
|
|
152
236
|
@Get("audit-log")
|
|
153
237
|
@CheckAbility("audit-log:read")
|
|
154
|
-
@ApiOperation({
|
|
238
|
+
@ApiOperation({
|
|
239
|
+
summary: "[admin] List this workspace's audit log entries, newest first",
|
|
240
|
+
})
|
|
155
241
|
@ApiQuery({ name: "userId", required: false })
|
|
156
|
-
@ApiQuery({
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
242
|
+
@ApiQuery({
|
|
243
|
+
name: "action",
|
|
244
|
+
required: false,
|
|
245
|
+
description: "AuditEvent discriminant, e.g. 'role_assigned'",
|
|
246
|
+
})
|
|
247
|
+
@ApiQuery({
|
|
248
|
+
name: "since",
|
|
249
|
+
required: false,
|
|
250
|
+
description: "ISO 8601 timestamp",
|
|
251
|
+
})
|
|
252
|
+
@ApiQuery({
|
|
253
|
+
name: "until",
|
|
254
|
+
required: false,
|
|
255
|
+
description: "ISO 8601 timestamp",
|
|
256
|
+
})
|
|
257
|
+
@ApiQuery({
|
|
258
|
+
name: "page",
|
|
259
|
+
required: false,
|
|
260
|
+
description: "1-indexed. Defaults to 1.",
|
|
261
|
+
})
|
|
262
|
+
@ApiQuery({
|
|
263
|
+
name: "limit",
|
|
264
|
+
required: false,
|
|
265
|
+
description: "Defaults to 25, capped at 100.",
|
|
266
|
+
})
|
|
161
267
|
@ApiResponse({ status: 200, type: AuditLogListResponseDto })
|
|
162
268
|
async listAuditLog(
|
|
163
269
|
@Req() req: Request,
|
|
@@ -200,17 +306,23 @@ export class AdminController {
|
|
|
200
306
|
})
|
|
201
307
|
@ApiBody({ type: DefinePermissionDto })
|
|
202
308
|
@ApiResponse({ status: 201, type: PermissionSummaryDto })
|
|
203
|
-
async definePermission(
|
|
309
|
+
async definePermission(
|
|
310
|
+
@Body() body: Record<string, unknown>,
|
|
311
|
+
@Req() req: Request,
|
|
312
|
+
) {
|
|
204
313
|
return this.auth.definePermission(
|
|
205
314
|
{
|
|
206
315
|
slug: requireString(body.slug, "slug"),
|
|
207
316
|
name: optionalString(body.name),
|
|
208
317
|
displayName: optionalString(body.displayName),
|
|
209
|
-
description:
|
|
318
|
+
description:
|
|
319
|
+
body.description === null ? null : optionalString(body.description),
|
|
210
320
|
group: optionalString(body.group),
|
|
211
|
-
groupOrder:
|
|
321
|
+
groupOrder:
|
|
322
|
+
typeof body.groupOrder === "number" ? body.groupOrder : undefined,
|
|
212
323
|
order: typeof body.order === "number" ? body.order : undefined,
|
|
213
|
-
isActive:
|
|
324
|
+
isActive:
|
|
325
|
+
typeof body.isActive === "boolean" ? body.isActive : undefined,
|
|
214
326
|
},
|
|
215
327
|
req.auth!.sub,
|
|
216
328
|
);
|
|
@@ -248,15 +360,21 @@ export class AdminController {
|
|
|
248
360
|
@ApiParam({ name: "roleId" })
|
|
249
361
|
@ApiBody({ type: UpdateRoleDto })
|
|
250
362
|
@ApiResponse({ status: 200, type: RoleSummaryDto })
|
|
251
|
-
async updateRole(
|
|
363
|
+
async updateRole(
|
|
364
|
+
@Param("roleId") roleId: string,
|
|
365
|
+
@Body() body: Record<string, unknown>,
|
|
366
|
+
@Req() req: Request,
|
|
367
|
+
) {
|
|
252
368
|
return this.auth.updateRole(
|
|
253
369
|
req.authz!,
|
|
254
370
|
roleId,
|
|
255
371
|
{
|
|
256
372
|
name: optionalString(body.name),
|
|
257
373
|
displayName: optionalString(body.displayName),
|
|
258
|
-
description:
|
|
259
|
-
|
|
374
|
+
description:
|
|
375
|
+
body.description === null ? null : optionalString(body.description),
|
|
376
|
+
isActive:
|
|
377
|
+
typeof body.isActive === "boolean" ? body.isActive : undefined,
|
|
260
378
|
},
|
|
261
379
|
req.auth!.sub,
|
|
262
380
|
);
|
|
@@ -266,83 +384,147 @@ export class AdminController {
|
|
|
266
384
|
@CheckAbility("roles:manage")
|
|
267
385
|
@ApiOperation({
|
|
268
386
|
summary: "[admin] Delete one of this workspace's roles",
|
|
269
|
-
description:
|
|
387
|
+
description:
|
|
388
|
+
"Soft-delete: existing assignments are left in place rather than cascade-deleted, and the role simply stops being resolved.",
|
|
270
389
|
})
|
|
271
390
|
@ApiParam({ name: "roleId" })
|
|
272
391
|
@ApiBody({ type: DeleteReasonDto, required: false })
|
|
273
392
|
@ApiResponse({ status: 200, type: OkResponseDto })
|
|
274
|
-
async deleteRole(
|
|
275
|
-
|
|
393
|
+
async deleteRole(
|
|
394
|
+
@Param("roleId") roleId: string,
|
|
395
|
+
@Body() body: Record<string, unknown>,
|
|
396
|
+
@Req() req: Request,
|
|
397
|
+
) {
|
|
398
|
+
await this.auth.deleteRole(
|
|
399
|
+
req.authz!,
|
|
400
|
+
roleId,
|
|
401
|
+
req.auth!.sub,
|
|
402
|
+
optionalString(body?.reason),
|
|
403
|
+
);
|
|
276
404
|
return { ok: true };
|
|
277
405
|
}
|
|
278
406
|
|
|
279
407
|
@Post("roles/:roleId/permissions")
|
|
280
408
|
@CheckAbility("roles:manage")
|
|
281
|
-
@ApiOperation({
|
|
409
|
+
@ApiOperation({
|
|
410
|
+
summary: "[admin] Attach a permission to one of this workspace's roles",
|
|
411
|
+
})
|
|
282
412
|
@ApiParam({ name: "roleId" })
|
|
283
413
|
@ApiBody({ type: AttachPermissionDto })
|
|
284
414
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
285
|
-
async attachPermissionToRole(
|
|
286
|
-
|
|
415
|
+
async attachPermissionToRole(
|
|
416
|
+
@Param("roleId") roleId: string,
|
|
417
|
+
@Body() body: Record<string, unknown>,
|
|
418
|
+
@Req() req: Request,
|
|
419
|
+
) {
|
|
420
|
+
await this.auth.attachPermissionToRole(
|
|
421
|
+
req.authz!,
|
|
422
|
+
roleId,
|
|
423
|
+
requireString(body.permission, "permission"),
|
|
424
|
+
req.auth!.sub,
|
|
425
|
+
);
|
|
287
426
|
return { ok: true };
|
|
288
427
|
}
|
|
289
428
|
|
|
290
429
|
@Post("users/:userId/roles")
|
|
291
430
|
@CheckAbility("roles:assign")
|
|
292
|
-
@ApiOperation({
|
|
431
|
+
@ApiOperation({
|
|
432
|
+
summary: "[admin] Assign a role to a member of this workspace",
|
|
433
|
+
})
|
|
293
434
|
@ApiParam({ name: "userId" })
|
|
294
435
|
@ApiBody({ type: AssignRoleDto })
|
|
295
436
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
296
|
-
async assignRole(
|
|
297
|
-
|
|
437
|
+
async assignRole(
|
|
438
|
+
@Param("userId") userId: string,
|
|
439
|
+
@Body() body: Record<string, unknown>,
|
|
440
|
+
@Req() req: Request,
|
|
441
|
+
) {
|
|
442
|
+
await this.auth.assignRole(
|
|
443
|
+
req.authz!,
|
|
444
|
+
userId,
|
|
445
|
+
requireString(body.role, "role"),
|
|
446
|
+
);
|
|
298
447
|
return { ok: true };
|
|
299
448
|
}
|
|
300
449
|
|
|
301
450
|
@Post("users/:userId/roles/:roleSlug/revoke")
|
|
302
451
|
@CheckAbility("roles:assign")
|
|
303
|
-
@ApiOperation({
|
|
452
|
+
@ApiOperation({
|
|
453
|
+
summary: "[admin] Revoke a role from a member of this workspace",
|
|
454
|
+
})
|
|
304
455
|
@ApiParam({ name: "userId" })
|
|
305
456
|
@ApiParam({ name: "roleSlug" })
|
|
306
457
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
307
|
-
async revokeRole(
|
|
458
|
+
async revokeRole(
|
|
459
|
+
@Param("userId") userId: string,
|
|
460
|
+
@Param("roleSlug") roleSlug: string,
|
|
461
|
+
@Req() req: Request,
|
|
462
|
+
) {
|
|
308
463
|
// Revoking your own `admin` would strip the very permission that authorised the call. In a
|
|
309
464
|
// workspace there is no way back in afterwards, so the route refuses it the same way
|
|
310
465
|
// self-block does. Assigning to yourself is fine; it can't lock anyone out.
|
|
311
|
-
if (userId === req.auth!.sub)
|
|
466
|
+
if (userId === req.auth!.sub)
|
|
467
|
+
throw new ForbiddenException("cannot change your own roles");
|
|
312
468
|
await this.auth.revokeRole(req.authz!, userId, roleSlug);
|
|
313
469
|
return { ok: true };
|
|
314
470
|
}
|
|
315
471
|
|
|
316
472
|
@Post("users/:userId/permissions")
|
|
317
473
|
@CheckAbility("permissions:grant")
|
|
318
|
-
@ApiOperation({
|
|
474
|
+
@ApiOperation({
|
|
475
|
+
summary: "[admin] Grant a permission directly to a member, bypassing roles",
|
|
476
|
+
description: "The grant is scoped to this workspace.",
|
|
477
|
+
})
|
|
319
478
|
@ApiParam({ name: "userId" })
|
|
320
479
|
@ApiBody({ type: GrantPermissionDto })
|
|
321
480
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
322
|
-
async grantPermission(
|
|
323
|
-
|
|
481
|
+
async grantPermission(
|
|
482
|
+
@Param("userId") userId: string,
|
|
483
|
+
@Body() body: Record<string, unknown>,
|
|
484
|
+
@Req() req: Request,
|
|
485
|
+
) {
|
|
486
|
+
await this.auth.grantPermission(
|
|
487
|
+
req.authz!,
|
|
488
|
+
userId,
|
|
489
|
+
requireString(body.permission, "permission"),
|
|
490
|
+
req.auth!.sub,
|
|
491
|
+
);
|
|
324
492
|
return { ok: true };
|
|
325
493
|
}
|
|
326
494
|
|
|
327
495
|
@Post("users/:userId/permissions/:permissionSlug/revoke")
|
|
328
496
|
@CheckAbility("permissions:grant")
|
|
329
|
-
@ApiOperation({
|
|
497
|
+
@ApiOperation({
|
|
498
|
+
summary: "[admin] Revoke a direct permission grant from a member",
|
|
499
|
+
})
|
|
330
500
|
@ApiParam({ name: "userId" })
|
|
331
501
|
@ApiParam({ name: "permissionSlug" })
|
|
332
502
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
333
|
-
async revokePermission(
|
|
503
|
+
async revokePermission(
|
|
504
|
+
@Param("userId") userId: string,
|
|
505
|
+
@Param("permissionSlug") permissionSlug: string,
|
|
506
|
+
@Req() req: Request,
|
|
507
|
+
) {
|
|
334
508
|
await this.auth.revokePermission(req.authz!, userId, permissionSlug);
|
|
335
509
|
return { ok: true };
|
|
336
510
|
}
|
|
337
511
|
|
|
338
512
|
@Post("users/:userId/block")
|
|
339
513
|
@CheckAbility("users:block")
|
|
340
|
-
@ApiOperation({
|
|
341
|
-
|
|
514
|
+
@ApiOperation({
|
|
515
|
+
summary: "[admin] Block a member, revoking all their sessions immediately",
|
|
516
|
+
description:
|
|
517
|
+
"Blocking disables the whole account, so it is only allowed against a member of the workspace you administer.",
|
|
518
|
+
})
|
|
342
519
|
@ApiParam({ name: "userId" })
|
|
343
520
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
344
|
-
async block(
|
|
345
|
-
|
|
521
|
+
async block(
|
|
522
|
+
@Param("userId") userId: string,
|
|
523
|
+
@Req() req: Request,
|
|
524
|
+
@Ip() ip: string,
|
|
525
|
+
) {
|
|
526
|
+
if (userId === req.auth!.sub)
|
|
527
|
+
throw new ForbiddenException("cannot block your own account");
|
|
346
528
|
await this.auth.block(req.authz!, userId, { userId: req.auth!.sub, ip });
|
|
347
529
|
return { ok: true };
|
|
348
530
|
}
|
|
@@ -353,21 +535,34 @@ export class AdminController {
|
|
|
353
535
|
@ApiParam({ name: "userId" })
|
|
354
536
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
355
537
|
async unblock(@Param("userId") userId: string, @Req() req: Request) {
|
|
356
|
-
await this.auth.unblock(req.authz!, userId, {
|
|
538
|
+
await this.auth.unblock(req.authz!, userId, {
|
|
539
|
+
userId: req.auth!.sub,
|
|
540
|
+
ip: req.ip,
|
|
541
|
+
});
|
|
357
542
|
return { ok: true };
|
|
358
543
|
}
|
|
359
544
|
|
|
360
545
|
@Post("users/:userId/deactivate")
|
|
361
546
|
@CheckAbility("users:block")
|
|
362
547
|
@ApiOperation({
|
|
363
|
-
summary:
|
|
364
|
-
|
|
548
|
+
summary:
|
|
549
|
+
"[admin] Deactivate a member, revoking all their sessions immediately",
|
|
550
|
+
description:
|
|
551
|
+
"Distinct from block/unblock — a routine administrative toggle, not a security action. Both independently deny login.",
|
|
365
552
|
})
|
|
366
553
|
@ApiParam({ name: "userId" })
|
|
367
554
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
368
|
-
async deactivate(
|
|
369
|
-
|
|
370
|
-
|
|
555
|
+
async deactivate(
|
|
556
|
+
@Param("userId") userId: string,
|
|
557
|
+
@Req() req: Request,
|
|
558
|
+
@Ip() ip: string,
|
|
559
|
+
) {
|
|
560
|
+
if (userId === req.auth!.sub)
|
|
561
|
+
throw new ForbiddenException("cannot deactivate your own account");
|
|
562
|
+
await this.auth.deactivate(req.authz!, userId, {
|
|
563
|
+
userId: req.auth!.sub,
|
|
564
|
+
ip,
|
|
565
|
+
});
|
|
371
566
|
return { ok: true };
|
|
372
567
|
}
|
|
373
568
|
|
|
@@ -376,7 +571,11 @@ export class AdminController {
|
|
|
376
571
|
@ApiOperation({ summary: "[admin] Reactivate a member" })
|
|
377
572
|
@ApiParam({ name: "userId" })
|
|
378
573
|
@ApiResponse({ status: 201, type: OkResponseDto })
|
|
379
|
-
async activate(
|
|
574
|
+
async activate(
|
|
575
|
+
@Param("userId") userId: string,
|
|
576
|
+
@Req() req: Request,
|
|
577
|
+
@Ip() ip: string,
|
|
578
|
+
) {
|
|
380
579
|
await this.auth.activate(req.authz!, userId, { userId: req.auth!.sub, ip });
|
|
381
580
|
return { ok: true };
|
|
382
581
|
}
|