import express from 'express'; import { sendEmail, checkEmail } from '../../utils/airtable.js'; import pg from '../../utils/db.js'; import crypto from 'crypto'; import { strictLimiter, authLimiter } from '../../middlewares/rate-limit.middleware.js'; const router = express.Router(); const randomToken = () => { return crypto.randomBytes(32).toString('hex'); }; router.get('/send', (req, res) => { res.status(200).json({ message: 'Use Post to /send to send verification code', }); }); // POST /api/v1/users/send router.post('/send', /* strictLimiter, */ async (req, res) => { try { const { email, mode } = req.body; if (!email) { return res.status(400).json({ success: false, message: 'Email is required' }); } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return res.status(400).json({ success: false, message: 'Invalid email format' }); } if (mode === 'login') { const user = await pg('users') .where('email', email) .first(); if (!user) { return res.status(404).json({ success: false, message: 'No account found with this email. Please sign up first.' }); } } const result = await sendEmail(email); res.status(200).json({ success: true, message: 'Verification code sent successfully', data: { email: result.email, } }); } catch (error) { console.error('Error in /send route:', error); res.status(500).json({ success: false, message: 'Failed to send verification code', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); // POST /api/v1/users/signup router.post('/signup', /* authLimiter, */ async (req, res) => { try { const { email, username, verificationCode } = req.body; if (!email || !username || !verificationCode) { return res.status(400).json({ success: false, message: 'Email, username, and verification code are required' }); } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return res.status(400).json({ success: false, message: 'Invalid email format' }); } if (username.length > 100) { return res.status(400).json({ success: false, message: 'Username must be 100 characters or less' }); } const codeValid = await checkEmail(email, verificationCode); if (!codeValid) { return res.status(400).json({ success: false, message: 'Invalid or expired verification code' }); } const existingUser = await pg('users') .where('email', email) .orWhere('username', username) .first(); if (existingUser) { return res.status(409).json({ success: false, message: existingUser.email === email ? 'Email already registered' : 'Username already taken' }); } const authToken = randomToken(); const [newUser] = await pg('users') .insert({ email, username, authorization: authToken, max_spaces: 3, is_admin: false }) .returning(['id', 'email', 'username', 'authorization', 'is_admin']); res.cookie('auth_token', newUser.authorization, { httpOnly: false, maxAge: 7 * 24 * 60 * 60 * 1000, sameSite: 'strict', secure: process.env.NODE_ENV === 'production' }); res.status(201).json({ success: true, message: 'User created successfully', data: { id: newUser.id, email: newUser.email, username: newUser.username, authorization: newUser.authorization, is_admin: newUser.is_admin } }); } catch (error) { console.error('Error in /signup route:', error); if (error.code === '23505') { return res.status(409).json({ success: false, message: 'Email or username already exists' }); } res.status(500).json({ success: false, message: 'Failed to create user account', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); // POST /api/v1/users/login router.post('/login', /* authLimiter, */ async (req, res) => { try { const { email, verificationCode } = req.body; if (!email || !verificationCode) { return res.status(400).json({ success: false, message: 'Email and verification code are required' }); } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return res.status(400).json({ success: false, message: 'Invalid email format' }); } const codeValid = await checkEmail(email, verificationCode); if (!codeValid) { return res.status(400).json({ success: false, message: 'Invalid or expired verification code' }); } const user = await pg('users') .where('email', email) .first(); if (!user) { return res.status(404).json({ success: false, message: 'User not found. Please sign up first.' }); } const newAuthToken = randomToken(); const [updatedUser] = await pg('users') .where('email', email) .update({ authorization: newAuthToken }) .returning(['email', 'username', 'authorization', 'is_admin']); res.cookie('auth_token', updatedUser.authorization, { httpOnly: false, maxAge: 7 * 24 * 60 * 60 * 1000, sameSite: 'strict', secure: process.env.NODE_ENV === 'production' }); res.status(200).json({ success: true, message: 'Login successful', data: { email: updatedUser.email, username: updatedUser.username, authorization: updatedUser.authorization, is_admin: updatedUser.is_admin } }); } catch (error) { console.error('Error in /login route:', error); res.status(500).json({ success: false, message: 'Failed to login', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); // POST /api/v1/users/signout router.post('/signout', async (req, res) => { try { const { authorization } = req.body; if (!authorization) { return res.status(400).json({ success: false, message: 'Authorization token is required' }); } const user = await pg('users') .where('authorization', authorization) .first(); if (!user) { return res.status(404).json({ success: false, message: 'Invalid authorization token' }); } const newAuthToken = randomToken(); const [updatedUser] = await pg('users') .where('authorization', authorization) .update({ authorization: newAuthToken }) .returning(['email']); res.clearCookie('auth_token'); res.status(200).json({ success: true, message: 'Sign out successful', data: { email: updatedUser.email, } }); } catch (error) { console.error('Error in /signout route:', error); res.status(500).json({ success: false, message: 'Failed to sign out', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); export default router;