Skip to content

Commit

Permalink
Feat: AuthenticationControllers and Routes, Model Updates.
Browse files Browse the repository at this point in the history
  • Loading branch information
ubongedem78 committed Apr 4, 2024
1 parent 8cd7db7 commit 3fd61be
Show file tree
Hide file tree
Showing 10 changed files with 510 additions and 6 deletions.
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"dotenv": "^16.3.1",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
"nodemailer": "^6.9.13",
"pg": "^8.11.3",
"pg-hstore": "^2.3.4",
"qrcode": "^1.5.3",
Expand Down
83 changes: 80 additions & 3 deletions src/controllers/auth.controller.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
const { createUser, loginUser } = require("../utils/auth.Util");
const {
createUser,
loginUser,
resetUserPassword,
updateVerifiedStatus,
updateUserPassword,
} = require("../utils/auth.Util");
const { sendOtpEmail, verificationEmail } = require("../utils/emailUtil");
const { generateOTP, verifyOTP } = require("../utils/generateToken");
const { ResponseHandler } = require("../utils/responseHandler");
const { InternalServerError } = require("../errors/index");

const register = async (req, res, next) => {
const {
Expand All @@ -23,7 +32,22 @@ const register = async (req, res, next) => {
countryCode
);

newUser = {
if (!user) {
throw new InternalServerError("User registration failed");
}

const verficationEmailResponse = await verificationEmail(
user,
await generateOTP(user.id)
);
console.log("verficationEmailResponse", verficationEmailResponse);

if (!verficationEmailResponse) {
throw new InternalServerError("Email not sent");
}

const newUser = {
userId: user.id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
Expand Down Expand Up @@ -51,14 +75,39 @@ const login = async (req, res, next) => {
});

const loggedInUser = {
userId: user.id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
phoneNumber: user.phone,
countryCode: user.countryCode,
};

ResponseHandler.success(res, user, 200, "Login successful");
ResponseHandler.success(res, loggedInUser, 200, "Login successful");
} catch (error) {
next(error);
}
};

const verifyAccount = async (req, res, next) => {
const { userId, otp } = req.body;

try {
const isVerified = await verifyOTP(userId, otp);
if (!isVerified) {
throw new InternalServerError("Account verification failed");
}

const updatedUser = await updateVerifiedStatus(userId, {
isVerified: true,
});

ResponseHandler.success(
res,
updatedUser,
200,
"Account verified successfully"
);
} catch (error) {
next(error);
}
Expand All @@ -74,8 +123,36 @@ const logout = async (req, res, next) => {
}
};

const resetPassword = async (req, res, next) => {
const { email } = req.body;
try {
await resetUserPassword(email);
ResponseHandler.success(res, null, 200, "Reset password OTP sent");
} catch (error) {
next(error);
}
};

const updatePassword = async (req, res, next) => {
const { email, password, otp } = req.body;
try {
const updatedUser = await updateUserPassword(email, { password }, otp);
ResponseHandler.success(
res,
updatedUser,
200,
"Password updated successfully"
);
} catch (error) {
next(error);
}
};

module.exports = {
register,
login,
logout,
resetPassword,
verifyAccount,
updatePassword,
};
25 changes: 24 additions & 1 deletion src/model/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ const User = sequelize.define(
type: STRING,
allowNull: false,
},
isVerified: {
type: BOOLEAN,
defaultValue: false,
},
role: {
type: ENUM("ADMIN", "USER"),
defaultValue: "USER",
Expand Down Expand Up @@ -177,6 +181,23 @@ const Transaction = sequelize.define("Transaction", {
},
});

const OTP = sequelize.define("OTP", {
id: {
type: UUID,
defaultValue: UUIDV4,
allowNull: false,
primaryKey: true,
},
userId: {
type: UUID,
allowNull: false,
},
otp: {
type: INTEGER,
allowNull: false,
},
});

User.prototype.createJWT = function () {
return jwt.sign({ userId: this.id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN,
Expand All @@ -203,6 +224,8 @@ Transaction.belongsTo(Wallet, {
as: "debitWallet",
foreignKey: "debitWalletId",
});
User.hasOne(OTP, { foreignKey: "userId" });
OTP.belongsTo(User, { foreignKey: "userId" });

sequelize
.sync({ alter: true })
Expand All @@ -213,4 +236,4 @@ sequelize
console.log("Database Sync Failed", error);
});

module.exports = { User, Wallet, Transaction };
module.exports = { User, Wallet, Transaction, OTP };
12 changes: 11 additions & 1 deletion src/routes/auth.route.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
const express = require("express");
const router = express.Router();
const { register, login, logout } = require("../controllers/auth.controller");
const {
register,
login,
logout,
verifyAccount,
updatePassword,
resetPassword,
} = require("../controllers/auth.controller");

router.route("/register").post(register);
router.route("/login").post(login);
router.route("/verify-account").post(verifyAccount);
router.route("/reset-password").post(resetPassword);
router.route("/update-password").post(updatePassword);
router.route("/logout").post(logout);

module.exports = router;
108 changes: 107 additions & 1 deletion src/utils/auth.Util.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
const { User } = require("../model/model");
const { BadRequestError, NotFoundError } = require("../errors/index");
const { sendOtpToEmail, verifyOtp } = require("./emailUtil");
const { generateOTP } = require("./generateToken");
const bcrypt = require("bcrypt");
const { InternalServerError } = require("../errors/index");



/**
* Create a new user and store the information in the database.
Expand Down Expand Up @@ -56,6 +62,11 @@ async function createUser(
);
}

const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
if (!emailRegex.test(email)) {
throw new BadRequestError("Invalid email address");
}

const existingUser = await User.findOne({
where: { email: email, phone: phoneNumber },
});
Expand All @@ -74,6 +85,8 @@ async function createUser(
countryCode: countryCode,
});

console.log("user", user);

return user;
}

Expand All @@ -87,6 +100,11 @@ async function createUser(
* @throws {NotFoundError} If the user is not found or the credentials are invalid.
*/
async function loginUser(email, password) {
if (!email || !password) {
throw new BadRequestError("Please Enter an Email or Password");
}
email = email.toLowerCase();
console.log(email);
const user = await User.findOne({ where: { email: email } });

if (!user) {
Expand All @@ -99,9 +117,97 @@ async function loginUser(email, password) {
throw new BadRequestError("Invalid Credentials");
}

const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
if (!emailRegex.test(email)) {
throw new BadRequestError("Invalid email address");
}

return user;
}

/**
* Update the verification status of a user on the User Model.
*
* @param {string} userId - The ID of the user.
* @param {object} data - The data to update (isVerified property).
* @returns {Promise<import('../model/User')>} A Promise that resolves to the updated user.
* @throws {NotFoundError} If the user is not found.
*/
async function updateVerifiedStatus(userId, data) {
const user = await User.findByPk(userId);
if (!user) {
throw new NotFoundError("User not found");
}

const updatedUser = await user.update(data);
return updatedUser;
}

/**
* Reset the password of a user.
*
* @param {string} email - The email address of the user.
* @returns {Promise<import('../model/User')>} A Promise that resolves to the user whose password was reset.
* @throws {BadRequestError} If required fields are not provided.
* @throws {NotFoundError} If the user is not found or the credentials are invalid.
*/
async function resetUserPassword(email) {
async function resetUserPassword(email) {
if (!email) {
throw new BadRequestError("Email is required");
}

const user = await User.findOne({ where: { email } });
if (!user) {
throw new NotFoundError("User not found");
}

const otp = await generateOTP(user.id);
if (!otp) {
throw new InternalServerError("Error generating token");
}

const emailResponse = await sendOtpToEmail(user, otp, "password");
if (!emailResponse) {
throw new InternalServerError("Error sending email");
}
}
}

/**
* Update the password of a user.
*
* @param {string} email - The email address of the user.
* @param {object} data - The data to update (password).
* @param {string} otp - The OTP sent to the user's email.
* @returns {Promise<import('../model/User')>} A Promise that resolves to the updated user.
* @throws {BadRequestError} If required fields are not provided.
* @throws {NotFoundError} If the user is not found.
*/
async function updateUserPassword(email, data, otp) {
if (!email || !data.password || !otp) {
throw new BadRequestError("OTP, email, and password are required");
}

const user = await User.findOne({ where: { email } });
if (!user) {
throw new NotFoundError("User not found");
}

const isOtpValid = await verifyOtp(otp, user.id);
if (!isOtpValid) {
throw new BadRequestError("Invalid OTP");
}

const hashedPassword = await bcrypt.hash(data.password, 12);
const updatedUser = await user.update({ password: hashedPassword });
return updatedUser;
}

module.exports = { createUser, loginUser };
module.exports = {
createUser,
loginUser,
resetUserPassword,
updateVerifiedStatus,
updateUserPassword,
};
Loading

0 comments on commit 3fd61be

Please sign in to comment.