131 lines
3.1 KiB
TypeScript
131 lines
3.1 KiB
TypeScript
import express, { Request, Response } from 'express';
|
|
import http from 'http';
|
|
import { Server as SocketIOServer } from 'socket.io';
|
|
import cors from 'cors';
|
|
import cookieParser from 'cookie-parser';
|
|
import session from 'express-session';
|
|
import passport from 'passport';
|
|
import path from 'path';
|
|
|
|
import { setupSocketIO } from './socket';
|
|
import { db } from './database';
|
|
import { config } from './config';
|
|
import {
|
|
securityHeaders,
|
|
createRateLimit,
|
|
requestLogger,
|
|
sanitizeInput
|
|
} from './middleware';
|
|
import { logger } from './logger';
|
|
import './auth';
|
|
|
|
const app = express();
|
|
app.set('trust proxy', 1);
|
|
const server = http.createServer(app);
|
|
const io = new SocketIOServer(server, {
|
|
cors: {
|
|
origin: config.security.corsOrigin,
|
|
methods: ['GET', 'POST'],
|
|
credentials: true
|
|
},
|
|
transports: ['polling', 'websocket']
|
|
});
|
|
|
|
// Security middleware
|
|
app.use(securityHeaders);
|
|
app.use(requestLogger);
|
|
app.use(sanitizeInput);
|
|
|
|
// Cookie parsing
|
|
app.use(cookieParser());
|
|
|
|
// Rate limiting
|
|
app.use('/api', createRateLimit);
|
|
|
|
// Session configuration
|
|
const SQLiteStore = require('connect-sqlite3')(session);
|
|
const sessionMiddleware = session({
|
|
store: new SQLiteStore({ db: 'sessions.db', dir: './data' }),
|
|
secret: process.env.NEXTAUTH_SECRET || 'fallback-secret-change-in-production',
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
cookie: { secure: false, maxAge: 24 * 60 * 60 * 1000 } // 24 hours
|
|
});
|
|
|
|
app.use(sessionMiddleware);
|
|
|
|
// Passport initialization
|
|
app.use(passport.initialize());
|
|
app.use(passport.session());
|
|
|
|
// Body parsing
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
|
|
// CORS with specific origin
|
|
app.use(cors({
|
|
origin: config.server.nodeEnv === 'development'
|
|
? ['http://localhost:3000', 'http://localhost:5173']
|
|
: config.security.corsOrigin,
|
|
credentials: true
|
|
}));
|
|
|
|
app.use(express.static(path.join(__dirname, '../client/dist')));
|
|
|
|
app.get('/health', (req, res) => {
|
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
});
|
|
|
|
// Auth routes
|
|
app.get('/auth/github', passport.authenticate('github'));
|
|
|
|
app.get('/auth/github/callback',
|
|
passport.authenticate('github', { failureRedirect: '/login' }),
|
|
(req, res) => {
|
|
res.redirect('/');
|
|
}
|
|
);
|
|
|
|
app.get('/api/user', (req, res) => {
|
|
if (req.user) {
|
|
res.json(req.user);
|
|
} else {
|
|
res.status(401).json({ error: 'Not authenticated' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/socket-token', (req, res) => {
|
|
if (req.user) {
|
|
// Create a token containing user info
|
|
const user = req.user as any; // Use any to bypass typing issues
|
|
const token = Buffer.from(JSON.stringify({
|
|
userId: user.id,
|
|
username: user.username,
|
|
timestamp: Date.now()
|
|
})).toString('base64');
|
|
|
|
res.json({ token, user: req.user });
|
|
} else {
|
|
res.status(401).json({ error: 'Not authenticated' });
|
|
}
|
|
});
|
|
|
|
|
|
|
|
// SPA catch-all route
|
|
app.get('*', (req, res) => {
|
|
res.sendFile(path.join(__dirname, '../client/dist/index.html'));
|
|
});
|
|
|
|
io.use((socket: any, next: any) => {
|
|
next();
|
|
});
|
|
|
|
setupSocketIO(io);
|
|
|
|
const PORT = config.server.port;
|
|
|
|
server.listen(PORT, () => {
|
|
console.log(`Server running on port ${PORT}`);
|
|
});
|