Skip to content

Commit

Permalink
Add user activation, metadata, routes, and soft delete logic; fix res…
Browse files Browse the repository at this point in the history
…ponse path and server errors
  • Loading branch information
bellaabdelouahab committed Jun 26, 2023
1 parent 8ebd680 commit 41865dd
Show file tree
Hide file tree
Showing 19 changed files with 271 additions and 346 deletions.
2 changes: 2 additions & 0 deletions backend-app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,5 @@ ios/build/
create-node-api.sh



docs/api_docs/*
2 changes: 1 addition & 1 deletion backend-app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ app.get('/', (req, res) => {

// handle undefined Routes
app.use('*', (req, res, next) => {
const err = new AppError(404, 'fail', 'Route Not Found', req.path);
const err = new AppError(404, 'fail', 'Route Not Found', req.originalUrl);
next(err, req, res, next);
});

Expand Down
1 change: 1 addition & 0 deletions backend-app/config/app_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ exports.ADMIN_EMAIL = process.env.ADMIN_EMAIL || "admin@gmail.com";
exports.ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "admin123";
exports.JWT_SECRET = process.env.JWT_SECRET || "sdfsdf";
exports.JWT_EXPIRES_IN = "360000";
exports.REQUIRE_ACTIVATION = process.env.REQUIRE_ACTIVATION || false;
// RATE_LIMIT_PER_HOUR
exports.RATE_LIMIT_PER_HOUR = process.env.RATE_LIMIT_PER_HOUR || 500;
4 changes: 2 additions & 2 deletions backend-app/config/logger_config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { addColors, format } = require('winston');
const { logFilePath } = require('./app_config');
// Define the current environment
const currentEnv = process.env.NODE_ENV || 'development';
const {CURRENT_ENV} = require('./app_config');

// Define log colors
const colors = {
Expand All @@ -27,7 +27,7 @@ const formatLogMessage = format.printf(
* when the log level is debug, debug and all the levels above it will be logged.
* when the log level is warn, warn and all the levels above it will be logged.
*/
const logLevel = currentEnv.toLowerCase() === 'development' ? 'debug' : 'warn';
const logLevel = CURRENT_ENV.toLowerCase() === "development" ? "debug" : "warn";

