Skip to content

Commit

Permalink
Yuri silveiraa/issue 26 (#94)
Browse files Browse the repository at this point in the history
* chore: add jsonwebtoken

* feat: add class JWTHelper

* feat: add middleware auth-jwt

* feat: add middleware auth-jwt

* feat: add middleware auth-jwt
  • Loading branch information
yuri-silveiraa authored Jun 12, 2024
1 parent 52c2bb2 commit 6bd4331
Show file tree
Hide file tree
Showing 8 changed files with 337 additions and 7 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@redocly/cli": "1.12.0",
"@types/bcrypt": "5.0.2",
"@types/express": "4.17.21",
"@types/jsonwebtoken": "9.0.6",
"@types/morgan": "1.9.9",
"@types/supertest": "6.0.2",
"@vitest/coverage-istanbul": "1.5.0",
Expand Down Expand Up @@ -78,7 +79,8 @@
"dotenv": "16.4.5",
"express": "4.19.2",
"glob": "10.3.12",
"joi": "17.13.1",
"joi": "17.13.0",
"jsonwebtoken": "9.0.2",
"morgan": "1.10.0"
},
"config": {
Expand Down
95 changes: 89 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ switch (process.env['MODE']) {
export default {
HOSTNAME: process.env['HOSTNAME'] || 'localhost',
PORT: process.env['PORT'] || 3000,
SECRET_KEY: process.env['SECRET_KEY'] || '321',
} as Record<string, string>;
12 changes: 12 additions & 0 deletions src/middlewares/auth/auth-jwt-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { AuthenticationJWT } from './auth-jwt.js';
import { UserRepository } from '@/features/user/repositories/user-repository/user-repository.js';
import { JWTHelper } from '@/shared/infra/jwt/jwt.js';
import env from 'src/config/env.js';

export function authJwtFactory() {
const jwt = new JWTHelper(env.SECRET_KEY);
const userRepository = new UserRepository();
const authJwt = new AuthenticationJWT(jwt, userRepository);

return { authJwt };
}
119 changes: 119 additions & 0 deletions src/middlewares/auth/auth-jwt.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import type { Request, Response } from 'express';
import { AuthenticationJWT } from './auth-jwt.js';
import type { UserRepository } from '@/features/user/repositories/user-repository/user-repository.js';
import { JWTHelper } from '@/shared/infra/jwt/jwt.js';

const secretKey = '321';

const makeSut = () => {
class UserRepositoryStub implements UserRepository {
create({ email, name, password, username }: any) {
return Promise.resolve({
createdAt: new Date(2024, 5, 1),
deletedAt: null,
email,
id: 'valid_id',
name,
password,
updatedAt: new Date(2024, 5, 1),
username,
});
}

findById(id: string): Promise<{
email: string;
id: string;
name: null | string;
username: string;
} | null> {
const user = {
email: 'fake@example.com',
id: 'fakeUserId',
name: 'FakeName',
username: 'FakeUserName',
};
if (id === 'fakeUserId') {
return Promise.resolve(user);
}
return Promise.resolve(null);
}
}

const userRepository = new UserRepositoryStub();
const jwtHelper = new JWTHelper(secretKey as string);
const auth = new AuthenticationJWT(jwtHelper, userRepository);

return { auth, jwtHelper, userRepository };
};

describe('jwtAuth middleware', () => {
let req: Partial<Request>;
let res: Partial<Response>;
let next: ReturnType<typeof vi.fn>;
const { auth, jwtHelper } = makeSut();

beforeEach(() => {
req = { headers: { authorization: 'Bearer' } };
res = { json: vi.fn(), status: vi.fn().mockReturnThis() };
next = vi.fn();
});

it('should call next if token is valid and user is found', async () => {
const token = jwtHelper.createToken({ userId: 'fakeUserId' });

req = { headers: { authorization: `Bearer ${token}` } };

await auth.jwtAuth(req as Request, res as Response, next);

expect(res.json).not.toHaveBeenCalled();
expect(res.status).not.toHaveBeenCalled();
expect(next).toHaveBeenCalled();
});

it('should return status code 401 with error message token missing', async () => {
req.headers!.authorization = undefined;

await auth.jwtAuth(req as Request, res as Response, next);

expect(res.status).toHaveBeenCalledWith(401);
expect(res.json).toHaveBeenCalledWith({ error: 'Token missing' });
expect(next).not.toHaveBeenCalled();
});

it('should return status code 401 with error message invalid token', async () => {
const token = 'invalidToken';

req = { headers: { authorization: `Bearer ${token}` } };

await auth.jwtAuth(req as Request, res as Response, next);

expect(res.status).toHaveBeenCalledWith(401);
expect(res.json).toHaveBeenCalledWith({ error: 'Invalid token' });
expect(next).not.toHaveBeenCalled();
});

it('should return status code 401 with error message invalid user', async () => {
const token = jwtHelper.createToken({ userId: '2' });

req = { headers: { authorization: `Bearer ${token}` } };

await auth.jwtAuth(req as Request, res as Response, next);
expect(res.status).toHaveBeenCalledWith(401);
expect(res.json).toHaveBeenCalledWith({ error: 'Invalid user' });
expect(next).not.toHaveBeenCalled();
});

it('should handle errors', async () => {
const mockedError = new Error('fakeError');
vi.spyOn(jwtHelper, 'parseToken').mockImplementation(() => {
throw mockedError;
});

req = { headers: { authorization: 'Bearer 23123' } };

await auth.jwtAuth(req as Request, res as Response, next);
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({ error: 'Internal Server Error' });
expect(next).not.toHaveBeenCalled();
});
});
35 changes: 35 additions & 0 deletions src/middlewares/auth/auth-jwt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { NextFunction, Request, Response } from 'express';
import type { UserRepository } from '@/features/user/repositories/user-repository/user-repository.js';
import type { JWTHelper } from '@/shared/infra/jwt/jwt.js';

export class AuthenticationJWT {
constructor(
private jwtHelper: JWTHelper,
private userRepository: UserRepository
) {}

async jwtAuth(req: Request, res: Response, next: NextFunction) {
try {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Token missing' });
}

const payload = this.jwtHelper.parseToken(token);
if (payload instanceof Error) {
return res.status(401).json({ error: 'Invalid token' });
}

const userId = payload.userId;
const user = await this.userRepository.findById(userId);
if (!user) {
return res.status(401).json({ error: 'Invalid user' });
}

next();
} catch (error) {
console.error('Erro durante a autenticação:', error);
return res.status(500).json({ error: 'Internal Server Error' });
}
}
}
Loading

0 comments on commit 6bd4331

Please sign in to comment.