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:
@@ -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
3
.gitignore
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
183
backend/src/services/jwtKeyManager.ts
Normal file
183
backend/src/services/jwtKeyManager.ts
Normal 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;
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user