• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atlp-rwanda / e-commerce-ones-and-zeroes-bn / acb286f9-cae3-4bfb-8760-af80895dd455

05 Jul 2024 09:07AM UTC coverage: 92.387% (-1.2%) from 93.573%
acb286f9-cae3-4bfb-8760-af80895dd455

push

circleci

web-flow
Merge pull request #62 from atlp-rwanda/bg-fixes-reset-password

[Fixes #187355105  #187355121 ] resetting password listing products

252 of 283 branches covered (89.05%)

Branch coverage included in aggregate %.

14 of 15 new or added lines in 3 files covered. (93.33%)

13 existing lines in 2 files now uncovered.

828 of 886 relevant lines covered (93.45%)

2.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.45
/src/controllers/userControllers.ts
1
import { Request, Response } from 'express';
2
import bcrypt from 'bcrypt';
8✔
3
import speakeasy from 'speakeasy';
8✔
4
import { generateToken } from '../helps/generateToken';
8✔
5
import { validateEmail, validatePassword } from '../validations/validations';
8✔
6
import {
8✔
7
  registerMessageTemplate,
8
  nodeMail,
9
  successfullyverifiedTemplate,
10
  successfullyDisabledAccountTemplate,
11
  successfullyRestoredAccountTemplate,
12
  twoFAMessageTemplate,
13
  resetPasswordEmail,
14
} from '../utils/emails';
15
import { db } from '../database/models';
8✔
16
import passport from '../config/google.auth';
8✔
17
import { registerToken } from '../config/jwt.token';
8✔
18
const jwt = require('jsonwebtoken');
8✔
19
const sgMail = require('@sendgrid/mail');
8✔
20
const dotenv = require('dotenv');
8✔
21
dotenv.config();
8✔
22
const secret = process.env.JWT_SECRET;
8✔
23

24
interface User {
25
  firstName: string;
26
  lastName: string;
27
  email: string;
28
  password: string;
29
  role: string;
30
  isVerified: boolean;
31
  isSeller?: boolean;
32
}
33

34
export default class UserController {
8✔
35
  static async getUsers(req: Request, res: Response): Promise<Response> {
36
    try {
4✔
37
      const users = await db.User.findAll();
4✔
38
      return res.status(200).json({
2✔
39
        status: 'success',
40

41
        data: users,
42
      });
43
    } catch (error) {
44
      return res.status(500).json({ message: 'Failed to fetch users' });
2✔
45
    }
46
  }
47

48
  static async registerUser(req: Request, res: Response): Promise<Response> {
49
    try {
6✔
50
      const { firstName, lastName, email, password } = req.body as User;
6✔
51

52
      if (!firstName || !lastName || !email || !password) {
6✔
53
        return res.status(400).json({ message: 'All fields are required' });
1✔
54
      }
55

56
      const existingUser = await db.User.findOne({ where: { email } });
5✔
57
      if (existingUser) {
4✔
58
        return res.status(400).json({ message: 'Email already exists' });
1✔
59
      }
60

61
      if (!validateEmail(email)) {
3✔
62
        return res.status(400).json({ message: 'Invalid email' });
1✔
63
      }
64

65
      if (!validatePassword(password)) {
2✔
66
        return res.status(400).json({ message: 'Password should be strong' });
1✔
67
      }
68

69
      const hashedPassword = bcrypt.hashSync(password, 10);
1✔
70

71
      const isSeller: boolean = req.body.isSeller;
1✔
72

73
      const newUser = await db.User.create({
1✔
74
        firstName,
75
        lastName,
76
        email,
77
        role: isSeller ? 'seller' : 'buyer',
1!
78
        password: hashedPassword,
79
      });
80

81
      const token = generateToken(
1✔
82
        newUser.userId,
83

84
        email,
85

86
        firstName,
87

88
        lastName,
89
        newUser.passwordLastChanged,
90

91
        newUser?.role,
3!
92
        newUser?.isVerified,
3!
93
      );
94

95
      await nodeMail(
1✔
96
        email,
97
        'You are required to Verify your email',
98
        registerMessageTemplate(firstName, token),
99
      );
100

101
      return res
1✔
102
        .status(200)
103
        .json({ message: 'Account created!', data: newUser, token });
104
    } catch (error: any) {
105
      console.log(error);
1✔
106
      return res.status(500).json({ message: 'Failed to register user' });
1✔
107
    }
108
  }
109

110
  // get single profile/user controller
111
  static async getSingleUser(req: Request, res: Response) {
112
    try {
2✔
113
      const singleUser = await db.User.findOne({
2✔
114
        where: {
115
          userId: req.params.id,
116
        },
117
      });
118
      if (singleUser) {
1✔
119
        return res.status(200).json({
1✔
120
          status: 'User Profile',
121
          data: singleUser,
122
          billing: singleUser.billingAddress,
123
        });
124
      }
125
    } catch (error: any) {
126
      return res.status(500).json({
1✔
127
        message: "provided ID doen't exist!",
128
        error: error.message,
129
      });
130
    }
131
  }
132
  //update single profile/user
133
  static async updateSingleUser(req: Request, res: Response) {
134
    try {
2✔
135
      const singleUser = await db.User.findOne({
2✔
136
        where: {
137
          userId: req.params.id,
138
        },
139
      });
140

141
      if (!singleUser) {
1!
142
        return res.status(404).json({
×
143
          status: 'Not Found',
144
          error: 'User not found',
145
        });
146
      }
147

148
      const {
149
        firstName,
150
        lastName,
151
        gender,
152
        birthdate,
153
        preferredLanguage,
154
        preferredCurrency,
155
        billingAddress,
156
      } = req.body;
1✔
157

158
      if (firstName) {
1✔
159
        singleUser.firstName = firstName;
1✔
160
      }
161
      if (lastName) {
1✔
162
        singleUser.lastName = lastName;
1✔
163
      }
164
      if (gender) {
1✔
165
        singleUser.gender = gender;
1✔
166
      }
167
      if (birthdate) {
1✔
168
        singleUser.birthdate = birthdate;
1✔
169
      }
170
      if (preferredLanguage) {
1✔
171
        singleUser.preferredLanguage = preferredLanguage;
1✔
172
      }
173
      if (preferredCurrency) {
1✔
174
        singleUser.preferredCurrency = preferredCurrency;
1✔
175
      }
176
      if (billingAddress) {
1✔
177
        singleUser.billingAddress = billingAddress;
1✔
178
      }
179

180
      singleUser.updatedAt = new Date();
1✔
181

182
      if (req.body.email) {
1!
183
        return res.status(400).json({
×
184
          status: 'Bad Request',
185
          error: 'Email cannot be updated',
186
        });
187
      }
188

189
      await singleUser.save();
1✔
190

191
      return res.status(200).json({
1✔
192
        status: 'Profile updated successfully',
193
        data: singleUser,
194
      });
195
    } catch (err: any) {
196
      return res.status(500).json({
1✔
197
        status: 'Internal Server Error',
198
        error: err.message,
199
      });
200
    }
201
  }
202

203
  static async registerUserGoogle(req: any, res: any) {
204
    const data = req.body;
2✔
205
    let firstName = data.given_name;
2✔
206
    let lastName = data.family_name;
2✔
207
    let email = data.email;
2✔
208
    const newUser = {
2✔
209
      firstName,
210
      lastName,
211
      email,
212
      isActive: true,
213
      isGoogle: true,
214
      password: 'google',
215
    };
216
    try {
2✔
217
      const emailExist = await db.User.findOne({
2✔
218
        where: { email: email, isGoogle: false },
219
      });
220
      if (emailExist) {
1!
221
        ('Email already exists');
×
222
        return res.status(401).json({
×
223
          message:
224
            'Email has registered using normal way, Go and login using email and password',
225
        });
226
      }
227
      const alreadyRegistered = await db.User.findOne({
1✔
228
        where: { email: email, isGoogle: true },
229
      });
230
      if (alreadyRegistered) {
1!
231
        const payLoad = {
×
232
          userId: alreadyRegistered.userId,
233
          firstName: alreadyRegistered.firstName,
234
          lastName: alreadyRegistered.lastName,
235
          role: alreadyRegistered.role,
236
        };
237
        const userToken = await registerToken(payLoad);
×
238
        return res.status(201).json({ message: 'User signed in!', userToken });
×
239
      }
240
      const createdUser = await db.User.create(newUser);
1✔
241
      const payLoad = {
1✔
242
        userId: createdUser.userId,
243
        firstName: createdUser.firstName,
244
        lastName: createdUser.lastName,
245
        role: createdUser.role,
246
      };
247
      const userToken = await registerToken(payLoad);
1✔
248
      return res.status(201).json({
1✔
249
        message: 'User registered Successful, Please Sign in!',
250
        token: userToken,
251
      });
252
    } catch (err) {
253
      return res.status(500).json({ message: 'Internal Serveral error!' });
1✔
254
    }
255
  }
256

257
  static async loginUserPage(req: any, res: any) {
258
    return res.send('User login <a href="/auth/google">google</a>');
1✔
259
  }
260

261
  static async googleAuth(req: any, res: any) {
262
    passport.authenticate('google', { scope: ['profile', 'email'] });
1✔
263
  }
264

265
  static async login(req: Request, res: Response) {
266
    try {
6✔
267
      const { email, password } = req.body;
6✔
268
      if (!email || !password) {
6✔
269
        return res
1✔
270
          .status(400)
271
          .json({ message: 'Email and password are required' });
272
      }
273

274
      const user = await db.User.findOne({ where: { email } });
5✔
275
      if (!user) {
4✔
276
        return res.status(404).json({ message: 'User not found' });
1✔
277
      }
278

279
      const isPasswordMatch = await bcrypt.compare(password, user.password);
3✔
280
      if (!isPasswordMatch) {
3✔
281
        return res.status(401).json({ message: 'Incorrect credentials' });
1✔
282
      }
283

284
      if (!user.isVerified) {
2✔
285
        return res.status(401).send({ message: 'Email not verified' });
1✔
286
      }
287

288
      if (user.role === 'seller') {
1!
289
        if (!user.use2FA) {
×
290
          // If 2FA is not enabled, return a JWT token without 2FA verification
291
          const token = generateToken(
×
292
            user.userId,
293
            user.email,
294
            user.firstName,
295
            user.lastName,
296
            user.role,
297
            user.passwordLastChanged,
298
            user.isVerified,
299
          );
300
          return res
×
301
            .status(200)
302
            .json({ message: 'User authenticated without 2FA', token });
303
        }
304

305
        // If use2FA is true, proceed with sending the 2FA token
306
        const token = speakeasy.totp({
×
307
          secret: user.secret,
308
          encoding: 'base32',
309
          step: 120, // Token is valid for 2 minutes
310
        });
311

312
        // Send the email with the token
313
        const name = user.name;
×
314
        await nodeMail(
×
315
          email,
316
          '2FA Token for One and Zero E-commerce',
317
          twoFAMessageTemplate(token),
318
        );
319

320
        // Send response with message to check email for the 2FA token
321
        return res
×
322
          .status(200)
323
          .json({ message: 'Check your email for the 2FA token', id: user.id });
324
      } else {
325
        // Generate JWT token without 2FA
326
        const token = generateToken(
1✔
327
          user.userId,
328
          user.email,
329
          user.firstName,
330
          user.lastName,
331
          user.role,
332
          user.passwordLastChanged,
333
          user.isVerified,
334
        );
335
        res.status(200).json({ message: 'User authenticated', token });
1✔
336
      }
337
    } catch (error: any) {
338
      res.status(500).json({ message: 'Error during login' });
1✔
339
    }
340
  }
341

342
  static async disableUser(req: Request, res: Response) {
343
    try {
6✔
344
      const { reason } = req.body;
6✔
345
      const existingUser = await db.User.findOne({
6✔
346
        where: { userId: req.params.id },
347
      });
348
      const user = (req as any).user;
6✔
349
      if (!existingUser) {
6✔
350
        return res.status(404).json({ message: 'No such User found' });
1✔
351
      }
352
      if (existingUser.dataValues.userId === user.userId) {
5✔
353
        return res.status(403).json({ message: 'User cannot self-disable' });
1✔
354
      }
355

356
      if (!existingUser.dataValues.isActive) {
3✔
357
        await db.User.update(
1✔
358
          { isActive: true },
359
          {
360
            where: {
361
              userId: req.params.id,
362
            },
363
          },
364
        );
365
        const restoredMessage: string = successfullyRestoredAccountTemplate(
1✔
366
          existingUser.dataValues.firstName,
367
        );
368
        await nodeMail(
1✔
369
          existingUser.dataValues.email,
370
          'Your account was restored',
371
          restoredMessage,
372
        );
373
        return res
1✔
374
          .status(200)
375
          .json({ message: 'User account was successfully restored' });
376
      }
377
      if (!reason) {
2✔
378
        return res
1✔
379
          .status(400)
380
          .json({ message: 'Missing reason for disabling account' });
381
      }
382
      await db.User.update(
1✔
383
        { isActive: false },
384
        {
385
          where: {
386
            userId: req.params.id,
387
          },
388
        },
389
      );
390
      const disabledMessage: string = successfullyDisabledAccountTemplate(
1✔
391
        existingUser.dataValues.firstName,
392
        reason,
393
      );
394

395
      await nodeMail(
1✔
396
        existingUser.dataValues.email,
397
        'Your account was disabled',
398
        disabledMessage,
399
      );
400
      return res
1✔
401
        .status(200)
402
        .json({ message: 'User account was successfully disabled' });
403
    } catch (err) {
404
      return res
1✔
405
        .status(500)
406
        .json({ message: 'Failed to disable user account' });
407
    }
408
  }
409

410
  static async isVerified(req: Request, res: Response) {
411
    try {
4✔
412
      const token = req.params.token;
4✔
413
      if (!token) {
4✔
414
        return res.status(400).json({ error: 'No token provided' });
1✔
415
      }
416

417
      let decoded: any;
418
      decoded = jwt.verify(token, secret!);
3✔
419
      const { userId } = decoded;
2✔
420
      const [updated] = await db.User.update(
2✔
421
        { isVerified: true },
422
        { where: { userId } },
423
      );
424

425
      if (updated === 0) {
2✔
426
        throw new Error('No user updated');
1✔
427
      }
428

429
      const email = decoded.email;
1✔
430
      const name = decoded.firstName;
1✔
431

432
      await nodeMail(
1✔
433
        email,
434
        'Welcome to One and Zero E-commerce',
435
        successfullyverifiedTemplate(name),
436
      );
437

438
      return res
1✔
439
        .status(200)
440
        .redirect(`${process.env.CLIENT_URL}/users/isVerified`);
441
    } catch (error) {
442
      if (error instanceof jwt.JsonWebTokenError) {
2✔
443
        return res
1✔
444
          .status(400)
445
          .redirect(`${process.env.CLIENT_URL}/users/isVerified`);
446
      } else {
447
        return res
1✔
448
          .status(500)
449
          .redirect(`${process.env.CLIENT_URL}/users/isVerified`);
450
      }
451
    }
452
  }
453
  static async updatePassword(req: any, res: Response) {
454
    const { token } = req;
5✔
455
    const { password, newPassword, verifyNewPassword } = req.body;
5✔
456

457
    try {
5✔
458
      const getDecodedToken = jwt.verify(token, secret);
5✔
459
      const userId = getDecodedToken.userId;
5✔
460
      const userData = await db.User.findOne({
4✔
461
        where: { userId: userId },
462
      });
463

464
      if (!userData) {
4✔
465
        return res.status(404).json({
1✔
466
          status: 'fail',
467
          message: 'User not found',
468
        });
469
      }
470

471
      //extract Hash Password from user detail
472
      const currentHash = userData.dataValues.password;
3✔
473

474
      try {
3✔
475
        const result = await bcrypt.compare(password, currentHash);
3✔
476
        if (result == false) {
3✔
477
          return res.status(401).json({
1✔
478
            status: 'fail',
479
            message: 'Wrong credentials',
480
          });
481
        }
482

483
        //hashNewPassword
484
        const saltRounds = 10;
2✔
485
        const salt: any = await bcrypt.genSalt(saltRounds);
2✔
486
        const newHashPassword = await bcrypt.hash(newPassword, salt);
2✔
487

488
        const [updatePassword] = await db.User.update(
2✔
489
          { password: newHashPassword },
490
          { where: { userId: userId } },
491
        );
492

493
        if (updatePassword > 0) {
1✔
494
          return res.status(200).json({
1✔
495
            status: 'OK',
496
            message: 'Password updated successfully',
497
          });
498
        }
499
      } catch (e) {
500
        return res.status(500).json({
1✔
501
          status: 'error',
502
          message: 'Server error',
503
        });
504
      }
505
    } catch (e) {
506
      res.status(500).json({
1✔
507
        status: 'fail',
508
        message: 'something went wrong: ' + e,
509
      });
510
    }
511
  }
512

513
  static async setUserRoles(req: Request, res: Response) {
514
    try {
4✔
515
      const { role } = req.body;
4✔
516
      if (!role)
3✔
517
        return res.status(400).json({
1✔
518
          message: 'role can not be empty',
519
        });
520
      const user = await db.User.findOne({ where: { userId: req.params.id } });
2✔
521
      if (!user)
2✔
522
        return res.status(404).json({
1✔
523
          message: 'user not found',
524
        });
525

526
      const updatedUser = await db.User.update(
1✔
527
        { role: role },
528
        { where: { userId: req.params.id } },
529
      );
530
      return res.status(200).json({
1✔
531
        message: 'user role updated',
532
      });
533
    } catch (error: any) {
534
      return res.status(500).json({
1✔
535
        status: 'error',
536
        message: error.message,
537
      });
538
    }
539
  }
540
}
541

542
dotenv.config();
8✔
543

544
export async function handlePasswordResetRequest(
8✔
545
  req: Request,
546
  res: Response,
547
): Promise<void> {
548
  try {
4✔
549
    const { email } = req.body;
4✔
550

551
    if (!email) {
4✔
552
      res.status(400).json({ error: 'Email is required' });
1✔
553
      return;
1✔
554
    }
555

556
    const user = await db.User.findOne({ where: { email: email } });
3✔
557
    if (!user) {
2✔
558
      res.status(404).json({ message: 'User not found' });
1✔
559
      return;
1✔
560
    }
561

562
    const token = generateToken(
1✔
563
      user.userId,
564
      user.email,
565
      user.firstName,
566
      user.lastName,
567
      user.role,
568
      user.passwordLastChanged,
569
      user.isVerified,
570
    );
571

572
    // Store the token in the user's record
573
    user.resetPasswordToken = token;
1✔
574
    user.resetPasswordExpires = new Date(Date.now() + 3600000); // 1 hour expiration
1✔
575

576
    await user.save();
1✔
577

578
    await nodeMail(
1✔
579
      email,
580
      'Reset password request',
581
      resetPasswordEmail(token, user.firstName),
582
    );
583

584
    res.status(200).json({ message: 'Password reset email sent successfully' });
1✔
585
  } catch (error) {
586
    res.status(500).json({ error: 'Internal server error' });
1✔
587
  }
588
}
589

590
export async function resetPassword(
8✔
591
  req: Request,
592
  res: Response,
593
): Promise<void> {
594
  try {
4✔
595
    const { newPassword } = req.body;
4✔
596
    const isValid = validatePassword(newPassword);
4✔
597
    if (!isValid) {
4✔
598
      res.status(404).json({ message: 'Password must be strong' });
4✔
599
      return;
4✔
600
    }
NEW
601
    const token = req.params.token;
×
602

603
    // Hash the new password
UNCOV
604
    const hashedPassword = await bcrypt.hash(newPassword, 10);
×
605

606
    // Verify the token and decode the payload
UNCOV
607
    const decodedToken = jwt.verify(token, secret) as { email: string };
×
608

609
    // Find user by decoded email from token
UNCOV
610
    const user = await db.User.findOne({
×
611
      where: {
612
        email: decodedToken.email,
613
      },
614
    });
615

UNCOV
616
    if (!user) {
×
UNCOV
617
      res.status(400).json({ error: 'Invalid token or user not found' });
×
UNCOV
618
      return;
×
619
    }
UNCOV
620
    user.password = hashedPassword;
×
UNCOV
621
    user.resetPasswordToken = undefined;
×
UNCOV
622
    user.resetPasswordExpires = undefined;
×
623

UNCOV
624
    await user.save();
×
625

UNCOV
626
    res.status(200).json({ message: 'Password reset successfully' });
×
627
  } catch (error) {
UNCOV
628
    res.status(500).json({ error: 'Internal server error' });
×
629
  }
630
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc