aboutsummaryrefslogtreecommitdiffstats
path: root/backend/src/index.ts
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2025-06-29 01:28:39 -0700
committerPinapelz <yukais@pinapelz.com>2025-06-29 01:28:39 -0700
commitff37cca46430ed714015647469f88ce06781457a (patch)
tree63f406a3c7fb463fed7f34efc8d04d58fe96e0cb /backend/src/index.ts
parente4fa1e69e7ebfb627c7198fd1a9881e9327ec4d4 (diff)
scaffold register,login,and auth endpoints
Diffstat (limited to 'backend/src/index.ts')
-rw-r--r--backend/src/index.ts258
1 files changed, 255 insertions, 3 deletions
diff --git a/backend/src/index.ts b/backend/src/index.ts
index fef1977..52a7a33 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -1,18 +1,270 @@
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';
const app = express();
const port = 5000;
-app.use(cors());
+app.use(cors({
+ origin: [
+ 'http://localhost:5173', // Vite dev server default port
+ ],
+ credentials: true
+}));
+
app.use(express.json());
+app.use(cookieParser());
+
+
+app.use(session({
+ secret: process.env.SESSION_SECRET || 'your-secret-key-change-this-in-production',
+ resave: false,
+ saveUninitialized: false,
+ cookie: {
+ secure: false,
+ httpOnly: true,
+ maxAge: 24 * 60 * 60 * 1000 // 24 hours
+ }
+}));
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;
+
+ 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' });
+ }
+});
+
+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) => {
- const users = await prisma.user.findMany();
- res.json(users);
+ 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.listen(port, () => {
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage