Security Authentication Issues.md - johnpeterman72/CursorRIPER.sigma GitHub Wiki
Security and Authentication Issues
Authentication System Problems
JWT Token Issues
Problem: JWT authentication failing or behaving inconsistently
Symptoms:
401 Unauthorized errors despite valid login
Token validation failures
"Invalid signature" errors
Users getting logged out unexpectedly
Solutions:
JWT Token Debugging and Validation
// jwt-debugger.js
const jwt = require('jsonwebtoken');
class JWTDebugger {
constructor(secret) {
this.secret = secret;
}
// Debug token without verification (inspect payload)
debugToken(token) {
try {
// Decode without verification to inspect payload
const decoded = jwt.decode(token, { complete: true });
console.log('=== JWT Token Debug ===');
console.log('Header:', decoded.header);
console.log('Payload:', decoded.payload);
// Check expiration
if (decoded.payload.exp) {
const expirationDate = new Date(decoded.payload.exp * 1000);
const now = new Date();
console.log('Expires:', expirationDate.toISOString());
console.log('Current time:', now.toISOString());
console.log('Is expired:', now > expirationDate);
console.log('Time until expiry:', Math.round((expirationDate - now) / 1000), 'seconds');
}
// Check issued at
if (decoded.payload.iat) {
const issuedAt = new Date(decoded.payload.iat * 1000);
console.log('Issued at:', issuedAt.toISOString());
}
return decoded;
} catch (error) {
console.error('Failed to decode token:', error.message);
return null;
}
}
// Verify token with detailed error reporting
verifyToken(token) {
try {
const decoded = jwt.verify(token, this.secret);
console.log('✓ Token verification successful');
return { valid: true, decoded, error: null };
} catch (error) {
console.error('✗ Token verification failed:', error.message);
let errorType = 'unknown';
let suggestion = '';
switch (error.name) {
case 'TokenExpiredError':
errorType = 'expired';
suggestion = 'Token has expired. User needs to login again or refresh token.';
break;
case 'JsonWebTokenError':
if (error.message.includes('invalid signature')) {
errorType = 'invalid_signature';
suggestion = 'Token signature is invalid. Check JWT secret consistency.';
} else if (error.message.includes('malformed')) {
errorType = 'malformed';
suggestion = 'Token format is invalid. Check token transmission.';
}
break;
case 'NotBeforeError':
errorType = 'not_active';
suggestion = 'Token is not active yet. Check nbf claim.';
break;
}
return {
valid: false,
decoded: null,
error: {
type: errorType,
message: error.message,
suggestion
}
};
}
}
// Generate a test token for debugging
generateTestToken(payload = {}, expiresIn = '1h') {
const defaultPayload = {
userId: 'test-user-123',
email: '[email protected]',
role: 'user',
...payload
};
return jwt.sign(defaultPayload, this.secret, { expiresIn });
}
// Test token lifecycle
testTokenLifecycle() {
console.log('=== JWT Token Lifecycle Test ===');
// Generate token
const token = this.generateTestToken({}, '10s'); // 10 second expiry for testing
console.log('Generated token:', token.substring(0, 50) + '...');
// Immediate verification
let result = this.verifyToken(token);
console.log('Immediate verification:', result.valid ? 'PASS' : 'FAIL');
// Test expiration
setTimeout(() => {
console.log('\nTesting after 11 seconds (should be expired):');
result = this.verifyToken(token);
console.log('Expiration test:', result.valid ? 'FAIL' : 'PASS');
}, 11000);
}
}
// Usage
const debugger = new JWTDebugger(process.env.JWT_SECRET);
// Debug an existing token
const problematicToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
debugger.debugToken(problematicToken);
debugger.verifyToken(problematicToken);
Enhanced JWT Middleware with Better Error Handling
// enhanced-jwt-middleware.js
const jwt = require('jsonwebtoken');
class EnhancedJWTMiddleware {
constructor(options = {}) {
this.secret = options.secret || process.env.JWT_SECRET;
this.algorithms = options.algorithms || ['HS256'];
this.issuer = options.issuer;
this.audience = options.audience;
this.clockTolerance = options.clockTolerance || 60; // 60 seconds
this.ignoreExpiration = options.ignoreExpiration || false;
if (!this.secret) {
throw new Error('JWT secret is required');
}
}
middleware() {
return async (req, res, next) => {
try {
const token = this.extractToken(req);
if (!token) {
return this.sendAuthError(res, 'missing_token', 'No authentication token provided');
}
const verification = await this.verifyTokenWithRetry(token);
if (!verification.valid) {
return this.sendAuthError(res, verification.error.type, verification.error.message);
}
// Attach user info to request
req.user = verification.decoded;
req.token = token;
// Check if token is close to expiry (within 5 minutes)
if (verification.decoded.exp) {
const expiryTime = verification.decoded.exp * 1000;
const fiveMinutes = 5 * 60 * 1000;
if (expiryTime - Date.now() < fiveMinutes) {
res.setHeader('X-Token-Refresh-Needed', 'true');
}
}
next();
} catch (error) {
console.error('JWT middleware error:', error);
this.sendAuthError(res, 'auth_error', 'Authentication failed');
}
};
}
extractToken(req) {
// Check Authorization header (Bearer token)
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
return authHeader.substring(7);
}
// Check query parameter (for WebSocket connections)
if (req.query.token) {
return req.query.token;
}
// Check cookies
if (req.cookies && req.cookies.access_token) {
return req.cookies.access_token;
}
return null;
}
async verifyTokenWithRetry(token, retries = 2) {
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const options = {
algorithms: this.algorithms,
clockTolerance: this.clockTolerance,
ignoreExpiration: this.ignoreExpiration
};
if (this.issuer) options.issuer = this.issuer;
if (this.audience) options.audience = this.audience;
const decoded = jwt.verify(token, this.secret, options);
// Additional validation
if (!decoded.userId && !decoded.sub) {
throw new Error('Token missing user identifier');
}
return { valid: true, decoded, error: null };
} catch (error) {
if (attempt === retries) {
return this.categorizeJWTError(error);
}
// Brief delay before retry
await new Promise(resolve => setTimeout(resolve, 100));
}
}
}
categorizeJWTError(error) {
let errorType = 'invalid_token';
let message = 'Invalid authentication token';
switch (error.name) {
case 'TokenExpiredError':
errorType = 'token_expired';
message = 'Authentication token has expired';
break;
case 'JsonWebTokenError':
if (error.message.includes('invalid signature')) {
errorType = 'invalid_signature';
message = 'Authentication token signature is invalid';
} else if (error.message.includes('malformed')) {
errorType = 'malformed_token';
message = 'Authentication token is malformed';
}
break;
case 'NotBeforeError':
errorType = 'token_not_active';
message = 'Authentication token is not yet active';
break;
}
return { valid: false, decoded: null, error: { type: errorType, message } };
}
sendAuthError(res, type, message) {
const statusCode = this.getStatusCodeForError(type);
res.status(statusCode).json({
error: {
code: type,
message,
timestamp: new Date().toISOString()
}
});
}
getStatusCodeForError(type) {
switch (type) {
case 'missing_token':
case 'malformed_token':
case 'invalid_signature':
case 'invalid_token':
return 401;
case 'token_expired':
return 401;
case 'token_not_active':
return 401;
default:
return 401;
}
}
// Utility method to generate tokens
generateToken(payload, options = {}) {
const defaultOptions = {
expiresIn: '24h',
issuer: this.issuer,
audience: this.audience
};
return jwt.sign(payload, this.secret, { ...defaultOptions, ...options });
}
// Utility method to refresh tokens
refreshToken(oldToken) {
try {
// Verify old token (ignoring expiration)
const decoded = jwt.verify(oldToken, this.secret, {
ignoreExpiration: true,
algorithms: this.algorithms
});
// Remove JWT specific claims
const { iat, exp, nbf, ...payload } = decoded;
// Generate new token
return this.generateToken(payload);
} catch (error) {
throw new Error('Cannot refresh invalid token');
}
}
}
// Usage
const jwtMiddleware = new EnhancedJWTMiddleware({
secret: process.env.JWT_SECRET,
issuer: 'cursoriper-framework',
audience: 'cursoriper-users',
clockTolerance: 30
});
module.exports = jwtMiddleware;
Session Management Issues
Problem: User sessions not persisting or behaving unexpectedly
Symptoms:
Users logged out randomly
Session data lost between requests
"Session not found" errors
Multiple concurrent sessions issues
Solutions:
Robust Session Management
// session-manager.js
const session = require('express-session');
const MongoStore = require('connect-mongo');
const RedisStore = require('connect-redis')(session);
const Redis = require('redis');
class SessionManager {
constructor(options = {}) {
this.options = {
secret: options.secret || process.env.SESSION_SECRET,
name: options.name || 'bmad.session',
maxAge: options.maxAge || 24 * 60 * 60 * 1000, // 24 hours
secure: options.secure !== undefined ? options.secure : process.env.NODE_ENV === 'production',
httpOnly: options.httpOnly !== undefined ? options.httpOnly : true,
sameSite: options.sameSite || (process.env.NODE_ENV === 'production' ? 'strict' : 'lax'),
rolling: options.rolling !== undefined ? options.rolling : true,
store: options.store || 'memory',
...options
};
this.store = this.createStore();
}
createStore() {
switch (this.options.store) {
case 'redis':
return this.createRedisStore();
case 'mongodb':
return this.createMongoStore();
case 'memory':
default:
console.warn('Using memory store for sessions - not recommended for production');
return undefined; // Express will use MemoryStore
}
}
createRedisStore() {
try {
const redisClient = Redis.createClient({
host: this.options.redis?.host || 'localhost',
port: this.options.redis?.port || 6379,
password: this.options.redis?.password,
db: this.options.redis?.db || 1
});
redisClient.on('error', (error) => {
console.error('Redis session store error:', error);
});
redisClient.on('connect', () => {
console.log('Redis session store connected');
});
return new RedisStore({
client: redisClient,
prefix: 'bmad:session:',
ttl: this.options.maxAge / 1000 // Redis expects TTL in seconds
});
} catch (error) {
console.error('Failed to create Redis session store:', error);
console.log('Falling back to memory store');
return undefined;
}
}
createMongoStore() {
try {
if (!this.options.mongodb?.uri) {
throw new Error('MongoDB URI is required for MongoDB session store');
}
return MongoStore.create({
mongoUrl: this.options.mongodb.uri,
dbName: this.options.mongodb.dbName || 'sessions',
collectionName: 'bmad_sessions',
ttl: this.options.maxAge / 1000, // MongoDB expects TTL in seconds
autoRemove: 'native',
touchAfter: 24 * 3600 // Update session once per 24 hours unless changed
});
} catch (error) {
console.error('Failed to create MongoDB session store:', error);
console.log('Falling back to memory store');
return undefined;
}
}
getMiddleware() {
const sessionConfig = {
secret: this.options.secret,
name: this.options.name,
resave: false,
saveUninitialized: false,
rolling: this.options.rolling,
store: this.store,
cookie: {
secure: this.options.secure,
httpOnly: this.options.httpOnly,
maxAge: this.options.maxAge,
sameSite: this.options.sameSite
}
};
// Validate configuration
if (!sessionConfig.secret) {
throw new Error('Session secret is required');
}
if (sessionConfig.secret === 'your-session-secret') {
console.warn('⚠️ Using default session secret - change this in production!');
}
return session(sessionConfig);
}
// Session debugging middleware
debugMiddleware() {
return (req, res, next) => {
console.log('Session Debug:', {
sessionID: req.sessionID,
session: req.session,
cookies: req.headers.cookie,
userAgent: req.headers['user-agent'],
ip: req.ip
});
next();
};
}
// Session validation middleware
validateSession() {
return (req, res, next) => {
if (!req.session) {
return res.status(500).json({
error: 'Session not available',
message: 'Session store is not working properly'
});
}
// Check for session hijacking
if (req.session.userAgent && req.session.userAgent !== req.headers['user-agent']) {
console.warn('Potential session hijacking detected:', {
sessionID: req.sessionID,
originalUA: req.session.userAgent,
currentUA: req.headers['user-agent'],
ip: req.ip
});
// Destroy potentially hijacked session
req.session.destroy((err) => {
if (err) console.error('Failed to destroy session:', err);
});
return res.status(401).json({
error: 'Session invalid',
message: 'Please login again'
});
}
// Store user agent on first request
if (!req.session.userAgent) {
req.session.userAgent = req.headers['user-agent'];
}
next();
};
}
// Utility methods
async getSessionCount() {
if (!this.store || !this.store.length) {
return null; // Not supported by store
}
return new Promise((resolve, reject) => {
this.store.length((err, count) => {
if (err) reject(err);
else resolve(count);
});
});
}
async getAllSessions() {
if (!this.store || !this.store.all) {
return null; // Not supported by store
}
return new Promise((resolve, reject) => {
this.store.all((err, sessions) => {
if (err) reject(err);
else resolve(sessions);
});
});
}
async destroySession(sessionId) {
if (!this.store || !this.store.destroy) {
return false;
}
return new Promise((resolve) => {
this.store.destroy(sessionId, (err) => {
if (err) {
console.error('Failed to destroy session:', err);
resolve(false);
} else {
resolve(true);
}
});
});
}
async clearAllSessions() {
if (!this.store || !this.store.clear) {
return false;
}
return new Promise((resolve) => {
this.store.clear((err) => {
if (err) {
console.error('Failed to clear sessions:', err);
resolve(false);
} else {
console.log('All sessions cleared');
resolve(true);
}
});
});
}
}
// Usage
const sessionManager = new SessionManager({
secret: process.env.SESSION_SECRET,
store: 'redis',
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD
},
secure: process.env.NODE_ENV === 'production',
maxAge: 24 * 60 * 60 * 1000 // 24 hours
});
module.exports = sessionManager;
Authorization and Permissions
Role-Based Access Control Issues
Problem: Users accessing resources they shouldn't have access to
Symptoms:
403 Forbidden errors for valid users
Users seeing data from other organizations
Role escalation vulnerabilities
Inconsistent permission checking
Solutions:
Comprehensive RBAC System
// rbac-manager.js
class RBACManager {
constructor(database) {
this.db = database;
this.permissionCache = new Map();
this.roleHierarchy = new Map();
this.setupRoleHierarchy();
}
setupRoleHierarchy() {
// Define role inheritance
this.roleHierarchy.set('admin', ['manager', 'user', 'viewer']);
this.roleHierarchy.set('manager', ['user', 'viewer']);
this.roleHierarchy.set('user', ['viewer']);
this.roleHierarchy.set('viewer', []);
}
async checkPermission(userId, permission, resource = null) {
try {
// Get user roles
const userRoles = await this.getUserRoles(userId);
if (userRoles.length === 0) {
console.warn(`No roles found for user ${userId}`);
return false;
}
// Check each role and inherited roles
for (const role of userRoles) {
const allRoles = this.getInheritedRoles(role.name);
for (const roleName of allRoles) {
const hasPermission = await this.roleHasPermission(roleName, permission, resource, userId);
if (hasPermission) {
return true;
}
}
}
console.log(`Permission denied: ${userId} does not have ${permission} on ${resource?.type}:${resource?.id}`);
return false;
} catch (error) {
console.error('Permission check error:', error);
return false; // Fail securely
}
}
async roleHasPermission(roleName, permission, resource, userId) {
// Check cached permissions
const cacheKey = `${roleName}:${permission}:${resource?.type || 'global'}:${resource?.id || 'all'}`;
if (this.permissionCache.has(cacheKey)) {
const cached = this.permissionCache.get(cacheKey);
if (cached.expires > Date.now()) {
return cached.value;
}
this.permissionCache.delete(cacheKey);
}
// Query database for permission
let hasPermission = false;
// Check direct role permissions
const directPermissions = await this.db.query(`
SELECT rp.* FROM role_permissions rp
JOIN roles r ON rp.role_id = r.id
WHERE r.name = ? AND rp.permission = ? AND rp.active = true
`, [roleName, permission]);
if (directPermissions.length > 0) {
// Check resource-specific permissions
if (resource) {
hasPermission = await this.checkResourcePermission(
directPermissions,
resource,
userId
);
} else {
hasPermission = true;
}
}
// Cache result for 5 minutes
this.permissionCache.set(cacheKey, {
value: hasPermission,
expires: Date.now() + 5 * 60 * 1000
});
return hasPermission;
}
async checkResourcePermission(permissions, resource, userId) {
for (const permission of permissions) {
// Check resource type match
if (permission.resource_type && permission.resource_type !== resource.type) {
continue;
}
// Check resource ID match (if specified)
if (permission.resource_id && permission.resource_id !== resource.id) {
continue;
}
// Check organization context
if (permission.organization_only) {
const userOrg = await this.getUserOrganization(userId);
const resourceOrg = await this.getResourceOrganization(resource);
if (userOrg !== resourceOrg) {
continue;
}
}
// Check custom conditions
if (permission.conditions) {
const conditionsMet = await this.evaluateConditions(
permission.conditions,
resource,
userId
);
if (!conditionsMet) {
continue;
}
}
return true; // Permission granted
}
return false; // No matching permission found
}
async evaluateConditions(conditions, resource, userId) {
try {
const parsedConditions = JSON.parse(conditions);
// Evaluate each condition
for (const condition of parsedConditions) {
switch (condition.type) {
case 'owner':
if (resource.created_by !== userId) return false;
break;
case 'team_member':
const isTeamMember = await this.isUserTeamMember(userId, resource.id);
if (!isTeamMember) return false;
break;
case 'organization_member':
const userOrg = await this.getUserOrganization(userId);
const resourceOrg = await this.getResourceOrganization(resource);
if (userOrg !== resourceOrg) return false;
break;
case 'custom':
const customResult = await this.evaluateCustomCondition(
condition.rule,
resource,
userId
);
if (!customResult) return false;
break;
}
}
return true; // All conditions met
} catch (error) {
console.error('Error evaluating conditions:', error);
return false; // Fail securely
}
}
getInheritedRoles(roleName) {
const inherited = [roleName];
const children = this.roleHierarchy.get(roleName) || [];
children.forEach(childRole => {
inherited.push(...this.getInheritedRoles(childRole));
});
return [...new Set(inherited)]; // Remove duplicates
}
async getUserRoles(userId) {
return await this.db.query(`
SELECT r.* FROM user_roles ur
JOIN roles r ON ur.role_id = r.id
WHERE ur.user_id = ? AND ur.active = true AND r.active = true
`, [userId]);
}
async getUserOrganization(userId) {
const [user] = await this.db.query(`
SELECT organization_id FROM users WHERE id = ?
`, [userId]);
return user?.organization_id;
}
async getResourceOrganization(resource) {
if (resource.organization_id) {
return resource.organization_id;
}
// Try to fetch from database if not provided
const [resourceData] = await this.db.query(`
SELECT organization_id FROM ${resource.type}s WHERE id = ?
`, [resource.id]);
return resourceData?.organization_id;
}
async isUserTeamMember(userId, resourceId) {
const [membership] = await this.db.query(`
SELECT 1 FROM team_members tm
JOIN teams t ON tm.team_id = t.id
WHERE tm.user_id = ? AND t.resource_id = ? AND tm.active = true
`, [userId, resourceId]);
return !!membership;
}
// Middleware for Express.js
requirePermission(permission, resourceExtractor = null) {
return async (req, res, next) => {
try {
if (!req.user) {
return res.status(401).json({
error: 'Authentication required',
message: 'Please login to access this resource'
});
}
let resource = null;
if (resourceExtractor) {
resource = await resourceExtractor(req);
}
const hasPermission = await this.checkPermission(
req.user.userId,
permission,
resource
);
if (!hasPermission) {
return res.status(403).json({
error: 'Permission denied',
message: 'You do not have permission to perform this action'
});
}
next();
} catch (error) {
console.error('Permission middleware error:', error);
res.status(500).json({
error: 'Authorization error',
message: 'Failed to check permissions'
});
}
};
}
// Utility method to clear permission cache
clearPermissionCache(pattern = null) {
if (pattern) {
for (const key of this.permissionCache.keys()) {
if (key.includes(pattern)) {
this.permissionCache.delete(key);
}
}
} else {
this.permissionCache.clear();
}
}
}
// Usage examples
const rbac = new RBACManager(database);
// Check permission programmatically
const canEdit = await rbac.checkPermission(userId, 'bmad.model.edit', {
type: 'business_model',
id: modelId
});
// Use as middleware
app.put('/api/business-models/:id',
jwtMiddleware.middleware(),
rbac.requirePermission('bmad.model.edit', async (req) => ({
type: 'business_model',
id: req.params.id
})),
updateBusinessModel
);
module.exports = RBACManager;
CORS and Security Headers
Cross-Origin Resource Sharing Issues
Problem: CORS errors preventing frontend from accessing API
Symptoms:
"Access to fetch at '...' has been blocked by CORS policy"
Preflight request failures
Missing CORS headers
Inconsistent CORS behavior
Solutions:
Comprehensive CORS Configuration
// cors-config.js
const cors = require('cors');
class CORSManager {
constructor(options = {}) {
this.options = {
// Default allowed origins
allowedOrigins: options.allowedOrigins || [
'http://localhost:3000',
'http://localhost:3001',
'https://app.cursoriper.com',
process.env.FRONTEND_URL
].filter(Boolean),
// Development settings
allowDevelopment: options.allowDevelopment !== undefined ?
options.allowDevelopment : process.env.NODE_ENV === 'development',
// Credential support
credentials: options.credentials !== undefined ?
options.credentials : true,
// Methods
methods: options.methods || ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
// Headers
allowedHeaders: options.allowedHeaders || [
'Origin',
'X-Requested-With',
'Content-Type',
'Accept',
'Authorization',
'Cache-Control',
'X-CSRF-Token'
],
// Exposed headers
exposedHeaders: options.exposedHeaders || [
'X-Total-Count',
'X-Page-Count',
'X-Current-Page',
'X-Token-Refresh-Needed'
],
// Preflight cache
maxAge: options.maxAge || 86400, // 24 hours
...options
};
}
getCORSMiddleware() {
return cors({
origin: this.originHandler.bind(this),
credentials: this.options.credentials,
methods: this.options.methods,
allowedHeaders: this.options.allowedHeaders,
exposedHeaders: this.options.exposedHeaders,
maxAge: this.options.maxAge,
optionsSuccessStatus: 200
});
}
originHandler(origin, callback) {
// Allow requests with no origin (mobile apps, etc.)
if (!origin) {
console.log('CORS: Allowing request with no origin');
return callback(null, true);
}
// Development mode - allow localhost and development domains
if (this.options.allowDevelopment) {
if (this.isDevelopmentOrigin(origin)) {
console.log(`CORS: Allowing development origin: ${origin}`);
return callback(null, true);
}
}
// Check allowed origins list
if (this.options.allowedOrigins.includes(origin)) {
console.log(`CORS: Allowing whitelisted origin: ${origin}`);
return callback(null, true);
}
// Check dynamic origin patterns
if (this.matchesOriginPattern(origin)) {
console.log(`CORS: Allowing pattern-matched origin: ${origin}`);
return callback(null, true);
}
// Log blocked origin for debugging
console.warn(`CORS: Blocking origin: ${origin}`);
callback(new Error(`Origin ${origin} not allowed by CORS policy`));
}
isDevelopmentOrigin(origin) {
const developmentPatterns = [
/^http:\/\/localhost:\d+$/,
/^http:\/\/127\.0\.0\.1:\d+$/,
/^http:\/\/0\.0\.0\.0:\d+$/,
/^http:\/\/.*\.local:\d+$/,
/^https:\/\/.*\.ngrok\.io$/,
/^https:\/\/.*\.vercel\.app$/,
/^https:\/\/.*\.netlify\.app$/
];
return developmentPatterns.some(pattern => pattern.test(origin));
}
matchesOriginPattern(origin) {
// Add custom origin pattern matching logic here
// For example, dynamic subdomains for multi-tenant apps
const dynamicPatterns = [
/^https:\/\/[\w-]+\.cursoriper\.com$/,
/^https:\/\/[\w-]+\.bmad\.app$/
];
return dynamicPatterns.some(pattern => pattern.test(origin));
}
// Debug middleware to log CORS information
debugMiddleware() {
return (req, res, next) => {
console.log('CORS Debug:', {
origin: req.headers.origin,
method: req.method,
headers: req.headers,
isPreflightRequest: req.method === 'OPTIONS'
});
next();
};
}
// Preflight handler for complex requests
preflightHandler() {
return (req, res, next) => {
if (req.method === 'OPTIONS') {
console.log('Handling CORS preflight request');
// Set CORS headers manually for better control
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Methods', this.options.methods.join(', '));
res.header('Access-Control-Allow-Headers', this.options.allowedHeaders.join(', '));
res.header('Access-Control-Max-Age', this.options.maxAge.toString());
return res.status(200).end();
}
next();
};
}
}
// Usage
const corsManager = new CORSManager({
allowedOrigins: [
'https://app.cursoriper.com',
'https://dashboard.cursoriper.com',
process.env.FRONTEND_URL
],
allowDevelopment: process.env.NODE_ENV === 'development',
credentials: true
});
// Apply CORS middleware
app.use(corsManager.getCORSMiddleware());
// Optional: Add debug middleware in development
if (process.env.NODE_ENV === 'development') {
app.use(corsManager.debugMiddleware());
}
module.exports = CORSManager;
Security Headers Middleware
// security-headers.js
const helmet = require('helmet');
class SecurityHeadersManager {
constructor(options = {}) {
this.options = {
// Content Security Policy
csp: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
scriptSrc: ["'self'", "https://cdn.jsdelivr.net"],
imgSrc: ["'self'", "data:", "https:"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
connectSrc: ["'self'", "https://api.cursoriper.com"],
frameSrc: ["'none'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
manifestSrc: ["'self'"],
workerSrc: ["'self'"]
},
reportOnly: process.env.NODE_ENV === 'development'
},
// HTTP Strict Transport Security
hsts: {
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true
},
// Other security options
noSniff: true,
frameguard: { action: 'deny' },
xssFilter: true,
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
...options
};
}
getSecurityMiddleware() {
return helmet({
contentSecurityPolicy: this.options.csp,
hsts: this.options.hsts,
noSniff: this.options.noSniff,
frameguard: this.options.frameguard,
xssFilter: this.options.xssFilter,
referrerPolicy: this.options.referrerPolicy,
// Additional security headers
crossOriginEmbedderPolicy: false, // Disable if causing issues
crossOriginOpenerPolicy: false, // Disable if causing issues
crossOriginResourcePolicy: false, // Disable if causing issues
// Hide X-Powered-By header
hidePoweredBy: true
});
}
// Custom security headers middleware
customSecurityHeaders() {
return (req, res, next) => {
// API-specific headers
res.setHeader('X-API-Version', '1.0');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
// Cache control for API responses
if (req.path.startsWith('/api/')) {
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
}
// Prevent MIME type sniffing
res.setHeader('X-Content-Type-Options', 'nosniff');
next();
};
}
// Rate limiting headers
rateLimitHeaders() {
return (req, res, next) => {
// Add rate limit information to headers
if (req.rateLimit) {
res.setHeader('X-RateLimit-Limit', req.rateLimit.limit);
res.setHeader('X-RateLimit-Remaining', req.rateLimit.remaining);
res.setHeader('X-RateLimit-Reset', req.rateLimit.reset);
}
next();
};
}
}
// Usage
const securityManager = new SecurityHeadersManager({
csp: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", process.env.API_URL].filter(Boolean)
}
}
});
// Apply security middleware
app.use(securityManager.getSecurityMiddleware());
app.use(securityManager.customSecurityHeaders());
app.use(securityManager.rateLimitHeaders());
module.exports = SecurityHeadersManager;
SSL/TLS Configuration Issues
HTTPS Certificate Problems
Problem: SSL/TLS certificates not working properly
Symptoms:
"Certificate has expired" errors
"Self-signed certificate" warnings
Mixed content warnings
SSL handshake failures
Solutions:
SSL Certificate Management
// ssl-manager.js
const https = require('https');
const fs = require('fs');
const path = require('path');
class SSLManager {
constructor(options = {}) {
this.options = {
certPath: options.certPath || './certs',
keyFile: options.keyFile || 'private.key',
certFile: options.certFile || 'certificate.crt',
caFile: options.caFile || 'ca_bundle.crt',
pfxFile: options.pfxFile,
passphrase: options.passphrase,
autoRenew: options.autoRenew || false,
...options
};
}
loadCertificates() {
try {
const certPath = path.resolve(this.options.certPath);
// Check if PFX file is provided (Windows/IIS style)
if (this.options.pfxFile) {
const pfxPath = path.join(certPath, this.options.pfxFile);
if (!fs.existsSync(pfxPath)) {
throw new Error(`PFX file not found: ${pfxPath}`);
}
return {
pfx: fs.readFileSync(pfxPath),
passphrase: this.options.passphrase
};
}
// Load separate key and certificate files
const keyPath = path.join(certPath, this.options.keyFile);
const certPath_file = path.join(certPath, this.options.certFile);
if (!fs.existsSync(keyPath)) {
throw new Error(`Private key file not found: ${keyPath}`);
}
if (!fs.existsSync(certPath_file)) {
throw new Error(`Certificate file not found: ${certPath_file}`);
}
const sslOptions = {
key: fs.readFileSync(keyPath),
cert: fs.readFileSync(certPath_file)
};
// Add CA bundle if available
const caPath = path.join(certPath, this.options.caFile);
if (fs.existsSync(caPath)) {
sslOptions.ca = fs.readFileSync(caPath);
}
// Add passphrase if provided
if (this.options.passphrase) {
sslOptions.passphrase = this.options.passphrase;
}
return sslOptions;
} catch (error) {
console.error('Failed to load SSL certificates:', error.message);
throw error;
}
}
validateCertificate(certPath) {
try {
const cert = fs.readFileSync(certPath, 'utf8');
const certificate = require('crypto').X509Certificate ?
new (require('crypto').X509Certificate)(cert) : null;
if (!certificate) {
console.warn('Certificate validation not supported in this Node.js version');
return { valid: true, warning: 'Validation not supported' };
}
const now = new Date();
const validFrom = new Date(certificate.validFrom);
const validTo = new Date(certificate.validTo);
const validation = {
valid: now >= validFrom && now <= validTo,
validFrom: validFrom.toISOString(),
validTo: validTo.toISOString(),
subject: certificate.subject,
issuer: certificate.issuer,
fingerprint: certificate.fingerprint,
serialNumber: certificate.serialNumber
};
// Check if certificate is expiring soon (within 30 days)
const thirtyDaysFromNow = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
if (validTo <= thirtyDaysFromNow) {
validation.warning = 'Certificate expires within 30 days';
}
// Check if certificate is self-signed
if (certificate.issuer === certificate.subject) {
validation.warning = 'Self-signed certificate detected';
}
return validation;
} catch (error) {
return {
valid: false,
error: error.message
};
}
}
createHTTPSServer(app) {
try {
const sslOptions = this.loadCertificates();
// Validate certificate
if (!this.options.pfxFile) {
const certPath = path.join(this.options.certPath, this.options.certFile);
const validation = this.validateCertificate(certPath);
if (!validation.valid) {
console.error('Invalid SSL certificate:', validation.error || 'Certificate validation failed');
if (process.env.NODE_ENV === 'production') {
throw new Error('Invalid SSL certificate in production');
}
}
if (validation.warning) {
console.warn('SSL Certificate Warning:', validation.warning);
}
console.log('SSL Certificate Info:', {
subject: validation.subject,
validFrom: validation.validFrom,
validTo: validation.validTo,
issuer: validation.issuer
});
}
// Create HTTPS server
const server = https.createServer(sslOptions, app);
// Handle SSL errors
server.on('tlsClientError', (err, tlsSocket) => {
console.error('TLS Client Error:', err.message);
});
server.on('secureConnection', (tlsSocket) => {
console.log('Secure connection established:', {
protocol: tlsSocket.getProtocol(),
cipher: tlsSocket.getCipher()
});
});
return server;
} catch (error) {
console.error('Failed to create HTTPS server:', error.message);
throw error;
}
}
// HTTP to HTTPS redirect middleware
httpsRedirect() {
return (req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
return res.redirect(`https://${req.header('host')}${req.url}`);
}
next();
};
}
// Generate self-signed certificate for development
generateSelfSignedCert() {
const forge = require('node-forge');
try {
// Generate key pair
const keys = forge.pki.rsa.generateKeyPair(2048);
// Create certificate
const cert = forge.pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = '01';
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
const attrs = [{
name: 'commonName',
value: 'localhost'
}, {
name: 'countryName',
value: 'US'
}, {
shortName: 'ST',
value: 'State'
}, {
name: 'localityName',
value: 'City'
}, {
name: 'organizationName',
value: 'CursorRIPER Development'
}];
cert.setSubject(attrs);
cert.setIssuer(attrs);
// Add extensions
cert.setExtensions([{
name: 'basicConstraints',
cA: true
}, {
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true
}, {
name: 'subjectAltName',
altNames: [{
type: 2, // DNS
value: 'localhost'
}, {
type: 7, // IP
ip: '127.0.0.1'
}]
}]);
// Sign certificate
cert.sign(keys.privateKey);
// Convert to PEM format
const certPem = forge.pki.certificateToPem(cert);
const keyPem = forge.pki.privateKeyToPem(keys.privateKey);
// Save to files
const certDir = path.resolve(this.options.certPath);
if (!fs.existsSync(certDir)) {
fs.mkdirSync(certDir, { recursive: true });
}
fs.writeFileSync(path.join(certDir, this.options.certFile), certPem);
fs.writeFileSync(path.join(certDir, this.options.keyFile), keyPem);
console.log('Self-signed certificate generated successfully');
console.log('Certificate saved to:', path.join(certDir, this.options.certFile));
console.log('Private key saved to:', path.join(certDir, this.options.keyFile));
return { cert: certPem, key: keyPem };
} catch (error) {
console.error('Failed to generate self-signed certificate:', error);
throw error;
}
}
}
// Usage
const sslManager = new SSLManager({
certPath: './certs',
keyFile: 'server.key',
certFile: 'server.crt'
});
// For production
try {
const httpsServer = sslManager.createHTTPSServer(app);
httpsServer.listen(443, () => {
console.log('HTTPS Server listening on port 443');
});
} catch (error) {
console.error('HTTPS setup failed:', error.message);
// Fallback to HTTP in development
if (process.env.NODE_ENV === 'development') {
app.listen(3000, () => {
console.log('HTTP Server listening on port 3000 (HTTPS failed)');
});
}
}
module.exports = SSLManager;
Last Updated: June 28, 2025
Framework Version: CursorRIPER.sigma v1.0+