MFA
This commit is contained in:
107
backend/migrations/20260115000001-add-two-factor-auth-fields.js
Normal file
107
backend/migrations/20260115000001-add-two-factor-auth-fields.js
Normal file
@@ -0,0 +1,107 @@
|
||||
"use strict";
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
// Add TOTP configuration fields
|
||||
await queryInterface.addColumn("Users", "twoFactorEnabled", {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false,
|
||||
});
|
||||
|
||||
await queryInterface.addColumn("Users", "twoFactorMethod", {
|
||||
type: Sequelize.ENUM("totp", "email"),
|
||||
allowNull: true,
|
||||
});
|
||||
|
||||
await queryInterface.addColumn("Users", "totpSecret", {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
|
||||
await queryInterface.addColumn("Users", "totpSecretIv", {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
|
||||
await queryInterface.addColumn("Users", "twoFactorEnabledAt", {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
});
|
||||
|
||||
// Add Email OTP fields (backup method)
|
||||
await queryInterface.addColumn("Users", "emailOtpCode", {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
|
||||
await queryInterface.addColumn("Users", "emailOtpExpiry", {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
});
|
||||
|
||||
await queryInterface.addColumn("Users", "emailOtpAttempts", {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
allowNull: false,
|
||||
});
|
||||
|
||||
// Add Recovery Codes fields
|
||||
await queryInterface.addColumn("Users", "recoveryCodesHash", {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
});
|
||||
|
||||
await queryInterface.addColumn("Users", "recoveryCodesGeneratedAt", {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
});
|
||||
|
||||
await queryInterface.addColumn("Users", "recoveryCodesUsedCount", {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
allowNull: false,
|
||||
});
|
||||
|
||||
// Add Step-up session tracking
|
||||
await queryInterface.addColumn("Users", "twoFactorVerifiedAt", {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
});
|
||||
|
||||
// Add temporary secret storage during setup
|
||||
await queryInterface.addColumn("Users", "twoFactorSetupPendingSecret", {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
|
||||
await queryInterface.addColumn("Users", "twoFactorSetupPendingSecretIv", {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
// Remove all 2FA fields in reverse order
|
||||
await queryInterface.removeColumn("Users", "twoFactorSetupPendingSecretIv");
|
||||
await queryInterface.removeColumn("Users", "twoFactorSetupPendingSecret");
|
||||
await queryInterface.removeColumn("Users", "twoFactorVerifiedAt");
|
||||
await queryInterface.removeColumn("Users", "recoveryCodesUsedCount");
|
||||
await queryInterface.removeColumn("Users", "recoveryCodesGeneratedAt");
|
||||
await queryInterface.removeColumn("Users", "recoveryCodesHash");
|
||||
await queryInterface.removeColumn("Users", "emailOtpAttempts");
|
||||
await queryInterface.removeColumn("Users", "emailOtpExpiry");
|
||||
await queryInterface.removeColumn("Users", "emailOtpCode");
|
||||
await queryInterface.removeColumn("Users", "twoFactorEnabledAt");
|
||||
await queryInterface.removeColumn("Users", "totpSecretIv");
|
||||
await queryInterface.removeColumn("Users", "totpSecret");
|
||||
await queryInterface.removeColumn("Users", "twoFactorMethod");
|
||||
await queryInterface.removeColumn("Users", "twoFactorEnabled");
|
||||
|
||||
// Remove the ENUM type
|
||||
await queryInterface.sequelize.query(
|
||||
'DROP TYPE IF EXISTS "enum_Users_twoFactorMethod";'
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
"use strict";
|
||||
|
||||
/** @type {import('sequelize-cli').Migration} */
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
// Add recentTotpCodes field for TOTP replay protection
|
||||
await queryInterface.addColumn("Users", "recentTotpCodes", {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment: "JSON array of hashed recently used TOTP codes for replay protection",
|
||||
});
|
||||
|
||||
// Remove deprecated columns (if they exist)
|
||||
await queryInterface.removeColumn("Users", "twoFactorEnabledAt").catch(() => {});
|
||||
await queryInterface.removeColumn("Users", "recoveryCodesUsedCount").catch(() => {});
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.removeColumn("Users", "recentTotpCodes");
|
||||
|
||||
// Re-add deprecated columns for rollback
|
||||
await queryInterface.addColumn("Users", "twoFactorEnabledAt", {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
});
|
||||
await queryInterface.addColumn("Users", "recoveryCodesUsedCount", {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
allowNull: false,
|
||||
});
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user