Backup: 2025-06-07 19:31 - Dockerhub prep

[Restore from backup: vip-coordinator-backup-2025-06-07-19-31-dockerhub-prep]
This commit is contained in:
2025-06-07 19:31:00 +02:00
parent ae3702c3b1
commit 8fb00ec041
6 changed files with 224 additions and 42 deletions

View File

@@ -8,7 +8,7 @@ DOMAIN=bsa.madeamess.online
VITE_API_URL=https://api.bsa.madeamess.online
# Authentication Configuration (Secure production keys)
JWT_SECRET=VipCoord2025JwtSecretKey8f9a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z
# JWT_SECRET - No longer needed! Keys are auto-generated and rotated every 24 hours
SESSION_SECRET=VipCoord2025SessionSecret9g8f7e6d5c4b3a2z1y0x9w8v7u6t5s4r3q2p1o0n9m8l7k6j5i4h3g2f1e
# Google OAuth Configuration

3
.gitignore vendored
View File

@@ -21,6 +21,9 @@ desktop.ini
# Backup directories (exclude from repo)
vip-coordinator-backup-*/
# Copy directories that shouldn't be in repo
vip-coordinator - Copy/
# ZIP files (exclude from repo)
*.zip

View File

@@ -1,49 +1,16 @@
import jwt from 'jsonwebtoken';
import jwtKeyManager, { User } from '../services/jwtKeyManager';
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production';
// JWT Key Manager now handles all token operations with automatic rotation
// No more static JWT_SECRET needed!
export interface User {
id: string;
google_id: string;
email: string;
name: string;
profile_picture_url?: string;
role: 'driver' | 'coordinator' | 'administrator';
created_at?: string;
last_login?: string;
is_active?: boolean;
updated_at?: string;
}
export { User } from '../services/jwtKeyManager';
export function generateToken(user: User): string {
return jwt.sign(
{
id: user.id,
google_id: user.google_id,
email: user.email,
name: user.name,
profile_picture_url: user.profile_picture_url,
role: user.role
},
JWT_SECRET,
{ expiresIn: '24h' }
);
return jwtKeyManager.generateToken(user);
}
export function verifyToken(token: string): User | null {
try {
const decoded = jwt.verify(token, JWT_SECRET) as any;
return {
id: decoded.id,
google_id: decoded.google_id,
email: decoded.email,
name: decoded.name,
profile_picture_url: decoded.profile_picture_url,
role: decoded.role
};
} catch (error) {
return null;
}
return jwtKeyManager.verifyToken(token);
}
// Simple Google OAuth2 client using fetch

View File

