From 6332f578ac0bc69c14f915a9ebc9584538e1a6be Mon Sep 17 00:00:00 2001 From: niyobertin Date: Fri, 17 May 2024 17:00:42 +0200 Subject: [PATCH] feat(change password):user should be prompted to chage password -once password expired in x amount of time user should be promtend to change password - when user ignored the notification on and no further operation is allowed except updating password. [Delivers #187419196] --- .env.example | 1 + __test__/product.test.ts | 4 +- __test__/user.test.ts | 9 ++- package.json | 2 + src/controllers/userControllers.ts | 11 +++- .../passwordExpiredNotification.ts | 57 +++++++++++++++++++ src/jobs/isPasswordExpired.ts | 42 ++++++++++++++ src/middlewares/isPasswordOutOfDate.ts | 24 ++++++++ src/routes/cartRoutes.ts | 12 ++-- src/routes/categoriesRoutes.ts | 11 ++-- src/routes/notificationRoutes.ts | 5 +- src/routes/paymentRoutes.ts | 3 +- src/routes/productsRoute.ts | 21 +++---- src/routes/roleRoutes.ts | 9 +-- src/routes/userRoutes.ts | 20 +++---- src/routes/wishesRoutes.ts | 9 +-- src/schemas/signUpSchema.ts | 1 + src/sequelize/config/config.js | 1 - .../migrations/b20240502074237-user.js | 3 + src/sequelize/models/users.ts | 5 ++ .../seeders/b20240412144111-demo-user.js | 4 ++ src/services/user.service.ts | 11 ++-- src/types.ts | 1 + src/utils/env.ts | 3 +- src/utils/server.ts | 4 +- 25 files changed, 215 insertions(+), 58 deletions(-) create mode 100644 src/email-templates/passwordExpiredNotification.ts create mode 100644 src/jobs/isPasswordExpired.ts create mode 100644 src/middlewares/isPasswordOutOfDate.ts diff --git a/.env.example b/.env.example index 02c9df4..6784fa8 100644 --- a/.env.example +++ b/.env.example @@ -23,3 +23,4 @@ POSTGRES_USER = database user POSTGRES_DB = name of your database POSTGRES_HOST = your database host DOCKER_DB_CONNECTION = postgres://postgres:postgres_password@database_host:postgres_port/postgres_db +TIME_FOR_PASSWORD_EXPIRATION = days --> password expiration time diff --git a/__test__/product.test.ts b/__test__/product.test.ts index d4045a9..569a478 100644 --- a/__test__/product.test.ts +++ b/__test__/product.test.ts @@ -422,6 +422,7 @@ expect(response.body).toEqual({ test("Return 500 for handle error", async () => { const response = await request(app) .get("/api/v1/products/review") + .set("Authorization", "Bearer " + token); expect(response.status).toBe(500) }) test('It should return status 200 for removed Product',async() =>{ @@ -453,7 +454,8 @@ test('It should return status 200 for removed category',async() =>{ expect(response.status).toBe(200); }); test("return status 200 when none seller role search products", async () => { - const response = await request(app).get("/api/v1/products/search").send(searchProduct); + const response = await request(app).get("/api/v1/products/search").send(searchProduct) + .set("Authorization", "Bearer " + buyerToken); expect(response.status).toBe(200); }); test("it should return status product is not available when searching product", async () => { diff --git a/__test__/user.test.ts b/__test__/user.test.ts index 0019e84..9939a2e 100644 --- a/__test__/user.test.ts +++ b/__test__/user.test.ts @@ -1,4 +1,5 @@ import request from "supertest"; +import { mocked } from "jest-mock"; import { beforeAll, beforeEach, afterEach, afterAll, test } from "@jest/globals"; import app from "../src/utils/server"; import User from "../src/sequelize/models/users"; @@ -25,14 +26,15 @@ const userData: any = { username: "testuser5", email: "test15@gmail.com", password: "test12345", + lastPasswordUpdateTime: new Date() }; - const dummySeller = { name: "dummy1234", username: "username1234", email: "soleilcyber00@gmail.com", password: "1234567890", + lastPasswordUpdateTime: "3000, 11, 18" }; const userTestData = { newPassword: "Test@123", @@ -62,7 +64,8 @@ const updateData:any = { country: "Rwanda", } - + jest.mock('../src/jobs/isPasswordExpired'); + describe("Testing user Routes", () => { beforeAll(async () => { try { @@ -103,7 +106,6 @@ describe("Testing user Routes", () => { const response = await request(app) .post("/api/v1/users/register") .send(userData); - expect(response.status).toBe(201); }, 20000); @@ -225,6 +227,7 @@ describe("Testing user Routes", () => { roleId: 2, }) .set("Authorization", "Bearer " + adminToken); + expect(response.status).toBe(200); }); diff --git a/package.json b/package.json index 7fe1d90..3becaa5 100644 --- a/package.json +++ b/package.json @@ -87,10 +87,12 @@ "cryptr": "^6.3.0", "dotenv": "^16.4.5", "email-validator": "^2.0.4", + "events": "^3.3.0", "express": "^4.19.2", "express-session": "^1.18.0", "husky": "^9.0.11", "ioredis": "^5.4.1", + "jest-mock": "^29.7.0", "joi": "^17.13.0", "jsonwebtoken": "^9.0.2", "lint-staged": "^15.2.2", diff --git a/src/controllers/userControllers.ts b/src/controllers/userControllers.ts index 26219d5..4947f6a 100644 --- a/src/controllers/userControllers.ts +++ b/src/controllers/userControllers.ts @@ -15,7 +15,8 @@ import { updateUserRoleService } from "../services/user.service"; import { generateRandomNumber } from "../utils/generateRandomNumber"; import { env } from "../utils/env"; import { Emailschema, resetPasswordSchema } from "../schemas/resetPasswordSchema"; -import Joi from "joi"; +import { clearExpiredUserData } from "../jobs/isPasswordExpired"; +import { use } from "passport"; export const fetchAllUsers = async (req: Request, res: Response) => { @@ -95,8 +96,9 @@ export const createUserController = async (req: Request, res: Response) => { const { name, email, username, password, role } = req.body; try { + let currentUpdateTime = new Date(); const { name, email, username, password } = req.body; - const user = await createUserService(name, email, username, password); + const user = await createUserService(name, email, username, password,currentUpdateTime); if (!user || user == null) { return res.status(409).json({ status: 409, @@ -136,9 +138,12 @@ export const updatePassword = async (req: Request, res: Response) => { } const password = await hashedPassword(newPassword); + const currentUpdateTime = new Date(); // @ts-ignore - const update = await updateUserPassword(user, password); + const update = await updateUserPassword(user, password,currentUpdateTime); if(update){ + //@ts-ignore + clearExpiredUserData(user.id) return res.status(200).json({ message: "Password updated successfully" }); } } catch (err: any) { diff --git a/src/email-templates/passwordExpiredNotification.ts b/src/email-templates/passwordExpiredNotification.ts new file mode 100644 index 0000000..97b6657 --- /dev/null +++ b/src/email-templates/passwordExpiredNotification.ts @@ -0,0 +1,57 @@ +const passwordExpirationHtmlContent = (userName: string): string => { + const htmlContent = ` + + + + + + Password Updated Confirmation + + + +
+

Password Expiration

+

Dear ${userName},

+

Your password has been Expired. + Vist our website to update it to continue using the system.

+

Thank you.

+

Your Website Team

+ +
+ + + `; + return htmlContent; + }; + + export { passwordExpirationHtmlContent }; + \ No newline at end of file diff --git a/src/jobs/isPasswordExpired.ts b/src/jobs/isPasswordExpired.ts new file mode 100644 index 0000000..a3c8e4a --- /dev/null +++ b/src/jobs/isPasswordExpired.ts @@ -0,0 +1,42 @@ +import cron from 'node-cron'; +import EventEmitter from 'events'; +import { getAllUsers } from '../services/user.service'; +import { env } from '../utils/env'; +import { sendEmailService } from '../services/mail.service'; +import { passwordExpirationHtmlContent } from '../email-templates/passwordExpiredNotification'; + +let latestExpiredUserData = new Set(); +export let expiredUserData = new Set(); + +class UpdatePasswordEventsEmitter extends EventEmitter {}; +export const passwordEventEmitter = new UpdatePasswordEventsEmitter(); + +export const isPasswordExpired = () =>{ + const millseconddPerMin = 1000 * 60; + cron.schedule('* * * * * *', async() => { + const currentTime = Date.now(); + const users = await getAllUsers(); + const emailPromises = []; + for (const user of users) { + const lastPasswordUpdateTime:any = user.dataValues.lastPasswordUpdateTime; + const timeDifference:any = currentTime - lastPasswordUpdateTime; + if(timeDifference >= (millseconddPerMin * parseInt(env.password_expiration_time))&& !latestExpiredUserData.has(user.id)){ + passwordEventEmitter.emit("password expired",user); + latestExpiredUserData.add(user.id); + emailPromises.push( + sendEmailService(user, 'Password expired', passwordExpirationHtmlContent(user.name)) + ); + }; + }; + await Promise.all(emailPromises); + expiredUserData = new Set([...latestExpiredUserData]); + }); +} +passwordEventEmitter.on("password expired", (user) => { + expiredUserData.add(user); +}); + +export const clearExpiredUserData = (userId:any) => { + latestExpiredUserData.delete(userId); + expiredUserData.delete(userId); +}; \ No newline at end of file diff --git a/src/middlewares/isPasswordOutOfDate.ts b/src/middlewares/isPasswordOutOfDate.ts new file mode 100644 index 0000000..30e1283 --- /dev/null +++ b/src/middlewares/isPasswordOutOfDate.ts @@ -0,0 +1,24 @@ +import { Request,Response,NextFunction } from 'express'; +import { isLoggedIn } from './isLoggedIn'; +import { expiredUserData } from '../jobs/isPasswordExpired'; + +export const isPasswordOutOfDate = async(req:Request,res:Response,next:NextFunction) =>{ + try { + await isLoggedIn(req,res,() => {}); + //@ts-ignore + const loggedInUserId: any = req.user.id; + const expiredUserIds = new Set([...expiredUserData].map((user: any) => user)); + if (expiredUserIds.has(loggedInUserId)) { + return res.status(403).json({ + message: "Your password expired, Update it to continue" + }); + }; + next(); + } catch (error:any) { + console.log('Error has occured',error.message); + next(error) + } +} + + + diff --git a/src/routes/cartRoutes.ts b/src/routes/cartRoutes.ts index dfcc4ca..578705f 100644 --- a/src/routes/cartRoutes.ts +++ b/src/routes/cartRoutes.ts @@ -4,14 +4,14 @@ import { isLoggedIn } from "../middlewares/isLoggedIn"; import { isQuantityValid } from "../middlewares/isQuantityValid"; import { isProductFound } from "../middlewares/isProductFound"; import { validateCart, validateRemoveProductQty, validateUpdateProductQty } from "../middlewares/cartValidation"; +import { isPasswordOutOfDate } from "../middlewares/isPasswordOutOfDate"; const cartRoutes = Router(); -cartRoutes.get("/",isLoggedIn,viewUserCart); -cartRoutes.post("/",isLoggedIn,validateCart, isQuantityValid, addItemToCart); -// cartRoutes.post("/",isLoggedIn,validateCart, isQuantityValid, addItemToCart); -cartRoutes.put("/",isLoggedIn,validateRemoveProductQty, isProductFound,removeProductFromCart); -cartRoutes.delete("/",isLoggedIn, clearAllProductFromCart); -cartRoutes.patch("/",isLoggedIn,validateUpdateProductQty, isProductFound, updateProductQuantity); +cartRoutes.get("/",isLoggedIn,isPasswordOutOfDate,viewUserCart); +cartRoutes.post("/",isLoggedIn,isPasswordOutOfDate,validateCart, isQuantityValid, addItemToCart); +cartRoutes.put("/",isLoggedIn,isPasswordOutOfDate,validateRemoveProductQty, isProductFound,removeProductFromCart); +cartRoutes.delete("/",isLoggedIn,isPasswordOutOfDate, clearAllProductFromCart); +cartRoutes.patch("/",isLoggedIn,isPasswordOutOfDate,validateUpdateProductQty, isProductFound, updateProductQuantity); export default cartRoutes; diff --git a/src/routes/categoriesRoutes.ts b/src/routes/categoriesRoutes.ts index 93122aa..6007672 100644 --- a/src/routes/categoriesRoutes.ts +++ b/src/routes/categoriesRoutes.ts @@ -11,12 +11,13 @@ import { import { categoriesDataSchema } from "../schemas/categorySchema"; import { isAseller } from "../middlewares/sellerAuth"; import { isLoggedIn } from "../middlewares/isLoggedIn"; +import { isPasswordOutOfDate } from "../middlewares/isPasswordOutOfDate"; const categoriesRouter = Router(); -categoriesRouter.get("/",isLoggedIn,isAseller,fetchCategories); -categoriesRouter.get("/:id",isLoggedIn,isAseller,fetchSingleCategory); -categoriesRouter.post("/",isLoggedIn,isAseller,upload.single('image'),validateSchema(categoriesDataSchema) +categoriesRouter.get("/",isLoggedIn,isPasswordOutOfDate,isAseller,fetchCategories); +categoriesRouter.get("/:id",isLoggedIn,isPasswordOutOfDate,isAseller,fetchSingleCategory); +categoriesRouter.post("/",isLoggedIn,isPasswordOutOfDate,isAseller,upload.single('image'),validateSchema(categoriesDataSchema) ,addCategories); -categoriesRouter.patch("/:id",isAseller,upload.single('image'),categoriesUpdate); -categoriesRouter.delete("/:id",isLoggedIn,isAseller,removeCategories); +categoriesRouter.patch("/:id",isAseller,isPasswordOutOfDate,upload.single('image'),categoriesUpdate); +categoriesRouter.delete("/:id",isLoggedIn,isPasswordOutOfDate,isAseller,removeCategories); export default categoriesRouter; \ No newline at end of file diff --git a/src/routes/notificationRoutes.ts b/src/routes/notificationRoutes.ts index 5392919..29f90e3 100644 --- a/src/routes/notificationRoutes.ts +++ b/src/routes/notificationRoutes.ts @@ -1,13 +1,14 @@ import { Router } from "express"; import { getUserNotifications, readNotification } from "../controllers/notificationController"; import { isLoggedIn } from "../middlewares/isLoggedIn"; +import { isPasswordOutOfDate } from "../middlewares/isPasswordOutOfDate"; const notificationRoutes = Router() -notificationRoutes.get("/",isLoggedIn,getUserNotifications) -notificationRoutes.get("/:id",isLoggedIn,readNotification) +notificationRoutes.get("/",isLoggedIn,isPasswordOutOfDate,getUserNotifications) +notificationRoutes.get("/:id",isLoggedIn,isPasswordOutOfDate,readNotification) diff --git a/src/routes/paymentRoutes.ts b/src/routes/paymentRoutes.ts index bae37d8..31b6d7a 100644 --- a/src/routes/paymentRoutes.ts +++ b/src/routes/paymentRoutes.ts @@ -3,10 +3,11 @@ import * as paymentController from "../controllers/paymentController" import { isLoggedIn } from "../middlewares/isLoggedIn"; import { hasItemsInCart } from "../middlewares/payments"; import { isAbuyer } from "../middlewares/isAbuyer"; +import { isPasswordOutOfDate } from "../middlewares/isPasswordOutOfDate"; const paymentRouter = express.Router() -paymentRouter.post('/checkout', isLoggedIn, isAbuyer, hasItemsInCart, paymentController.createCheckoutSession); +paymentRouter.post('/checkout', isLoggedIn,isPasswordOutOfDate, isAbuyer, hasItemsInCart, paymentController.createCheckoutSession); paymentRouter.get('/success', paymentController.handleSuccess); paymentRouter.get('/canceled', paymentController.handleFailure); diff --git a/src/routes/productsRoute.ts b/src/routes/productsRoute.ts index cd8f5f3..c445721 100644 --- a/src/routes/productsRoute.ts +++ b/src/routes/productsRoute.ts @@ -9,20 +9,21 @@ import { isCategoryExist } from "../middlewares/isCategoryExist"; import { addReviewController, deleteReviewController, getreviewController, updateReviewController} from "../controllers/productControllers" import { addReviewValidate, updateReviewValidate } from "../schemas/review"; import { hasPurchasedProduct } from "../middlewares/hasPurchased"; +import { isPasswordOutOfDate } from "../middlewares/isPasswordOutOfDate"; const productsRouter = Router(); -productsRouter.get("/search", searchProductController) +productsRouter.get("/search",isPasswordOutOfDate, searchProductController) -productsRouter.get("/",fetchProducts); -productsRouter.get("/:id",fetchSingleProduct); -productsRouter.post("/",isLoggedIn,isAseller,upload.array('images'), +productsRouter.get("/",isLoggedIn,isPasswordOutOfDate,fetchProducts); +productsRouter.get("/:id",isLoggedIn,isPasswordOutOfDate,fetchSingleProduct); +productsRouter.post("/",isLoggedIn,isPasswordOutOfDate,isAseller,upload.array('images'), validateSchema(productDataSchema),isCategoryExist,addProducts); -productsRouter.patch("/:id",isLoggedIn,isAseller,upload.array('images'),productsUpdate); -productsRouter.patch("/:id/status",isLoggedIn,isAseller,productAvailability); -productsRouter.delete("/:id",isLoggedIn,isAseller,removeProducts); +productsRouter.patch("/:id",isLoggedIn,isPasswordOutOfDate,isAseller,upload.array('images'),productsUpdate); +productsRouter.patch("/:id/status",isLoggedIn,isPasswordOutOfDate,isAseller,productAvailability); +productsRouter.delete("/:id",isLoggedIn,isPasswordOutOfDate,isAseller,removeProducts); productsRouter.get("/:pid/reviews", getreviewController) -productsRouter.post("/:pid/reviews",isLoggedIn, validateSchema(addReviewValidate), hasPurchasedProduct, addReviewController) -productsRouter.delete("/:pid/reviews", isLoggedIn, deleteReviewController) -productsRouter.patch("/:pid/reviews", isLoggedIn, validateSchema(updateReviewValidate), updateReviewController) +productsRouter.post("/:pid/reviews",isLoggedIn,isPasswordOutOfDate,validateSchema(addReviewValidate), hasPurchasedProduct, addReviewController) +productsRouter.delete("/:pid/reviews", isLoggedIn,isPasswordOutOfDate, deleteReviewController) +productsRouter.patch("/:pid/reviews", isLoggedIn,isPasswordOutOfDate, validateSchema(updateReviewValidate), updateReviewController) export default productsRouter; \ No newline at end of file diff --git a/src/routes/roleRoutes.ts b/src/routes/roleRoutes.ts index 132324a..284011d 100644 --- a/src/routes/roleRoutes.ts +++ b/src/routes/roleRoutes.ts @@ -4,12 +4,13 @@ import { isLoggedIn } from '../middlewares/isLoggedIn'; import{isAdmin} from '../middlewares/isAdmin'; import { validateSchema } from '../middlewares/validator'; import {roleSchema} from '../schemas/roleSchema'; +import { isPasswordOutOfDate } from '../middlewares/isPasswordOutOfDate'; const RoleRouter = express.Router(); -RoleRouter.post('/', isLoggedIn, isAdmin, validateSchema(roleSchema), roleController.createRole); -RoleRouter.get('/', roleController.getRoles); -RoleRouter.patch('/:id', isLoggedIn, isAdmin, validateSchema(roleSchema),roleController.updateRole); -RoleRouter.delete('/:id', isLoggedIn, isAdmin, roleController.deleteRole); +RoleRouter.post('/', isLoggedIn,isPasswordOutOfDate, isAdmin, validateSchema(roleSchema), roleController.createRole); +RoleRouter.get('/',roleController.getRoles); +RoleRouter.patch('/:id', isLoggedIn,isPasswordOutOfDate, isAdmin, validateSchema(roleSchema),roleController.updateRole); +RoleRouter.delete('/:id', isLoggedIn,isPasswordOutOfDate, isAdmin, roleController.deleteRole); export default RoleRouter; \ No newline at end of file diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index d41bdca..c3e9f89 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -17,33 +17,29 @@ import { roleExist } from "../middlewares/roleExist"; import { userExist } from "../middlewares/userExist"; import { isDisabled } from "../middlewares/isDisabled"; import { verifyToken } from "../middlewares/verifyToken"; - - - +import { isPasswordOutOfDate } from "../middlewares/isPasswordOutOfDate"; const userRoutes = Router(); userRoutes.get("/", fetchAllUsers); -userRoutes.put("/passwordupdate", isLoggedIn, validateSchema(passwordUpdateSchema), updatePassword) +userRoutes.put("/passwordupdate", isLoggedIn,validateSchema(passwordUpdateSchema), updatePassword) userRoutes.post("/login", emailValidation,validateSchema(logInSchema),isDisabled,userLogin); -userRoutes.post("/register", emailValidation, validateSchema(signUpSchema), createUserController); -userRoutes.put("/passwordupdate", isLoggedIn, validateSchema(passwordUpdateSchema), updatePassword); +userRoutes.post("/register", emailValidation,validateSchema(signUpSchema), createUserController); userRoutes.get("/2fa-verify/:token",tokenVerification); userRoutes.post("/2fa-verify",otpVerification); userRoutes.get('/profile', - isLoggedIn, + isLoggedIn, isPasswordOutOfDate, getProfileController ); userRoutes.post('/logout', isLoggedIn, logout); userRoutes.patch('/profile', - isLoggedIn, + isLoggedIn,isPasswordOutOfDate, upload.single('profileImage'), validateSchema(profileSchemas), isUploadedFileImage, updateProfileController -) - -userRoutes.patch("/:id/role",isLoggedIn, isAdmin, validateSchema(roleUpdateSchema), userExist, roleExist, updateUserRole) -userRoutes.patch('/:userId/status',isLoggedIn, isAdmin, changeUserAccountStatus); +); +userRoutes.patch("/:id/role",isLoggedIn,isPasswordOutOfDate, isAdmin, validateSchema(roleUpdateSchema), userExist, roleExist, updateUserRole) +userRoutes.patch('/:userId/status',isLoggedIn,isPasswordOutOfDate, isAdmin, changeUserAccountStatus); userRoutes.get("/auth/google", authenticateUser); userRoutes.get("/auth/google/callback", callbackFn); diff --git a/src/routes/wishesRoutes.ts b/src/routes/wishesRoutes.ts index 87185bc..18857e7 100644 --- a/src/routes/wishesRoutes.ts +++ b/src/routes/wishesRoutes.ts @@ -3,12 +3,13 @@ import * as wishesController from '../controllers/wishesController' import { isLoggedIn } from '../middlewares/isLoggedIn' import { isAseller } from '../middlewares/sellerAuth' import { isAbuyer } from '../middlewares/isAbuyer' +import { isPasswordOutOfDate } from '../middlewares/isPasswordOutOfDate' const wishesRouter = express.Router() -wishesRouter.post('/wishes', isLoggedIn, isAbuyer, wishesController.addToWishes) -wishesRouter.get('/wishes', isLoggedIn, wishesController.getUserWishes) -wishesRouter.delete('/products/:id/wishes', isLoggedIn, isAbuyer, wishesController.deleteWish) -wishesRouter.get('/products/:id/wishes', isLoggedIn, isAseller, wishesController.getProductWishes) +wishesRouter.post('/wishes', isLoggedIn,isPasswordOutOfDate,isAbuyer, wishesController.addToWishes) +wishesRouter.get('/wishes', isLoggedIn,isPasswordOutOfDate, wishesController.getUserWishes) +wishesRouter.delete('/products/:id/wishes', isLoggedIn,isPasswordOutOfDate, isAbuyer, wishesController.deleteWish) +wishesRouter.get('/products/:id/wishes', isLoggedIn,isPasswordOutOfDate, isAseller, wishesController.getProductWishes) export default wishesRouter \ No newline at end of file diff --git a/src/schemas/signUpSchema.ts b/src/schemas/signUpSchema.ts index 9d1c448..2a435b2 100644 --- a/src/schemas/signUpSchema.ts +++ b/src/schemas/signUpSchema.ts @@ -5,6 +5,7 @@ export const signUpSchema = Joi.object({ username: Joi.string().min(4).required(), email: Joi.string().min(6).required().email(), password: Joi.string().min(6).max(20).required(), + lastPasswordUpdateTime:Joi.date(), role: Joi.string().optional(), }).options({ allowUnknown: false }); diff --git a/src/sequelize/config/config.js b/src/sequelize/config/config.js index 6560c78..28f37cb 100644 --- a/src/sequelize/config/config.js +++ b/src/sequelize/config/config.js @@ -10,7 +10,6 @@ module.exports = { test: { url: process.env.TEST_DB, dialect: "postgres", - dialectOptions: process.env.IS_REMOTE === "true" ? { diff --git a/src/sequelize/migrations/b20240502074237-user.js b/src/sequelize/migrations/b20240502074237-user.js index 780c390..471ec5d 100644 --- a/src/sequelize/migrations/b20240502074237-user.js +++ b/src/sequelize/migrations/b20240502074237-user.js @@ -28,6 +28,9 @@ module.exports = { type: Sequelize.STRING, allowNull: true, }, + lastPasswordUpdateTime:{ + type: Sequelize.DATE, + }, createdAt: { allowNull: false, type: Sequelize.DATE, diff --git a/src/sequelize/models/users.ts b/src/sequelize/models/users.ts index b8c864b..c467133 100644 --- a/src/sequelize/models/users.ts +++ b/src/sequelize/models/users.ts @@ -12,6 +12,7 @@ export interface UserAttributes { username: string; email: string; password: string | undefined; + lastPasswordUpdateTime?: Date; roleId: number | undefined; isActive?:boolean; createdAt?: Date; @@ -24,6 +25,7 @@ class User extends Model implements UserAttributes { username!: string; email!: string; password!: string; + lastPasswordUpdateTime!:Date | undefined; isActive: boolean | undefined; roleId!: number | undefined; createdAt!: Date | undefined; @@ -54,6 +56,9 @@ User.init( allowNull: true, type: DataTypes.STRING, }, + lastPasswordUpdateTime:{ + type: DataTypes.DATE + }, roleId: { type: DataTypes.NUMBER, allowNull: false, diff --git a/src/sequelize/seeders/b20240412144111-demo-user.js b/src/sequelize/seeders/b20240412144111-demo-user.js index e07fc49..a15c350 100644 --- a/src/sequelize/seeders/b20240412144111-demo-user.js +++ b/src/sequelize/seeders/b20240412144111-demo-user.js @@ -29,6 +29,7 @@ module.exports = { username: "soleil00", email: "soleil@soleil00.com", password: await bcrypt.hash("soleil00", 10), + lastPasswordUpdateTime:new Date(), roleId: 3, createdAt: new Date(), updatedAt: new Date(), @@ -38,6 +39,7 @@ module.exports = { username: "yes", email: "soleil@soleil0w.com", password: await bcrypt.hash("soleil00", 10), + lastPasswordUpdateTime:new Date(), roleId: 1, createdAt: new Date(), updatedAt: new Date(), @@ -47,6 +49,7 @@ module.exports = { username: "jehovanis", email: "mugabo.kefa00@gmail.com", password: await bcrypt.hash("Test@123", 10), + lastPasswordUpdateTime:new Date(), roleId: 2, createdAt: new Date(), updatedAt: new Date(), @@ -56,6 +59,7 @@ module.exports = { username: "Jabo24", email: "jaboinnovates@gmail.com", password: await bcrypt.hash("Test@123", 10), + lastPasswordUpdateTime:new Date(), roleId: 2, createdAt: new Date(), updatedAt: new Date(), diff --git a/src/services/user.service.ts b/src/services/user.service.ts index a30d7c5..4ee6984 100644 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -29,7 +29,7 @@ export const callbackFn = passport.authenticate("google", { export const getAllUsers = async () => { try { const users = await User.findAll({ - attributes: ['id', 'name', 'username', 'email', 'roleId', 'createdAt', 'updatedAt','isActive'], + attributes: ['id', 'name', 'username', 'email','lastPasswordUpdateTime', 'roleId', 'createdAt', 'updatedAt','isActive'], }); if (users.length === 0) { console.log("no user"); @@ -52,7 +52,8 @@ export const loggedInUser = async (email: string) => { throw new Error(err.message); } }; -export const createUserService = async (name: string, email: string, username: string, password: string): Promise => { + //changes +export const createUserService = async (name: string, email: string, username: string, password: string,lastPasswordUpdateTime:Date): Promise => { const existingUser = await User.findOne({ where: { email } }); if (existingUser) { return null; @@ -67,6 +68,8 @@ export const createUserService = async (name: string, email: string, username: s name, email, username, + //changes + lastPasswordUpdateTime, password: hashPassword, }); await Profile.create({ @@ -110,8 +113,8 @@ export const findUserById = async (id: string) => { throw new Error(error.message); } }; -export const updateUserPassword = async (user: User, password: string) => { - const update = await User.update({ password: password}, { where: { id: user.id}}) +export const updateUserPassword = async (user: User, password: string, lastPasswordUpdateTime: Date) => { + const update = await User.update({ password: password,lastPasswordUpdateTime: lastPasswordUpdateTime}, { where: { id: user.id}}) return update }; diff --git a/src/types.ts b/src/types.ts index dc44242..26ea412 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,6 +6,7 @@ export interface IUser { username: string; email: string; password: string; + lastPasswordUpdateTime?:Date; roleId?: number; userRole?:IRole; createdAt?: Date; diff --git a/src/utils/env.ts b/src/utils/env.ts index 1f03768..345c50e 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -17,5 +17,6 @@ export const env = { local_url: `${process.env.LOCAL_URL}:${process.env.PORT}/api/v1/users/2fa-verify`, redis_url: process.env.REDIS_URL as string, client_url: process.env.CLIENT_URL as string, - stripe_secret: process.env.STRIPE_SECRET_KEY as string + stripe_secret: process.env.STRIPE_SECRET_KEY as string, + password_expiration_time: process.env.TIME_FOR_PASSWORD_EXPIRATION as string }; \ No newline at end of file diff --git a/src/utils/server.ts b/src/utils/server.ts index f1012fb..c650cee 100644 --- a/src/utils/server.ts +++ b/src/utils/server.ts @@ -5,6 +5,7 @@ import { createServer, Server as HTTPServer } from "http"; import path from "path"; import { Server as SocketIOServer } from "socket.io"; import socket from "../config/socketCofing"; +import { isPasswordExpired } from "../jobs/isPasswordExpired"; import appROutes from "../routes"; import homeRoute from "../routes/homeRoutes"; @@ -55,7 +56,7 @@ app.use( app.use(passport.initialize()); app.use(passport.session()); -app.use("/api/v1/chats", express.static(path.join(__dirname, "../../public"))); +app.use("/api/v1/chats", express.static(path.join(__dirname, '../../public'))); app.use("/", homeRoute); app.use("/api/v1", appROutes); @@ -65,4 +66,5 @@ app.use("/api/v1/roles", RoleRouter); socket(io); findExpiredProduct() +isPasswordExpired(); export default server;