|
import { Router } from 'express' |
|
import bcrypt from 'bcryptjs' |
|
import jwt from 'jsonwebtoken' |
|
import Joi from 'joi' |
|
import { prisma } from '../config/database' |
|
import { authMiddleware, AuthRequest } from '../middleware/auth' |
|
import { authRateLimiter } from '../middleware/rateLimiter' |
|
import { asyncHandler } from '../middleware/errorHandler' |
|
|
|
const router = Router() |
|
|
|
|
|
const registerSchema = Joi.object({ |
|
email: Joi.string().email().required(), |
|
username: Joi.string().alphanum().min(3).max(20).required(), |
|
password: Joi.string().min(6).required(), |
|
displayName: Joi.string().min(1).max(50).required(), |
|
}) |
|
|
|
const loginSchema = Joi.object({ |
|
email: Joi.string().email().required(), |
|
password: Joi.string().required(), |
|
}) |
|
|
|
const changePasswordSchema = Joi.object({ |
|
currentPassword: Joi.string().required(), |
|
newPassword: Joi.string().min(6).required(), |
|
}) |
|
|
|
|
|
const generateToken = (userId: string) => { |
|
return jwt.sign( |
|
{ userId }, |
|
process.env.JWT_SECRET!, |
|
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' } |
|
) |
|
} |
|
|
|
|
|
router.post('/register', authRateLimiter, asyncHandler(async (req, res) => { |
|
const { error, value } = registerSchema.validate(req.body) |
|
if (error) { |
|
return res.status(400).json({ |
|
success: false, |
|
error: error.details[0].message |
|
}) |
|
} |
|
|
|
const { email, username, password, displayName } = value |
|
|
|
|
|
const existingUser = await prisma.user.findFirst({ |
|
where: { |
|
OR: [ |
|
{ email }, |
|
{ username } |
|
] |
|
} |
|
}) |
|
|
|
if (existingUser) { |
|
return res.status(409).json({ |
|
success: false, |
|
error: existingUser.email === email ? 'Email already registered' : 'Username already taken' |
|
}) |
|
} |
|
|
|
|
|
const hashedPassword = await bcrypt.hash(password, 12) |
|
|
|
|
|
const user = await prisma.user.create({ |
|
data: { |
|
email, |
|
username, |
|
password: hashedPassword, |
|
displayName, |
|
}, |
|
select: { |
|
id: true, |
|
email: true, |
|
username: true, |
|
displayName: true, |
|
avatar: true, |
|
bio: true, |
|
isOnline: true, |
|
lastSeen: true, |
|
isAdmin: true, |
|
isVerified: true, |
|
createdAt: true, |
|
updatedAt: true, |
|
} |
|
}) |
|
|
|
|
|
const token = generateToken(user.id) |
|
|
|
res.status(201).json({ |
|
success: true, |
|
data: { |
|
user, |
|
token |
|
} |
|
}) |
|
})) |
|
|
|
|
|
router.post('/login', authRateLimiter, asyncHandler(async (req, res) => { |
|
const { error, value } = loginSchema.validate(req.body) |
|
if (error) { |
|
return res.status(400).json({ |
|
success: false, |
|
error: error.details[0].message |
|
}) |
|
} |
|
|
|
const { email, password } = value |
|
|
|
|
|
const user = await prisma.user.findUnique({ |
|
where: { email } |
|
}) |
|
|
|
if (!user) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: 'Invalid credentials' |
|
}) |
|
} |
|
|
|
|
|
const isValidPassword = await bcrypt.compare(password, user.password) |
|
if (!isValidPassword) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: 'Invalid credentials' |
|
}) |
|
} |
|
|
|
|
|
await prisma.user.update({ |
|
where: { id: user.id }, |
|
data: { |
|
isOnline: true, |
|
lastSeen: new Date() |
|
} |
|
}) |
|
|
|
|
|
const token = generateToken(user.id) |
|
|
|
|
|
const { password: _, ...userWithoutPassword } = user |
|
|
|
res.json({ |
|
success: true, |
|
data: { |
|
user: userWithoutPassword, |
|
token |
|
} |
|
}) |
|
})) |
|
|
|
|
|
router.post('/logout', authMiddleware, asyncHandler(async (req: AuthRequest, res) => { |
|
|
|
await prisma.user.update({ |
|
where: { id: req.user!.id }, |
|
data: { |
|
isOnline: false, |
|
lastSeen: new Date() |
|
} |
|
}) |
|
|
|
res.json({ |
|
success: true, |
|
message: 'Logged out successfully' |
|
}) |
|
})) |
|
|
|
|
|
router.get('/me', authMiddleware, asyncHandler(async (req: AuthRequest, res) => { |
|
res.json({ |
|
success: true, |
|
data: req.user |
|
}) |
|
})) |
|
|
|
|
|
router.post('/change-password', authMiddleware, asyncHandler(async (req: AuthRequest, res) => { |
|
const { error, value } = changePasswordSchema.validate(req.body) |
|
if (error) { |
|
return res.status(400).json({ |
|
success: false, |
|
error: error.details[0].message |
|
}) |
|
} |
|
|
|
const { currentPassword, newPassword } = value |
|
|
|
|
|
const user = await prisma.user.findUnique({ |
|
where: { id: req.user!.id } |
|
}) |
|
|
|
if (!user) { |
|
return res.status(404).json({ |
|
success: false, |
|
error: 'User not found' |
|
}) |
|
} |
|
|
|
|
|
const isValidPassword = await bcrypt.compare(currentPassword, user.password) |
|
if (!isValidPassword) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: 'Current password is incorrect' |
|
}) |
|
} |
|
|
|
|
|
const hashedNewPassword = await bcrypt.hash(newPassword, 12) |
|
|
|
|
|
await prisma.user.update({ |
|
where: { id: user.id }, |
|
data: { |
|
password: hashedNewPassword |
|
} |
|
}) |
|
|
|
res.json({ |
|
success: true, |
|
message: 'Password changed successfully' |
|
}) |
|
})) |
|
|
|
|
|
router.post('/refresh', asyncHandler(async (req, res) => { |
|
const { refreshToken } = req.body |
|
|
|
if (!refreshToken) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: 'Refresh token required' |
|
}) |
|
} |
|
|
|
try { |
|
const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!) as { userId: string } |
|
|
|
const user = await prisma.user.findUnique({ |
|
where: { id: decoded.userId }, |
|
select: { |
|
id: true, |
|
email: true, |
|
username: true, |
|
displayName: true, |
|
avatar: true, |
|
bio: true, |
|
isOnline: true, |
|
lastSeen: true, |
|
isAdmin: true, |
|
isVerified: true, |
|
createdAt: true, |
|
updatedAt: true, |
|
} |
|
}) |
|
|
|
if (!user) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: 'Invalid refresh token' |
|
}) |
|
} |
|
|
|
const newToken = generateToken(user.id) |
|
|
|
res.json({ |
|
success: true, |
|
data: { |
|
user, |
|
token: newToken |
|
} |
|
}) |
|
} catch (error) { |
|
return res.status(401).json({ |
|
success: false, |
|
error: 'Invalid refresh token' |
|
}) |
|
} |
|
})) |
|
|
|
export default router |
|
|