ideas-generator/server/routes/users.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

198 lines
No EOL
4.9 KiB
JavaScript

const express = require('express');
const { User } = require('../models');
const { hybridAuthenticate } = require('../middleware/azureAuth');
const { requireAdmin } = require('../middleware/auth');
const router = express.Router();
// Get all users (admin only)
router.get('/', hybridAuthenticate, requireAdmin, async (req, res, next) => {
try {
const users = await User.findAll({
order: [['createdAt', 'DESC']],
attributes: { exclude: ['password'] } // Exclude sensitive data
});
res.json({
users: users.map(user => ({
id: user.id,
name: user.name,
email: user.email,
preferences: user.preferences,
isActive: user.isActive,
createdAt: user.createdAt,
updatedAt: user.updatedAt
})),
total: users.length
});
} catch (error) {
next(error);
}
});
// Create new user (admin only)
router.post('/', async (req, res, next) => {
try {
const { name, email, role = 'user' } = req.body;
if (!name || !email) {
return res.status(400).json({
error: 'Validation Error',
message: 'Name and email are required'
});
}
// Check if user already exists
const existingUser = await User.findOne({ where: { email } });
if (existingUser) {
return res.status(409).json({
error: 'User Exists',
message: 'A user with this email already exists'
});
}
const user = await User.create({
name,
email,
preferences: {
theme: 'light',
notifications: true,
role: role,
defaultAssistant: 'creator-bot-push-the-boundaries-of-technology'
},
isActive: true
});
res.status(201).json({
message: 'User created successfully',
user: {
id: user.id,
name: user.name,
email: user.email,
preferences: user.preferences,
isActive: user.isActive,
createdAt: user.createdAt
}
});
} catch (error) {
if (error.name === 'SequelizeValidationError') {
return res.status(400).json({
error: 'Validation Error',
message: error.errors.map(e => e.message).join(', ')
});
}
next(error);
}
});
// Update user (admin only)
router.put('/:userId', async (req, res, next) => {
try {
const { userId } = req.params;
const { name, email, isActive, preferences } = req.body;
const user = await User.findByPk(userId);
if (!user) {
return res.status(404).json({
error: 'User Not Found',
message: 'Specified user not found'
});
}
const updateData = {};
if (name !== undefined) updateData.name = name;
if (email !== undefined) updateData.email = email;
if (isActive !== undefined) updateData.isActive = isActive;
// Handle preferences update (role and allowedAgents)
if (preferences !== undefined) {
updateData.preferences = {
...user.preferences,
...preferences
};
}
await user.update(updateData);
res.json({
message: 'User updated successfully',
user: {
id: user.id,
name: user.name,
email: user.email,
preferences: user.preferences,
isActive: user.isActive,
updatedAt: user.updatedAt
}
});
} catch (error) {
if (error.name === 'SequelizeValidationError') {
return res.status(400).json({
error: 'Validation Error',
message: error.errors.map(e => e.message).join(', ')
});
}
next(error);
}
});
// Toggle user status (admin only)
router.patch('/:userId/toggle-status', async (req, res, next) => {
try {
const { userId } = req.params;
const user = await User.findByPk(userId);
if (!user) {
return res.status(404).json({
error: 'User Not Found',
message: 'Specified user not found'
});
}
await user.update({ isActive: !user.isActive });
res.json({
message: `User ${user.isActive ? 'enabled' : 'disabled'} successfully`,
user: {
id: user.id,
name: user.name,
email: user.email,
isActive: user.isActive
}
});
} catch (error) {
next(error);
}
});
// Delete user (admin only - soft delete by setting isActive to false)
router.delete('/:userId', async (req, res, next) => {
try {
const { userId } = req.params;
const user = await User.findByPk(userId);
if (!user) {
return res.status(404).json({
error: 'User Not Found',
message: 'Specified user not found'
});
}
// Soft delete by setting isActive to false
await user.update({ isActive: false });
res.json({
message: 'User deleted successfully',
userId: user.id
});
} catch (error) {
next(error);
}
});
module.exports = router;