ideas-generator/server/migrations/add-authentication-fields.js
DJP 013f57fe60 Implement hybrid Azure AD SSO + Password authentication system
 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>
2025-09-09 16:14:02 -04:00

79 lines
No EOL
2.8 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;