password reset
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user