proxy-oidcv2/src/server.js
2025-12-03 22:12:02 +01:00

249 lines
6.7 KiB
JavaScript

import express from 'express';
import session from 'express-session';
import FileStore from 'session-file-store';
import bodyParser from 'body-parser';
import cors from 'cors';
import fs from 'fs';
import path from 'path';
import config from './config.js';
import { initDatabase } from './db.js';
import { initOIDC, isOIDCEnabled } from './middleware/oidcMiddleware.js';
import {
requestLogger,
securityHeaders,
errorHandler,
} from './middleware/security.js';
import authRoutes from './routes/authRoutes.js';
import { authCallback } from './controllers/authController.js';
import adminRoutes from './routes/adminRoutes.js';
import dashboardRoutes from './routes/dashboardRoutes.js';
import reverseProxyMiddleware from './middleware/proxyMiddleware.js';
const app = express();
const FileStoreSession = FileStore(session);
// Create sessions directory FIRST (before any middleware)
try {
const sessionsDir = path.join(process.cwd(), 'sessions');
fs.mkdirSync(sessionsDir, { recursive: true });
} catch (error) {
console.error('✗ Failed to create sessions directory:', error);
process.exit(1);
}
// Initialize
async function initialize() {
console.log('🚀 Initializing Secure Proxy...');
// Database initialization
try {
await initDatabase(config.db.path);
console.log('✓ Database initialized');
} catch (error) {
console.error('✗ Database initialization failed:', error);
process.exit(1);
}
// OIDC initialization
try {
await initOIDC();
} catch (error) {
console.error('✗ OIDC initialization failed:', error);
console.log('⚠️ Running in OIDC-disabled mode for development');
}
}
// Middleware
app.set('trust proxy', 1);
app.use(requestLogger);
app.use(securityHeaders);
app.use(bodyParser.json({ limit: '10mb' }));
app.use(bodyParser.urlencoded({ limit: '10mb', extended: true }));
app.use(cors({
origin: config.proxyUrl,
credentials: true,
}));
// Session configuration
app.use(
session({
store: new FileStoreSession({ path: './sessions' }),
secret: config.sessionSecret,
resave: false,
saveUninitialized: false,
cookie: {
secure: config.nodeEnv === 'production',
httpOnly: true,
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000, // 24 hours
},
})
);
// Static files
app.use(express.static('public'));
// Development mode: auto-create session for /admin and /api access
app.use((req, res, next) => {
// In dev mode without OIDC, create a session automatically
if (req.path.startsWith('/admin') || req.path.startsWith('/api') || req.path.startsWith('/dashboard')) {
if (!isOIDCEnabled() && !req.session.user) {
req.session.user = {
sub: 'dev-user-' + Date.now(),
name: 'Dev User',
email: 'dev@localhost',
isAdmin: true,
};
}
}
next();
});
// Routes
app.use('/auth', authRoutes);
app.use('/api', adminRoutes);
app.use('/dashboard', dashboardRoutes);
// OAuth callback redirect (Keycloak sends to /callback, not /auth/callback)
app.post('/callback', authCallback);
// Home page
app.get('/', (req, res) => {
if (req.session?.user) {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Secure Proxy Home</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 10px 25px rgba(0,0,0,0.2);
max-width: 600px;
width: 100%;
}
h1 {
color: #333;
margin-bottom: 20px;
}
.user-info {
background: #f5f5f5;
padding: 20px;
border-radius: 4px;
margin-bottom: 20px;
}
.user-info p {
margin: 8px 0;
color: #666;
}
.buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
a, button {
padding: 10px 20px;
border-radius: 4px;
text-decoration: none;
border: none;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #764ba2;
}
.btn-secondary {
background: #e0e0e0;
color: #333;
}
.btn-secondary:hover {
background: #d0d0d0;
}
</style>
</head>
<body>
<div class="container">
<h1>Welcome to Secure Proxy</h1>
<div class="user-info">
<p><strong>Name:</strong> ${req.session.user.name || 'N/A'}</p>
<p><strong>Email:</strong> ${req.session.user.email || 'N/A'}</p>
<p><strong>ID:</strong> ${req.session.user.sub}</p>
${req.session.user.isAdmin ? '<p><strong>Role:</strong> Administrator</p>' : ''}
</div>
<div class="buttons">
${req.session.user.isAdmin ? '<a href="/admin" class="btn btn-primary">Admin Panel</a>' : ''}
<a href="/auth/profile" class="btn btn-primary">Profile</a>
<a href="/auth/logout" class="btn btn-secondary">Logout</a>
</div>
</div>
</body>
</html>
`);
} else {
res.redirect('/auth/login-page');
}
});
// Admin panel
app.get('/admin', (req, res, next) => {
if (!req.session?.user?.isAdmin) {
return res.status(403).send('Access denied');
}
res.sendFile('public/admin.html', { root: '.' });
});
// 404 handler before reverse proxy
app.use((req, res, next) => {
// Skip reverse proxy for known routes
if (
req.path.startsWith('/auth') ||
req.path.startsWith('/api') ||
req.path.startsWith('/dashboard') ||
req.path === '/' ||
req.path === '/admin'
) {
return next();
}
// Apply reverse proxy for other paths
reverseProxyMiddleware(req, res, next);
});
// Error handling
app.use(errorHandler);
// Start server
initialize().then(() => {
app.listen(config.port, () => {
console.log(`✓ Server running on port ${config.port}`);
console.log(`✓ Environment: ${config.nodeEnv}`);
console.log(`✓ Proxy URL: ${config.proxyUrl}`);
});
}).catch((error) => {
console.error('Initialization failed:', error);
process.exit(1);
});
export default app;