Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development #52

Merged
merged 21 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
APP_URL="http://localhost:3000"
APP_FRONT="http://localhost:3100"
PORT=3000
MONGO_URI="mongodb://archetype:archetype@localhost:27501/"
DBNAME="NEMBBMS"
VERIFICATION_CODE_LIFE_TIME=10
RESEND_VC_LIFE_TIME=2
JWT_SECRET="c44a89a1a70313e49ee9a9155364cdb8"
JWT_REFRESH="69b6a16a472a429ba0b11cf6504f2794"
SENDGRID_API_KEY="SG.your_api_key"
RECAPTCHA_KEY="google_recpatcha_key"
2 changes: 1 addition & 1 deletion .github/workflows/telegram-notifier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ jobs:
token: ${{ secrets.token }} # savethe bot token at settings/secrets with name: token
to: ${{ secrets.to }} # save your chat id at settings/secrets with name: chat
thread_id: ${{secrets.threadid}} # set this for sending message in thread or group topic
disable_web_page_preview: false # set this to true to disable link previw in telegram
disable_web_page_preview: true # set this to true to disable link previw in telegram
disable_notification: false # set tjis true to send message in silet mode
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ first make sure have the `docker` engine installed and runing on your machine <b

`VERIFICATION_CODE_LIFE_TIME` : time to expire of the user verification code in minutes since it get generated, used in `user` module

`RESEND_VC_LIFE_TIME` : verification code resend cool-off time in minute, default is 2, means only one verification code can be generated in every 2 minute, used in `user` modules

`JWT_SECRET` : JSON Web Token Secret signing key, makesure to change this

`SENDGRID_API_KEY` : This environment variable is used to authenticate your application with the SendGrid email service API.
Expand Down
7 changes: 4 additions & 3 deletions common/express-validators.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { body, validationResult } from 'express-validator';
import { body, validationResult, param } from 'express-validator';

