diff options
| author | Pinapelz <yukais@pinapelz.com> | 2025-06-29 19:55:51 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2025-06-29 22:06:39 -0700 |
| commit | eda5691cfcb3be0bb6ccf1b2ad4fa92801ad86c4 (patch) | |
| tree | ee83300ec2d7fc2763ba6b7887d61c2af0208c7a /backend/src | |
| parent | 1b66788e84c1d2eef875534cd02685b56d08547f (diff) | |
seperate routes and middleware into seperate files
Diffstat (limited to 'backend/src')
| -rw-r--r-- | backend/src/config/db.ts | 17 | ||||
| -rw-r--r-- | backend/src/index.ts | 242 | ||||
| -rw-r--r-- | backend/src/middleware/requireAuth.ts | 28 | ||||
| -rw-r--r-- | backend/src/routes/authRoutes.ts | 116 | ||||
| -rw-r--r-- | backend/src/routes/userRoutes.ts | 41 | ||||
| -rw-r--r-- | backend/src/utils/session.ts | 35 |
6 files changed, 249 insertions, 230 deletions
diff --git a/backend/src/config/db.ts b/backend/src/config/db.ts new file mode 100644 index 0000000..024a7ed --- /dev/null +++ b/backend/src/config/db.ts @@ -0,0 +1,17 @@ +import { PrismaClient } from '@prisma/client'; + +export const prisma = new PrismaClient(); + +process.on('beforeExit', async () => { + await prisma.$disconnect(); +}); + +process.on('SIGINT', async () => { + await prisma.$disconnect(); + process.exit(0); +}); + +process.on('SIGTERM', async () => { + await prisma.$disconnect(); + process.exit(0); +}); diff --git a/backend/src/index.ts b/backend/src/index.ts index 37b64a8..3e2c559 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,10 +1,13 @@ import express from 'express'; import cors from 'cors'; -import bcrypt from 'bcrypt'; -import crypto from 'crypto'; import session from 'express-session'; import cookieParser from 'cookie-parser'; -import { PrismaClient } from '@prisma/client'; +import { requireAuth } from './middleware/requireAuth'; +import { startSessionCleanup } from './utils/session'; + +// Routes +import * as authRoutes from './routes/authRoutes'; +import * as userRoutes from './routes/userRoutes'; const app = express(); const port = 5000; @@ -31,241 +34,20 @@ app.use(session({ } })); -const prisma = new PrismaClient(); - - declare module 'express-session' { interface SessionData { userId: number; } } -// Middleware to check if user is authenticated -const requireAuth = async (req: express.Request, res: express.Response, next: express.NextFunction) => { - if (!req.session.userId) { - return res.status(401).json({ error: 'Authentication required' }); - } - - try { - const user = await prisma.user.findUnique({ - where: { id: req.session.userId }, - select: { id: true, username: true, email: true } - }); - - if (!user) { - req.session.destroy((err) => { - if (err) console.error('Session destroy error:', err); - }); - return res.status(401).json({ error: 'Invalid session' }); - } - - // Attach user to request object - (req as any).user = user; - next(); - } catch (error) { - console.error('Auth middleware error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}; - -// Create session in database -const createSession = async (userId: number): Promise<string> => { - const sessionId = crypto.randomUUID(); - const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours - - await prisma.session.create({ - data: { - id: sessionId, - userId, - expiresAt - } - }); - - return sessionId; -}; - -// Clean up expired sessions -const cleanupExpiredSessions = async () => { - try { - await prisma.session.deleteMany({ - where: { - expiresAt: { - lt: new Date() - } - } - }); - } catch (error) { - console.error('Session cleanup error:', error); - } -}; - -// Run cleanup every hour -setInterval(cleanupExpiredSessions, 60 * 60 * 1000); - -app.post('/api/register', async (req, res) => { - try { - const { username, password, email } = req.body; - - if (!username || !password || !email) { - return res.status(400).json({ error: 'All fields are required' }); - } - - const existingUser = await prisma.user.findFirst({ - where: { - OR: [ - { username }, - { email } - ] - } - }); - - if (existingUser) { - return res.status(400).json({ error: 'Username or email already exists' }); - } - - const salt = crypto.randomBytes(16).toString('hex'); - const hashedPassword = await bcrypt.hash(password + salt, 12); - - const user = await prisma.user.create({ - data: { - username, - password: hashedPassword, - salt, - email, - } - }); - - // Create session for the new user - req.session.userId = user.id; - const sessionId = await createSession(user.id); - - res.status(201).json({ - id: user.id, - username: user.username, - email: user.email, - sessionId - }); - } catch (error) { - console.error('Registration error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -app.post('/api/authenticate', async (req, res) => { - try { - const { username, password } = req.body; +startSessionCleanup(); - if (!username || !password) { - return res.status(400).json({ error: 'Username and password are required' }); - } +app.post('/api/register', authRoutes.handleRegistration); +app.post('/api/authenticate', authRoutes.handleAuthentication); +app.post('/api/logout', requireAuth, authRoutes.handleLogout); - const user = await prisma.user.findUnique({ - where: { username } - }); - - if (!user) { - return res.status(401).json({ error: 'Invalid credentials' }); - } - - const isValidPassword = await bcrypt.compare(password + user.salt, user.password); - if (!isValidPassword) { - return res.status(401).json({ error: 'Invalid credentials' }); - } - - // Create session - req.session.userId = user.id; - const sessionId = await createSession(user.id); - - res.json({ - id: user.id, - username: user.username, - email: user.email, - sessionId - }); - } catch (error) { - console.error('Login error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -app.post('/api/logout', requireAuth, async (req, res) => { - try { - const userId = req.session.userId; - - // Remove all sessions for this user from database - await prisma.session.deleteMany({ - where: { userId } - }); - - // Destroy the session - req.session.destroy((err) => { - if (err) { - console.error('Session destroy error:', err); - return res.status(500).json({ error: 'Logout failed' }); - } - - res.clearCookie('connect.sid'); // Clear the session cookie - res.json({ message: 'Logged out successfully' }); - }); - } catch (error) { - console.error('Logout error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -app.get('/api/me', requireAuth, async (req, res) => { - try { - const user = (req as any).user; - res.json(user); - } catch (error) { - console.error('Me endpoint error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -app.get('/api/users', async (req, res) => { - try { - const users = await prisma.user.findMany({ - select: { - id: true, - username: true, - } - }); - res.json(users); - } catch (error) { - console.error('Users endpoint error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Check session status -app.get('/api/session', async (req, res) => { - try { - if (!req.session.userId) { - return res.json({ authenticated: false }); - } - - const user = await prisma.user.findUnique({ - where: { id: req.session.userId }, - select: { id: true, username: true, email: true } - }); - - if (!user) { - req.session.destroy((err) => { - if (err) console.error('Session destroy error:', err); - }); - return res.json({ authenticated: false }); - } - - res.json({ - authenticated: true, - user - }); - } catch (error) { - console.error('Session check error:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); +app.get('/api/me', userRoutes.handleMeRoute); +app.get('/api/session', userRoutes.handleGetCurrentSession); app.listen(port, () => { console.log(`Server listening on port ${port}`); diff --git a/backend/src/middleware/requireAuth.ts b/backend/src/middleware/requireAuth.ts new file mode 100644 index 0000000..915d52b --- /dev/null +++ b/backend/src/middleware/requireAuth.ts @@ -0,0 +1,28 @@ +import express from 'express'; +import { prisma } from '../config/db'; + +export const requireAuth = async (req: express.Request, res: express.Response, next: express.NextFunction) => { + if (!req.session.userId) { + return res.status(401).json({ error: 'Authentication required' }); + } + + try { + const user = await prisma.user.findUnique({ + where: { id: req.session.userId }, + select: { id: true, username: true, email: true } + }); + + if (!user) { + req.session.destroy((err) => { + if (err) console.error('Session destroy error:', err); + }); + return res.status(401).json({ error: 'Invalid session' }); + } + + (req as any).user = user; + next(); + } catch (error) { + console.error('Auth middleware error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}; diff --git a/backend/src/routes/authRoutes.ts b/backend/src/routes/authRoutes.ts new file mode 100644 index 0000000..4c6c374 --- /dev/null +++ b/backend/src/routes/authRoutes.ts @@ -0,0 +1,116 @@ +import { prisma } from '../config/db'; +import express from 'express'; +import { createSession } from '../utils/session' +import bcrypt from 'bcrypt'; +import crypto from 'crypto'; + +export const handleRegistration = async (req: express.Request, res: express.Response) => { + try { + const { username, password, email } = req.body; + + if (!username || !password || !email) { + return res.status(400).json({ error: 'All fields are required' }); + } + + const existingUser = await prisma.user.findFirst({ + where: { + OR: [ + { username }, + { email } + ] + } + }); + + if (existingUser) { + return res.status(400).json({ error: 'Username or email already exists' }); + } + + const salt = crypto.randomBytes(16).toString('hex'); + const hashedPassword = await bcrypt.hash(password + salt, 12); + + const user = await prisma.user.create({ + data: { + username, + password: hashedPassword, + salt, + email, + } + }); + + // Create session for the new user + req.session.userId = user.id; + const sessionId = await createSession(user.id); + + res.status(201).json({ + id: user.id, + username: user.username, + email: user.email, + sessionId + }); + } catch (error) { + console.error('Registration error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} + +export const handleAuthentication = async (req: express.Request, res: express.Response) => { + try { + const { username, password } = req.body; + + if (!username || !password) { + return res.status(400).json({ error: 'Username and password are required' }); + } + + const user = await prisma.user.findUnique({ + where: { username } + }); + + if (!user) { + return res.status(401).json({ error: 'Invalid credentials' }); + } + + const isValidPassword = await bcrypt.compare(password + user.salt, user.password); + if (!isValidPassword) { + return res.status(401).json({ error: 'Invalid credentials' }); + } + + // Create session + req.session.userId = user.id; + const sessionId = await createSession(user.id); + + res.json({ + id: user.id, + username: user.username, + email: user.email, + sessionId + }); + } catch (error) { + console.error('Login error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} + +export const handleLogout = async (req: express.Request, res: express.Response,) => { + try { + const userId = req.session.userId; + + // Remove all sessions for this user from database + await prisma.session.deleteMany({ + where: { userId } + }); + + // Destroy the session + req.session.destroy((err) => { + if (err) { + console.error('Session destroy error:', err); + return res.status(500).json({ error: 'Logout failed' }); + } + + res.clearCookie('connect.sid'); // Clear the session cookie + res.json({ message: 'Logged out successfully' }); + }); + } catch (error) { + console.error('Logout error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} diff --git a/backend/src/routes/userRoutes.ts b/backend/src/routes/userRoutes.ts new file mode 100644 index 0000000..a03ece0 --- /dev/null +++ b/backend/src/routes/userRoutes.ts @@ -0,0 +1,41 @@ +// Routes about self (or users in general) +import express from 'express'; +import { prisma } from '../config/db'; + +export const handleMeRoute = async (req: express.Request, res: express.Response) => { + try { + const user = (req as any).user; + res.json(user); + } catch (error) { + console.error('Me endpoint error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} + +export const handleGetCurrentSession = async (req: express.Request, res: express.Response) => { + try { + if (!req.session.userId) { + return res.json({ authenticated: false }); + } + + const user = await prisma.user.findUnique({ + where: { id: req.session.userId }, + select: { id: true, username: true, email: true } + }); + + if (!user) { + req.session.destroy((err) => { + if (err) console.error('Session destroy error:', err); + }); + return res.json({ authenticated: false }); + } + + res.json({ + authenticated: true, + user + }); + } catch (error) { + console.error('Session check error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +} diff --git a/backend/src/utils/session.ts b/backend/src/utils/session.ts new file mode 100644 index 0000000..cc1facd --- /dev/null +++ b/backend/src/utils/session.ts @@ -0,0 +1,35 @@ +import crypto from 'crypto'; +import { prisma } from '../config/db'; + +export const createSession = async (userId: number): Promise<string> => { + const sessionId = crypto.randomUUID(); + const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours + + await prisma.session.create({ + data: { + id: sessionId, + userId, + expiresAt + } + }); + + return sessionId; +}; + +export const cleanupExpiredSessions = async () => { + try { + await prisma.session.deleteMany({ + where: { + expiresAt: { + lt: new Date() + } + } + }); + } catch (error) { + console.error('Session cleanup error:', error); + } +}; + +export const startSessionCleanup = () => { + setInterval(cleanupExpiredSessions, 60 * 60 * 1000); +}; |
