Fix jwt-token
This commit is contained in:
284
server/middleware/multi-tenant-auth.js
Normal file
284
server/middleware/multi-tenant-auth.js
Normal file
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* Multi-Tenant Authentication Middleware
|
||||
* Routes authentication requests to appropriate providers
|
||||
*/
|
||||
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { User, Tenant } = require('../models');
|
||||
const { AuthConfig, AuthProviders } = require('../config/auth-providers');
|
||||
const SAMLAuth = require('./saml-auth');
|
||||
const OAuthAuth = require('./oauth-auth');
|
||||
const LDAPAuth = require('./ldap-auth');
|
||||
|
||||
class MultiTenantAuth {
|
||||
constructor() {
|
||||
this.authConfig = new AuthConfig();
|
||||
this.providers = new Map();
|
||||
|
||||
// Initialize authentication providers
|
||||
this.initializeProviders();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all authentication providers
|
||||
*/
|
||||
initializeProviders() {
|
||||
this.providers.set(AuthProviders.SAML, new SAMLAuth());
|
||||
this.providers.set(AuthProviders.OAUTH, new OAuthAuth());
|
||||
this.providers.set(AuthProviders.LDAP, new LDAPAuth());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine tenant from request
|
||||
* Can be from subdomain, header, or JWT
|
||||
*/
|
||||
async determineTenant(req) {
|
||||
// Method 1: Subdomain (tenant.yourapp.com)
|
||||
const subdomain = req.hostname.split('.')[0];
|
||||
if (subdomain && subdomain !== 'www' && subdomain !== 'api') {
|
||||
return subdomain;
|
||||
}
|
||||
|
||||
// Method 2: Custom header
|
||||
const tenantHeader = req.headers['x-tenant-id'];
|
||||
if (tenantHeader) {
|
||||
return tenantHeader;
|
||||
}
|
||||
|
||||
// Method 3: From JWT token (for existing sessions)
|
||||
const token = req.headers.authorization?.split(' ')[1];
|
||||
if (token) {
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
return decoded.tenantId;
|
||||
} catch (error) {
|
||||
// Token invalid, continue with other methods
|
||||
}
|
||||
}
|
||||
|
||||
// Method 4: Query parameter (for redirects)
|
||||
if (req.query.tenant) {
|
||||
return req.query.tenant;
|
||||
}
|
||||
|
||||
// Default to 'default' tenant for backward compatibility
|
||||
return 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authentication configuration for tenant
|
||||
*/
|
||||
async getTenantAuthConfig(tenantId) {
|
||||
const tenant = await Tenant.findOne({ where: { slug: tenantId } });
|
||||
if (!tenant) {
|
||||
// Return default local auth for unknown tenants
|
||||
return {
|
||||
type: AuthProviders.LOCAL,
|
||||
enabled: true,
|
||||
config: {},
|
||||
userMapping: this.authConfig.getDefaultUserMapping(),
|
||||
roleMapping: this.authConfig.getDefaultRoleMapping()
|
||||
};
|
||||
}
|
||||
|
||||
return this.authConfig.getProvider(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate user based on tenant configuration
|
||||
*/
|
||||
async authenticate(req, res, next) {
|
||||
try {
|
||||
const tenantId = await this.determineTenant(req);
|
||||
const authConfig = await this.getTenantAuthConfig(tenantId);
|
||||
|
||||
// Attach tenant info to request
|
||||
req.tenant = { id: tenantId, authConfig };
|
||||
|
||||
// Route to appropriate authentication provider
|
||||
switch (authConfig.type) {
|
||||
case AuthProviders.LOCAL:
|
||||
return this.authenticateLocal(req, res, next);
|
||||
|
||||
case AuthProviders.SAML:
|
||||
return this.authenticateSAML(req, res, next);
|
||||
|
||||
case AuthProviders.OAUTH:
|
||||
return this.authenticateOAuth(req, res, next);
|
||||
|
||||
case AuthProviders.LDAP:
|
||||
return this.authenticateLDAP(req, res, next);
|
||||
|
||||
default:
|
||||
return this.authenticateLocal(req, res, next);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Multi-tenant auth error:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'Authentication service error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Local JWT authentication (existing system)
|
||||
*/
|
||||
async authenticateLocal(req, res, next) {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Access token required'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
const user = await User.findByPk(decoded.userId, {
|
||||
attributes: ['id', 'username', 'email', 'role', 'is_active', 'tenant_id']
|
||||
});
|
||||
|
||||
if (!user || !user.is_active) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid or inactive user'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify user belongs to tenant
|
||||
if (user.tenant_id !== req.tenant.id) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: 'User not authorized for this tenant'
|
||||
});
|
||||
}
|
||||
|
||||
req.user = user;
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('JWT verification error:', error);
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: 'Invalid or expired token'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SAML authentication for Active Directory
|
||||
*/
|
||||
async authenticateSAML(req, res, next) {
|
||||
const provider = this.providers.get(AuthProviders.SAML);
|
||||
return provider.authenticate(req, res, next);
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth 2.0/OpenID Connect authentication
|
||||
*/
|
||||
async authenticateOAuth(req, res, next) {
|
||||
const provider = this.providers.get(AuthProviders.OAUTH);
|
||||
return provider.authenticate(req, res, next);
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP authentication
|
||||
*/
|
||||
async authenticateLDAP(req, res, next) {
|
||||
const provider = this.providers.get(AuthProviders.LDAP);
|
||||
return provider.authenticate(req, res, next);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update user from external provider
|
||||
*/
|
||||
async createOrUpdateExternalUser(tenantId, externalUser, authConfig) {
|
||||
const userMapping = authConfig.userMapping;
|
||||
const roleMapping = authConfig.roleMapping;
|
||||
|
||||
// Map external user attributes to internal user fields
|
||||
const userData = {
|
||||
username: this.getAttributeValue(externalUser, userMapping.username),
|
||||
email: this.getAttributeValue(externalUser, userMapping.email),
|
||||
first_name: this.getAttributeValue(externalUser, userMapping.firstName),
|
||||
last_name: this.getAttributeValue(externalUser, userMapping.lastName),
|
||||
phone_number: this.getAttributeValue(externalUser, userMapping.phoneNumber),
|
||||
tenant_id: tenantId,
|
||||
is_active: true,
|
||||
external_provider: authConfig.type,
|
||||
external_id: externalUser.id || externalUser.sub || externalUser.nameID,
|
||||
last_login: new Date()
|
||||
};
|
||||
|
||||
// Map roles from external provider
|
||||
const externalRoles = externalUser.roles || externalUser.groups || [];
|
||||
userData.role = this.mapExternalRole(externalRoles, roleMapping);
|
||||
|
||||
// Find or create user
|
||||
const [user, created] = await User.findOrCreate({
|
||||
where: {
|
||||
external_id: userData.external_id,
|
||||
tenant_id: tenantId
|
||||
},
|
||||
defaults: userData
|
||||
});
|
||||
|
||||
// Update existing user
|
||||
if (!created) {
|
||||
await user.update(userData);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribute value from external user object
|
||||
*/
|
||||
getAttributeValue(externalUser, attributeNames) {
|
||||
if (!Array.isArray(attributeNames)) {
|
||||
attributeNames = [attributeNames];
|
||||
}
|
||||
|
||||
for (const attrName of attributeNames) {
|
||||
if (externalUser[attrName]) {
|
||||
return externalUser[attrName];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map external roles to internal roles
|
||||
*/
|
||||
mapExternalRole(externalRoles, roleMapping) {
|
||||
for (const externalRole of externalRoles) {
|
||||
if (roleMapping[externalRole]) {
|
||||
return roleMapping[externalRole];
|
||||
}
|
||||
}
|
||||
|
||||
return roleMapping.default || 'viewer';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JWT token for authenticated user
|
||||
*/
|
||||
generateJWTToken(user, tenantId) {
|
||||
return jwt.sign(
|
||||
{
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
tenantId: tenantId,
|
||||
provider: user.external_provider || 'local'
|
||||
},
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MultiTenantAuth;
|
||||
Reference in New Issue
Block a user