/[sudobot]/trunk/src/api/controllers/UserController.ts
ViewVC logotype

Contents of /trunk/src/api/controllers/UserController.ts

Parent Directory Parent Directory | Revision Log Revision Log


Revision 395 - (show annotations)
Mon Jul 29 17:30:01 2024 UTC (8 months, 1 week ago) by rakin
File MIME type: application/typescript
File size: 5819 byte(s)
feat(api): allow admins to view/create users
1 /**
2 * This file is part of SudoBot.
3 *
4 * Copyright (C) 2021-2022 OSN Inc.
5 *
6 * SudoBot is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * SudoBot is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with SudoBot. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 import Request from "../Request";
21 import User from "../../models/User";
22 import Controller from "../Controller";
23 import { body } from 'express-validator';
24 import bcrypt from 'bcrypt';
25 import jwt from 'jsonwebtoken';
26 import KeyValuePair from "../../types/KeyValuePair";
27 import Response from "../Response";
28 import { NextFunction, Response as ExpressResponse } from "express";
29 import ValidatorError from "../middleware/ValidatorError";
30 import RequireAuth from "../middleware/RequireAuth";
31
32 function RequireAdmin(request: Request, response: ExpressResponse, next: NextFunction) {
33 if (!request.user?.isAdmin) {
34 response.status(403).send({ error: "Forbidden", code: 403 });
35 return;
36 }
37
38 next();
39 }
40
41 export default class UserController extends Controller {
42 middleware(): KeyValuePair<Function[]> {
43 return {
44 index: [RequireAuth, RequireAdmin],
45 create: [
46 RequireAuth,
47 RequireAdmin,
48 body(["password"]).isLength({ min: 2 }),
49 body(["username"]).custom(async username => {
50 const user = await User.findOne({ username });
51
52 if (user) {
53 return Promise.reject("Username is already in use");
54 }
55
56 return username;
57 }),
58 body(["discord_id"]).custom(value => /\d+/g.test(value) ? value : Promise.reject("Invalid Snowflake Given"))
59 ],
60 login: [
61 body(["username", "password"]).isLength({ min: 2 }),
62 ],
63 delete: [
64 RequireAuth,
65 body(["username", "password"]).isLength({ min: 2 }),
66 ]
67 };
68 }
69
70 globalMiddleware(): Function[] {
71 return [ValidatorError];
72 }
73
74 public async index() {
75 return await User.find().select(["_id", "username", "createdAt"]).limit(30);
76 }
77
78 public async create(request: Request) {
79 const user = new User();
80
81 user.username = request.body.username;
82 user.discord_id = request.body.discord_id;
83 user.createdAt = new Date();
84 user.tokenUpdatedAt = new Date();
85
86 try {
87 await user.save();
88 }
89 catch (e) {
90 return { error: "DB validation error", error_type: 'db_validation' };
91 }
92
93 const salt = await bcrypt.genSalt();
94 user.password = await bcrypt.hash(request.body.password, salt);
95
96 const token = await jwt.sign({
97 username: user.username,
98 discord_id: user.discord_id,
99 _id: user.id
100 }, process.env.JWT_SECRET!, {
101 expiresIn: "2 days",
102 issuer: "SudoBot API",
103 });
104
105 user.token = token;
106
107 try {
108 await user.save();
109 }
110 catch (e) {
111 return { error: "Token signing error", error_type: 'token_signing' };
112 }
113
114 user.password = undefined;
115 return user;
116 }
117
118 public async delete(request: Request) {
119 const { username, password } = request.body;
120 const user = await User.findOne({ username });
121
122 if (!user) {
123 return { error: "Username is incorrect." };
124 }
125
126 if (!(await bcrypt.compare(password, user.password!))) {
127 return { error: "Password is incorrect." };
128 }
129
130 await user.delete();
131
132 user.password = undefined;
133 user.token = undefined;
134 user.tokenUpdatedAt = undefined;
135
136 return {
137 message: "Account deletion successful",
138 user
139 };
140 }
141
142 public async login(request: Request) {
143 const { username, password } = request.body;
144 const user = await User.findOne({ username });
145
146 if (!user) {
147 return { error: "Username is incorrect." };
148 }
149
150 if (!(await bcrypt.compare(password, user.password!))) {
151 return { error: "Password is incorrect." };
152 }
153
154 let { token } = user;
155
156 try {
157 if (!token) {
158 throw new Error("Token is not set");
159 }
160
161 if (!jwt.verify(token, process.env.JWT_SECRET!)) {
162 throw new Error("Token is not valid");
163 }
164 }
165 catch (e) {
166 console.log(e);
167
168 const newToken = await jwt.sign({
169 username: user.username,
170 discord_id: user.discord_id,
171 _id: user.id
172 }, process.env.JWT_SECRET!, {
173 expiresIn: "2 days",
174 issuer: "SudoBot API",
175 });
176
177 token = newToken;
178 user.tokenUpdatedAt = new Date();
179 user.token = newToken;
180 await user.save();
181 }
182
183 return {
184 message: "Login successful",
185 username,
186 token,
187 expires: new Date(user.tokenUpdatedAt!.getTime() + (2 * 24 * 60 * 60 * 1000)),
188 guilds: this.client.guilds.cache.filter(g => user.guilds.includes(g.id) ?? false)
189 };
190 }
191 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26