Fix jwt-token
This commit is contained in:
54
server/migrations/20250913-add-auth-session-config.js
Normal file
54
server/migrations/20250913-add-auth-session-config.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Migration: Add session and role mapping configuration to tenants
|
||||
* Adds session_timeout, require_mfa, allow_concurrent_sessions, and role_mappings fields
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
// Add session configuration fields
|
||||
await queryInterface.addColumn('tenants', 'session_timeout', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 480, // 8 hours in minutes
|
||||
allowNull: false,
|
||||
comment: 'Session timeout in minutes'
|
||||
});
|
||||
|
||||
await queryInterface.addColumn('tenants', 'require_mfa', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false,
|
||||
comment: 'Whether multi-factor authentication is required'
|
||||
});
|
||||
|
||||
await queryInterface.addColumn('tenants', 'allow_concurrent_sessions', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: true,
|
||||
allowNull: false,
|
||||
comment: 'Whether users can have multiple concurrent sessions'
|
||||
});
|
||||
|
||||
await queryInterface.addColumn('tenants', 'role_mappings', {
|
||||
type: Sequelize.JSONB,
|
||||
allowNull: true,
|
||||
comment: 'Mapping of external groups/attributes to system roles'
|
||||
});
|
||||
|
||||
// Update auth_provider enum to include 'ad'
|
||||
await queryInterface.sequelize.query(`
|
||||
ALTER TYPE "enum_tenants_auth_provider" ADD VALUE 'ad';
|
||||
`);
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
// Remove the added columns
|
||||
await queryInterface.removeColumn('tenants', 'session_timeout');
|
||||
await queryInterface.removeColumn('tenants', 'require_mfa');
|
||||
await queryInterface.removeColumn('tenants', 'allow_concurrent_sessions');
|
||||
await queryInterface.removeColumn('tenants', 'role_mappings');
|
||||
|
||||
// Note: Removing enum values is complex in PostgreSQL and typically not done in production
|
||||
// The 'ad' value will remain in the enum even after this rollback
|
||||
}
|
||||
};
|
||||
@@ -44,7 +44,7 @@ module.exports = (sequelize) => {
|
||||
|
||||
// Authentication Configuration
|
||||
auth_provider: {
|
||||
type: DataTypes.ENUM('local', 'saml', 'oauth', 'ldap', 'custom_sso'),
|
||||
type: DataTypes.ENUM('local', 'saml', 'oauth', 'ldap', 'ad', 'custom_sso'),
|
||||
defaultValue: 'local',
|
||||
comment: 'Primary authentication provider'
|
||||
},
|
||||
@@ -137,6 +137,32 @@ module.exports = (sequelize) => {
|
||||
comment: 'Additional tenant metadata'
|
||||
},
|
||||
|
||||
// Session Configuration
|
||||
session_timeout: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 480, // 8 hours in minutes
|
||||
validate: {
|
||||
min: 15, // Minimum 15 minutes
|
||||
max: 1440 // Maximum 24 hours
|
||||
},
|
||||
comment: 'Session timeout in minutes'
|
||||
},
|
||||
require_mfa: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
comment: 'Whether multi-factor authentication is required'
|
||||
},
|
||||
allow_concurrent_sessions: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: true,
|
||||
comment: 'Whether users can have multiple concurrent sessions'
|
||||
},
|
||||
role_mappings: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
comment: 'Mapping of external groups/attributes to system roles'
|
||||
},
|
||||
|
||||
created_at: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW
|
||||
|
||||
@@ -450,4 +450,266 @@ router.put('/users/:userId/status', authenticateToken, requirePermissions(['user
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /tenant/auth
|
||||
* Get authentication configuration (auth admins or higher)
|
||||
*/
|
||||
router.get('/auth', authenticateToken, requirePermissions(['auth.view']), async (req, res) => {
|
||||
try {
|
||||
// Determine tenant from request
|
||||
const tenantId = await multiAuth.determineTenant(req);
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Unable to determine tenant'
|
||||
});
|
||||
}
|
||||
|
||||
const tenant = await Tenant.findOne({ where: { slug: tenantId } });
|
||||
if (!tenant) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Tenant not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Return auth configuration (excluding sensitive credentials)
|
||||
const authConfig = {
|
||||
auth_provider: tenant.auth_provider,
|
||||
auth_config: tenant.auth_config ? {
|
||||
...tenant.auth_config,
|
||||
// Hide sensitive fields
|
||||
client_secret: tenant.auth_config.client_secret ? '***HIDDEN***' : undefined,
|
||||
bind_password: tenant.auth_config.bind_password ? '***HIDDEN***' : undefined,
|
||||
service_password: tenant.auth_config.service_password ? '***HIDDEN***' : undefined,
|
||||
certificate: tenant.auth_config.certificate ? '***HIDDEN***' : undefined
|
||||
} : {},
|
||||
session_timeout: tenant.session_timeout || 480,
|
||||
require_mfa: tenant.require_mfa || false,
|
||||
allow_concurrent_sessions: tenant.allow_concurrent_sessions !== false
|
||||
};
|
||||
|
||||
console.log(`✅ Auth config retrieved for tenant "${tenantId}" by "${req.user.username}"`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: authConfig
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error retrieving auth config:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to retrieve authentication configuration'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PUT /tenant/auth
|
||||
* Update authentication configuration (auth admins or higher)
|
||||
*/
|
||||
router.put('/auth', authenticateToken, requirePermissions(['auth.edit']), async (req, res) => {
|
||||
try {
|
||||
// Determine tenant from request
|
||||
const tenantId = await multiAuth.determineTenant(req);
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Unable to determine tenant'
|
||||
});
|
||||
}
|
||||
|
||||
const tenant = await Tenant.findOne({ where: { slug: tenantId } });
|
||||
if (!tenant) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Tenant not found'
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
auth_provider,
|
||||
auth_config,
|
||||
session_timeout,
|
||||
require_mfa,
|
||||
allow_concurrent_sessions,
|
||||
role_mappings
|
||||
} = req.body;
|
||||
|
||||
// Validate auth provider
|
||||
const validProviders = ['local', 'saml', 'oauth', 'ldap', 'ad'];
|
||||
if (auth_provider && !validProviders.includes(auth_provider)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid authentication provider'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate session timeout
|
||||
if (session_timeout && (session_timeout < 15 || session_timeout > 1440)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Session timeout must be between 15 and 1440 minutes'
|
||||
});
|
||||
}
|
||||
|
||||
// Prepare update data
|
||||
const updateData = {};
|
||||
|
||||
if (auth_provider) updateData.auth_provider = auth_provider;
|
||||
if (auth_config) {
|
||||
// Merge with existing config, preserving hidden sensitive fields
|
||||
const existingConfig = tenant.auth_config || {};
|
||||
updateData.auth_config = {
|
||||
...existingConfig,
|
||||
...auth_config,
|
||||
// Restore hidden fields if they weren't changed
|
||||
client_secret: auth_config.client_secret === '***HIDDEN***' ? existingConfig.client_secret : auth_config.client_secret,
|
||||
bind_password: auth_config.bind_password === '***HIDDEN***' ? existingConfig.bind_password : auth_config.bind_password,
|
||||
service_password: auth_config.service_password === '***HIDDEN***' ? existingConfig.service_password : auth_config.service_password,
|
||||
certificate: auth_config.certificate === '***HIDDEN***' ? existingConfig.certificate : auth_config.certificate
|
||||
};
|
||||
}
|
||||
|
||||
if (session_timeout !== undefined) updateData.session_timeout = session_timeout;
|
||||
if (require_mfa !== undefined) updateData.require_mfa = require_mfa;
|
||||
if (allow_concurrent_sessions !== undefined) updateData.allow_concurrent_sessions = allow_concurrent_sessions;
|
||||
if (role_mappings) updateData.role_mappings = role_mappings;
|
||||
|
||||
// Update tenant
|
||||
await tenant.update(updateData);
|
||||
|
||||
console.log(`✅ Auth config updated for tenant "${tenantId}" by admin "${req.user.username}"`);
|
||||
|
||||
// Return updated config (with hidden sensitive fields)
|
||||
const updatedConfig = {
|
||||
auth_provider: tenant.auth_provider,
|
||||
auth_config: tenant.auth_config ? {
|
||||
...tenant.auth_config,
|
||||
client_secret: tenant.auth_config.client_secret ? '***HIDDEN***' : undefined,
|
||||
bind_password: tenant.auth_config.bind_password ? '***HIDDEN***' : undefined,
|
||||
service_password: tenant.auth_config.service_password ? '***HIDDEN***' : undefined,
|
||||
certificate: tenant.auth_config.certificate ? '***HIDDEN***' : undefined
|
||||
} : {},
|
||||
session_timeout: tenant.session_timeout,
|
||||
require_mfa: tenant.require_mfa,
|
||||
allow_concurrent_sessions: tenant.allow_concurrent_sessions,
|
||||
role_mappings: tenant.role_mappings
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Authentication configuration updated successfully',
|
||||
data: updatedConfig
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error updating auth config:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to update authentication configuration'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /tenant/auth/test
|
||||
* Test authentication configuration (auth admins or higher)
|
||||
*/
|
||||
router.post('/auth/test', authenticateToken, requirePermissions(['auth.edit']), async (req, res) => {
|
||||
try {
|
||||
// Determine tenant from request
|
||||
const tenantId = await multiAuth.determineTenant(req);
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Unable to determine tenant'
|
||||
});
|
||||
}
|
||||
|
||||
const tenant = await Tenant.findOne({ where: { slug: tenantId } });
|
||||
if (!tenant) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Tenant not found'
|
||||
});
|
||||
}
|
||||
|
||||
const { test_username, test_password } = req.body;
|
||||
|
||||
// Simulate authentication test based on provider
|
||||
const authProvider = tenant.auth_provider;
|
||||
let testResult = {
|
||||
success: false,
|
||||
message: 'Authentication test not implemented for this provider',
|
||||
details: {}
|
||||
};
|
||||
|
||||
switch (authProvider) {
|
||||
case 'local':
|
||||
testResult = {
|
||||
success: true,
|
||||
message: 'Local authentication is always available',
|
||||
details: { provider: 'local' }
|
||||
};
|
||||
break;
|
||||
|
||||
case 'saml':
|
||||
// In real implementation, this would test SAML SSO endpoint
|
||||
testResult = {
|
||||
success: true,
|
||||
message: 'SAML configuration appears valid (test connection would be performed in production)',
|
||||
details: {
|
||||
provider: 'saml',
|
||||
sso_url: tenant.auth_config?.sso_url,
|
||||
entity_id: tenant.auth_config?.entity_id
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
case 'oauth':
|
||||
// In real implementation, this would test OAuth endpoint
|
||||
testResult = {
|
||||
success: true,
|
||||
message: 'OAuth configuration appears valid (test connection would be performed in production)',
|
||||
details: {
|
||||
provider: 'oauth',
|
||||
oauth_provider: tenant.auth_config?.oauth_provider,
|
||||
client_id: tenant.auth_config?.client_id
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
case 'ldap':
|
||||
case 'ad':
|
||||
// In real implementation, this would test LDAP/AD connection
|
||||
testResult = {
|
||||
success: true,
|
||||
message: `${authProvider.toUpperCase()} configuration appears valid (test connection would be performed in production)`,
|
||||
details: {
|
||||
provider: authProvider,
|
||||
server: tenant.auth_config?.ldap_server || tenant.auth_config?.domain_controller
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(`✅ Auth test performed for tenant "${tenantId}" by admin "${req.user.username}"`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Authentication test completed',
|
||||
data: testResult
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error testing auth config:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to test authentication configuration'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user