@codai/cbd
Version:
Codai Better Database - High-Performance Vector Memory System with HPKV-inspired architecture and MCP server
954 lines (827 loc) • 32.1 kB
JavaScript
/**
* 🛡️ CBD Security Gateway - Enterprise Authentication & Authorization
*
* Phase 4.2.3.1 Implementation
* Features: JWT Authentication, MFA, RBAC, OAuth2, Session Management
*
* Created: August 2, 2025
* Security Level: Enterprise Grade
*/
const express = require('express');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const bcrypt = require('bcrypt');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
// Initialize Express app for Security Gateway
const app = express();
const PORT = 4400; // Security Gateway Port
// Security Configuration
const SECURITY_CONFIG = {
jwt: {
secret: process.env.JWT_SECRET || crypto.randomBytes(64).toString('hex'),
refreshSecret: process.env.JWT_REFRESH_SECRET || crypto.randomBytes(64).toString('hex'),
accessTokenExpiry: '15m',
refreshTokenExpiry: '7d',
algorithm: 'HS256'
},
mfa: {
enabled: true,
issuer: 'CBD Universal Database',
algorithm: 'sha256',
digits: 6,
period: 30
},
session: {
maxSessions: 5,
sessionTimeout: 30 * 60 * 1000, // 30 minutes
extendOnActivity: true
},
security: {
saltRounds: 12,
maxLoginAttempts: 5,
lockoutDuration: 15 * 60 * 1000, // 15 minutes
passwordMinLength: 8,
requireSpecialChars: true
}
};
// In-memory stores (replace with database in production)
const users = new Map();
const sessions = new Map();
const refreshTokens = new Map();
const loginAttempts = new Map();
const auditLog = new Map();
// Security Error Class
class CBDSecurityError extends Error {
constructor(message, code, statusCode = 500) {
super(message);
this.name = 'CBDSecurityError';
this.code = code;
this.statusCode = statusCode;
}
}
// 🔐 Authentication Service
class CBDAuthenticationService {
constructor(config) {
this.config = config;
this.setupDefaultUsers();
}
setupDefaultUsers() {
// Create default admin user
const adminId = 'admin_001';
const adminPassword = this.hashPassword('Admin123!@#');
users.set(adminId, {
id: adminId,
username: 'admin',
email: 'admin@cbd.local',
passwordHash: adminPassword,
roles: ['admin'],
permissions: ['*'],
active: true,
mfaEnabled: false,
createdAt: new Date().toISOString(),
lastLogin: null,
loginCount: 0
});
// Create default developer user
const devId = 'dev_001';
const devPassword = this.hashPassword('Dev123!@#');
users.set(devId, {
id: devId,
username: 'developer',
email: 'dev@cbd.local',
passwordHash: devPassword,
roles: ['developer'],
permissions: ['cbd:read', 'cbd:write', 'schema:read', 'stats:read'],
active: true,
mfaEnabled: false,
createdAt: new Date().toISOString(),
lastLogin: null,
loginCount: 0
});
console.log('🔐 Default users created:');
console.log(' 👑 Admin: username=admin, password=Admin123!@#');
console.log(' 👨💻 Developer: username=developer, password=Dev123!@#');
}
hashPassword(password) {
return bcrypt.hashSync(password, this.config.security.saltRounds);
}
validatePassword(password, hash) {
return bcrypt.compareSync(password, hash);
}
generateAccessToken(user) {
const payload = {
sub: user.id,
username: user.username,
email: user.email,
roles: user.roles,
permissions: user.permissions,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + this.parseExpiry(this.config.jwt.accessTokenExpiry)
};
return jwt.sign(payload, this.config.jwt.secret, { algorithm: this.config.jwt.algorithm });
}
generateRefreshToken(user) {
const payload = {
sub: user.id,
type: 'refresh',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + this.parseExpiry(this.config.jwt.refreshTokenExpiry)
};
const token = jwt.sign(payload, this.config.jwt.refreshSecret, { algorithm: this.config.jwt.algorithm });
// Store refresh token
refreshTokens.set(token, {
userId: user.id,
createdAt: new Date(),
expiresAt: new Date(Date.now() + this.parseExpiry(this.config.jwt.refreshTokenExpiry) * 1000)
});
return token;
}
parseExpiry(expiry) {
if (typeof expiry === 'number') return expiry;
const match = expiry.match(/^(\d+)([smhd])$/);
if (!match) return 900; // Default 15 minutes
const value = parseInt(match[1]);
const unit = match[2];
switch (unit) {
case 's': return value;
case 'm': return value * 60;
case 'h': return value * 60 * 60;
case 'd': return value * 24 * 60 * 60;
default: return 900;
}
}
async authenticate(credentials) {
const { username, password, mfaToken } = credentials;
// Check for rate limiting
const attempts = loginAttempts.get(username) || { count: 0, lastAttempt: null };
if (attempts.count >= this.config.security.maxLoginAttempts) {
const timeSinceLastAttempt = Date.now() - attempts.lastAttempt;
if (timeSinceLastAttempt < this.config.security.lockoutDuration) {
await this.logSecurityEvent('AUTHENTICATION_BLOCKED', {
username,
reason: 'rate_limit_exceeded',
attemptsCount: attempts.count
});
throw new CBDSecurityError('Account temporarily locked due to too many failed attempts', 'ACCOUNT_LOCKED', 423);
} else {
// Reset attempts after lockout period
loginAttempts.delete(username);
}
}
// Find user
const user = Array.from(users.values()).find(u => u.username === username || u.email === username);
if (!user) {
this.recordFailedAttempt(username);
await this.logSecurityEvent('AUTHENTICATION_FAILURE', { username, reason: 'user_not_found' });
throw new CBDSecurityError('Invalid credentials', 'INVALID_CREDENTIALS', 401);
}
// Check if user is active
if (!user.active) {
await this.logSecurityEvent('AUTHENTICATION_FAILURE', { username, reason: 'user_inactive' });
throw new CBDSecurityError('User account is inactive', 'USER_INACTIVE', 401);
}
// Validate password
if (!this.validatePassword(password, user.passwordHash)) {
this.recordFailedAttempt(username);
await this.logSecurityEvent('AUTHENTICATION_FAILURE', { username, reason: 'invalid_password' });
throw new CBDSecurityError('Invalid credentials', 'INVALID_CREDENTIALS', 401);
}
// Check MFA if enabled
if (user.mfaEnabled && !mfaToken) {
await this.logSecurityEvent('AUTHENTICATION_MFA_REQUIRED', { userId: user.id, username });
return {
requiresMFA: true,
mfaChallenge: await this.generateMFAChallenge(user),
message: 'Multi-factor authentication required'
};
}
if (user.mfaEnabled && mfaToken && !await this.validateMFA(user, mfaToken)) {
this.recordFailedAttempt(username);
await this.logSecurityEvent('AUTHENTICATION_FAILURE', { username, reason: 'invalid_mfa' });
throw new CBDSecurityError('Invalid MFA token', 'MFA_INVALID', 401);
}
// Reset failed attempts on successful authentication
loginAttempts.delete(username);
// Update user login information
user.lastLogin = new Date().toISOString();
user.loginCount = (user.loginCount || 0) + 1;
// Generate tokens
const accessToken = this.generateAccessToken(user);
const refreshToken = this.generateRefreshToken(user);
// Create session
const session = this.createSession(user, accessToken);
await this.logSecurityEvent('AUTHENTICATION_SUCCESS', {
userId: user.id,
username: user.username,
sessionId: session.id
});
return {
success: true,
accessToken,
refreshToken,
expiresIn: this.parseExpiry(this.config.jwt.accessTokenExpiry),
user: this.sanitizeUser(user),
session: {
id: session.id,
expiresAt: session.expiresAt
}
};
}
recordFailedAttempt(username) {
const attempts = loginAttempts.get(username) || { count: 0, lastAttempt: null };
attempts.count++;
attempts.lastAttempt = Date.now();
loginAttempts.set(username, attempts);
}
createSession(user, accessToken) {
const sessionId = `session_${user.id}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const session = {
id: sessionId,
userId: user.id,
accessToken,
createdAt: new Date(),
expiresAt: new Date(Date.now() + this.config.session.sessionTimeout),
lastActivity: new Date(),
ipAddress: null, // Will be set by middleware
userAgent: null // Will be set by middleware
};
sessions.set(sessionId, session);
// Cleanup old sessions for user
this.cleanupUserSessions(user.id);
return session;
}
cleanupUserSessions(userId) {
const userSessions = Array.from(sessions.entries())
.filter(([_, session]) => session.userId === userId)
.sort((a, b) => b[1].createdAt - a[1].createdAt);
// Keep only the most recent sessions
if (userSessions.length > this.config.session.maxSessions) {
for (let i = this.config.session.maxSessions; i < userSessions.length; i++) {
sessions.delete(userSessions[i][0]);
}
}
}
async validateToken(token) {
try {
const decoded = jwt.verify(token, this.config.jwt.secret);
const user = users.get(decoded.sub);
if (!user || !user.active) {
throw new CBDSecurityError('User not active', 'USER_INACTIVE', 401);
}
// Find and validate session
const userSession = Array.from(sessions.values()).find(
session => session.userId === user.id && session.accessToken === token
);
if (!userSession) {
throw new CBDSecurityError('Session not found', 'SESSION_INVALID', 401);
}
// Check session expiry
if (new Date() > userSession.expiresAt) {
sessions.delete(userSession.id);
throw new CBDSecurityError('Session expired', 'SESSION_EXPIRED', 401);
}
// Extend session if configured
if (this.config.session.extendOnActivity) {
userSession.lastActivity = new Date();
userSession.expiresAt = new Date(Date.now() + this.config.session.sessionTimeout);
}
return { valid: true, user: decoded, session: userSession };
} catch (error) {
if (error instanceof CBDSecurityError) {
throw error;
}
await this.logSecurityEvent('TOKEN_VALIDATION_FAILURE', { error: error.message });
return { valid: false, error: error.message };
}
}
async refreshAccessToken(refreshToken) {
try {
// Validate refresh token
const decoded = jwt.verify(refreshToken, this.config.jwt.refreshSecret);
const tokenData = refreshTokens.get(refreshToken);
if (!tokenData || tokenData.userId !== decoded.sub) {
throw new CBDSecurityError('Invalid refresh token', 'REFRESH_TOKEN_INVALID', 401);
}
if (new Date() > tokenData.expiresAt) {
refreshTokens.delete(refreshToken);
throw new CBDSecurityError('Refresh token expired', 'REFRESH_TOKEN_EXPIRED', 401);
}
const user = users.get(decoded.sub);
if (!user || !user.active) {
throw new CBDSecurityError('User not active', 'USER_INACTIVE', 401);
}
// Generate new access token
const newAccessToken = this.generateAccessToken(user);
// Update session with new token
const userSession = Array.from(sessions.values()).find(s => s.userId === user.id);
if (userSession) {
userSession.accessToken = newAccessToken;
userSession.lastActivity = new Date();
}
await this.logSecurityEvent('TOKEN_REFRESH_SUCCESS', { userId: user.id });
return {
success: true,
accessToken: newAccessToken,
expiresIn: this.parseExpiry(this.config.jwt.accessTokenExpiry)
};
} catch (error) {
if (error instanceof CBDSecurityError) {
throw error;
}
await this.logSecurityEvent('TOKEN_REFRESH_FAILURE', { error: error.message });
throw new CBDSecurityError('Token refresh failed', 'REFRESH_FAILED', 401);
}
}
async generateMFAChallenge(user) {
// Simulate MFA challenge generation
const challenge = crypto.randomBytes(16).toString('hex');
return {
type: 'totp',
challenge,
qrCode: `otpauth://totp/${encodeURIComponent(this.config.mfa.issuer)}:${encodeURIComponent(user.email)}?secret=${challenge}&issuer=${encodeURIComponent(this.config.mfa.issuer)}`
};
}
async validateMFA(user, token) {
// Simulate MFA validation (implement with actual TOTP library in production)
return token === '123456'; // Simple validation for demo
}
sanitizeUser(user) {
const { passwordHash, ...sanitized } = user;
return sanitized;
}
async logSecurityEvent(eventType, details) {
const event = {
id: `sec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
timestamp: new Date().toISOString(),
eventType,
...details
};
auditLog.set(event.id, event);
console.log(`🔒 Security Event: ${eventType}`, details);
return event;
}
}
// 🛡️ Authorization Service
class CBDAuthorizationService {
constructor() {
this.permissions = new Map();
this.roles = new Map();
this.setupDefaultRoles();
}
setupDefaultRoles() {
// Admin role with full permissions
this.roles.set('admin', {
name: 'Administrator',
description: 'Full system access',
permissions: ['*'] // All permissions
});
// Database Admin role
this.roles.set('db_admin', {
name: 'Database Administrator',
description: 'Database management and user administration',
permissions: [
'cbd:*',
'schema:*',
'users:read',
'users:write',
'stats:read',
'system:health'
]
});
// Developer role
this.roles.set('developer', {
name: 'Developer',
description: 'Development and testing access',
permissions: [
'cbd:read',
'cbd:write',
'schema:read',
'stats:read',
'system:health'
]
});
// Analyst role
this.roles.set('analyst', {
name: 'Data Analyst',
description: 'Read-only access with analytics',
permissions: [
'cbd:read',
'stats:read',
'analytics:read',
'system:health'
]
});
// Read-only role
this.roles.set('reader', {
name: 'Read Only',
description: 'Read-only access to data',
permissions: [
'cbd:read',
'stats:read',
'system:health'
]
});
console.log('🛡️ Authorization roles configured:', Array.from(this.roles.keys()));
}
async authorize(user, resource, action) {
const permission = `${resource}:${action}`;
// Check if user has wildcard permission
if (user.permissions?.includes('*')) {
return {
authorized: true,
reason: 'wildcard_permission',
grantedBy: 'direct_permission'
};
}
// Check direct permission
if (user.permissions?.includes(permission)) {
return {
authorized: true,
reason: 'direct_permission',
grantedBy: 'user_permission'
};
}
// Check resource wildcard permission
const resourceWildcard = `${resource}:*`;
if (user.permissions?.includes(resourceWildcard)) {
return {
authorized: true,
reason: 'resource_wildcard',
grantedBy: 'user_permission'
};
}
// Check role-based permissions
for (const roleName of user.roles || []) {
const role = this.roles.get(roleName);
if (!role) continue;
if (role.permissions.includes('*') ||
role.permissions.includes(permission) ||
role.permissions.includes(resourceWildcard)) {
return {
authorized: true,
reason: `role_permission:${roleName}`,
grantedBy: `role:${roleName}`
};
}
}
// Log authorization failure
await this.logAuthorizationEvent('AUTHORIZATION_DENIED', {
userId: user.sub || user.id,
username: user.username,
permission,
userPermissions: user.permissions,
userRoles: user.roles
});
return {
authorized: false,
reason: 'insufficient_permissions',
required: permission
};
}
// Express middleware for route protection
requirePermission(resource, action) {
return async (req, res, next) => {
try {
const user = req.user;
if (!user) {
return res.status(401).json({
success: false,
error: 'Authentication required',
code: 'AUTH_REQUIRED'
});
}
const authResult = await this.authorize(user, resource, action);
if (!authResult.authorized) {
return res.status(403).json({
success: false,
error: 'Insufficient permissions',
code: 'INSUFFICIENT_PERMISSIONS',
required: `${resource}:${action}`,
reason: authResult.reason
});
}
// Log successful authorization
await this.logAuthorizationEvent('AUTHORIZATION_GRANTED', {
userId: user.sub || user.id,
username: user.username,
permission: `${resource}:${action}`,
grantedBy: authResult.grantedBy
});
req.authorizationContext = authResult;
next();
} catch (error) {
next(error);
}
};
}
// Check multiple permissions (all required)
requirePermissions(...permissions) {
return async (req, res, next) => {
try {
const user = req.user;
if (!user) {
return res.status(401).json({
success: false,
error: 'Authentication required',
code: 'AUTH_REQUIRED'
});
}
const authResults = [];
for (const permission of permissions) {
const [resource, action] = permission.split(':');
const result = await this.authorize(user, resource, action);
authResults.push(result);
if (!result.authorized) {
return res.status(403).json({
success: false,
error: 'Insufficient permissions',
code: 'INSUFFICIENT_PERMISSIONS',
required: permissions,
failed: permission
});
}
}
req.authorizationContext = { permissions: authResults };
next();
} catch (error) {
next(error);
}
};
}
// Check if user has any of the specified permissions
requireAnyPermission(...permissions) {
return async (req, res, next) => {
try {
const user = req.user;
if (!user) {
return res.status(401).json({
success: false,
error: 'Authentication required',
code: 'AUTH_REQUIRED'
});
}
for (const permission of permissions) {
const [resource, action] = permission.split(':');
const result = await this.authorize(user, resource, action);
if (result.authorized) {
req.authorizationContext = result;
return next();
}
}
return res.status(403).json({
success: false,
error: 'Insufficient permissions',
code: 'INSUFFICIENT_PERMISSIONS',
required: `Any of: ${permissions.join(', ')}`
});
} catch (error) {
next(error);
}
};
}
async logAuthorizationEvent(eventType, details) {
const event = {
id: `auth_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
timestamp: new Date().toISOString(),
eventType,
...details
};
auditLog.set(event.id, event);
return event;
}
}
// Initialize services
const authService = new CBDAuthenticationService(SECURITY_CONFIG);
const authzService = new CBDAuthorizationService();
// 🔓 Authentication Middleware
const authenticateToken = async (req, res, next) => {
try {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
success: false,
error: 'Access token required',
code: 'TOKEN_REQUIRED'
});
}
const validation = await authService.validateToken(token);
if (!validation.valid) {
return res.status(401).json({
success: false,
error: validation.error || 'Invalid token',
code: 'TOKEN_INVALID'
});
}
req.user = validation.user;
req.session = validation.session;
// Update session activity info
if (req.session) {
req.session.ipAddress = req.ip;
req.session.userAgent = req.get('User-Agent');
}
next();
} catch (error) {
console.error('Authentication middleware error:', error);
res.status(401).json({
success: false,
error: 'Authentication failed',
code: 'AUTH_FAILED'
});
}
};
// Express middleware
app.use(express.json());
app.use(cors({
origin: ['http://localhost:4001', 'http://localhost:4007', 'http://localhost:4100'],
credentials: true
}));
// Rate limiting
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // Limit each IP to 10 requests per windowMs
message: {
success: false,
error: 'Too many authentication attempts',
code: 'RATE_LIMIT_EXCEEDED'
}
});
// 🚀 API Routes
// Public endpoints
app.post('/auth/login', authLimiter, async (req, res) => {
try {
const { username, password, mfaToken } = req.body;
if (!username || !password) {
return res.status(400).json({
success: false,
error: 'Username and password required',
code: 'MISSING_CREDENTIALS'
});
}
const result = await authService.authenticate({ username, password, mfaToken });
res.json(result);
} catch (error) {
console.error('Login error:', error);
res.status(error.statusCode || 500).json({
success: false,
error: error.message,
code: error.code || 'LOGIN_FAILED'
});
}
});
app.post('/auth/refresh', async (req, res) => {
try {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(400).json({
success: false,
error: 'Refresh token required',
code: 'REFRESH_TOKEN_REQUIRED'
});
}
const result = await authService.refreshAccessToken(refreshToken);
res.json(result);
} catch (error) {
res.status(error.statusCode || 500).json({
success: false,
error: error.message,
code: error.code || 'REFRESH_FAILED'
});
}
});
// Protected endpoints
app.get('/auth/profile', authenticateToken, (req, res) => {
res.json({
success: true,
user: authService.sanitizeUser(users.get(req.user.sub)),
session: {
id: req.session.id,
createdAt: req.session.createdAt,
lastActivity: req.session.lastActivity,
expiresAt: req.session.expiresAt
}
});
});
app.post('/auth/logout', authenticateToken, async (req, res) => {
try {
// Remove session
if (req.session) {
sessions.delete(req.session.id);
}
// Remove refresh tokens for user
const userRefreshTokens = Array.from(refreshTokens.entries())
.filter(([_, data]) => data.userId === req.user.sub);
for (const [token, _] of userRefreshTokens) {
refreshTokens.delete(token);
}
await authService.logSecurityEvent('LOGOUT_SUCCESS', {
userId: req.user.sub,
username: req.user.username
});
res.json({
success: true,
message: 'Successfully logged out'
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Logout failed',
code: 'LOGOUT_FAILED'
});
}
});
// Admin endpoints
app.get('/auth/sessions',
authenticateToken,
authzService.requirePermission('system', 'admin'),
(req, res) => {
const sessionList = Array.from(sessions.values()).map(session => ({
id: session.id,
userId: session.userId,
username: users.get(session.userId)?.username,
createdAt: session.createdAt,
lastActivity: session.lastActivity,
expiresAt: session.expiresAt,
ipAddress: session.ipAddress,
userAgent: session.userAgent
}));
res.json({
success: true,
sessions: sessionList,
total: sessionList.length
});
}
);
app.get('/auth/audit-log',
authenticateToken,
authzService.requirePermission('system', 'admin'),
(req, res) => {
const logs = Array.from(auditLog.values())
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
.slice(0, 100); // Last 100 events
res.json({
success: true,
events: logs,
total: auditLog.size
});
}
);
// System endpoints
app.get('/health', (req, res) => {
res.json({
success: true,
service: 'CBD Security Gateway',
version: '4.2.3',
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
features: {
authentication: true,
authorization: true,
mfa: SECURITY_CONFIG.mfa.enabled,
sessionManagement: true,
auditLogging: true
}
});
});
app.get('/stats', authenticateToken, (req, res) => {
res.json({
success: true,
stats: {
users: users.size,
activeSessions: sessions.size,
refreshTokens: refreshTokens.size,
auditEvents: auditLog.size,
loginAttempts: loginAttempts.size,
roles: authzService.roles.size
}
});
});
// Error handling middleware
app.use((error, req, res, next) => {
console.error('Security Gateway Error:', error);
res.status(error.statusCode || 500).json({
success: false,
error: error.message || 'Internal server error',
code: error.code || 'INTERNAL_ERROR'
});
});
// Start server
app.listen(PORT, () => {
console.log(`🛡️ CBD Security Gateway running on port ${PORT}`);
console.log(`📍 Health check: http://localhost:${PORT}/health`);
console.log(`🔐 Login endpoint: http://localhost:${PORT}/auth/login`);
console.log(`📊 Stats endpoint: http://localhost:${PORT}/stats`);
console.log('');
console.log('🔑 Test Credentials:');
console.log(' 👑 Admin: username=admin, password=Admin123!@#');
console.log(' 👨💻 Developer: username=developer, password=Dev123!@#');
console.log('');
console.log('📖 API Usage Examples:');
console.log(' Login: POST /auth/login { "username": "admin", "password": "Admin123!@#" }');
console.log(' Profile: GET /auth/profile (requires Bearer token)');
console.log(' Refresh: POST /auth/refresh { "refreshToken": "..." }');
console.log(' Logout: POST /auth/logout (requires Bearer token)');
});
module.exports = app;