password reset

This commit is contained in:
jackiettran
2025-10-10 22:54:45 -04:00
parent 462dbf6b7a
commit b9e6cfc54d
15 changed files with 1976 additions and 178 deletions

View File

@@ -84,6 +84,14 @@ const User = sequelize.define(
type: DataTypes.DATE,
allowNull: true,
},
passwordResetToken: {
type: DataTypes.STRING,
allowNull: true,
},
passwordResetTokenExpiry: {
type: DataTypes.DATE,
allowNull: true,
},
defaultAvailableAfter: {
type: DataTypes.STRING,
defaultValue: "09:00",
@@ -124,6 +132,11 @@ const User = sequelize.define(
type: DataTypes.DATE,
allowNull: true,
},
jwtVersion: {
type: DataTypes.INTEGER,
defaultValue: 0,
allowNull: false,
},
},
{
hooks: {
@@ -222,4 +235,59 @@ User.prototype.verifyEmail = async function () {
});
};
// Password reset methods
User.prototype.generatePasswordResetToken = async function () {
const crypto = require("crypto");
// Generate random token for email URL
const token = crypto.randomBytes(32).toString("hex");
// Hash token before storing in database (SHA-256)
const hashedToken = crypto.createHash("sha256").update(token).digest("hex");
const expiry = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
await this.update({
passwordResetToken: hashedToken,
passwordResetTokenExpiry: expiry,
});
// Return plain token for email URL (not stored in DB)
return token;
};
User.prototype.isPasswordResetTokenValid = function (token) {
if (!this.passwordResetToken || !this.passwordResetTokenExpiry) {
return false;
}
// Check if token is expired first
if (new Date() > new Date(this.passwordResetTokenExpiry)) {
return false;
}
const crypto = require("crypto");
// Hash the incoming token to compare with stored hash
const hashedToken = crypto.createHash("sha256").update(token).digest("hex");
// Use timing-safe comparison to prevent timing attacks
const storedTokenBuffer = Buffer.from(this.passwordResetToken, "hex");
const hashedTokenBuffer = Buffer.from(hashedToken, "hex");
// Ensure buffers are same length for timingSafeEqual
if (storedTokenBuffer.length !== hashedTokenBuffer.length) {
return false;
}
return crypto.timingSafeEqual(storedTokenBuffer, hashedTokenBuffer);
};
User.prototype.resetPassword = async function (newPassword) {
return this.update({
password: newPassword,
passwordResetToken: null,
passwordResetTokenExpiry: null,
// Increment JWT version to invalidate all existing sessions
jwtVersion: this.jwtVersion + 1,
});
};
module.exports = User;