✅ Backend Implementation: - Add Azure AD JWT token validation middleware - Create hybrid authentication system supporting both Azure AD and password auth - Implement auto-provisioning for new Azure AD users - Add admin controls to toggle password authentication - Update all API routes to use hybrid authentication - Add database fields for authentication (password, lastLoginAt) - Create comprehensive auth routes with validation endpoints ✅ Frontend Implementation: - Install and configure Azure MSAL browser library - Create Azure AD authentication service with popup/redirect support - Build hybrid authentication service managing both auth methods - Update Login.vue with modern dual-authentication UI - Implement dynamic password auth toggle based on admin settings - Update App.vue for proper session management and validation - Modify API service to handle both token types ✅ Security Features: - Azure AD tenant validation (Oliver Agency) - Role-based access control with auto-admin assignment - JWT token validation for both auth methods - Automatic user provisioning with proper defaults - Session validation and automatic logout on token expiry ✅ Admin Features: - Toggle password authentication on/off - Manage users from both authentication methods - Full role and agent access control - Azure AD user auto-provisioning as regular users ✅ Configuration: - Azure AD: Tenant e519c2e6-bc6d-4fdf-8d9c-923c2f002385 - Client ID: 9079054c-9620-4757-a256-23413042f1ef - Development redirect URI support - Fallback password authentication for testing 🔧 Technical Stack: - Azure MSAL Browser & Node libraries - JWT token validation and hybrid middleware - Database schema updates with migrations - Vue.js integration with MSAL - Express.js hybrid authentication routes 🚀 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
79 lines
No EOL
2.8 KiB
JavaScript
79 lines
No EOL
2.8 KiB
JavaScript
const { sequelize } = require('../config/database');
|
||
const bcrypt = require('bcrypt');
|
||
|
||
const migration = {
|
||
async up() {
|
||
const queryInterface = sequelize.getQueryInterface();
|
||
|
||
// Add password column
|
||
await queryInterface.addColumn('users', 'password', {
|
||
type: sequelize.Sequelize.STRING,
|
||
allowNull: true, // Initially null, we'll update existing users
|
||
});
|
||
|
||
// Add lastLoginAt column
|
||
await queryInterface.addColumn('users', 'lastLoginAt', {
|
||
type: sequelize.Sequelize.DATE,
|
||
allowNull: true,
|
||
});
|
||
|
||
// Update existing users with default passwords and ensure they have proper role setup
|
||
const users = await sequelize.query('SELECT id, email FROM users', {
|
||
type: sequelize.QueryTypes.SELECT,
|
||
});
|
||
|
||
for (const user of users) {
|
||
// Hash default password for existing users
|
||
const defaultPassword = 'changeMe123!';
|
||
const hashedPassword = await bcrypt.hash(defaultPassword, 10);
|
||
|
||
// Special handling for daveporter@oliver.agency
|
||
if (user.email === 'daveporter@oliver.agency') {
|
||
await sequelize.query(
|
||
`UPDATE users SET
|
||
password = :password,
|
||
preferences = COALESCE(preferences, '{}'::jsonb) || '{"role": "admin", "allowedAgents": null}'::jsonb
|
||
WHERE id = :id`,
|
||
{
|
||
replacements: { password: hashedPassword, id: user.id },
|
||
type: sequelize.QueryTypes.UPDATE,
|
||
}
|
||
);
|
||
} else {
|
||
// Regular users get no agents by default
|
||
await sequelize.query(
|
||
`UPDATE users SET
|
||
password = :password,
|
||
preferences = COALESCE(preferences, '{}'::jsonb) || '{"role": "user", "allowedAgents": []}'::jsonb
|
||
WHERE id = :id`,
|
||
{
|
||
replacements: { password: hashedPassword, id: user.id },
|
||
type: sequelize.QueryTypes.UPDATE,
|
||
}
|
||
);
|
||
}
|
||
}
|
||
|
||
// Make password column required after updating existing records
|
||
await queryInterface.changeColumn('users', 'password', {
|
||
type: sequelize.Sequelize.STRING,
|
||
allowNull: false,
|
||
});
|
||
|
||
console.log('✅ Authentication fields added successfully');
|
||
console.log('ℹ️ Existing users have been set with default password: changeMe123!');
|
||
console.log('ℹ️ daveporter@oliver.agency has been set as admin');
|
||
console.log('ℹ️ Other users have been set as regular users with no agent access');
|
||
},
|
||
|
||
async down() {
|
||
const queryInterface = sequelize.getQueryInterface();
|
||
|
||
await queryInterface.removeColumn('users', 'password');
|
||
await queryInterface.removeColumn('users', 'lastLoginAt');
|
||
|
||
console.log('✅ Authentication fields removed');
|
||
},
|
||
};
|
||
|
||
module.exports = migration; |