@simple-auth-kit/cli 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
- package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
- package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
- package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
- package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
- package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
- package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
- package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
- package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
- package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
- package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
- package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
- package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
- package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
- package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
- package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
- package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +57 -22
- package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +67 -19
- package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
- package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
- package/registry/combos/express-prisma/variants/base/src/seed.ts +78 -20
- package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
- package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +81 -26
- package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +73 -21
- package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
- package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
- package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
- package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +109 -29
- package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
- package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
- package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
- package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
- package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
- package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
- package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
- package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
- package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
- package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +59 -28
- package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
- package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +98 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
- package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +78 -20
- package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +55 -27
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +104 -29
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
- package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +109 -29
- package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
- package/registry.json +6 -4
- package/simple-auth-kit.ts +582 -124
- package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
- package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
- package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
- package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
- package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
- package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
- package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
- package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
- package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
- package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
- package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
import type { NextFunction, Request, RequestHandler, Response } from "express";
|
|
2
|
-
import type { AuthConfig } from "./auth.config.js";
|
|
3
|
-
import { AuthService } from "./auth.service.js";
|
|
4
|
-
import { HttpError } from "./http-error.js";
|
|
5
|
-
import { authenticated, createTieredRouter, publicRoute, type TierMiddleware } from "./route-tiers.js";
|
|
6
|
-
import "./request-context.js";
|
|
7
|
-
|
|
8
|
-
function requireString(value: unknown, field: string): string {
|
|
9
|
-
if (typeof value !== "string" || value.length === 0) throw new HttpError(400, `${field} is required`);
|
|
10
|
-
return value;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const optionalString = (value: unknown): string | undefined => (typeof value === "string" ? value : undefined);
|
|
14
|
-
|
|
15
|
-
export interface AuthRouterDeps extends TierMiddleware {
|
|
16
|
-
auth: AuthService;
|
|
17
|
-
config: AuthConfig;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Identity endpoints — everything that is about *a user*, never about a group of them.
|
|
22
|
-
* Administration lives in admin.router.ts.
|
|
23
|
-
*
|
|
24
|
-
* Replaces the reference combo's `AuthController` (a Nest `@Controller`). Same endpoints, same
|
|
25
|
-
* validation, same status codes — just an Express `Router()` instead of a decorated class, and
|
|
26
|
-
* middleware passed in where the controller lists `@UseGuards(...)`. Every handler is wrapped in
|
|
27
|
-
* try/catch + `next(err)` so a thrown `HttpError`/`AuthCoreError` reaches the error-handling
|
|
28
|
-
* middleware mounted in create-auth-app.ts instead of leaking a bare 500.
|
|
29
|
-
*/
|
|
30
|
-
export function createAuthRouter(deps: AuthRouterDeps): RequestHandler {
|
|
31
|
-
const { auth, config, authentication, authorization } = deps;
|
|
32
|
-
const router = createTieredRouter({ authentication, authorization });
|
|
33
|
-
|
|
34
|
-
router.route("post", "/signup", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
|
|
35
|
-
try {
|
|
36
|
-
const body = req.body as Record<string, unknown>;
|
|
37
|
-
const tokens = await auth.signup({
|
|
38
|
-
email: requireString(body.email, "email"),
|
|
39
|
-
password: requireString(body.password, "password"),
|
|
40
|
-
firstName: optionalString(body.firstName),
|
|
41
|
-
lastName: optionalString(body.lastName),
|
|
42
|
-
displayName: optionalString(body.displayName),
|
|
43
|
-
phone: optionalString(body.phone),
|
|
44
|
-
username: optionalString(body.username),
|
|
45
|
-
userAgent: req.headers["user-agent"],
|
|
46
|
-
ip: req.ip,
|
|
47
|
-
});
|
|
48
|
-
res.status(201).json(tokens);
|
|
49
|
-
} catch (err) {
|
|
50
|
-
next(err);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
router.route("post", "/login", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
|
|
55
|
-
try {
|
|
56
|
-
const body = req.body as Record<string, unknown>;
|
|
57
|
-
const result = await auth.login({
|
|
58
|
-
identifier: requireString(body.identifier, "identifier"),
|
|
59
|
-
password: requireString(body.password, "password"),
|
|
60
|
-
userAgent: req.headers["user-agent"],
|
|
61
|
-
ip: req.ip,
|
|
62
|
-
});
|
|
63
|
-
res.status(200).json(result);
|
|
64
|
-
} catch (err) {
|
|
65
|
-
next(err);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
router.route("post", "/login/2fa", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
|
|
70
|
-
try {
|
|
71
|
-
const body = req.body as Record<string, unknown>;
|
|
72
|
-
const tokens = await auth.loginTwoFactor({
|
|
73
|
-
challengeToken: requireString(body.challengeToken, "challengeToken"),
|
|
74
|
-
code: requireString(body.code, "code"),
|
|
75
|
-
userAgent: req.headers["user-agent"],
|
|
76
|
-
ip: req.ip,
|
|
77
|
-
});
|
|
78
|
-
res.status(200).json(tokens);
|
|
79
|
-
} catch (err) {
|
|
80
|
-
next(err);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
router.route("post", "/refresh", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
|
|
85
|
-
try {
|
|
86
|
-
const body = req.body as Record<string, unknown>;
|
|
87
|
-
const tokens = await auth.refresh(requireString(body.refreshToken, "refreshToken"));
|
|
88
|
-
res.status(200).json(tokens);
|
|
89
|
-
} catch (err) {
|
|
90
|
-
next(err);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// `roles`/`permissions` are whatever applies to this request — see authz.middleware.ts for how
|
|
95
|
-
// they are resolved. A request that names no authorization scope gets empty arrays.
|
|
96
|
-
//
|
|
97
|
-
// `permissions` is the caller's whole authorization: `defineAbilitiesFor(permissions)` from
|
|
98
|
-
// ability.ts rebuilds, in the client, the identical CASL ability the server's own route check
|
|
99
|
-
// just answered with, so the UI cannot disagree with what the API will allow. The slugs go on
|
|
100
|
-
// the wire rather than a serialised ability, so the payload is something a human can read in a
|
|
101
|
-
// log — and it is the same `req.authz` the ability was built from, not a re-resolution.
|
|
102
|
-
router.route("get", "/me", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
|
|
103
|
-
try {
|
|
104
|
-
const { sub, sessionId } = req.auth!;
|
|
105
|
-
const [{ twoFactorEnabled }, profile] = await Promise.all([auth.getTwoFactorStatus(sub), auth.getProfile(sub)]);
|
|
106
|
-
res.status(200).json({
|
|
107
|
-
sub,
|
|
108
|
-
sessionId,
|
|
109
|
-
roles: req.authz?.roles ?? [],
|
|
110
|
-
permissions: req.authz?.permissions ?? [],
|
|
111
|
-
twoFactorEnabled,
|
|
112
|
-
email: profile.email,
|
|
113
|
-
firstName: profile.firstName,
|
|
114
|
-
lastName: profile.lastName,
|
|
115
|
-
displayName: profile.displayName,
|
|
116
|
-
phone: profile.phone,
|
|
117
|
-
username: profile.username,
|
|
118
|
-
photo: profile.photo,
|
|
119
|
-
});
|
|
120
|
-
} catch (err) {
|
|
121
|
-
next(err);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
router.route("get", "/sessions", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
|
|
126
|
-
try {
|
|
127
|
-
const sessions = await auth.listActiveSessions(req.auth!.sub);
|
|
128
|
-
res.status(200).json(sessions);
|
|
129
|
-
} catch (err) {
|
|
130
|
-
next(err);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
router.route("post", "/logout", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
|
|
135
|
-
try {
|
|
136
|
-
const { sessionId, jti, sub } = req.auth!;
|
|
137
|
-
// Recorded so the row can later be told apart from a session an administrator ended —
|
|
138
|
-
// see `sessions.revoked_by`.
|
|
139
|
-
await auth.logout(sessionId, jti, config.accessTokenTtlSeconds, { userId: sub, ip: req.ip });
|
|
140
|
-
res.status(200).json({ ok: true });
|
|
141
|
-
} catch (err) {
|
|
142
|
-
next(err);
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
router.route("post", "/logout-all", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
|
|
147
|
-
try {
|
|
148
|
-
await auth.logoutAll(req.auth!.sub, { userId: req.auth!.sub, ip: req.ip });
|
|
149
|
-
res.status(200).json({ ok: true });
|
|
150
|
-
} catch (err) {
|
|
151
|
-
next(err);
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
router.route("post", "/logout-others", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
|
|
156
|
-
try {
|
|
157
|
-
await auth.logoutOthers(req.auth!.sub, req.auth!.sessionId, { userId: req.auth!.sub, ip: req.ip });
|
|
158
|
-
res.status(200).json({ ok: true });
|
|
159
|
-
} catch (err) {
|
|
160
|
-
next(err);
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
router.route("post", "/password/change", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
|
|
165
|
-
try {
|
|
166
|
-
const body = req.body as Record<string, unknown>;
|
|
167
|
-
await auth.changePassword(req.auth!.sub, req.auth!.sessionId, {
|
|
168
|
-
currentPassword: requireString(body.currentPassword, "currentPassword"),
|
|
169
|
-
newPassword: requireString(body.newPassword, "newPassword"),
|
|
170
|
-
});
|
|
171
|
-
res.status(200).json({ ok: true });
|
|
172
|
-
} catch (err) {
|
|
173
|
-
next(err);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
router.route("patch", "/me", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
|
|
178
|
-
try {
|
|
179
|
-
const body = req.body as Record<string, unknown>;
|
|
180
|
-
const profile = await auth.updateProfile(req.auth!.sub, {
|
|
181
|
-
firstName: body.firstName === null ? null : optionalString(body.firstName),
|
|
182
|
-
lastName: body.lastName === null ? null : optionalString(body.lastName),
|
|
183
|
-
displayName: body.displayName === null ? null : optionalString(body.displayName),
|
|
184
|
-
phone: body.phone === null ? null : optionalString(body.phone),
|
|
185
|
-
username: body.username === null ? null : optionalString(body.username),
|
|
186
|
-
photo: body.photo === null ? null : optionalString(body.photo),
|
|
187
|
-
});
|
|
188
|
-
res.status(200).json(profile);
|
|
189
|
-
} catch (err) {
|
|
190
|
-
next(err);
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
router.route("post", "/2fa/enroll", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
|
|
195
|
-
try {
|
|
196
|
-
const result = await auth.enrollTwoFactor(req.auth!.sub);
|
|
197
|
-
res.status(200).json(result);
|
|
198
|
-
} catch (err) {
|
|
199
|
-
next(err);
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
router.route("post", "/2fa/confirm", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
|
|
204
|
-
try {
|
|
205
|
-
const body = req.body as Record<string, unknown>;
|
|
206
|
-
const result = await auth.confirmTwoFactor(req.auth!.sub, requireString(body.code, "code"));
|
|
207
|
-
res.status(200).json(result);
|
|
208
|
-
} catch (err) {
|
|
209
|
-
next(err);
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
router.route("post", "/2fa/disable", authenticated(), async (req: Request, res: Response, next: NextFunction) => {
|
|
214
|
-
try {
|
|
215
|
-
const body = req.body as Record<string, unknown>;
|
|
216
|
-
await auth.disableTwoFactor(req.auth!.sub, requireString(body.code, "code"));
|
|
217
|
-
res.status(200).json({ ok: true });
|
|
218
|
-
} catch (err) {
|
|
219
|
-
next(err);
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
router.route("post", "/password/forgot", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
|
|
224
|
-
try {
|
|
225
|
-
const body = req.body as Record<string, unknown>;
|
|
226
|
-
await auth.requestPasswordReset(requireString(body.email, "email"));
|
|
227
|
-
res.status(200).json({ ok: true }); // always 200 — never reveal whether the email exists
|
|
228
|
-
} catch (err) {
|
|
229
|
-
next(err);
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
router.route("post", "/password/reset", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
|
|
234
|
-
try {
|
|
235
|
-
const body = req.body as Record<string, unknown>;
|
|
236
|
-
await auth.resetPassword(requireString(body.token, "token"), requireString(body.newPassword, "newPassword"));
|
|
237
|
-
res.status(200).json({ ok: true });
|
|
238
|
-
} catch (err) {
|
|
239
|
-
next(err);
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
router.route("get", "/oauth/:provider/start", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
|
|
244
|
-
try {
|
|
245
|
-
const result = await auth.startOAuth(requireString(req.params.provider, "provider"));
|
|
246
|
-
res.status(200).json(result);
|
|
247
|
-
} catch (err) {
|
|
248
|
-
next(err);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
router.route("get", "/oauth/:provider/callback", publicRoute(), async (req: Request, res: Response, next: NextFunction) => {
|
|
253
|
-
try {
|
|
254
|
-
const tokens = await auth.completeOAuthCallback(
|
|
255
|
-
requireString(req.params.provider, "provider"),
|
|
256
|
-
requireString(req.query.code, "code"),
|
|
257
|
-
requireString(req.query.state, "state"),
|
|
258
|
-
);
|
|
259
|
-
res.status(200).json(tokens);
|
|
260
|
-
} catch (err) {
|
|
261
|
-
next(err);
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
return router.handler;
|
|
266
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { eq } from "drizzle-orm";
|
|
2
|
-
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
3
|
-
import type { PasswordResetStoreDeps } from "@/lib/auth/core/password-reset.js";
|
|
4
|
-
import * as schema from "./schema.js";
|
|
5
|
-
import { toId } from "./id.helper.js";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Plain class, no decorators — Drizzle-specific implementation of `PasswordResetStoreDeps`
|
|
9
|
-
* (registry/core/password-reset.js), mirroring the reference combo's PasswordResetRepository.
|
|
10
|
-
*/
|
|
11
|
-
export class PasswordResetRepository implements PasswordResetStoreDeps {
|
|
12
|
-
constructor(private readonly db: NodePgDatabase<typeof schema>) {}
|
|
13
|
-
|
|
14
|
-
async saveResetToken(input: { userId: string; tokenHash: string; expiresAt: string }): Promise<void> {
|
|
15
|
-
await this.db.insert(schema.passwordResetTokens).values({
|
|
16
|
-
userId: toId(input.userId),
|
|
17
|
-
tokenHash: input.tokenHash,
|
|
18
|
-
expiresAt: new Date(input.expiresAt),
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async findValidResetToken(tokenHash: string): Promise<{ userId: string; expiresAt: string; consumedAt: string | null } | null> {
|
|
23
|
-
const [row] = await this.db.select().from(schema.passwordResetTokens).where(eq(schema.passwordResetTokens.tokenHash, tokenHash)).limit(1);
|
|
24
|
-
if (!row) return null;
|
|
25
|
-
return { userId: row.userId.toString(), expiresAt: row.expiresAt.toISOString(), consumedAt: row.consumedAt?.toISOString() ?? null };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async consumeResetToken(tokenHash: string): Promise<void> {
|
|
29
|
-
await this.db.update(schema.passwordResetTokens).set({ consumedAt: new Date() }).where(eq(schema.passwordResetTokens.tokenHash, tokenHash));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async setPasswordHash(userId: string, passwordHash: string): Promise<void> {
|
|
33
|
-
await this.db.update(schema.users).set({ passwordHash }).where(eq(schema.users.id, toId(userId)));
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
import type { NextFunction, Request, RequestHandler, Response } from "express";
|
|
2
|
-
import { AuthService } from "./auth.service.js";
|
|
3
|
-
import { HttpError } from "./http-error.js";
|
|
4
|
-
import { ability, createTieredRouter, type TierMiddleware } from "./route-tiers.js";
|
|
5
|
-
import "./request-context.js";
|
|
6
|
-
|
|
7
|
-
function requireString(value: unknown, field: string): string {
|
|
8
|
-
if (typeof value !== "string" || value.length === 0) throw new HttpError(400, `${field} is required`);
|
|
9
|
-
return value;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const optionalString = (value: unknown): string | undefined => (typeof value === "string" ? value : undefined);
|
|
13
|
-
|
|
14
|
-
export interface AdminRouterDeps extends TierMiddleware {
|
|
15
|
-
auth: AuthService;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Administration of the whole deployment. Authority is a permission, never a role name: the authz
|
|
20
|
-
* middleware resolves the caller's roles and permissions from the database on this request, builds
|
|
21
|
-
* the CASL ability from them, and each route's `ability(...)` tier checks its slugs against it.
|
|
22
|
-
* There is no role-based bypass — holding a role called "admin" that carries no permissions gets
|
|
23
|
-
* exactly the same 403 as holding no role at all, which is why `npm run seed` (which provisions the
|
|
24
|
-
* catalog and the roles that carry it) is a prerequisite for this router doing anything.
|
|
25
|
-
*
|
|
26
|
-
* Each route says what it demands; the rows say who is granted it. The slugs below are checked at
|
|
27
|
-
* compile time against `PERMISSION_CATALOG` in `rbac.defaults.ts`, while who holds them — and
|
|
28
|
-
* whether the permission is active at all — is edited through this very router and takes effect on
|
|
29
|
-
* the next request. `GET /auth/me` returns the caller's slugs, and a console rebuilds the same
|
|
30
|
-
* ability from them with the same function the middleware used.
|
|
31
|
-
*
|
|
32
|
-
* Replaces the reference combo's `AdminController`. Mounted under `/auth/admin` by
|
|
33
|
-
* create-auth-app.ts; `createTieredRouter` stands in for its
|
|
34
|
-
* `@UseGuards(AuthGuard, AuthzGuard, AbilityGuard)` — see route-tiers.ts for why the tier is a
|
|
35
|
-
* required argument rather than a decorator.
|
|
36
|
-
*/
|
|
37
|
-
export function createAdminRouter(deps: AdminRouterDeps): RequestHandler {
|
|
38
|
-
const { auth, authentication, authorization } = deps;
|
|
39
|
-
const admin = createTieredRouter({ authentication, authorization });
|
|
40
|
-
|
|
41
|
-
admin.route("get", "/users", ability("users:read"), async (req: Request, res: Response, next: NextFunction) => {
|
|
42
|
-
try {
|
|
43
|
-
const { search, page, limit } = req.query as Record<string, string | undefined>;
|
|
44
|
-
res.status(200).json(await auth.listUsers({ search, page: page ? Number(page) : undefined, limit: limit ? Number(limit) : undefined }));
|
|
45
|
-
} catch (err) {
|
|
46
|
-
next(err);
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
admin.route("post", "/users", ability("users:manage"), async (req: Request, res: Response, next: NextFunction) => {
|
|
51
|
-
try {
|
|
52
|
-
const body = req.body as Record<string, unknown>;
|
|
53
|
-
res.status(201).json(
|
|
54
|
-
await auth.createUser(
|
|
55
|
-
{
|
|
56
|
-
email: requireString(body.email, "email"),
|
|
57
|
-
password: requireString(body.password, "password"),
|
|
58
|
-
firstName: optionalString(body.firstName),
|
|
59
|
-
lastName: optionalString(body.lastName),
|
|
60
|
-
displayName: optionalString(body.displayName),
|
|
61
|
-
phone: optionalString(body.phone),
|
|
62
|
-
username: optionalString(body.username),
|
|
63
|
-
roles: Array.isArray(body.roles) ? body.roles.filter((role): role is string => typeof role === "string") : undefined,
|
|
64
|
-
},
|
|
65
|
-
req.auth!.sub,
|
|
66
|
-
),
|
|
67
|
-
);
|
|
68
|
-
} catch (err) {
|
|
69
|
-
next(err);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
admin.route("get", "/users/:userId", ability("users:read"), async (req: Request, res: Response, next: NextFunction) => {
|
|
74
|
-
try {
|
|
75
|
-
res.status(200).json(await auth.getUser(requireString(req.params.userId, "userId")));
|
|
76
|
-
} catch (err) {
|
|
77
|
-
next(err);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
admin.route("patch", "/users/:userId", ability("users:manage"), async (req: Request, res: Response, next: NextFunction) => {
|
|
82
|
-
try {
|
|
83
|
-
const body = req.body as Record<string, unknown>;
|
|
84
|
-
res.status(200).json(
|
|
85
|
-
await auth.updateUser(
|
|
86
|
-
requireString(req.params.userId, "userId"),
|
|
87
|
-
{
|
|
88
|
-
firstName: body.firstName === null ? null : optionalString(body.firstName),
|
|
89
|
-
lastName: body.lastName === null ? null : optionalString(body.lastName),
|
|
90
|
-
displayName: body.displayName === null ? null : optionalString(body.displayName),
|
|
91
|
-
phone: body.phone === null ? null : optionalString(body.phone),
|
|
92
|
-
username: body.username === null ? null : optionalString(body.username),
|
|
93
|
-
photo: body.photo === null ? null : optionalString(body.photo),
|
|
94
|
-
},
|
|
95
|
-
req.auth!.sub,
|
|
96
|
-
),
|
|
97
|
-
);
|
|
98
|
-
} catch (err) {
|
|
99
|
-
next(err);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
admin.route("delete", "/users/:userId", ability("users:manage"), async (req: Request, res: Response, next: NextFunction) => {
|
|
104
|
-
try {
|
|
105
|
-
const userId = requireString(req.params.userId, "userId");
|
|
106
|
-
if (userId === req.auth!.sub) throw new HttpError(403, "cannot delete your own account");
|
|
107
|
-
const body = (req.body ?? {}) as Record<string, unknown>;
|
|
108
|
-
await auth.deleteUser(userId, req.auth!.sub, optionalString(body.reason));
|
|
109
|
-
res.status(200).json({ ok: true });
|
|
110
|
-
} catch (err) {
|
|
111
|
-
next(err);
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
admin.route("get", "/audit-log", ability("audit-log:read"), async (req: Request, res: Response, next: NextFunction) => {
|
|
116
|
-
try {
|
|
117
|
-
const { userId, action, since, until, page, limit } = req.query as Record<string, string | undefined>;
|
|
118
|
-
res.status(200).json(
|
|
119
|
-
await auth.listAuditLog({ userId, action, since, until, page: page ? Number(page) : undefined, limit: limit ? Number(limit) : undefined }),
|
|
120
|
-
);
|
|
121
|
-
} catch (err) {
|
|
122
|
-
next(err);
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
admin.route("get", "/permissions", ability("permissions:read"), async (_req: Request, res: Response, next: NextFunction) => {
|
|
127
|
-
try {
|
|
128
|
-
res.status(200).json(await auth.listPermissions());
|
|
129
|
-
} catch (err) {
|
|
130
|
-
next(err);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
admin.route("post", "/permissions", ability("permissions:define"), async (req: Request, res: Response, next: NextFunction) => {
|
|
135
|
-
try {
|
|
136
|
-
const body = req.body as Record<string, unknown>;
|
|
137
|
-
res.status(201).json(
|
|
138
|
-
await auth.definePermission(
|
|
139
|
-
{
|
|
140
|
-
slug: requireString(body.slug, "slug"),
|
|
141
|
-
name: optionalString(body.name),
|
|
142
|
-
displayName: optionalString(body.displayName),
|
|
143
|
-
description: body.description === null ? null : optionalString(body.description),
|
|
144
|
-
group: optionalString(body.group),
|
|
145
|
-
groupOrder: typeof body.groupOrder === "number" ? body.groupOrder : undefined,
|
|
146
|
-
order: typeof body.order === "number" ? body.order : undefined,
|
|
147
|
-
isActive: typeof body.isActive === "boolean" ? body.isActive : undefined,
|
|
148
|
-
},
|
|
149
|
-
req.auth!.sub,
|
|
150
|
-
),
|
|
151
|
-
);
|
|
152
|
-
} catch (err) {
|
|
153
|
-
next(err);
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
admin.route("get", "/roles", ability("roles:manage"), async (_req: Request, res: Response, next: NextFunction) => {
|
|
158
|
-
try {
|
|
159
|
-
res.status(200).json(await auth.listRoles());
|
|
160
|
-
} catch (err) {
|
|
161
|
-
next(err);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
admin.route("post", "/roles", ability("roles:manage"), async (req: Request, res: Response, next: NextFunction) => {
|
|
166
|
-
try {
|
|
167
|
-
const body = req.body as Record<string, unknown>;
|
|
168
|
-
res.status(201).json(
|
|
169
|
-
await auth.createRole(
|
|
170
|
-
{
|
|
171
|
-
slug: requireString(body.slug, "slug"),
|
|
172
|
-
name: optionalString(body.name),
|
|
173
|
-
displayName: optionalString(body.displayName),
|
|
174
|
-
description: optionalString(body.description) ?? null,
|
|
175
|
-
},
|
|
176
|
-
req.auth!.sub,
|
|
177
|
-
),
|
|
178
|
-
);
|
|
179
|
-
} catch (err) {
|
|
180
|
-
next(err);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
admin.route("patch", "/roles/:roleId", ability("roles:manage"), async (req: Request, res: Response, next: NextFunction) => {
|
|
185
|
-
try {
|
|
186
|
-
const body = req.body as Record<string, unknown>;
|
|
187
|
-
res.status(200).json(
|
|
188
|
-
await auth.updateRole(
|
|
189
|
-
requireString(req.params.roleId, "roleId"),
|
|
190
|
-
{
|
|
191
|
-
name: optionalString(body.name),
|
|
192
|
-
displayName: optionalString(body.displayName),
|
|
193
|
-
description: body.description === null ? null : optionalString(body.description),
|
|
194
|
-
isActive: typeof body.isActive === "boolean" ? body.isActive : undefined,
|
|
195
|
-
},
|
|
196
|
-
req.auth!.sub,
|
|
197
|
-
),
|
|
198
|
-
);
|
|
199
|
-
} catch (err) {
|
|
200
|
-
next(err);
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
admin.route("delete", "/roles/:roleId", ability("roles:manage"), async (req: Request, res: Response, next: NextFunction) => {
|
|
205
|
-
try {
|
|
206
|
-
const body = (req.body ?? {}) as Record<string, unknown>;
|
|
207
|
-
await auth.deleteRole(requireString(req.params.roleId, "roleId"), req.auth!.sub, optionalString(body.reason));
|
|
208
|
-
res.status(200).json({ ok: true });
|
|
209
|
-
} catch (err) {
|
|
210
|
-
next(err);
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
admin.route("post", "/roles/:roleId/permissions", ability("roles:manage"), async (req: Request, res: Response, next: NextFunction) => {
|
|
215
|
-
try {
|
|
216
|
-
const body = req.body as Record<string, unknown>;
|
|
217
|
-
await auth.attachPermissionToRole(requireString(req.params.roleId, "roleId"), requireString(body.permission, "permission"), req.auth!.sub);
|
|
218
|
-
res.status(201).json({ ok: true });
|
|
219
|
-
} catch (err) {
|
|
220
|
-
next(err);
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
admin.route("post", "/users/:userId/roles", ability("roles:assign"), async (req: Request, res: Response, next: NextFunction) => {
|
|
225
|
-
try {
|
|
226
|
-
const body = req.body as Record<string, unknown>;
|
|
227
|
-
await auth.assignRole(requireString(req.params.userId, "userId"), requireString(body.role, "role"));
|
|
228
|
-
res.status(201).json({ ok: true });
|
|
229
|
-
} catch (err) {
|
|
230
|
-
next(err);
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
admin.route("post", "/users/:userId/roles/:roleSlug/revoke", ability("roles:assign"), async (req: Request, res: Response, next: NextFunction) => {
|
|
235
|
-
try {
|
|
236
|
-
const userId = requireString(req.params.userId, "userId");
|
|
237
|
-
// Revoking your own `admin` would strip the very permission that let you call this,
|
|
238
|
-
// with no route back in. Assigning to yourself is fine — it can't lock anyone out.
|
|
239
|
-
if (userId === req.auth!.sub) throw new HttpError(403, "cannot change your own roles");
|
|
240
|
-
await auth.revokeRole(userId, requireString(req.params.roleSlug, "roleSlug"));
|
|
241
|
-
res.status(201).json({ ok: true });
|
|
242
|
-
} catch (err) {
|
|
243
|
-
next(err);
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
admin.route("post", "/users/:userId/permissions", ability("permissions:grant"), async (req: Request, res: Response, next: NextFunction) => {
|
|
248
|
-
try {
|
|
249
|
-
const body = req.body as Record<string, unknown>;
|
|
250
|
-
await auth.grantPermission(requireString(req.params.userId, "userId"), requireString(body.permission, "permission"), req.auth!.sub);
|
|
251
|
-
res.status(201).json({ ok: true });
|
|
252
|
-
} catch (err) {
|
|
253
|
-
next(err);
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
admin.route("post", "/users/:userId/permissions/:permissionSlug/revoke", ability("permissions:grant"), async (req: Request, res: Response, next: NextFunction) => {
|
|
258
|
-
try {
|
|
259
|
-
await auth.revokePermission(requireString(req.params.userId, "userId"), requireString(req.params.permissionSlug, "permissionSlug"));
|
|
260
|
-
res.status(201).json({ ok: true });
|
|
261
|
-
} catch (err) {
|
|
262
|
-
next(err);
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
admin.route("post", "/users/:userId/block", ability("users:block"), async (req: Request, res: Response, next: NextFunction) => {
|
|
267
|
-
try {
|
|
268
|
-
const userId = requireString(req.params.userId, "userId");
|
|
269
|
-
if (userId === req.auth!.sub) throw new HttpError(403, "cannot block your own account");
|
|
270
|
-
await auth.block(userId, { userId: req.auth!.sub, ip: req.ip });
|
|
271
|
-
res.status(201).json({ ok: true });
|
|
272
|
-
} catch (err) {
|
|
273
|
-
next(err);
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
admin.route("post", "/users/:userId/unblock", ability("users:block"), async (req: Request, res: Response, next: NextFunction) => {
|
|
278
|
-
try {
|
|
279
|
-
await auth.unblock(requireString(req.params.userId, "userId"), { userId: req.auth!.sub, ip: req.ip });
|
|
280
|
-
res.status(201).json({ ok: true });
|
|
281
|
-
} catch (err) {
|
|
282
|
-
next(err);
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
admin.route("post", "/users/:userId/deactivate", ability("users:block"), async (req: Request, res: Response, next: NextFunction) => {
|
|
287
|
-
try {
|
|
288
|
-
const userId = requireString(req.params.userId, "userId");
|
|
289
|
-
if (userId === req.auth!.sub) throw new HttpError(403, "cannot deactivate your own account");
|
|
290
|
-
await auth.deactivate(userId, { userId: req.auth!.sub, ip: req.ip });
|
|
291
|
-
res.status(201).json({ ok: true });
|
|
292
|
-
} catch (err) {
|
|
293
|
-
next(err);
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
admin.route("post", "/users/:userId/activate", ability("users:block"), async (req: Request, res: Response, next: NextFunction) => {
|
|
298
|
-
try {
|
|
299
|
-
await auth.activate(requireString(req.params.userId, "userId"), { userId: req.auth!.sub, ip: req.ip });
|
|
300
|
-
res.status(201).json({ ok: true });
|
|
301
|
-
} catch (err) {
|
|
302
|
-
next(err);
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
return admin.handler;
|
|
307
|
-
}
|