@@ -8,6 +8,7 @@ import scheduleValidationService from './services/scheduleValidationService';
import FlightTrackingScheduler from './services/flightTrackingScheduler';
import enhancedDataService from './services/enhancedDataService';
import databaseService from './services/databaseService';
import jwtKeyManager from './services/jwtKeyManager'; // Initialize JWT Key Manager
dotenv.config();
@@ -745,6 +746,34 @@ app.post('/api/admin/test-api/:apiType', async (req: Request, res: Response) =>
}
});
// JWT Key Management endpoints (admin only)
app.get('/api/admin/jwt-status', requireAuth, requireRole(['administrator']), (req: Request, res: Response) => {
const jwtKeyManager = require('./services/jwtKeyManager').default;
const status = jwtKeyManager.getStatus();
res.json({
keyRotationEnabled: true,
rotationInterval: '24 hours',
gracePeriod: '24 hours',
...status,
message: 'JWT keys are automatically rotated every 24 hours for enhanced security'
});
});
app.post('/api/admin/jwt-rotate', requireAuth, requireRole(['administrator']), (req: Request, res: Response) => {
const jwtKeyManager = require('./services/jwtKeyManager').default;
try {
jwtKeyManager.forceRotation();
res.json({
success: true,
message: 'JWT key rotation triggered successfully. New tokens will use the new key.'
});
} catch (error) {
res.status(500).json({ error: 'Failed to rotate JWT keys' });
}
});
// Initialize database and start server
async function startServer() {
try {

View File

@@ -0,0 +1,183 @@
import crypto from 'crypto';
import jwt from 'jsonwebtoken';
export interface User {
id: string;
google_id: string;
email: string;
name: string;
profile_picture_url?: string;
role: 'driver' | 'coordinator' | 'administrator';
created_at?: string;
last_login?: string;
is_active?: boolean;
updated_at?: string;
}
class JWTKeyManager {
private currentSecret: string;
private previousSecret: string | null = null;
private rotationInterval: NodeJS.Timeout | null = null;
private gracePeriodTimeout: NodeJS.Timeout | null = null;
constructor() {
console.log('🔑 Initializing JWT Key Manager with automatic rotation');
this.currentSecret = this.generateSecret();
this.startRotation();
}
private generateSecret(): string {
const secret = crypto.randomBytes(64).toString('hex');
console.log('🔄 Generated new JWT signing key (length:', secret.length, 'chars)');
return secret;
}
private startRotation() {
// Rotate every 24 hours (86400000 ms)
this.rotationInterval = setInterval(() => {
this.rotateKey();
}, 24 * 60 * 60 * 1000);
console.log('⏰ JWT key rotation scheduled every 24 hours');
// Also rotate on startup after 1 hour to test the system
setTimeout(() => {
console.log('🧪 Performing initial key rotation test...');
this.rotateKey();
}, 60 * 60 * 1000); // 1 hour
}
private rotateKey() {
console.log('🔄 Rotating JWT signing key...');
// Store current secret as previous
this.previousSecret = this.currentSecret;
// Generate new current secret
this.currentSecret = this.generateSecret();
console.log('✅ JWT key rotation completed. Grace period: 24 hours');
// Clear any existing grace period timeout
if (this.gracePeriodTimeout) {
clearTimeout(this.gracePeriodTimeout);
}
// Clean up previous secret after 24 hours (grace period)
this.gracePeriodTimeout = setTimeout(() => {
this.previousSecret = null;
console.log('🧹 Grace period ended. Previous JWT key cleaned up');
}, 24 * 60 * 60 * 1000);
}
generateToken(user: User): string {
const payload = {
id: user.id,
google_id: user.google_id,
email: user.email,
name: user.name,
profile_picture_url: user.profile_picture_url,
role: user.role,
iat: Math.floor(Date.now() / 1000) // Issued at time
};
return jwt.sign(payload, this.currentSecret, {
expiresIn: '24h',
issuer: 'vip-coordinator',
audience: 'vip-coordinator-users'
});
}
verifyToken(token: string): User | null {
try {
// Try current secret first
const decoded = jwt.verify(token, this.currentSecret, {
issuer: 'vip-coordinator',
audience: 'vip-coordinator-users'
}) as any;
return {
id: decoded.id,
google_id: decoded.google_id,
email: decoded.email,
name: decoded.name,
profile_picture_url: decoded.profile_picture_url,
role: decoded.role
};
} catch (error) {
// Try previous secret during grace period
if (this.previousSecret) {
try {
const decoded = jwt.verify(token, this.previousSecret, {
issuer: 'vip-coordinator',
audience: 'vip-coordinator-users'
}) as any;
console.log('🔄 Token verified using previous key (grace period)');
return {
id: decoded.id,
google_id: decoded.google_id,
email: decoded.email,
name: decoded.name,
profile_picture_url: decoded.profile_picture_url,
role: decoded.role
};
} catch (gracePeriodError) {
console.log('❌ Token verification failed with both current and previous keys');
return null;
}
}
console.log('❌ Token verification failed:', error instanceof Error ? error.message : 'Unknown error');
return null;
}
}
// Get status for monitoring/debugging
getStatus() {
return {
hasCurrentKey: !!this.currentSecret,
hasPreviousKey: !!this.previousSecret,
rotationActive: !!this.rotationInterval,
gracePeriodActive: !!this.gracePeriodTimeout
};
}
// Cleanup on shutdown
destroy() {
console.log('🛑 Shutting down JWT Key Manager...');
if (this.rotationInterval) {
clearInterval(this.rotationInterval);
this.rotationInterval = null;
}
if (this.gracePeriodTimeout) {
clearTimeout(this.gracePeriodTimeout);
this.gracePeriodTimeout = null;
}
console.log('✅ JWT Key Manager shutdown complete');
}
// Manual rotation for testing/emergency
forceRotation() {
console.log('🚨 Manual key rotation triggered');
this.rotateKey();
}
}
// Singleton instance
export const jwtKeyManager = new JWTKeyManager();
// Graceful shutdown handling
process.on('SIGTERM', () => {
jwtKeyManager.destroy();
});
process.on('SIGINT', () => {
jwtKeyManager.destroy();
});
export default jwtKeyManager;

View File

@@ -1,6 +1,6 @@
// API Configuration
// Use environment variable with fallback to production URL
export const API_BASE_URL = import.meta.env.VITE_API_URL || 'https://api.bsa.madeamess.online';
// Use environment variable with fallback to localhost for development
export const API_BASE_URL = (import.meta as any).env.VITE_API_URL || 'http://localhost:3000';
// Helper function for API calls
export const apiCall = (endpoint: string, options?: RequestInit) => {