From 47f96ece22ae974760e1db774f85e895d54bba8b Mon Sep 17 00:00:00 2001 From: Gowtham M Date: Fri, 5 Dec 2025 11:27:19 +0530 Subject: [PATCH] Password scope added : GWM --- app/controllers/auth.controller.js | 17 +- app/models/establishment_user.model.js | 208 ++++++++++++++----------- app/models/user.model.js | 38 ++--- app/services/email.service.js | 6 +- 4 files changed, 146 insertions(+), 123 deletions(-) diff --git a/app/controllers/auth.controller.js b/app/controllers/auth.controller.js index cf29333..5ad086b 100644 --- a/app/controllers/auth.controller.js +++ b/app/controllers/auth.controller.js @@ -17,12 +17,12 @@ exports.login = async (req, res) => { let userData = null; // Try EstablishmentUser first - userData = await EstablishmentUser.findOne({ where: { email } }); + userData = await EstablishmentUser.scope("withSensitive").findOne({ where: { email } }); if (userData) { userRole = "EstablishmentUser"; } else { // Try Admin user - userData = await User.findOne({ where: { email } }); + userData = await User.scope("withSensitive").findOne({ where: { email } }); if (userData) { userRole = "Admin"; } @@ -74,17 +74,16 @@ exports.login = async (req, res) => { }); // Set token in HTTP-only cookie (IMPORTANT PART) + const isProd = process.env.NODE_ENV === "production"; + res.cookie("auth_token", token, { - // httpOnly: true, - // secure: process.env.NODE_ENV === "production", // true in prod (HTTPS) - // sameSite: "lax", // or "strict" if suitable - // maxAge: 6 * 60 * 60 * 1000, // 6 hours in ms httpOnly: true, - secure: true, - sameSite: "none", - maxAge: 6 * 60 * 60 * 1000, // 6 hours in ms + secure: isProd, // only true in production (HTTPS) + sameSite: isProd ? "none" : "lax", // 'none' requires HTTPS, so use 'lax' locally + maxAge: 6 * 60 * 60 * 1000, // 6 hours }); + // Optionally return minimal user info (WITHOUT password) return res.status(200).json({ status: "success", diff --git a/app/models/establishment_user.model.js b/app/models/establishment_user.model.js index 9426d17..e6ee4fa 100644 --- a/app/models/establishment_user.model.js +++ b/app/models/establishment_user.model.js @@ -1,100 +1,120 @@ module.exports = (sequelize, DataTypes) => { - const EstablishmentUser = sequelize.define( - "establishment_users", - { - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true, - }, - establishment_id: { - type: DataTypes.INTEGER, - allowNull: false, - }, - name: { - type: DataTypes.STRING, - allowNull: false, - }, - email: { - type: DataTypes.STRING, - allowNull: false, - unique: true, - }, - password: { - type: DataTypes.STRING, - allowNull: false, - }, - gender: { - type: DataTypes.STRING, - allowNull: true, - }, - is_active: { - type: DataTypes.BOOLEAN, - defaultValue: true, - }, - created_at: { - type: DataTypes.DATE, - defaultValue: DataTypes.NOW, - }, - created_by: { - type: DataTypes.INTEGER, - allowNull: false, - }, - updated_at: { - type: DataTypes.DATE, - allowNull: true, - }, - updated_by: { - type: DataTypes.INTEGER, - allowNull: true, - }, - last_login: { - type: DataTypes.DATE, - allowNull: true, - }, - reset_otp: { - type: DataTypes.STRING, - allowNull: true, - }, - reset_otp_expires_at: { - type: DataTypes.DATE, - allowNull: true, + const EstablishmentUser = sequelize.define( + "establishment_users", + { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + + establishment_id: { + type: DataTypes.INTEGER, + allowNull: false, + }, + + name: { + type: DataTypes.STRING, + allowNull: false, + }, + + email: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + }, + + password: { + type: DataTypes.STRING, + allowNull: false, + }, + + gender: { + type: DataTypes.STRING, + allowNull: true, + }, + + is_active: { + type: DataTypes.BOOLEAN, + defaultValue: true, + }, + + created_at: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, + + created_by: { + type: DataTypes.INTEGER, + allowNull: false, + }, + + updated_at: { + type: DataTypes.DATE, + allowNull: true, + }, + + updated_by: { + type: DataTypes.INTEGER, + allowNull: true, + }, + + last_login: { + type: DataTypes.DATE, + allowNull: true, + }, + + reset_otp: { + type: DataTypes.STRING, + allowNull: true, + }, + + reset_otp_expires_at: { + type: DataTypes.DATE, + allowNull: true, + }, + }, + { + // ============================================================ + // Model Options + // ============================================================ + timestamps: false, + tableName: "establishment_users", + + // 1. Hide sensitive fields by default + defaultScope: { + attributes: { + exclude: ["password", "reset_otp", "reset_otp_expires_at"], }, }, - { - timestamps: false, - tableName: "establishment_users", + + // 2. Scope for login / sensitive queries + scopes: { + withSensitive: { + attributes: { + include: ["password", "reset_otp", "reset_otp_expires_at"], + }, + }, }, - // { - // // 1. EXCLUDE sensitive fields from all queries - // defaultScope: { - // attributes: { exclude: ["password", "reset_otp", "reset_otp_expires_at"] }, - // }, + } + ); - // // ------------------------------------------------------------- - // // 2. Special scope for login or OTP flows - // // Use: User.scope("withSensitive").findOne(...) - // // ------------------------------------------------------------- - // scopes: { - // withSensitive: { - // attributes: { - // include: ["password", "reset_otp", "reset_otp_expires_at"], - // }, - // }, - // }, - // } - ); - - EstablishmentUser.associate = (models) => { - - EstablishmentUser.belongsTo(models.Establishment, { - foreignKey: 'establishment_id', - as: 'establishments', - }); - - }; - - - return EstablishmentUser; + // 3. Remove sensitive fields from all API responses + EstablishmentUser.prototype.toJSON = function () { + const values = { ...this.get() }; + delete values.password; + delete values.reset_otp; + delete values.reset_otp_expires_at; + return values; }; - \ No newline at end of file + + // Relationships + EstablishmentUser.associate = (models) => { + EstablishmentUser.belongsTo(models.Establishment, { + foreignKey: "establishment_id", + as: "establishments", + }); + }; + + return EstablishmentUser; +}; diff --git a/app/models/user.model.js b/app/models/user.model.js index 46bfb24..dc008b4 100644 --- a/app/models/user.model.js +++ b/app/models/user.model.js @@ -1,5 +1,7 @@ module.exports = (sequelize, DataTypes) => { - const User = sequelize.define("admin_users", { + const User = sequelize.define( + "admin_users", + { id: { type: DataTypes.INTEGER, autoIncrement: true, @@ -35,24 +37,24 @@ module.exports = (sequelize, DataTypes) => { defaultValue: true, }, }, - // { - // // 1. EXCLUDE sensitive fields from all queries - // defaultScope: { - // attributes: { exclude: ["password", "reset_otp", "reset_otp_expires_at"] }, - // }, + { + // 1. EXCLUDE sensitive fields from all queries + defaultScope: { + attributes: { exclude: ["password", "reset_otp", "reset_otp_expires_at"] }, + }, - // // ------------------------------------------------------------- - // // 2. Special scope for login or OTP flows - // // Use: User.scope("withSensitive").findOne(...) - // // ------------------------------------------------------------- - // scopes: { - // withSensitive: { - // attributes: { - // include: ["password", "reset_otp", "reset_otp_expires_at"], - // }, - // }, - // }, - // } + // ------------------------------------------------------------- + // 2. Special scope for login or OTP flows + // Use: User.scope("withSensitive").findOne(...) + // ------------------------------------------------------------- + scopes: { + withSensitive: { + attributes: { + include: ["password", "reset_otp", "reset_otp_expires_at"], + }, + }, + }, + } ); return User; diff --git a/app/services/email.service.js b/app/services/email.service.js index 454cd3f..f5bd10d 100644 --- a/app/services/email.service.js +++ b/app/services/email.service.js @@ -3,11 +3,13 @@ const { NotificationTemplate } = require("../models"); // adjust path if needed const logger = require("../services/logger"); require("dotenv").config(); +const port = Number(process.env.MAIL_PORT); const transporter = nodemailer.createTransport({ host: process.env.MAIL_HOST, - port: process.env.MAIL_PORT, - secure: false, // true for 465, false for 587 + port, + secure: port === 465, // SSL + requireTLS: port === 587, // Enforce TLS for 587 auth: { user: process.env.MAIL_USER, pass: process.env.MAIL_PASS,