eletrotupi / tcc/ commit / 30e8bab

auth: controllers + jwt riggings

Pedro Lucas Porcellis porcellis@eletrotupi.com · 6 months ago 30e8babc0ef57b3aad96d854007a54810eb9e1fc Browse files
Parents 90f9ece
Changed files
7
Additions
+215
Deletions
-1
api/.env.example modified+2
api/.env.example
1 1
DATABASE_URL="file:./orbit.db"
2 +
JWT_SECRET=your-secret-key-here-change-in-production
3 +
FRONTEND_URL=http://192.168.3.96:8000
/dev/null → api/src/controllers/auth.ts
1 +
import { Request, Response, NextFunction } from 'express';
2 +
import {
3 +
  authenticateUser,
4 +
  generateToken,
5 +
  initiatePasswordReset,
6 +
  resetPassword
7 +
} from '@app/models/user';
8 +
import {
9 +
  validateRequiredFields
10 +
} from '@app/utils/validators';
11 +
import { verifyToken } from '@app/lib/jwt';
12 +

13 +
export const AuthController = {
14 +
  login: async (req: Request, res: Response, next: NextFunction) => {
15 +
    try {
16 +
      const { email, password } = req.body;
17 +

18 +
      const requiredValidation = validateRequiredFields(req.body, [
19 +
        'email', 'password'
20 +
      ]);
21 +

22 +
      if (!requiredValidation.valid) {
23 +
        return res.status(400).json({
24 +
          error: 'Missing required fields',
25 +
          missing: requiredValidation.missing
26 +
        });
27 +
      }
28 +

29 +
      const user = await authenticateUser(email, password);
30 +
      const jwtToken = generateToken(user.id, user.email);
31 +

32 +
      res.json({
33 +
        token: jwtToken,
34 +
        user: {
35 +
          id: user.id,
36 +
          email: user.email,
37 +
          firstName: user.firstName,
38 +
          lastName: user.lastName
39 +
        }
40 +
      });
41 +
    } catch (err: any) {
42 +
      if (err.message === 'Invalid credentials') {
43 +
        return res.status(401).json({ error: 'Invalid credentials' });
44 +
      }
45 +
      next(err);
46 +
    }
47 +
  },
48 +

49 +
  forgotPassword: async (req: Request, res: Response, next: NextFunction) => {
50 +
    try {
51 +
      const { email } = req.body;
52 +

53 +
      const requiredValidation = validateRequiredFields(req.body, ['email']);
54 +

55 +
      if (!requiredValidation.valid) {
56 +
        return res.status(400).json({
57 +
          error: 'Email is required',
58 +
          missing: requiredValidation.missing
59 +
        });
60 +
      }
61 +

62 +
      const result = await initiatePasswordReset(email);
63 +

64 +
      // In production, don't send token in response
65 +
      // Instead, send email with reset link
66 +
      res.json({
67 +
        message: 'Password reset instructions sent to your email',
68 +
        ...(!process.env.NODE_ENV || process.env.NODE_ENV === 'development' ? { token: result.token } : {})
69 +
      });
70 +
    } catch (err) {
71 +
      next(err);
72 +
    }
73 +
  },
74 +

75 +
  resetPassword: async (req: Request, res: Response, next: NextFunction) => {
76 +
    try {
77 +
      const { token, password } = req.body;
78 +

79 +
      const requiredValidation = validateRequiredFields(req.body, [
80 +
        'token', 'password'
81 +
      ]);
82 +

83 +
      if (!requiredValidation.valid) {
84 +
        return res.status(400).json({
85 +
          error: 'Missing required fields',
86 +
          missing: requiredValidation.missing
87 +
        });
88 +
      }
89 +

90 +
      await resetPassword(token, password);
91 +

92 +
      res.json({ message: 'Password successfully reset' });
93 +
    } catch (err: any) {
94 +
      if (err.message.includes('token')) {
95 +
        return res.status(400).json({ error: err.message });
96 +
      }
97 +
      next(err);
98 +
    }
99 +
  },
100 +

101 +
  verify: async (req: Request, res: Response, next: NextFunction) => {
102 +
    try {
103 +
      const authHeader = req.headers.authorization;
104 +

105 +
      if (!authHeader || !authHeader.startsWith('Bearer ')) {
106 +
        return res.status(401).json({ error: 'No token provided' });
107 +
      }
108 +

109 +
      const token = authHeader.substring(7);
110 +
      const payload = verifyToken(token);
111 +

112 +
      res.json({
113 +
        valid: true,
114 +
        userId: payload.userId,
115 +
        email: payload.email
116 +
      });
117 +
    } catch (err: any) {
118 +
      if (err.name === 'JsonWebTokenError' || err.name === 'TokenExpiredError') {
119 +
        return res.status(401).json({ error: 'Invalid or expired token' });
120 +
      }
121 +
      next(err);
122 +
    }
123 +
  }
124 +
};
api/src/controllers/users.ts
1 1
import { Request, Response, NextFunction } from 'express';
2 2
import {
3 3
  createUser,
4 4
  generateToken
5 5
} from '@app/models/user';
27 27

