claude-flow-novice
Version:
Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.
317 lines (316 loc) • 11 kB
JavaScript
/**
* Authentication Middleware
*
* Comprehensive authentication and authorization middleware for Express.js
* with JWT token validation, role-based access control, and security features.
*/ import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { logger } from '../utils/logger.js';
import { StandardError, ErrorCode } from '../../lib/errors.js';
/**
* Authentication middleware that validates JWT tokens
*
* @param config Authentication configuration
* @returns Express middleware function
*/ export function authenticationMiddleware(config) {
return async (req, res, next)=>{
try {
// Check for authorization header
const authHeader = req.headers.authorization;
if (!authHeader) {
res.status(401).json({
error: 'Unauthorized',
message: 'No authorization header provided',
errorCode: 'AUTH_001'
});
return;
}
// Validate authorization header format
if (!authHeader.startsWith('Bearer ')) {
res.status(401).json({
error: 'Unauthorized',
message: 'Invalid authorization header format',
errorCode: 'AUTH_003'
});
return;
}
// Extract and sanitize token
const token = authHeader.slice(7).trim();
if (!token) {
res.status(401).json({
error: 'Unauthorized',
message: 'No token provided',
errorCode: 'AUTH_005'
});
return;
}
// Verify JWT token
let decoded;
try {
decoded = jwt.verify(token, config.jwtSecret);
} catch (error) {
// Handle specific JWT errors
if (error instanceof jwt.TokenExpiredError) {
res.status(401).json({
error: 'Unauthorized',
message: 'Token has expired',
errorCode: 'AUTH_004'
});
return;
} else if (error instanceof jwt.JsonWebTokenError) {
res.status(401).json({
error: 'Unauthorized',
message: 'Invalid or expired token',
errorCode: 'AUTH_002'
});
return;
} else {
logger.error('Unexpected JWT verification error:', error);
res.status(401).json({
error: 'Unauthorized',
message: 'Token verification failed',
errorCode: 'AUTH_006'
});
return;
}
}
// Attach user information to request
req.user = {
userId: decoded.userId,
email: decoded.email,
role: decoded.role,
iat: decoded.iat,
exp: decoded.exp
};
// Log successful authentication
logger.info(`User authenticated: ${decoded.userId} (${decoded.email})`, {
userId: decoded.userId,
role: decoded.role,
ip: req.ip,
userAgent: req.headers['user-agent']
});
next();
} catch (error) {
logger.error('Authentication middleware error:', error);
res.status(500).json({
error: 'Internal Server Error',
message: 'Authentication service unavailable',
errorCode: 'AUTH_500'
});
}
};
}
/**
* Authorization middleware for role-based access control
*
* @param requiredRoles Array of roles that are allowed to access the resource
* @returns Express middleware function
*/ export function authorizationMiddleware(requiredRoles) {
return (req, res, next)=>{
try {
// Check if user is authenticated
if (!req.user) {
res.status(401).json({
error: 'Unauthorized',
message: 'User not authenticated'
});
return;
}
// Check if user has required role
if (!requiredRoles.includes(req.user.role)) {
res.status(403).json({
error: 'Forbidden',
message: 'Insufficient permissions',
requiredRoles,
userRole: req.user.role
});
return;
}
// Log successful authorization
logger.info(`User authorized: ${req.user.userId} (${req.user.role})`, {
userId: req.user.userId,
role: req.user.role,
requiredRoles
});
next();
} catch (error) {
logger.error('Authorization middleware error:', error);
res.status(500).json({
error: 'Internal Server Error',
message: 'Authorization service unavailable'
});
}
};
}
/**
* Password utilities for secure password handling
*/ export class PasswordUtils {
/**
* Hash a password using bcrypt
*
* @param password Plain text password
* @param rounds Number of bcrypt rounds (default 12)
* @returns Hashed password
*/ static async hashPassword(password, rounds = 12) {
try {
return await bcrypt.hash(password, rounds);
} catch (error) {
logger.error('Password hashing error:', error);
throw new StandardError('Failed to hash password', ErrorCode.INTERNAL_ERROR);
}
}
/**
* Generate salt for password hashing
*
* @param rounds Number of bcrypt rounds (default 12)
* @returns Salt string
*/ static async generateSalt(rounds = 12) {
try {
return await bcrypt.genSalt(rounds);
} catch (error) {
logger.error('Salt generation error:', error);
throw new StandardError('Failed to generate salt', ErrorCode.INTERNAL_ERROR);
}
}
/**
* Compare a plain text password with a hash
*
* @param password Plain text password
* @param hash Hashed password
* @returns True if passwords match
*/ static async comparePassword(password, hash) {
try {
return await bcrypt.compare(password, hash);
} catch (error) {
logger.error('Password comparison error:', error);
throw new StandardError('Failed to compare passwords', ErrorCode.INTERNAL_ERROR);
}
}
/**
* Validate password strength
*
* @param password Password to validate
* @returns True if password meets strength requirements
*/ static validatePasswordStrength(password) {
// Minimum 8 characters, at least one uppercase, one lowercase, one number, and one special character
const minLength = 8;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
return password.length >= minLength && hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChar;
}
}
/**
* JWT utilities for token management
*/ export class JWTUtils {
/**
* Generate a JWT token
*
* @param payload Token payload
* @param secret JWT secret
* @param expiresIn Token expiration time
* @returns JWT token
*/ static generateToken(payload, secret, expiresIn = '24h') {
try {
return jwt.sign(payload, secret, {
expiresIn
});
} catch (error) {
logger.error('Token generation error:', error);
throw new StandardError('Failed to generate token', ErrorCode.INTERNAL_ERROR);
}
}
/**
* Verify a JWT token
*
* @param token JWT token
* @param secret JWT secret
* @returns Decoded payload
*/ static verifyToken(token, secret) {
try {
return jwt.verify(token, secret);
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
throw new StandardError('Token has expired', ErrorCode.TOKEN_EXPIRED);
} else if (error instanceof jwt.JsonWebTokenError) {
throw new StandardError('Invalid token', ErrorCode.INVALID_TOKEN);
} else {
throw new StandardError('Token verification failed', ErrorCode.INTERNAL_ERROR);
}
}
}
/**
* Decode a JWT token without verification (for debugging)
*
* @param token JWT token
* @returns Decoded payload
*/ static decodeToken(token) {
try {
return jwt.decode(token);
} catch (error) {
logger.error('Token decoding error:', error);
return null;
}
}
}
/**
* Rate limiting utilities for authentication endpoints
*/ export class AuthRateLimit {
static attempts = new Map();
/**
* Check if user has exceeded login attempts
*
* @param identifier User identifier (email, IP, etc.)
* @param maxAttempts Maximum allowed attempts
* @param windowMs Time window in milliseconds
* @returns True if attempts exceeded
*/ static hasExceededAttempts(identifier, maxAttempts = 5, windowMs = 15 * 60 * 1000 // 15 minutes
) {
const now = Date.now();
const attempts = this.attempts.get(identifier);
if (!attempts) {
this.attempts.set(identifier, {
count: 1,
lastAttempt: now
});
return false;
}
// Reset if window has expired
if (now - attempts.lastAttempt > windowMs) {
this.attempts.set(identifier, {
count: 1,
lastAttempt: now
});
return false;
}
// Increment attempts
attempts.count++;
attempts.lastAttempt = now;
// Check if exceeded
if (attempts.count > maxAttempts) {
logger.warn(`Login attempts exceeded for ${identifier}`, {
identifier,
attempts: attempts.count,
windowMs
});
return true;
}
return false;
}
/**
* Clear failed attempts for a user
*
* @param identifier User identifier
*/ static clearAttempts(identifier) {
this.attempts.delete(identifier);
}
}
export default {
authenticationMiddleware,
authorizationMiddleware,
PasswordUtils,
JWTUtils,
AuthRateLimit
};
//# sourceMappingURL=authentication.js.map