Skip to content

Commit

Permalink
fix test env route, add signup tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeh committed Sep 8, 2024
1 parent 1d97000 commit 54992f5
Show file tree
Hide file tree
Showing 24 changed files with 664 additions and 103 deletions.
4 changes: 4 additions & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/typeorm": "^10.0.2",
"bcrypt": "catalog:",
"class-transformer": "catalog:",
"lodash": "^4.17.21",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
Expand All @@ -36,8 +38,10 @@
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/bcrypt": "^5.0.2",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/lodash": "^4.17.7",
"@types/node": "^20.3.1",
"@types/passport-jwt": "^4.0.1",
"@types/passport-local": "^1.0.38",
Expand Down
22 changes: 0 additions & 22 deletions api/src/app.controller.spec.ts

This file was deleted.

4 changes: 2 additions & 2 deletions api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ApiConfigModule } from '@api/modules/config/app-config.module';
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard } from '@auth/guards/jwt-auth.guard';
import { AuthModule } from '@auth/auth.module';
import { AuthModule } from '@api/modules/auth/auth.module';
import { JwtAuthGuard } from '@api/modules/auth/guards/jwt-auth.guard';

@Module({
imports: [ApiConfigModule, AuthModule],
Expand Down
4 changes: 2 additions & 2 deletions api/src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { AuthenticationModule } from '@auth/authentication/authentication.module';
import { AuthorisationModule } from '@auth/authorisation/authorisation.module';
import { AuthenticationModule } from '@api/modules/auth/authentication/authentication.module';
import { AuthorisationModule } from '@api/modules/auth/authorisation/authorisation.module';

@Module({
imports: [AuthenticationModule, AuthorisationModule],
Expand Down
24 changes: 22 additions & 2 deletions api/src/modules/auth/authentication/authentication.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
import { Controller } from '@nestjs/common';
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { User } from '@shared/entities/users/user.entity';
import { AuthenticationService } from '@api/modules/auth/authentication/authentication.service';
import { LoginDto } from '@api/modules/auth/dtos/login.dto';
import { LocalAuthGuard } from '@api/modules/auth/guards/local-auth.guard';
import { GetUser } from '@api/modules/auth/decorators/get-user.decorator';
import { Public } from '@api/modules/auth/decorators/is-public.decorator';

@Controller('authentication')
export class AuthenticationController {}
export class AuthenticationController {
constructor(private authService: AuthenticationService) {}

@Public()
@Post('signup')
async signup(@Body() signupDto: LoginDto) {
return this.authService.signup(signupDto);
}

@UseGuards(LocalAuthGuard)
@Post('login')
async login(@GetUser() user: User) {
return this.authService.signIn(user);
}
}
4 changes: 3 additions & 1 deletion api/src/modules/auth/authentication/authentication.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { ApiConfigModule } from '@api/modules/config/app-config.module';
import { ApiConfigService } from '@api/modules/config/app-config.service';
import { JwtStrategy } from '@auth/strategies/jwt.strategy';
import { UsersService } from '@api/modules/users/users.service';
import { UsersModule } from '@api/modules/users/users.module';
import { LocalStrategy } from '@api/modules/auth/strategies/local.strategy';
import { JwtStrategy } from '@api/modules/auth/strategies/jwt.strategy';

@Module({
imports: [
Expand All @@ -24,6 +25,7 @@ import { UsersModule } from '@api/modules/users/users.module';
],
providers: [
AuthenticationService,
LocalStrategy,
{
provide: JwtStrategy,
useFactory: (users: UsersService, config: ApiConfigService) => {
Expand Down
42 changes: 40 additions & 2 deletions api/src/modules/auth/authentication/authentication.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,42 @@
import { Injectable } from '@nestjs/common';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '@api/modules/users/users.service';
import { User } from '@shared/entities/users/user.entity';
import * as bcrypt from 'bcrypt';
import { LoginDto } from '@api/modules/auth/dtos/login.dto';
import { JwtPayload } from '@api/modules/auth/strategies/jwt.strategy';

@Injectable()
export class AuthenticationService {}
export class AuthenticationService {
constructor(
private readonly usersService: UsersService,
private readonly jwt: JwtService,
) {}
async validateUser(email: string, password: string): Promise<User> {
const user = await this.usersService.findByEmail(email);
if (user && (await bcrypt.compare(password, user.password))) {
return user;
}
throw new UnauthorizedException(`Invalid credentials`);
}

async signup(signupDto: LoginDto): Promise<void> {
const passwordHash = await bcrypt.hash(signupDto.password, 10);
await this.usersService.createUser({
email: signupDto.email,
password: passwordHash,
});
}

async login(loginDto: LoginDto): Promise<{ access_token: string }> {
const user = await this.validateUser(loginDto.email, loginDto.password);
return {
access_token: this.jwt.sign({ id: user.id }),
};
}
async signIn(user: User): Promise<{ user: User; accessToken: string }> {
const payload: JwtPayload = { id: user.id };
const accessToken: string = this.jwt.sign(payload);
return { user, accessToken };
}
}
8 changes: 8 additions & 0 deletions api/src/modules/auth/dtos/login.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @note: Depending on how we will proceed with the repo structure, we might need to move this file to the shared module.
*/

export class LoginDto {
email: string;
password: string;
}
2 changes: 1 addition & 1 deletion api/src/modules/auth/guards/jwt-auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
import { IS_PUBLIC_KEY } from '@auth/decorators/is-public.decorator';
import { IS_PUBLIC_KEY } from '@api/modules/auth/decorators/is-public.decorator';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
Expand Down
5 changes: 5 additions & 0 deletions api/src/modules/auth/guards/local-auth.guard.ts
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') {}
58 changes: 29 additions & 29 deletions api/src/modules/auth/strategies/local.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
// import { Injectable, UnauthorizedException } from '@nestjs/common';
// import { PassportStrategy } from '@nestjs/passport';
//
// import { Strategy } from 'passport-local';
// import { User } from '@shared/dto/users/user.entity';
// import { AuthService } from '@api/modules/auth/auth.service';
//
// /**
// * @description: LocalStrategy is used by passport to authenticate by email and password rather than a token.
// */
//
// @Injectable()
// export class LocalStrategy extends PassportStrategy(Strategy) {
// constructor(private readonly authService: AuthService) {
// super({ usernameField: 'email' });
// }
//
// async validate(email: string, password: string): Promise<User> {
// const user: User | null = await this.authService.validateUser(
// email,
// password,
// );
//
// if (!user) {
// throw new UnauthorizedException();
// }
// return user;
// }
// }
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';

import { Strategy } from 'passport-local';
import { User } from '@shared/entities/users/user.entity';
import { AuthenticationService } from '@api/modules/auth/authentication/authentication.service';

/**
* @description: LocalStrategy is used by passport to authenticate by email and password rather than a token.
*/

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthenticationService) {
super({ usernameField: 'email' });
}

async validate(email: string, password: string): Promise<User> {
const user: User | null = await this.authService.validateUser(
email,
password,
);

if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
14 changes: 6 additions & 8 deletions api/src/modules/config/app-config.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Global, Module } from '@nestjs/common';
import { Global, Module, OnModuleInit } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { join } from 'path';
import { ApiConfigService } from '@api/modules/config/app-config.service';
import { DatabaseModule } from '@api/modules/config/database/database.module';
import { resolveConfigPath } from '@api/modules/config/path-resolver';

@Global()
@Module({
imports: [
DatabaseModule,
/**
* @note: Check if we can abstract the conf to ApiConfigService
*/
Expand All @@ -16,15 +16,13 @@ import { DatabaseModule } from '@api/modules/config/database/database.module';
cache: true,
// TODO: This is a bit ugly, we should find a way to make this more elegant
envFilePath: [
join(
__dirname,
`../../../../../../shared/config/.env.${process.env.NODE_ENV}`,
),
join(__dirname, '../../../../../../shared/config/.env'),
resolveConfigPath(`shared/config/.env.${process.env.NODE_ENV}`),
resolveConfigPath(`shared/config/.env`),
],
}),
DatabaseModule,
],
providers: [ConfigService, ApiConfigService],
exports: [ApiConfigService, DatabaseModule],
exports: [ApiConfigService],
})
export class ApiConfigModule {}
5 changes: 5 additions & 0 deletions api/src/modules/config/app-config.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { DATABASE_ENTITIES } from '@shared/entities/database.entities';
import { readdirSync } from 'fs';
import { join } from 'path';

export type JWTConfig = {
secret: string;
Expand All @@ -18,6 +20,9 @@ export class ApiConfigService {
* @note: Maybe it's a good idea to move the datasource config to shared folder, to be used potentially for a e2e test agent
*/
getDatabaseConfig() {
console.log('ALL ENVSSS');
console.log(readdirSync(join(__dirname, '../../../../shared/config')));

return {
host: this.configService.get('DB_HOST'),
port: this.configService.get('DB_PORT'),
Expand Down
18 changes: 18 additions & 0 deletions api/src/modules/config/path-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { join } from 'path';
import { readdirSync } from 'fs';

// TODO: Workaround: This should be prob fixed in the jest conf

const TEST_RELATIVE_PATH = '../../../../';
const DEFAULT_RELATIVE_PATH = '../../../../../../';

/**
* @description: Resolve the path of the config file depending on the environment
*/
export function resolveConfigPath(relativePath: string): string {
const rootDir =
process.env.NODE_ENV === 'test'
? TEST_RELATIVE_PATH
: DEFAULT_RELATIVE_PATH;
return join(__dirname, rootDir, relativePath);
}
16 changes: 15 additions & 1 deletion api/src/modules/users/users.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { ConflictException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '@shared/entities/users/user.entity';
import { Repository } from 'typeorm';
Expand All @@ -12,4 +12,18 @@ export class UsersService {
where: { id },
});
}

async findByEmail(email: string): Promise<User | null> {
return this.repo.findOne({ where: { email } });
}

async createUser(createUserDto: { email: string; password: string }) {
const existingUser = await this.findByEmail(createUserDto.email);
if (existingUser) {
throw new ConflictException(
`Email ${createUserDto.email} already exists`,
);
}
return this.repo.save(createUserDto);
}
}
24 changes: 0 additions & 24 deletions api/test/app.e2e-spec.ts

This file was deleted.

Loading

0 comments on commit 54992f5

Please sign in to comment.