/[sudobot]/branches/7.x/src/api/controllers/VerificationController.ts
ViewVC logotype

Contents of /branches/7.x/src/api/controllers/VerificationController.ts

Parent Directory Parent Directory | Revision Log Revision Log


Revision 577 - (show annotations)
Mon Jul 29 18:52:37 2024 UTC (8 months ago) by rakinar2
File MIME type: application/typescript
File size: 7708 byte(s)
chore: add old version archive branches (2.x to 9.x-dev)
1 /*
2 * This file is part of SudoBot.
3 *
4 * Copyright (C) 2021-2023 OSN Developers.
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 { VerificationEntry } from "@prisma/client";
21 import axios from "axios";
22 import bcrypt from "bcrypt";
23 import { z } from "zod";
24 import { Action } from "../../decorators/Action";
25 import { Validate } from "../../decorators/Validate";
26 import { zSnowflake } from "../../types/SnowflakeSchema";
27 import { logError } from "../../utils/logger";
28 import Controller from "../Controller";
29 import Request from "../Request";
30 import Response from "../Response";
31
32 const showInfoSchema = z.object({
33 token: z.string(),
34 userId: zSnowflake
35 });
36
37 const emailVerificationSchema = z.object({
38 emailVerificationToken: z.string(),
39 verificationToken: z.string(),
40 userId: zSnowflake
41 });
42
43 export default class VerificationController extends Controller {
44 checkExpiry(entry: VerificationEntry) {
45 const config = this.client.configManager.config[entry.guildId!]?.verification;
46 return (
47 entry.createdAt.getTime() + (config?.max_time ?? 0) > Date.now() &&
48 entry.attempts <= ((config?.max_attempts ?? 0) === 0 ? Number.POSITIVE_INFINITY : config!.max_attempts)
49 );
50 }
51
52 @Action("GET", "/challenge/verify")
53 async showInfo(request: Request) {
54 const parsed = showInfoSchema.safeParse(request.query);
55
56 if (!parsed.success) {
57 return new Response({
58 status: 422,
59 body: "Invalid Payload"
60 });
61 }
62
63 const { token, userId } = parsed.data;
64
65 const info = await this.client.prisma.verificationEntry.findFirst({
66 where: {
67 userId,
68 token
69 }
70 });
71
72 const guild = this.client.guilds.cache.get(info?.guildId!);
73
74 if (!info || !guild || !this.checkExpiry(info)) {
75 return new Response({
76 status: 404,
77 body: {
78 error: "Not found"
79 }
80 });
81 }
82
83 return {
84 ...info,
85 guildName: guild.name,
86 icon: guild.icon
87 };
88 }
89
90 @Action("PUT", "/challenge/verify/email/initiate")
91 @Validate(
92 z.object({
93 verificationToken: z.string(),
94 email: z.string().email(),
95 userId: zSnowflake
96 })
97 )
98 async verifyByEmail(request: Request) {
99 const { email, verificationToken, userId } = request.parsedBody;
100 const key = request.headers["x-frontend-key"];
101
102 if (key !== process.env.FRONTEND_AUTH_KEY) {
103 return new Response({
104 status: 401,
105 body: {
106 error: "Unauthorized"
107 }
108 });
109 }
110
111 const entry = await this.client.prisma.verificationEntry.findFirst({
112 where: {
113 userId,
114 token: verificationToken
115 }
116 });
117
118 if (!entry || !this.checkExpiry(entry)) {
119 return new Response({
120 status: 401,
121 body: {
122 error: "Unauthorized"
123 }
124 });
125 }
126
127 const emailVerificationToken = await bcrypt.hash(userId + (Math.random() * 100000000).toString(), await bcrypt.genSalt());
128
129 await this.client.prisma.verificationEntry.update({
130 where: {
131 id: entry.id
132 },
133 data: {
134 meta: {
135 email,
136 emailVerificationToken
137 }
138 }
139 });
140
141 return {
142 success: true,
143 data: {
144 ...entry,
145 guildName: this.client.guilds.cache.get(entry.guildId)?.name,
146 meta: {
147 email,
148 emailVerificationToken
149 }
150 }
151 };
152 }
153
154 @Action("POST", "/challenge/verify/email/finish")
155 @Validate(emailVerificationSchema)
156 async verifyByEmailFinish(request: Request) {
157 const { emailVerificationToken, verificationToken, userId } = request.parsedBody;
158
159 const entry = await this.client.prisma.verificationEntry.findFirst({
160 where: {
161 userId,
162 token: verificationToken
163 }
164 });
165
166 if (!entry || !this.checkExpiry(entry) || typeof entry.meta !== "object") {
167 return new Response({
168 status: 401,
169 body: {
170 error: "Unauthorized"
171 }
172 });
173 }
174
175 const info = entry.meta as {
176 emailVerificationToken: string;
177 email: string;
178 };
179
180 if (!info.emailVerificationToken || !info.email || info.emailVerificationToken !== emailVerificationToken) {
181 return new Response({
182 status: 403,
183 body: {
184 error: "Forbidden"
185 }
186 });
187 }
188
189 const result = await this.client.verification.attemptToVerifyUserByToken(userId, verificationToken, "Email");
190
191 if (!result) {
192 return new Response({
193 status: 401,
194 body: {
195 success: false,
196 error: "We were unable to verify you."
197 }
198 });
199 }
200
201 return {
202 success: true
203 };
204 }
205
206 @Action("POST", "/challenge/verify/captcha")
207 @Validate(
208 z.object({
209 responseToken: z.string(),
210 verificationToken: z.string(),
211 userId: zSnowflake
212 })
213 )
214 async verifyByCaptcha(request: Request) {
215 const { responseToken, verificationToken, userId } = request.parsedBody;
216
217 try {
218 const response = await axios.post(
219 "https://www.google.com/recaptcha/api/siteverify",
220 new URLSearchParams({
221 secret: process.env.RECAPTCHA_SITE_SECRET!,
222 response: responseToken
223 }).toString(),
224 {
225 headers: {
226 "Content-Type": "application/x-www-form-urlencoded"
227 }
228 }
229 );
230
231 if (!response.data.success) {
232 throw new Error();
233 }
234 } catch (error) {
235 logError(error);
236
237 return new Response({
238 status: 401,
239 body: {
240 success: false,
241 error: "We were unable to verify you."
242 }
243 });
244 }
245
246 const result = await this.client.verification.attemptToVerifyUserByToken(userId, verificationToken, "Captcha");
247
248 if (!result) {
249 return new Response({
250 status: 401,
251 body: {
252 success: false,
253 error: "We were unable to verify you."
254 }
255 });
256 }
257
258 return {
259 success: true
260 };
261 }
262 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26