Files
marketplace/server/src/routes/auth.ts
delta-lynx-89e8 b37b734c82 Initial marketplace implementation
Full-stack marketplace for buying/selling second-hand items.
React 19 + TypeScript + Tailwind CSS v4 frontend with 17 screens,
Express + Prisma + Socket.io backend, Stripe payments, JWT auth.

Deployed at https://marketplace.173.212.212.157.sslip.io/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 07:00:44 -08:00

149 lines
4.9 KiB
TypeScript

import { Router } from 'express';
import { prisma } from '../config/database.js';
import { hashPassword, comparePassword } from '../utils/password.js';
import { generateAccessToken, generateRefreshToken, verifyRefreshToken } from '../utils/jwt.js';
import { validate } from '../middleware/validate.js';
import { authenticate } from '../middleware/auth.js';
import { registerSchema, loginSchema } from '../validators/auth.js';
import { AppError } from '../middleware/errorHandler.js';
const router = Router();
router.post('/register', validate(registerSchema), async (req, res, next) => {
try {
const { fullName, email, password } = req.body;
const existing = await prisma.user.findUnique({ where: { email } });
if (existing) throw new AppError(409, 'Email already registered');
const passwordHash = await hashPassword(password);
const user = await prisma.user.create({
data: { fullName, email, passwordHash },
select: { id: true, email: true, fullName: true, nickname: true, avatar: true, phone: true, location: true, bio: true, rating: true, showEmail: true, showPhone: true, showLocation: true, createdAt: true },
});
const accessToken = generateAccessToken(user.id);
const refreshToken = generateRefreshToken(user.id);
await prisma.session.create({
data: {
userId: user.id,
refreshToken,
userAgent: req.headers['user-agent'] || null,
ipAddress: req.ip || null,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
},
});
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: process.env['NODE_ENV'] === 'production',
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000,
});
res.status(201).json({ user, accessToken });
} catch (error) {
next(error);
}
});
router.post('/login', validate(loginSchema), async (req, res, next) => {
try {
const { email, password } = req.body;
const user = await prisma.user.findUnique({ where: { email } });
if (!user) throw new AppError(401, 'Invalid email or password');
if (!user.isActive) throw new AppError(403, 'Account is disabled');
const valid = await comparePassword(password, user.passwordHash);
if (!valid) throw new AppError(401, 'Invalid email or password');
const accessToken = generateAccessToken(user.id);
const refreshToken = generateRefreshToken(user.id);
await prisma.session.create({
data: {
userId: user.id,
refreshToken,
userAgent: req.headers['user-agent'] || null,
ipAddress: req.ip || null,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
},
});
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: process.env['NODE_ENV'] === 'production',
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000,
});
const { passwordHash: _, ...userData } = user;
res.json({ user: userData, accessToken });
} catch (error) {
next(error);
}
});
router.post('/refresh', async (req, res, next) => {
try {
const token = req.cookies?.refreshToken;
if (!token) throw new AppError(401, 'No refresh token');
const payload = verifyRefreshToken(token);
const session = await prisma.session.findUnique({ where: { refreshToken: token } });
if (!session || session.expiresAt < new Date()) {
if (session) await prisma.session.delete({ where: { id: session.id } });
throw new AppError(401, 'Invalid refresh token');
}
const accessToken = generateAccessToken(payload.userId);
const newRefreshToken = generateRefreshToken(payload.userId);
await prisma.session.update({
where: { id: session.id },
data: { refreshToken: newRefreshToken, expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) },
});
res.cookie('refreshToken', newRefreshToken, {
httpOnly: true,
secure: process.env['NODE_ENV'] === 'production',
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000,
});
res.json({ accessToken });
} catch (error) {
next(error);
}
});
router.get('/me', authenticate, async (req, res, next) => {
try {
const user = await prisma.user.findUnique({
where: { id: req.userId },
select: { id: true, email: true, fullName: true, nickname: true, avatar: true, phone: true, location: true, bio: true, rating: true, showEmail: true, showPhone: true, showLocation: true, createdAt: true },
});
if (!user) throw new AppError(404, 'User not found');
res.json({ user });
} catch (error) {
next(error);
}
});
router.post('/logout', authenticate, async (req, res, next) => {
try {
const token = req.cookies?.refreshToken;
if (token) {
await prisma.session.deleteMany({ where: { refreshToken: token } });
}
res.clearCookie('refreshToken');
res.json({ message: 'Logged out' });
} catch (error) {
next(error);
}
});
export default router;