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
|
VITE_API_URL=https://api.bsa.madeamess.online
|
||||||
|
|
||||||
# Authentication Configuration (Secure production keys)
|
# 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
|
SESSION_SECRET=VipCoord2025SessionSecret9g8f7e6d5c4b3a2z1y0x9w8v7u6t5s4r3q2p1o0n9m8l7k6j5i4h3g2f1e
|
||||||
|
|
||||||
# Google OAuth Configuration
|
# Google OAuth Configuration
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -21,6 +21,9 @@ desktop.ini
|
|||||||
# Backup directories (exclude from repo)
|
# Backup directories (exclude from repo)
|
||||||
vip-coordinator-backup-*/
|
vip-coordinator-backup-*/
|
||||||
|
|
||||||
|
# Copy directories that shouldn't be in repo
|
||||||
|
vip-coordinator - Copy/
|
||||||
|
|
||||||
# ZIP files (exclude from repo)
|
# ZIP files (exclude from repo)
|
||||||
*.zip
|
*.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 {
|
export { User } from '../services/jwtKeyManager';
|
||||||
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 function generateToken(user: User): string {
|
export function generateToken(user: User): string {
|
||||||
return jwt.sign(
|
return jwtKeyManager.generateToken(user);
|
||||||
{
|
|
||||||
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' }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verifyToken(token: string): User | null {
|
export function verifyToken(token: string): User | null {
|
||||||
try {
|
return jwtKeyManager.verifyToken(token);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple Google OAuth2 client using fetch
|
// Simple Google OAuth2 client using fetch
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import scheduleValidationService from './services/scheduleValidationService';
|
|||||||
import FlightTrackingScheduler from './services/flightTrackingScheduler';
|
import FlightTrackingScheduler from './services/flightTrackingScheduler';
|
||||||
import enhancedDataService from './services/enhancedDataService';
|
import enhancedDataService from './services/enhancedDataService';
|
||||||
import databaseService from './services/databaseService';
|
import databaseService from './services/databaseService';
|
||||||
|
import jwtKeyManager from './services/jwtKeyManager'; // Initialize JWT Key Manager
|
||||||
|
|
||||||
dotenv.config();
|
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
|
// Initialize database and start server
|
||||||
async function startServer() {
|
async function startServer() {
|
||||||
try {
|
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
|
// API Configuration
|
||||||
// Use environment variable with fallback to production URL
|
// Use environment variable with fallback to localhost for development
|
||||||
export const API_BASE_URL = import.meta.env.VITE_API_URL || 'https://api.bsa.madeamess.online';
|
export const API_BASE_URL = (import.meta as any).env.VITE_API_URL || 'http://localhost:3000';
|
||||||
|
|
||||||
// Helper function for API calls
|
// Helper function for API calls
|
||||||
export const apiCall = (endpoint: string, options?: RequestInit) => {
|
export const apiCall = (endpoint: string, options?: RequestInit) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user