auth.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import { Router, Request, Response } from 'express';
  2. import bcrypt from 'bcryptjs';
  3. import jwt from 'jsonwebtoken';
  4. import { prisma } from '../lib/prisma';
  5. import { authMiddleware } from '../lib/auth';
  6. const router = Router();
  7. const JWT_SECRET = process.env.JWT_SECRET || 'fallback-secret';
  8. const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
  9. // POST /api/auth/register
  10. router.post('/register', async (req: Request, res: Response) => {
  11. try {
  12. const { email, name, password } = req.body;
  13. if (!email || !name || !password) {
  14. res.status(400).json({ error: 'email, name, and password are required' });
  15. return;
  16. }
  17. if (password.length < 6) {
  18. res.status(400).json({ error: 'Password must be at least 6 characters' });
  19. return;
  20. }
  21. const existing = await prisma.user.findUnique({ where: { email } });
  22. if (existing) {
  23. res.status(409).json({ error: 'Email already registered' });
  24. return;
  25. }
  26. const hashed = await bcrypt.hash(password, 12);
  27. const user = await prisma.user.create({
  28. data: { email, name, password: hashed },
  29. select: { id: true, email: true, name: true, role: true, avatarUrl: true },
  30. });
  31. const token = jwt.sign(
  32. { userId: user.id, email: user.email, role: user.role },
  33. JWT_SECRET,
  34. { expiresIn: JWT_EXPIRES_IN } as jwt.SignOptions
  35. );
  36. res.cookie('token', token, {
  37. httpOnly: true,
  38. secure: false, // set true if serving over HTTPS
  39. sameSite: 'lax',
  40. maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
  41. });
  42. res.status(201).json({ user, token });
  43. } catch (err) {
  44. console.error('Register error:', err);
  45. res.status(500).json({ error: 'Internal server error' });
  46. }
  47. });
  48. // POST /api/auth/login
  49. router.post('/login', async (req: Request, res: Response) => {
  50. try {
  51. const { email, password } = req.body;
  52. if (!email || !password) {
  53. res.status(400).json({ error: 'email and password are required' });
  54. return;
  55. }
  56. const user = await prisma.user.findUnique({ where: { email } });
  57. if (!user) {
  58. res.status(401).json({ error: 'Invalid credentials' });
  59. return;
  60. }
  61. const valid = await bcrypt.compare(password, user.password);
  62. if (!valid) {
  63. res.status(401).json({ error: 'Invalid credentials' });
  64. return;
  65. }
  66. const token = jwt.sign(
  67. { userId: user.id, email: user.email, role: user.role },
  68. JWT_SECRET,
  69. { expiresIn: JWT_EXPIRES_IN } as jwt.SignOptions
  70. );
  71. res.cookie('token', token, {
  72. httpOnly: true,
  73. secure: false, // set true if serving over HTTPS
  74. sameSite: 'lax',
  75. maxAge: 7 * 24 * 60 * 60 * 1000,
  76. });
  77. res.json({
  78. user: {
  79. id: user.id,
  80. email: user.email,
  81. name: user.name,
  82. role: user.role,
  83. avatarUrl: user.avatarUrl,
  84. },
  85. token,
  86. });
  87. } catch (err) {
  88. console.error('Login error:', err);
  89. res.status(500).json({ error: 'Internal server error' });
  90. }
  91. });
  92. // POST /api/auth/logout
  93. router.post('/logout', (_req: Request, res: Response) => {
  94. res.clearCookie('token');
  95. res.json({ message: 'Logged out' });
  96. });
  97. // GET /api/auth/me
  98. router.get('/me', authMiddleware, async (req: Request, res: Response) => {
  99. try {
  100. const user = await prisma.user.findUnique({
  101. where: { id: req.user!.userId },
  102. select: { id: true, email: true, name: true, role: true, avatarUrl: true },
  103. });
  104. if (!user) {
  105. res.status(404).json({ error: 'User not found' });
  106. return;
  107. }
  108. res.json({ user });
  109. } catch (err) {
  110. console.error('Me error:', err);
  111. res.status(500).json({ error: 'Internal server error' });
  112. }
  113. });
  114. export default router;