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

atlp-rwanda / e-commerce-ones-and-zeroes-bn / a17a0734-997b-4c0a-998e-56882adf51d0

02 Jul 2024 03:04PM UTC coverage: 93.573% (-0.6%) from 94.143%
a17a0734-997b-4c0a-998e-56882adf51d0

push

circleci

web-flow
Merge pull request #61 from atlp-rwanda/fx-user-login-and-registration-and-emails-theme

[ Fixes #187373269 #187355094 #187355101 ] fixing -user-login-and-registration-and-google-login-emails-theme

253 of 283 branches covered (89.4%)

Branch coverage included in aggregate %.

7 of 9 new or added lines in 2 files covered. (77.78%)

3 existing lines in 1 file now uncovered.

839 of 884 relevant lines covered (94.91%)

2.93 hits per line

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

91.51
/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
      return res.status(500).json({ message: 'Failed to register user' });
1✔
106
    }
107
  }
108

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

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

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

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

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

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

188
      await singleUser.save();
1✔
189

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

437
      return res
1✔
438
        .status(200)
439
        .redirect(`${process.env.CLIENT_URL}/users/isVerified`);
440
    } catch (error) {
441
      if (error instanceof jwt.JsonWebTokenError) {
2✔
442
        return res
1✔
443
          .status(400)
444
          .redirect(`${process.env.CLIENT_URL}/users/isVerified`);
445
      } else {
446
        return res
1✔
447
          .status(500)
448
          .redirect(`${process.env.CLIENT_URL}/users/isVerified`);
449
      }
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({ error: '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(email, 'Reset password request', resetPasswordEmail(token));
1✔
579

580
    res.status(200).json({ message: 'Password reset email sent successfully' });
1✔
581
  } catch (error) {
582
    res.status(500).json({ error: 'Internal server error' });
1✔
583
  }
584
}
585
export async function resetPassword(
8✔
586
  req: Request,
587
  res: Response,
588
): Promise<void> {
589
  try {
4✔
590
    const { newPassword } = req.body;
4✔
591
    const token = req.params.token;
4✔
592

593
    if (!newPassword) {
4✔
594
      res.status(400).json({ error: 'New password is required' });
1✔
595
      return;
1✔
596
    }
597

598
    // Hash the new password
599
    const hashedPassword = await bcrypt.hash(newPassword, 10);
3✔
600

601
    // Verify the token and decode the payload
602
    const decodedToken = jwt.verify(token, secret) as { email: string };
3✔
603

604
    // Find user by decoded email from token
605
    const user = await db.User.findOne({
3✔
606
      where: {
607
        email: decodedToken.email,
608
      },
609
    });
610

611
    if (!user) {
2✔
612
      res.status(400).json({ error: 'Invalid token or user not found' });
1✔
613
      return;
1✔
614
    }
615
    user.password = hashedPassword;
1✔
616
    user.resetPasswordToken = undefined;
1✔
617
    user.resetPasswordExpires = undefined;
1✔
618

619
    await user.save();
1✔
620

621
    res.status(200).json({ message: 'Password reset successfully' });
1✔
622
  } catch (error) {
623
    res.status(500).json({ error: 'Internal server error' });
1✔
624
  }
625
}
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