const { DataTypes } = require("sequelize"); const sequelize = require("../config/database"); const bcrypt = require("bcryptjs"); const User = sequelize.define( "User", { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true, }, username: { type: DataTypes.STRING, unique: true, allowNull: true, }, email: { type: DataTypes.STRING, unique: true, allowNull: true, validate: { isEmail: true, }, }, password: { type: DataTypes.STRING, allowNull: true, }, firstName: { type: DataTypes.STRING, allowNull: false, }, lastName: { type: DataTypes.STRING, allowNull: false, }, phone: { type: DataTypes.STRING, allowNull: true, }, authProvider: { type: DataTypes.ENUM("local", "google"), defaultValue: "local", }, providerId: { type: DataTypes.STRING, allowNull: true, }, address1: { type: DataTypes.STRING, }, address2: { type: DataTypes.STRING, }, city: { type: DataTypes.STRING, }, state: { type: DataTypes.STRING, }, zipCode: { type: DataTypes.STRING, }, country: { type: DataTypes.STRING, }, profileImage: { type: DataTypes.STRING, }, isVerified: { type: DataTypes.BOOLEAN, defaultValue: false, }, defaultAvailableAfter: { type: DataTypes.STRING, defaultValue: "09:00", }, defaultAvailableBefore: { type: DataTypes.STRING, defaultValue: "17:00", }, defaultSpecifyTimesPerDay: { type: DataTypes.BOOLEAN, defaultValue: false, }, defaultWeeklyTimes: { type: DataTypes.JSONB, defaultValue: { sunday: { availableAfter: "09:00", availableBefore: "17:00" }, monday: { availableAfter: "09:00", availableBefore: "17:00" }, tuesday: { availableAfter: "09:00", availableBefore: "17:00" }, wednesday: { availableAfter: "09:00", availableBefore: "17:00" }, thursday: { availableAfter: "09:00", availableBefore: "17:00" }, friday: { availableAfter: "09:00", availableBefore: "17:00" }, saturday: { availableAfter: "09:00", availableBefore: "17:00" }, }, }, stripeConnectedAccountId: { type: DataTypes.STRING, allowNull: true, }, stripeCustomerId: { type: DataTypes.STRING, allowNull: true, }, loginAttempts: { type: DataTypes.INTEGER, defaultValue: 0, }, lockUntil: { type: DataTypes.DATE, allowNull: true, }, }, { hooks: { beforeCreate: async (user) => { if (user.password) { user.password = await bcrypt.hash(user.password, 12); } }, beforeUpdate: async (user) => { if (user.changed("password") && user.password) { user.password = await bcrypt.hash(user.password, 12); } }, }, } ); User.prototype.comparePassword = async function (password) { if (!this.password) { return false; } return bcrypt.compare(password, this.password); }; // Account lockout constants const MAX_LOGIN_ATTEMPTS = 5; const LOCK_TIME = 2 * 60 * 60 * 1000; // 2 hours // Check if account is locked User.prototype.isLocked = function () { return !!(this.lockUntil && this.lockUntil > Date.now()); }; // Increment login attempts and lock account if necessary User.prototype.incLoginAttempts = async function () { // If we have a previous lock that has expired, restart at 1 if (this.lockUntil && this.lockUntil < Date.now()) { return this.update({ loginAttempts: 1, lockUntil: null, }); } const updates = { loginAttempts: this.loginAttempts + 1 }; // Lock account after max attempts if (this.loginAttempts + 1 >= MAX_LOGIN_ATTEMPTS && !this.isLocked()) { updates.lockUntil = Date.now() + LOCK_TIME; } return this.update(updates); }; // Reset login attempts after successful login User.prototype.resetLoginAttempts = async function () { return this.update({ loginAttempts: 0, lockUntil: null, }); }; module.exports = User;