Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): rewrite the implementation workflow #22

Merged
merged 9 commits into from
Jun 11, 2022
Prev Previous commit
Next Next commit
feat(domain): implement the authentication with email method
  • Loading branch information
tericcabrel committed Jun 11, 2022
commit c1551684adc07f9fb1003a41c4c2cb7f1e151961
4 changes: 2 additions & 2 deletions packages/database/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@
"db:seed": "prisma db seed"
},
"dependencies": {
"@prisma/client": "^3.12.0",
"@prisma/client": "^3.15.1",
"cuid": "^2.1.8"
},
"devDependencies": {
"@types/jest": "^27.4.1",
"dotenv": "^16.0.0",
"jest": "^27.5.1",
"prisma": "^3.12.0",
"prisma": "^3.15.1",
"ts-jest": "^27.1.3",
"typescript": "^4.6.2"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE `users` ADD COLUMN `password` VARCHAR(100) NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE `users` MODIFY `oauthProvider` ENUM('email', 'github', 'google', 'stackoverflow') NOT NULL;
2 changes: 2 additions & 0 deletions packages/database/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum RoleName {
}

enum OauthProvider {
email
github
google
stackoverflow
Expand All @@ -41,6 +42,7 @@ model Role {
model User {
id String @id @db.VarChar(50)
email String @unique @db.VarChar(100)
password String? @db.VarChar(100)
username String? @db.VarChar(50)
name String @map("name") @db.VarChar(50)
timezone String? @db.VarChar(50)
Expand Down
2 changes: 2 additions & 0 deletions packages/database/prisma/schema.test.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ enum RoleName {
}

enum OauthProvider {
email
github
google
stackoverflow
Expand All @@ -40,6 +41,7 @@ model Role {
model User {
id String @id @db.VarChar(50)
email String @unique @db.VarChar(100)
password String? @db.VarChar(100)
username String? @db.VarChar(50)
name String @map("name") @db.VarChar(50)
timezone String? @db.VarChar(50)
Expand Down
1 change: 1 addition & 0 deletions packages/database/src/repositories/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default class UserRepository implements UserRepositoryInterface {
isEnabled: user.isEnabled,
name: user.name,
oauthProvider: user.oauthProvider,
password: user.password,
pictureUrl: user.pictureUrl,
roleId: user.roleId,
timezone: user.timezone,
Expand Down
4 changes: 3 additions & 1 deletion packages/domain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
"dependencies": {
"@sharingan/database": "1.0.0",
"@sharingan/utils": "1.0.0",
"axios": "^0.26.1"
"axios": "^0.26.1",
"bcrypt": "^5.0.1"
},
"devDependencies": {
"@ngneat/falso": "^5.6.0",
"@types/bcrypt": "^5.0.0",
"@types/jest": "^27.4.1",
"add": "^2.0.6",
"jest": "^27.5.1",
Expand Down
7 changes: 7 additions & 0 deletions packages/domain/src/users/dtos/create-user-dto.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import { OauthProvider, User, dbId } from '@sharingan/database';

import { hashPassword } from '../../utils/helpers';

type Input = {
email: string;
name: string;
oauthProvider: OauthProvider;
password?: string | null;
pictureUrl: string | null;
roleId: string;
timezone: string | null;
username: string | null;
};

export default class CreateUserDto {
readonly hashedPassword: string | null;

private readonly userId: string;
private enabled = false;

constructor(private _input: Input) {
this.userId = dbId.generate();
this.hashedPassword = this._input.password ? hashPassword(this._input.password) : null;
}

get email(): string {
Expand All @@ -34,6 +40,7 @@ export default class CreateUserDto {
isEnabled: this.enabled,
name: this._input.name,
oauthProvider: this._input.oauthProvider,
password: this.hashedPassword,
pictureUrl: this._input.pictureUrl,
roleId: this._input.roleId,
timezone: this._input.timezone,
Expand Down
18 changes: 18 additions & 0 deletions packages/domain/src/users/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Role, User, UserRepositoryInterface } from '@sharingan/database';
import SharinganError, { errors } from '@sharingan/utils';
import bcrypt from 'bcrypt';

import CreateUserDto from './dtos/create-user-dto';
import UpdateUserDto from './dtos/update-user-dto';
Expand Down Expand Up @@ -43,4 +45,20 @@ export default class UserService {
async findById(id: string): Promise<User | null> {
return this._userRepository.findById(id);
}

async login(email: string, password: string): Promise<User> {
const user = await this.findByEmail(email);

if (!user) {
throw new SharinganError(errors.LOGIN_FAILED_EMAIL, 'LOGIN_FAILED');
}

const isPasswordValid = user.password ? bcrypt.compareSync(password, user.password) : false;

if (!isPasswordValid) {
throw new SharinganError(errors.LOGIN_FAILED_PASSWORD, 'LOGIN_FAILED');
}

return user;
}
}
7 changes: 7 additions & 0 deletions packages/domain/src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import bcrypt from 'bcrypt';

export const hashPassword = (password: string): string => {
const SALT_ROUNDS = 10;

return bcrypt.hashSync(password, SALT_ROUNDS);
};
4 changes: 2 additions & 2 deletions packages/domain/tests/services/folders/folder.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('Test Folder service', () => {

it('should create a root folder for a user', async () => {
// GIVEN
const user = await createTestUser();
const user = await createTestUser({});

const creatUserRootFolderDto = new CreateUserRootFolderDto(user.id);

Expand Down Expand Up @@ -107,7 +107,7 @@ describe('Test Folder service', () => {

it("should not find the user's root folder", async () => {
// GIVEN
const user = await createTestUser();
const user = await createTestUser({});

// WHEN
// THEN
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Session } from '@sharingan/database';
import { isValidUUIDV4 } from '@sharingan/utils';

import { CreateSessionDto } from '../../../../index';
import { generateTestId } from '../../../setup/test-utils';

describe('Test Create Session DTO', () => {
it('should return a valid role object', () => {
const userId = generateTestId();

const dto = new CreateSessionDto({
expireDate: new Date(),
userId,
});

const session = dto.toSession();

expect(session).toMatchObject<Session>({
expires: expect.any(Date),
id: expect.any(String),
token: expect.any(String),
userId,
});

expect(isValidUUIDV4(session.token)).toEqual(true);
});
});
58 changes: 58 additions & 0 deletions packages/domain/tests/services/sessions/session.service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Session } from '@sharingan/database';

import { sessionService } from '../../../index';
import {
createTestSession,
createTestSessionDto,
deleteTestUserSessions,
generateTestId,
} from '../../setup/test-utils';

describe('Test Session Service', function () {
it('should create a session', async () => {
// GIVEN
const userId = generateTestId();
const dto = createTestSessionDto(userId);

// WHEN
const sessionCreated = await sessionService.create(dto);

// THEN
expect(sessionCreated).toMatchObject<Session>(dto.toSession());

await deleteTestUserSessions(sessionCreated.userId);
});

it('should find a session by token', async () => {
// GIVEN
const userId = generateTestId();
const session = await createTestSession({ userId });

// WHEN
const sessionFound = await sessionService.findByToken(session.token);

// THEN
expect(session).toEqual(sessionFound);

await deleteTestUserSessions(session.userId);
});

it('should delete all session of a user', async () => {
// GIVEN
const userId = generateTestId();

const sessionsCreated = await Promise.all([
createTestSession({ userId }),
createTestSession({ userId }),
createTestSession({ userId }),
]);

// WHEN
await sessionService.deleteUserSessions(userId);

// THEN
const userSessions = await Promise.all(sessionsCreated.map(({ token }) => sessionService.findByToken(token)));

expect(userSessions.every((session) => !session)).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { User } from '@sharingan/database';
import { CreateUserDto } from '../../../../index';

describe('Test Create User DTO', () => {
it('should return a valid user object', () => {
it('should return a valid user object without password', () => {
const dto = new CreateUserDto({
email: '[email protected]',
name: 'user admin',
Expand All @@ -23,6 +23,39 @@ describe('Test Create User DTO', () => {
isEnabled: false,
name: 'user admin',
oauthProvider: 'github',
password: null,
pictureUrl: 'pictureUrl',
roleId: 'roleId',
timezone: 'Europe/Paris',
updatedAt: expect.any(Date),
username: 'shuser',
};

expect(user).toMatchObject(expectedUser);
});

it('should return a valid user object with a password', () => {
const dto = new CreateUserDto({
email: '[email protected]',
name: 'user admin',
oauthProvider: 'email',
password: 'str0ngPassw0rd',
pictureUrl: 'pictureUrl',
roleId: 'roleId',
timezone: 'Europe/Paris',
username: 'shuser',
});

const user = dto.toUser();

const expectedUser: User = {
createdAt: expect.any(Date),
email: '[email protected]',
id: expect.any(String),
isEnabled: false,
name: 'user admin',
oauthProvider: 'email',
password: dto.hashedPassword,
pictureUrl: 'pictureUrl',
roleId: 'roleId',
timezone: 'Europe/Paris',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('Test Update User DTO', () => {
});

const roleId = generateTestId();
const currentUser = createTestUserDto(roleId).toUser();
const currentUser = createTestUserDto({ roleId }).toUser();

const userToUpdate = dto.toUser(currentUser);

Expand All @@ -25,6 +25,7 @@ describe('Test Update User DTO', () => {
isEnabled: false,
name: 'user updated',
oauthProvider: 'github',
password: null,
pictureUrl: 'updated pictureUrl',
roleId: 'updated roleId',
timezone: 'Europe/Paris',
Expand All @@ -45,7 +46,7 @@ describe('Test Update User DTO', () => {
});

const roleId = generateTestId();
const currentUser = createTestUserDto(roleId).toUser();
const currentUser = createTestUserDto({ roleId }).toUser();

dto.isEnabled = true;
const userToUpdate = dto.toUser(currentUser);
Expand All @@ -57,6 +58,7 @@ describe('Test Update User DTO', () => {
isEnabled: true,
name: 'user updated',
oauthProvider: 'github',
password: null,
pictureUrl: 'updated pictureUrl',
roleId: 'updated roleId',
timezone: 'Europe/Paris',
Expand Down
Loading