-
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.
[Fix][Refactor][Test] Login password validation
Added proper password validation on login. Fixed the cardinal sin of saving a password in plain-text. Refactored AuthService.findUser to be easier to use. Session serializer now stores only user ids in the cookie. Adapted UserService unit tests to the changes. Resolves #17.
- Loading branch information
1 parent
17c2068
commit 645b774
Showing
9 changed files
with
9,540 additions
and
56 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
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
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,17 +1,25 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { Injectable, UnauthorizedException } from '@nestjs/common'; | ||
|
||
import AuthError from '../errors/auth.error'; | ||
import { AuthService } from '../services/auth.service'; | ||
import { PassportStrategy } from '@nestjs/passport'; | ||
import { Strategy } from 'passport-local'; | ||
import { AuthService } from '../services/auth.service'; | ||
|
||
@Injectable() | ||
export class AuthLocalStrategy extends PassportStrategy(Strategy, 'local') { | ||
constructor(private readonly authService: AuthService) { | ||
super(); | ||
} | ||
|
||
validate(username: string, password: string) { | ||
async validate(username: string, password: string) { | ||
const userDetails = { username, password }; | ||
const user = this.authService.validateUser(userDetails); | ||
return user; | ||
try { | ||
const user = await this.authService.validateUser(userDetails); | ||
return user; | ||
} catch (ex) { | ||
if (ex instanceof AuthError) { | ||
throw new UnauthorizedException(ex.message); | ||
} | ||
} | ||
} | ||
} |
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,22 +1,22 @@ | ||
import { AuthService } from '../services/auth.service'; | ||
import { IUser } from 'src/users/entities/user.entity'; | ||
import { Injectable } from '@nestjs/common'; | ||
import { PassportSerializer } from '@nestjs/passport'; | ||
import { User } from 'src/users/entities/user.entity'; | ||
|
||
type Done = (err: Error, user: User) => void; | ||
type Done = (err: Error, user: IUser) => void; | ||
|
||
@Injectable() | ||
export class SessionSerializerUtil extends PassportSerializer { | ||
constructor(private readonly authService: AuthService) { | ||
super(); | ||
} | ||
|
||
serializeUser(user: User, done: Done) { | ||
done(null, user); | ||
serializeUser(user: IUser, done: Done) { | ||
done(null, { id: user.id }); | ||
} | ||
|
||
async deserializeUser(user: User, done: Done) { | ||
const userFromDB = await this.authService.validateUser(user); | ||
async deserializeUser(user: IUser, done: Done) { | ||
const userFromDB = await this.authService.findUser(user); | ||
return userFromDB ? done(null, userFromDB) : done(null, null); | ||
} | ||
} |
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 |
---|---|---|
@@ -1,3 +1,5 @@ | ||
import * as bcrypt from 'bcrypt'; | ||
|
||
import { IUser, User } from '../entities/user.entity'; | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
|
||
|
@@ -10,15 +12,23 @@ describe('UsersService', () => { | |
|
||
const mockUser: IUser = { | ||
id: uuid(), | ||
password: 'correct_password', | ||
username: 'gosho', | ||
email: '[email protected]', | ||
googleId: '133773578008135', | ||
discordId: '8008135420889696', | ||
githubId: '42080085', | ||
}; | ||
|
||
const hashedPassword = bcrypt.hashSync(mockUser.password, 10); | ||
|
||
const mockUsersRepository = { | ||
findOne: jest.fn().mockImplementation(() => Promise.resolve(mockUser)), | ||
findOne: jest.fn().mockImplementation(() => | ||
Promise.resolve({ | ||
...mockUser, | ||
password: hashedPassword, | ||
}), | ||
), | ||
save: jest | ||
.fn() | ||
.mockImplementation((entity: IUser) => | ||
|
@@ -41,31 +51,73 @@ describe('UsersService', () => { | |
expect(service).toBeDefined(); | ||
}); | ||
|
||
it('findById should search for a user by id and return it', async () => { | ||
expect(await service.findByUsername(mockUser.username)).toEqual({ | ||
...mockUser, | ||
password: hashedPassword, | ||
}); | ||
}); | ||
|
||
it('findByUsername should search for a user by username and return it', async () => { | ||
expect(await service.findByUsername(mockUser.username)).toEqual(mockUser); | ||
expect(await service.findByUsername(mockUser.username)).toEqual({ | ||
...mockUser, | ||
password: hashedPassword, | ||
}); | ||
}); | ||
|
||
it('findByEmail should search for a user by email and return it', async () => { | ||
expect(await service.findByEmail(mockUser.email)).toEqual(mockUser); | ||
expect(await service.findByEmail(mockUser.email)).toEqual({ | ||
...mockUser, | ||
password: hashedPassword, | ||
}); | ||
}); | ||
|
||
it('findByGoogleId should search for a user by a google id and return it', async () => { | ||
expect(await service.findByGoogleId(mockUser.googleId)).toEqual(mockUser); | ||
expect(await service.findByGoogleId(mockUser.googleId)).toEqual({ | ||
...mockUser, | ||
password: hashedPassword, | ||
}); | ||
}); | ||
|
||
it('findByDiscordId should search for a user by a discord id and return it', async () => { | ||
expect(await service.findByDiscordId(mockUser.discordId)).toEqual(mockUser); | ||
expect(await service.findByDiscordId(mockUser.discordId)).toEqual({ | ||
...mockUser, | ||
password: hashedPassword, | ||
}); | ||
}); | ||
|
||
it('findByGithubId should search for a user by github id and return it', async () => { | ||
expect(await service.findByGithubId(mockUser.githubId)).toEqual(mockUser); | ||
expect(await service.findByGithubId(mockUser.githubId)).toEqual({ | ||
...mockUser, | ||
password: hashedPassword, | ||
}); | ||
}); | ||
|
||
it('createUser should create new users and return it', async () => { | ||
expect(await service.createUser({ username: 'gosho' })).toEqual({ | ||
id: expect.any(String), | ||
username: 'gosho', | ||
it('createUser should create a new user, hash the password and return it', async () => { | ||
const user = await service.createUser({ | ||
username: mockUser.username, | ||
password: mockUser.password, | ||
}); | ||
|
||
expect(user.id).toEqual(expect.any(String)); | ||
expect(user.username).toEqual(mockUser.username); | ||
expect(user.password).toEqual(expect.any(String)); | ||
expect(user.password).not.toBe(mockUser.password); | ||
|
||
expect(mockUsersRepository.save).toBeCalledTimes(1); | ||
}); | ||
|
||
it('verifyPassword should return false on wrong password', async () => { | ||
const wrongPassword = 'wrong_password'; | ||
|
||
expect( | ||
await service.verifyPassword(mockUser.username, wrongPassword), | ||
).toEqual(false); | ||
}); | ||
|
||
it('verifyPassword should return true on correct password', async () => { | ||
expect( | ||
await service.verifyPassword(mockUser.username, mockUser.password), | ||
).toEqual(true); | ||
}); | ||
}); |
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