/**
* @description - This is the configuration for the logger
Expand Down
27 changes: 27 additions & 0 deletions backend-app/constants/meta_data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@


const enableMetaData = (model) => {
model.add({
deleted: {
type: Boolean,
default: false
},
UpdatedBy: {
type: String,
},
createdBy: {
type: String,
default: 'System'
},
deletedBy: {
type: String,
},
deletedAt: {
type: Date,
},
})
};



exports.enableMetaData = enableMetaData;
177 changes: 165 additions & 12 deletions backend-app/controllers/auth_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ const jwt = require('jsonwebtoken');
const User = require('../models/user_model');
const AppError = require('../utils/app_error');
const Role = require('../utils/authorization/role/role');
const { JWT_SECRET, JWT_EXPIRES_IN } = require('../config/app_config');
const {
JWT_SECRET,
JWT_EXPIRES_IN,
REQUIRE_ACTIVATION,
} = require("../config/app_config");
const role = new Role();

const createToken = (id) => {
Expand All @@ -17,6 +21,11 @@ const createToken = (id) => {
}
);
};
const generateActivationKey = async () => {
const randomBytesPromiseified = promisify(require('crypto').randomBytes);
const activationKey = (await randomBytesPromiseified(32)).toString('hex');
return activationKey;
};

exports.login = async (req, res, next) => {
try {
Expand Down Expand Up @@ -73,29 +82,28 @@ exports.login = async (req, res, next) => {

exports.signup = async (req, res, next) => {
try {
const activationKey = await generateActivationKey();
const Roles = await role.getRoles();
const exists = await User.findOne({
email: req.body.email,
});
if (exists)
return next(
new AppError(409, 'fail', 'Email already exists'),
req,
res,
next
);
const user = await User.create({
name: req.body.name,
email: req.body.email,
password: req.body.password,
roles: [Roles.USER.type],
authorities: Roles.USER.authorities,
restrictions: Roles.USER.restrictions,
...(REQUIRE_ACTIVATION && { activationKey }),
});
const token = createToken(user.id);


// Remove the password and activation key from the output
user.password = undefined;

user.activationKey = undefined;

Logger.info(
`User ${user._id} with email ${user.email} has been created with activation key ${activationKey}`
);

res.status(201).json({
status: 'success',
token,
Expand All @@ -108,6 +116,141 @@ exports.signup = async (req, res, next) => {
}
};

exports.activateAccount = async (req, res, next) => {
try {
const { id,activationKey } = req.query;

if (!activationKey) {
return next(
new AppError(400, 'fail', 'Please provide activation key'),
req,
res,
next
);
}

// find user by activation key

const user = await User.findOne({
_id: id,
});

if (!user) {
return next(
new AppError(404, 'fail', 'User does not exist'),
req,
res,
next
);
}

if (!user.activationKey) {
return next(
new AppError(409, 'fail', 'User is already active'),
req,
res,
next
);
}

if (activationKey !== user.activationKey) {
return next(
new AppError(400, 'fail', 'Please provide correct activation key'),
req,
res,
next
);
}
// activate user
user.active = true;
user.activationKey = undefined;
await user.save();
// Remove the password from the output
user.password = undefined;

res.status(200).json({
status: 'success',
data: {
user,
},
});
} catch (err) {
next(err);
}
};


exports.updatePassword = async (req, res, next) => {
try {
const { email, resetKey, password } = req.body;

const user = await User.findOne({ email }).select("+password");

if (!resetKey) {
return next(new AppError(404, "fail", "Please provide reset key"));
}

if (resetKey !== user.resetKey) {
return next(new AppError(404, "fail", "Invalid reset key"));
}

user.password = password;
user.resetKey = undefined;
await user.save();

const token = createToken(user.id);
user.password = undefined;

res.status(200).json({
status: "success",
token,
data: { user },
});
} catch (err) {
next(err);
}
};


exports.forgotPassword = async (req, res, next) => {
try {
const { email } = req.body;

if (!email) {
return next(new AppError(404, "fail", "Please provide email"));
}

const user = await User.findOne({ email });

if (!user) {
return next(
new AppError(404, "fail", "User with this email does not exist")
);
}

const resetKey = user.generateResetKey();
await user.save();

Logger.info(
`User ${user.name} with email ${user.email} has requested for password reset with reset key ${resetKey}`
);

// send email with reset key
// TODO: send email with reset key

res.status(200).json({
status: "success",
});
} catch (err) {
next(err);
}
};






exports.protect = async (req, res, next) => {
try {
// 1) check if the token is there
Expand Down Expand Up @@ -155,6 +298,16 @@ exports.protect = async (req, res, next) => {
)
);
req.user = user;
// check if account is active
if (!user.active)
return next(
new AppError(
403,
'fail',
'Your account is not active. Please activate your account to continue.'
)
);

next();
} catch (err) {
// check if the token is expired
Expand Down
10 changes: 9 additions & 1 deletion backend-app/controllers/base_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ const APIFeatures = require('../utils/api_features');
*/
exports.deleteOne = (Model) => async (req, res, next) => {
try {
const doc = await Model.findByIdAndDelete(req.params.id);
const doc = await Model.findByIdAndUpdate(req.params.id, {
deleted: true,
...(req.user && { deletedBy: req.user._id }),
deletedAt: Date.now(),
});


if (!doc) {
return next(
Expand All @@ -35,6 +40,9 @@ exports.deleteOne = (Model) => async (req, res, next) => {
*/
exports.updateOne = (Model) => async (req, res, next) => {
try {
// get the user who is updating the document
const userid = req.user._id;
req.body.updatedBy = userid;
const doc = await Model.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
Expand Down
4 changes: 3 additions & 1 deletion backend-app/controllers/user_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ exports.getMe = (req, res, next) => {
exports.deleteMe = async (req, res, next) => {
try {
await User.findByIdAndUpdate(req.user.id, {
active: false,
deleted: true,
deletedAt: Date.now(),
deletedBy: req.user.id,
});

res.status(204).json({
Expand Down
Loading

0 comments on commit 41865dd

Please sign in to comment.