Skip to content

Commit 6bd4331

Browse files
Yuri silveiraa/issue 26 (#94)
* chore: add jsonwebtoken * feat: add class JWTHelper * feat: add middleware auth-jwt * feat: add middleware auth-jwt * feat: add middleware auth-jwt
1 parent 52c2bb2 commit 6bd4331

File tree

8 files changed

+337
-7
lines changed

8 files changed

+337
-7
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"@redocly/cli": "1.12.0",
4141
"@types/bcrypt": "5.0.2",
4242
"@types/express": "4.17.21",
43+
"@types/jsonwebtoken": "9.0.6",
4344
"@types/morgan": "1.9.9",
4445
"@types/supertest": "6.0.2",
4546
"@vitest/coverage-istanbul": "1.5.0",
@@ -78,7 +79,8 @@
7879
"dotenv": "16.4.5",
7980
"express": "4.19.2",
8081
"glob": "10.3.12",
81-
"joi": "17.13.1",
82+
"joi": "17.13.0",
83+
"jsonwebtoken": "9.0.2",
8284
"morgan": "1.10.0"
8385
},
8486
"config": {

pnpm-lock.yaml

Lines changed: 89 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/config/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ switch (process.env['MODE']) {
3232
export default {
3333
HOSTNAME: process.env['HOSTNAME'] || 'localhost',
3434
PORT: process.env['PORT'] || 3000,
35+
SECRET_KEY: process.env['SECRET_KEY'] || '321',
3536
} as Record<string, string>;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { AuthenticationJWT } from './auth-jwt.js';
2+
import { UserRepository } from '@/features/user/repositories/user-repository/user-repository.js';
3+
import { JWTHelper } from '@/shared/infra/jwt/jwt.js';
4+
import env from 'src/config/env.js';
5+
6+
export function authJwtFactory() {
7+
const jwt = new JWTHelper(env.SECRET_KEY);
8+
const userRepository = new UserRepository();
9+
const authJwt = new AuthenticationJWT(jwt, userRepository);
10+
11+
return { authJwt };
12+
}

src/middlewares/auth/auth-jwt.test.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import type { Request, Response } from 'express';
2+
import { AuthenticationJWT } from './auth-jwt.js';
3+
import type { UserRepository } from '@/features/user/repositories/user-repository/user-repository.js';
4+
import { JWTHelper } from '@/shared/infra/jwt/jwt.js';
5+
6+
const secretKey = '321';
7+
8+
const makeSut = () => {
9+
class UserRepositoryStub implements UserRepository {
10+
create({ email, name, password, username }: any) {
11+
return Promise.resolve({
12+
createdAt: new Date(2024, 5, 1),
13+
deletedAt: null,
14+
email,
15+
id: 'valid_id',
16+
name,
17+
password,
18+
updatedAt: new Date(2024, 5, 1),
19+
username,
20+
});
21+
}
22+
23+
findById(id: string): Promise<{
24+
email: string;
25+
id: string;
26+
name: null | string;
27+
username: string;
28+
} | null> {
29+
const user = {
30+
31+
id: 'fakeUserId',
32+
name: 'FakeName',
33+
username: 'FakeUserName',
34+
};
35+
if (id === 'fakeUserId') {
36+
return Promise.resolve(user);
37+
}
38+
return Promise.resolve(null);
39+
}
40+
}
41+
42+
const userRepository = new UserRepositoryStub();
43+
const jwtHelper = new JWTHelper(secretKey as string);
44+
const auth = new AuthenticationJWT(jwtHelper, userRepository);
45+
46+
return { auth, jwtHelper, userRepository };
47+
};
48+
49+
describe('jwtAuth middleware', () => {
50+
let req: Partial<Request>;
51+
let res: Partial<Response>;
52+
let next: ReturnType<typeof vi.fn>;
53+
const { auth, jwtHelper } = makeSut();
54+
55+
beforeEach(() => {
56+
req = { headers: { authorization: 'Bearer' } };
57+
res = { json: vi.fn(), status: vi.fn().mockReturnThis() };
58+
next = vi.fn();
59+
});
60+
61+
it('should call next if token is valid and user is found', async () => {
62+
const token = jwtHelper.createToken({ userId: 'fakeUserId' });
63+
64+
req = { headers: { authorization: `Bearer ${token}` } };
65+
66+
await auth.jwtAuth(req as Request, res as Response, next);
67+
68+
expect(res.json).not.toHaveBeenCalled();
69+
expect(res.status).not.toHaveBeenCalled();
70+
expect(next).toHaveBeenCalled();
71+
});
72+
73+
it('should return status code 401 with error message token missing', async () => {
74+
req.headers!.authorization = undefined;
75+
76+
await auth.jwtAuth(req as Request, res as Response, next);
77+
78+
expect(res.status).toHaveBeenCalledWith(401);
79+
expect(res.json).toHaveBeenCalledWith({ error: 'Token missing' });
80+
expect(next).not.toHaveBeenCalled();
81+
});
82+
83+
it('should return status code 401 with error message invalid token', async () => {
84+
const token = 'invalidToken';
85+
86+
req = { headers: { authorization: `Bearer ${token}` } };
87+
88+
await auth.jwtAuth(req as Request, res as Response, next);
89+
90+
expect(res.status).toHaveBeenCalledWith(401);
91+
expect(res.json).toHaveBeenCalledWith({ error: 'Invalid token' });
92+
expect(next).not.toHaveBeenCalled();
93+
});
94+
95+
it('should return status code 401 with error message invalid user', async () => {
96+
const token = jwtHelper.createToken({ userId: '2' });
97+
98+
req = { headers: { authorization: `Bearer ${token}` } };
99+
100+
await auth.jwtAuth(req as Request, res as Response, next);
101+
expect(res.status).toHaveBeenCalledWith(401);
102+
expect(res.json).toHaveBeenCalledWith({ error: 'Invalid user' });
103+
expect(next).not.toHaveBeenCalled();
104+
});
105+
106+
it('should handle errors', async () => {
107+
const mockedError = new Error('fakeError');
108+
vi.spyOn(jwtHelper, 'parseToken').mockImplementation(() => {
109+
throw mockedError;
110+
});
111+
112+
req = { headers: { authorization: 'Bearer 23123' } };
113+
114+
await auth.jwtAuth(req as Request, res as Response, next);
115+
expect(res.status).toHaveBeenCalledWith(500);
116+
expect(res.json).toHaveBeenCalledWith({ error: 'Internal Server Error' });
117+
expect(next).not.toHaveBeenCalled();
118+
});
119+
});

src/middlewares/auth/auth-jwt.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { NextFunction, Request, Response } from 'express';
2+
import type { UserRepository } from '@/features/user/repositories/user-repository/user-repository.js';
3+
import type { JWTHelper } from '@/shared/infra/jwt/jwt.js';
4+
5+
export class AuthenticationJWT {
6+
constructor(
7+
private jwtHelper: JWTHelper,
8+
private userRepository: UserRepository
9+
) {}
10+
11+
async jwtAuth(req: Request, res: Response, next: NextFunction) {
12+
try {
13+
const token = req.headers.authorization?.split(' ')[1];
14+
if (!token) {
15+
return res.status(401).json({ error: 'Token missing' });
16+
}
17+
18+
const payload = this.jwtHelper.parseToken(token);
19+
if (payload instanceof Error) {
20+
return res.status(401).json({ error: 'Invalid token' });
21+
}
22+
23+
const userId = payload.userId;
24+
const user = await this.userRepository.findById(userId);
25+
if (!user) {
26+
return res.status(401).json({ error: 'Invalid user' });
27+
}
28+
29+
next();
30+
} catch (error) {
31+
console.error('Erro durante a autenticação:', error);
32+
return res.status(500).json({ error: 'Internal Server Error' });
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)