28 28
      const user = await createUser(
29 29
        email, firstName, lastName, password
30 30
      );
31 31

32 -
      const jwtToken = generateToken(user.id);
32 +
      const jwtToken = generateToken(user.id, user.email);
33 33

34 34
      res.status(201).json({ token: jwtToken });
35 35
    } catch (err) {
36 36
      next(err);
37 37
    }
38 38
  }
39 39
};
api/src/index.ts modified+14
api/src/index.ts
1 1
import express from 'express';
2 +
import cors from 'cors';
3 +
import morgan from 'morgan';
2 4
import userRouter from '@app/routes/users'
5 +
import authRouter from '@app/routes/auth'
3 6
import { errorHandler } from '@app/middleware/errorHandler';
4 7

5 8
const port = process.env.PORT || 3000;
6 9

7 10
const app = express();
11 +

12 +
// Configure CORS
13 +
// Fucking expo
14 +
app.use(cors({
15 +
  //origin: process.env.FRONTEND_URL || 'http://localhost:8081', // Expo default port
16 +
  origin: "*",
17 +
  credentials: true
18 +
}));
19 +

8 20
app.use(express.json())
21 +
app.use(morgan('combined'))
9 22

10 23
app.use("/users", userRouter);
24 +
app.use("/auth", authRouter);
11 25

12 26
app.use(errorHandler); // always last
13 27

14 28
app.listen(port, () => {
15 29
  console.log(`Server booted on port ${port}`)
16 30
})
17 31

/dev/null → api/src/lib/jwt.ts
1 +
import jwt from 'jsonwebtoken';
2 +
import crypto from 'crypto';
3 +

4 +
const JWT_SECRET = process.env.JWT_SECRET || 'development-secret-change-in-production';
5 +
const JWT_EXPIRES_IN = '7d';
6 +
const PASSWORD_RESET_EXPIRES_IN = '1h';
7 +

8 +
interface TokenPayload {
9 +
  userId: number;
10 +
  email: string;
11 +
}
12 +

13 +
export const generateToken = (userId: number, email: string): string => {
14 +
  const payload: TokenPayload = { userId, email };
15 +
  return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
16 +
};
17 +

18 +
export const verifyToken = (token: string): TokenPayload => {
19 +
  return jwt.verify(token, JWT_SECRET) as TokenPayload;
20 +
};
21 +

22 +
export const generatePasswordResetToken = (): string => {
23 +
  return crypto.randomBytes(32).toString('hex');
24 +
};
25 +

26 +
export const getPasswordResetExpiration = (): Date => {
27 +
  const expirationTime = new Date();
28 +
  expirationTime.setHours(expirationTime.getHours() + 1); // 1 hour from now
29 +
  return expirationTime;
30 +
};
31 +

32 +
export const isTokenExpired = (expirationDate: Date): boolean => {
33 +
  return new Date() > expirationDate;
34 +
};
/dev/null → api/src/middleware/auth.ts
1 +
import { Request, Response, NextFunction } from 'express';
2 +
import { verifyToken } from '@app/lib/jwt';
3 +

4 +
export interface AuthenticatedRequest extends Request {
5 +
  userId?: number;
6 +
  userEmail?: string;
7 +
}
8 +

9 +
export const requireAuth = (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
10 +
  try {
11 +
    const authHeader = req.headers.authorization;
12 +

13 +
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
14 +
      return res.status(401).json({ error: 'Unauthorized: No token provided' });
15 +
    }
16 +

17 +
    const token = authHeader.substring(7);
18 +
    const payload = verifyToken(token);
19 +

20 +
    req.userId = payload.userId;
21 +
    req.userEmail = payload.email;
22 +

23 +
    next();
24 +
  } catch (err: any) {
25 +
    if (err.name === 'JsonWebTokenError') {
26 +
      return res.status(401).json({ error: 'Unauthorized: Invalid token' });
27 +
    }
28 +
    if (err.name === 'TokenExpiredError') {
29 +
      return res.status(401).json({ error: 'Unauthorized: Token expired' });
30 +
    }
31 +
    next(err);
32 +
  }
33 +
};
/dev/null → api/src/routes/auth.ts
1 +
import { Router } from 'express';
2 +
import { AuthController } from '@app/controllers/auth';
3 +

4 +
const router = Router();
5 +

6 +
router.post('/login', AuthController.login);
7 +
router.post('/forgot-password', AuthController.forgotPassword);
8 +
router.post('/reset-password', AuthController.resetPassword);
9 +
router.get('/verify', AuthController.verify);
10 +

11 +
export default router;