/**
* // @todo: we may need to implement this as global middleware, testing is required to do that.
Expand All @@ -14,7 +14,7 @@ export const validate = (req, res, next) => {
return next();
}
const extractedErrors = [];
errors.array().map(err => extractedErrors.push({ [err.param]: err.msg }));
errors.array().map(err => extractedErrors.push({ [err.path]: err.msg }));
return res.status(422).json({
errors: extractedErrors,
});
Expand All @@ -35,7 +35,8 @@ export const mobileValidator = body('mobile').matches(mobilePattern, 'g').withMe

export const emailValidator = body('email').isEmail().withMessage('please enter valid email address');

export const codeValidator = body('code').exists({ checkFalsy: true, checkNull: true }).withMessage('code is not valid');
export const paramCodeValidator = param('code').exists({ checkFalsy: true, checkNull: true }).withMessage('code is not valid');
export const bodyCodeValidator = body('code').exists({ checkFalsy: true, checkNull: true }).withMessage('code is not valid');

export const captchaValidator = body('captcha').exists({ checkFalsy: true, checkNull: true }).withMessage('Captcha is not valid');

Expand Down
3 changes: 2 additions & 1 deletion config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ const env = process.env;
const logTypes = process.env.NODE_ENV !== 'test' ? ['error', 'info', 'warn', 'log', 'success', 'validation'] : [];
export default {
LOG_TYPES: logTypes,
APP_URL: env.APP_URL,
APP_FRONT: env.APP_FRONT,
PORT: env.PORT,
MONGO_URI: env.MONGO_URI,
DBNAME: env.DBNAME,
VERIFICATION_CODE_LIFE_TIME: env.VERIFICATION_CODE_LIFE_TIME,
RESEND_VC_LIFE_TIME: env.RESEND_VC_LIFE_TIME,
// AUTH_TABLE_NAME: env.AUTH_TABLE_NAME,
JWT_SECRET: env.JWT_SECRET,
SENDGRID_API_KEY: env.SENDGRID_API_KEY,
Expand Down
155 changes: 67 additions & 88 deletions modules/user/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class userController {
const userEmail = email.toLowerCase();
const userName = name.toLowerCase();
const userLastname = lastName.toLowerCase();
const userPassword = this.sha256(password);

try {

Expand All @@ -40,7 +41,7 @@ class userController {
name: userName,
lastName: userLastname,
verificationCode,
password
password: userPassword
});

if (process.env.NODE_ENV !== 'test') {
Expand Down Expand Up @@ -81,30 +82,37 @@ class userController {
const userEmail = req.body.email.toLowerCase();
const userInfo = await this.model.findEntityByParams({ email: userEmail });
if (_.get(userInfo, 'verified') || !userInfo) {
res.send({ status: 'failed' });
return res.send({ status: 'failed' });
} else {
const vcDate = new Date(userInfo.verificationCodeDate);
vcDate.setMinutes(vcDate.getMinutes() + config.VERIFICATION_CODE_LIFE_TIME);
let verificationCodeDate = userInfo.verificationCodeDate;
if (vcDate.getTime() < Date.now()) {
if (
(process.env.NODE_ENV !== 'test' && vcDate.getTime() < Date.now()) ||
process.env.NODE_ENV === 'test') {
const verificationCode = this.generateVerificationCode();
verificationCodeDate = new Date();
await this.model.updateEntityByModel(userInfo, {
verificationCode,
verificationCodeDate
});
const templateTags = [
{ name: "__USERNAME", value: userInfo.email },
{ name: "__CONFIRMATION_URL", value: verificationCode }, // Todo: #44 is here
];

SendGrid.sendMailByTemplate(
'Confirm your email address',
'signup-confirmation',
templateTags,
[newUser.email],
'no-reply@site.com'
);
if (process.env.NODE_ENV !== 'test') {
const key = this.sha256(verificationCode);
const verificationURL = `${config.APP_URL}/user/verify/${key}`;
const templateTags = [
{ name: "__USERNAME", value: userInfo.email },
{ name: "__CONFIRMATION_URL", value: verificationURL },
];

SendGrid.sendMailByTemplate(
'Confirm your email address',
'signup-confirmation',
templateTags,
[newUser.email],
'no-reply@site.com'
);
}
}
res.send({ status: 'success', verificationCodeDate });
}
Expand All @@ -120,21 +128,22 @@ class userController {
*/
verify = async (req, res) => {
try {
const code = req.body.code;
const userInfo = await this.model.findEntityByParams({
email: req.body.email.toLowerCase(),
verificationCode: code
});
let frontURL = `${config.APP_FRONT}/confirmation/`;
const code = req.params.code;
const userInfo = await this.model.findEntityByParams({ verificationCode: code });
if (userInfo === null) {
return res.send({ verified: false });
frontURL += `failed`;
return res.redirect(frontURL);
}
const vcDate = new Date(userInfo.verificationCodeDate);
vcDate.setMinutes(vcDate.getMinutes() + config.VERIFICATION_CODE_LIFE_TIME);
if (userInfo.verificationCode === code && vcDate.getTime() > Date.now()) {
res.send({ verified: true, email: userInfo.email, code: userInfo.verificationCode });
if (vcDate.getTime() > Date.now()) {
await this.model.updateEntityByModel(userInfo, { verified: true });
frontURL += `success`;
} else {
res.send({ verified: false });
frontURL += `failed`;
}
res.redirect(frontURL);
} catch (error) {
this.errorHandler(error, res);
}
Expand All @@ -146,23 +155,18 @@ class userController {
* @param res
* @param next
*/
userAuth = async ({ body: { email, password } }, res, next) => {
userAuth = async (req, res, next) => {
try {
const { body: { email, password } } = req;
const userEmail = email.toLowerCase();
const pwd = this.sha256(password);
let userInfo = await this.model.findEntityByParams({ email: userEmail, password: pwd }, { 'password': false });
if (userInfo === null) {
return res.status(400).send({
errorCode: 'AUTHFAILED',
additionalInformation: {
message: 'username or password is wrong!'
}
});
return res.send({ status: 'failed', message: 'username or password is wrong!' });
}
userInfo = userInfo.toObject();
const token = await Auth.sign(userInfo);
const token = await Auth.sign(userInfo.toObject());
res.set('Authorization', token);
res._user = userInfo;
req._user = userInfo;
next();
} catch (error) {
this.errorHandler(error, res);
Expand All @@ -174,7 +178,7 @@ class userController {
* @param req
* @param res
*/
login = async ({ _user: { name = ``, lastName = ``, email = `` } }, res) => {
login = async ({ _user: { name = ``, lastName = ``, email = `` } = {} }, res) => {
res.send({ name, lastName, email });
}

Expand All @@ -183,21 +187,15 @@ class userController {
* @param req
* @param res
*/
changeUserPassword = async ({ _user, body: { password, new: newPWD } }, res) => {
changeUserPassword = async (req, res) => {
try {
const userInfo = await this.model.findEntityByParams({ _id: _user._id });
const currentPassword = this.sha256(password);
const newPassword = this.sha256(newPWD);
if (currentPassword === userInfo.password) {
await this.model.updateEntityByModel(userInfo, { password: newPassword });
res.send({ status: true });
const { _user, body: { password, new: newPWD } = {} } = {} = req;
const userInfo = await this.model.findEntityByParams({ email: _user.email });
if (!!userInfo && this.sha256(password) === userInfo.password) {
await this.model.updateEntityByModel(userInfo, { password: this.sha256(newPWD) });
res.send({ status: 'success' });
} else {
res.status(400).send({
errorCode: 'VALIDATIONFAILED',
additionalInformation: {
message: 'current password is wrong!'
}
});
res.send({ status: 'failed', message: 'current password is wrong' });
}
} catch (error) {
this.errorHandler(error, res);
Expand Down Expand Up @@ -229,46 +227,37 @@ class userController {
const userEmail = email.toLowerCase();
const userInfo = await this.model.findEntityByParams({ email: userEmail }, { password: false });
if (userInfo === null) {
return res.status(400).send({
errorCode: 'AUTHFAILED',
additionalInformation: {
message: 'username is wrong'
}
});
return res.send({ status: 'failed', message: 'user does not exists' });
}
const vcDate = new Date(userInfo.verificationCodeDate);
vcDate.setMinutes(vcDate.getMinutes() + config.VERIFICATION_CODE_LIFE_TIME);
if (Date.now() < vcDate.getTime()) {
res.send({ success: true, verificationCodeDate: userInfo.verificationCodeDate });
res.send({ status: 'success', verificationCodeDate: userInfo.verificationCodeDate });
} else if (userInfo.verified === true) {
const verificationCode = this.generateVerificationCode();
const verificationCodeDate = new Date();
await this.model.updateEntityByModel(userInfo, {
verificationCode,
verificationCodeDate
});
if (process.env.NODE_ENV !== 'test') {
const templateTags = [
{ name: "__USERNAME", value: userInfo.email },
{ name: "__RESET_URL", value: verificationCode }, // Todo: #44 is here
];

const templateTags = [
{ name: "__USERNAME", value: userInfo.email },
{ name: "__RESET_URL", value: verificationCode }, // Todo: #44 is here
];

SendGrid.sendMailByTemplate(
'Forgot your password?',
'forget-password',
templateTags,
[newUser.email],
'no-reply@site.com'
);
SendGrid.sendMailByTemplate(
'Forgot your password?',
'forget-password',
templateTags,
[newUser.email],
'no-reply@site.com'
);
}

res.send({ success: true, verificationCodeDate });
res.send({ success: 'success', verificationCodeDate });
} else {
res.status(400).send({
errorCode: 'NOTVERIFIED',
additionalInformation: {
message: 'user not verified!'
}
});
res.send({ status: 'failed' });
}
} catch (error) {
this.errorHandler(error, res);
Expand All @@ -285,26 +274,16 @@ class userController {
const userEmail = email.toLowerCase();
const userInfo = await this.model.findEntityByParams({ email: userEmail });
if (userInfo === null) {
return res.status(400).send({
errorCode: 'AUTHFAILED',
additionalInformation: {
message: 'username is wrong!'
}
});
return res.send({ status: 'failed', message: 'user does not exists' });
}
const secureKeyDate = new Date(userInfo.verificationCodeDate);
secureKeyDate.setMinutes(secureKeyDate.getMinutes() + config.VERIFICATION_CODE_LIFE_TIME);
if (userInfo.verificationCode === code && userInfo.verified === true && secureKeyDate.getTime() > Date.now()) {
password = (password) ? this.sha256(password).toString() : '';
await this.model.updateEntityByModel(userInfo, { password });
res.send({ success: true });
const newPassword = (password) ? this.sha256(password).toString() : '';
await this.model.updateEntityByModel(userInfo, { password: newPassword });
res.send({ status: 'success' });
} else {
res.status(400).send({
errorCode: 'INVALIDCODE',
additionalInformation: {
message: 'verification code is not valid!'
}
});
res.send({ status: 'failed', message: 'invalid or expired request' });
}
} catch (error) {
this.errorHandler(error, res);
Expand Down
2 changes: 1 addition & 1 deletion modules/user/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class usersRouter {

userRouter.post('/', [...validator.signup()], userCtrl.signup);
userRouter.post('/resendVerification', [...validator.resendVerification()], userCtrl.resendVerification);
userRouter.post('/verify', [...validator.verify()], userCtrl.verify);
userRouter.get('/verify/:code', [...validator.verify()], userCtrl.verify);
userRouter.post('/login', [...validator.login()], userCtrl.userAuth, userCtrl.login);
userRouter.post('/changePassword', [Auth.isLoggedIn, ...validator.changePassword()], userCtrl.changeUserPassword);
userRouter.post('/updateProfile', [Auth.isLoggedIn, ...validator.updateProfile()], userCtrl.updateProfile);
Expand Down
Loading
Loading