-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* delete old user provider * enhance security, move login and register to auth * close app after test Co-authored-by: abdelillah <aghomari.professionnel@gmail.com>
- Loading branch information
1 parent
ecfb747
commit 003df0e
Showing
27 changed files
with
603 additions
and
418 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import { mockCreateResponse, mockCreateUser, mockUsers } from '../test-utils/data/data-test'; | ||
import { AuthController } from './auth.controller'; | ||
import { UsersService } from '../users/users.service'; | ||
import { AuthService } from './auth.service'; | ||
import { JwtService } from '@nestjs/jwt'; | ||
import mockedJwtService from '../test-utils/mocks/jwt-mock.service'; | ||
import * as request from 'supertest'; | ||
import { INestApplication, ValidationPipe } from '@nestjs/common'; | ||
import { ConfigService } from '@nestjs/config'; | ||
import * as passport from 'passport'; | ||
import { LocalStrategy } from './strategies/local.strategy'; | ||
|
||
describe('AuthController', () => { | ||
let app: INestApplication; | ||
const loginUser = { | ||
email: mockUsers[0].email, | ||
password: 'Super123+', | ||
}; | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
controllers: [AuthController], | ||
providers: [ | ||
AuthService, | ||
LocalStrategy, | ||
{ | ||
provide: JwtService, | ||
useValue: mockedJwtService, | ||
}, | ||
{ | ||
provide: ConfigService, | ||
useValue: { | ||
get: jest.fn().mockReturnValue('60s'), | ||
} | ||
}, | ||
{ | ||
provide: UsersService, | ||
useValue: { | ||
findUserByEmail: (email: string) => mockUsers.find(user => user.email === email), | ||
create: () => mockCreateResponse | ||
}, | ||
}, | ||
], | ||
}) | ||
.compile(); | ||
|
||
app = module.createNestApplication(); | ||
app.useGlobalPipes(new ValidationPipe()); | ||
app.use(passport.initialize()) | ||
await app.init(); | ||
}); | ||
|
||
describe('login', () => { | ||
|
||
it('should return 401 if username does not exist', () => { | ||
return request(app.getHttpServer()) | ||
.post('/auth/login') | ||
.send({email: 'unknown', password: 'anything'}) | ||
.expect(401) | ||
}) | ||
|
||
it('should return 401 if password is incorrect', () => { | ||
return request(app.getHttpServer()) | ||
.post('/auth/login') | ||
.send({email: mockUsers[0].email, password: 'anything'}) | ||
.expect(401) | ||
}) | ||
|
||
it('should return a user with jwt', () => { | ||
|
||
const result = { | ||
_id: mockUsers[0]._id, | ||
email: mockUsers[0].email, | ||
username: mockUsers[0].username | ||
}; | ||
|
||
return request(app.getHttpServer()) | ||
.post('/auth/login') | ||
.send(loginUser) | ||
.expect(201) | ||
.expect(result) | ||
}); | ||
}); | ||
|
||
describe('register', () => { | ||
|
||
it('should return a user without password', () => { | ||
|
||
return request(app.getHttpServer()) | ||
.post('/auth/register') | ||
.send(mockCreateUser) | ||
.expect(201) | ||
.expect(mockCreateResponse); | ||
}); | ||
}); | ||
|
||
afterAll(done => { | ||
app.close(); | ||
done(); | ||
}) | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Body, Controller, Post, Request, Res, UseGuards } from '@nestjs/common'; | ||
import { AuthService } from './auth.service'; | ||
import { LocalAuthGuard } from './guards/local-auth.guard'; | ||
import { Response } from 'express'; | ||
import { JwtAuthGuard } from './guards/jwt-auth.guard'; | ||
import { UsersService } from '../users/users.service'; | ||
import { CreateUserDto } from '../users/dto/create-user.dto'; | ||
import { IRequestWithUser } from '../users/interfaces/request-with-user.interface'; | ||
|
||
@Controller('auth') | ||
export class AuthController { | ||
constructor( | ||
private readonly authService: AuthService, | ||
private readonly usersService: UsersService, | ||
) { } | ||
|
||
@Post('register') | ||
create(@Body() createUserDto: CreateUserDto) { | ||
return this.usersService.create(createUserDto); | ||
} | ||
|
||
@UseGuards(LocalAuthGuard) | ||
@Post('login') | ||
login(@Request() request: IRequestWithUser, @Res() response: Response) { | ||
const { user } = request; | ||
const cookie = this.authService.getCookieWithJwtToken(user._id); | ||
response.setHeader('Set-Cookie', cookie); | ||
user.password = undefined; | ||
return response.send(user); | ||
} | ||
|
||
@UseGuards(JwtAuthGuard) | ||
@Post('logout') | ||
async logOut(@Request() request: IRequestWithUser, @Res() response: Response) { | ||
response.setHeader('Set-Cookie', this.authService.getCookieForLogOut()); | ||
return response.sendStatus(200); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,22 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { ConfigModule } from '@nestjs/config'; | ||
import { JwtModule } from '@nestjs/jwt'; | ||
import { PassportModule } from '@nestjs/passport'; | ||
import { AuthService } from './auth.service'; | ||
import { jwtConstants } from './constants'; | ||
import { JwtStrategy } from './strategies/jwt.strategy'; | ||
import { LocalStrategy } from './strategies/local.strategy'; | ||
import { AuthController } from './auth.controller'; | ||
import { UsersModule } from '../users/users.module'; | ||
|
||
@Module({ | ||
imports: [ | ||
PassportModule, | ||
JwtModule.register({ | ||
secret: jwtConstants.secret, | ||
signOptions: { expiresIn: '60s' }, | ||
}), | ||
], | ||
providers: [AuthService, JwtStrategy], | ||
exports: [AuthService], | ||
controllers: [], | ||
imports: [ | ||
ConfigModule, | ||
PassportModule, | ||
UsersModule, | ||
JwtModule.register({}), | ||
], | ||
providers: [AuthService, JwtStrategy, LocalStrategy], | ||
exports: [AuthService], | ||
controllers: [AuthController], | ||
}) | ||
export class AuthModule {} | ||
export class AuthModule { } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,64 @@ | ||
import { ConfigService } from '@nestjs/config'; | ||
import { JwtService } from '@nestjs/jwt'; | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import { mockUsers } from '../test-utils/data/data-test'; | ||
import mockedJwtService from '../test-utils/mocks/jwt-mock.service'; | ||
import { UsersService } from '../users/users.service'; | ||
import { AuthService } from './auth.service'; | ||
|
||
describe('AuthService', () => { | ||
let service: AuthService; | ||
let service: AuthService; | ||
let get: jest.Mock; | ||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [ | ||
AuthService, | ||
{ | ||
provide: JwtService, | ||
useValue: mockedJwtService, | ||
}, | ||
{ | ||
provide: ConfigService, | ||
useValue: { | ||
get: jest.fn().mockReturnValue('60s'), | ||
} | ||
}, | ||
{ | ||
provide: UsersService, | ||
useValue: { | ||
findUserByEmail: jest.fn().mockReturnValue(mockUsers[0]) | ||
}, | ||
}, | ||
], | ||
}).compile(); | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [ | ||
AuthService, | ||
{ | ||
provide: JwtService, | ||
useValue: mockedJwtService, | ||
}, | ||
], | ||
}).compile(); | ||
service = module.get<AuthService>(AuthService); | ||
}); | ||
|
||
describe('when ask for a cookie', () => { | ||
it('should return a string', () => { | ||
get | ||
let userId = '0'; | ||
let expectedResult = 'Authentication=mercure23beta; HttpOnly; Path=/; Max-Age=60s'; | ||
let jwt = service.getCookieWithJwtToken(userId); | ||
expect(jwt).toBe(expectedResult); | ||
}); | ||
}); | ||
|
||
service = module.get<AuthService>(AuthService); | ||
}); | ||
describe('when logout', () => { | ||
it('should return an empty', () => { | ||
let expectedResult = "Authentication=; HttpOnly; Path=/; Max-Age=0"; | ||
let jwt = service.getCookieForLogOut(); | ||
expect(jwt).toBe(expectedResult); | ||
}); | ||
}); | ||
|
||
describe('when login', () => { | ||
it('should return a string', async () => { | ||
const userId = '1'; | ||
let jwt = await service.login(userId); | ||
expect(jwt).toBe('mercure23beta'); | ||
describe('when ask for authenticated user', () => { | ||
it('should return a user', async () => { | ||
let userEmail = '96abdou96@gmail.com'; | ||
let userPassword = 'Super123+'; | ||
let user = await service.getAuthenticatedUser(userEmail, userPassword); | ||
expect(user).toBe(mockUsers[0]); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,46 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { Injectable, NotFoundException, UnauthorizedException } from '@nestjs/common'; | ||
import { ConfigService } from '@nestjs/config'; | ||
import { JwtService } from '@nestjs/jwt'; | ||
import * as bcrypt from 'bcrypt'; | ||
import { IUser } from '../users/interfaces/user.interface'; | ||
import { UsersService } from '../users/users.service'; | ||
|
||
@Injectable() | ||
export class AuthService { | ||
constructor(private jwtService: JwtService) {} | ||
constructor( | ||
private jwtService: JwtService, | ||
private configService: ConfigService, | ||
private usersService: UsersService | ||
) { } | ||
|
||
async login(userId: string): Promise<string> { | ||
const payload = { userId }; | ||
return this.jwtService.sign(payload); | ||
} | ||
public getCookieWithJwtToken(userId: string): string { | ||
const payload = { userId }; | ||
const token = this.jwtService.sign(payload, { | ||
secret: this.configService.get('JWT_SECRET'), | ||
expiresIn: `${this.configService.get('JWT_EXPIRATION_TIME')}` | ||
}); | ||
return `Authentication=${token}; HttpOnly; Path=/; Max-Age=${this.configService.get('JWT_EXPIRATION_TIME')}`; | ||
} | ||
|
||
public async getAuthenticatedUser(email: string, password: string): Promise<IUser> { | ||
const user = await this.usersService.findUserByEmail(email); | ||
if (!user) { | ||
throw new UnauthorizedException("User doesn't exist"); | ||
} | ||
await this.checkPassword(password, user); | ||
return user; | ||
} | ||
|
||
public getCookieForLogOut() { | ||
return `Authentication=; HttpOnly; Path=/; Max-Age=0`; | ||
} | ||
|
||
|
||
async checkPassword(password: string, user: IUser): Promise<boolean> { | ||
const match = await bcrypt.compare(password, user.password); | ||
if (!match) { | ||
throw new UnauthorizedException('Wrong email or password.'); | ||
} | ||
return match; | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { AuthGuard } from '@nestjs/passport'; | ||
|
||
@Injectable() | ||
export class LocalAuthGuard extends AuthGuard('local') {} |
Oops, something went wrong.