auth: controllers + jwt riggings
Pedro Lucas Porcellis porcellis@eletrotupi.com · 6 months ago
30e8babc0ef57b3aad96d854007a54810eb9e1fc Browse filesParents
90f9eceChanged files
7
Additions
+215
Deletions
-1
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
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;