Skip to content

Commit

Permalink
feat(shared && auth): adding env service and using it at the auth module
Browse files Browse the repository at this point in the history
  • Loading branch information
leonardodimarchi committed Oct 27, 2023
1 parent 4a26fb5 commit 35bd004
Show file tree
Hide file tree
Showing 15 changed files with 422 additions and 155 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ DB_PORT=5432
DB_USERNAME=user
DB_PASSWORD=password
DB_DATABASE=backend_db
JWT_SECRET=SECRET
JWT_EXPIRES_IN=7d

6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ This is where i'm going to place my ideas and things that i want to do or use in
- [ ] Add user reset password module
- [x] Add some module to interact with user (such as course or quizzes)
- [x] Auth module with passport
- [ ] Validate request user permissions when calling a method (for example, an admin user cannot enroll at a course)
- [x] Validate request user permissions when calling a method (for example, an admin user cannot enroll at a course)
- [x] Add base user roles such as Admin, Student and Instructor
- [ ] Environment service
- [ ] Add JWT Secret
- [x] Environment service
- [x] Add JWT Secret
- [ ] Translate Auth error messages
- [ ] Translate role guard error messages
- [x] SWC
399 changes: 275 additions & 124 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"dotenv": "^16.3.1",
"envalid": "^8.0.0",
"nestjs-i18n": "^10.3.5",
"passport": "^0.6.0",
"passport-jwt": "^4.0.1",
Expand Down
4 changes: 3 additions & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CourseModule } from '@modules/course/course.module';
import { UserModule } from '@modules/user/user.module';
import { Module } from '@nestjs/common';
import { DatabaseModule } from '@shared/infra/database/database.module';
import { EnvModule } from '@shared/infra/env/env.module';
import { AcceptLanguageResolver, I18nModule, QueryResolver } from 'nestjs-i18n';
import * as path from 'path';

Expand All @@ -20,8 +21,9 @@ import * as path from 'path';
'../src/generated/i18n.generated.ts',
),
}),
AuthModule,
DatabaseModule,
EnvModule,
AuthModule,
UserModule,
CourseModule,
],
Expand Down
16 changes: 13 additions & 3 deletions src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { PasswordEncryptionService } from '@modules/user/domain/services/passwor
import { UserModule } from '@modules/user/user.module';
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { EnvModule } from '@shared/infra/env/env.module';
import { EnvVariableKeys } from '@shared/infra/env/interfaces/env-variables';
import { EnvService } from '@shared/infra/env/interfaces/env.service';
import { LoginUseCase } from './domain/usecases/login.usecase';
import { JwtStrategy } from './infra/strategies/auth-jwt.strategy';
import { LocalStrategy } from './infra/strategies/auth-local.strategy';
Expand All @@ -11,9 +14,16 @@ import { AuthController } from './presenter/controllers/auth.controller';
@Module({
imports: [
UserModule,
JwtModule.register({
secret: 'SECRET',
signOptions: { expiresIn: '60s' },
EnvModule,
JwtModule.registerAsync({
imports: [EnvModule],
inject: [EnvService],
useFactory: (env: EnvService) => {
return {
secret: env.get(EnvVariableKeys.JWT_SECRET),
signOptions: { expiresIn: env.get(EnvVariableKeys.JWT_EXPIRES_IN) },
};
},
}),
],
controllers: [AuthController],
Expand Down
10 changes: 8 additions & 2 deletions src/modules/auth/infra/strategies/auth-jwt.strategy.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import { faker } from '@faker-js/faker';
import { UserRole } from '@modules/user/domain/entities/user/user-role.enum';
import { EnvService } from '@shared/infra/env/interfaces/env.service';
import { UUID } from 'crypto';
import { createMock } from 'test/utils/create-mock';
import { JwtStrategy } from './auth-jwt.strategy';

describe('AuthJwtStrategy', () => {
let strategy: JwtStrategy;

beforeEach(() => {
strategy = new JwtStrategy();
strategy = new JwtStrategy(createMock<EnvService>());
});

it('should return a request user entity', () => {
const id = faker.number.int();
const id = faker.string.uuid() as UUID;
const email = faker.internet.email();
const roles = [UserRole.ADMIN];

const result = strategy.validate({
sub: id,
roles,
email,
});

Expand Down
6 changes: 4 additions & 2 deletions src/modules/auth/infra/strategies/auth-jwt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import { RequestUserEntity } from '@modules/auth/domain/entities/request-user.en
import { UserRole } from '@modules/user/domain/entities/user/user-role.enum';
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { EnvVariableKeys } from '@shared/infra/env/interfaces/env-variables';
import { EnvService } from '@shared/infra/env/interfaces/env.service';
import { UUID } from 'crypto';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
constructor(private readonly envService: EnvService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'SECRET',
secretOrKey: envService.get(EnvVariableKeys.JWT_SECRET),
});
}

Expand Down
13 changes: 9 additions & 4 deletions src/shared/infra/database/database.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { dataSourceOptions } from './typeorm/datasource-options';
import { EnvModule } from '../env/env.module';
import { EnvService } from '../env/interfaces/env.service';
import { getDatasourceOptions } from './typeorm/datasource-options';

@Module({
imports: [
EnvModule,
TypeOrmModule.forRootAsync({
useFactory: () => {
return dataSourceOptions;
}
imports: [EnvModule],
inject: [EnvService],
useFactory: (env: EnvService) => {
return getDatasourceOptions(env.getAll());
},
}),
],
})
Expand Down
14 changes: 11 additions & 3 deletions src/shared/infra/database/typeorm/cli-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { DataSource } from "typeorm";
import { dataSourceOptions } from "./datasource-options";
import { DataSource } from 'typeorm';
import { getDatasourceOptions } from './datasource-options';

export default new DataSource(dataSourceOptions);
import { DotEnvService } from '@shared/infra/env/services/dot-env.service';
import { config as initDotEnv } from 'dotenv';
import { cleanEnv } from 'envalid';

initDotEnv();

const env = cleanEnv(process.env, DotEnvService.dotEnvSpecs);

export default new DataSource(getDatasourceOptions(env));
28 changes: 15 additions & 13 deletions src/shared/infra/database/typeorm/datasource-options.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { EnvVariables } from '@shared/infra/env/interfaces/env-variables';
import { join } from 'path';
import * as dotenv from 'dotenv';
import { DataSourceOptions } from 'typeorm';

dotenv.config();

export const dataSourceOptions: DataSourceOptions = {
type: 'postgres',
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
migrations: [join(__dirname, 'migrations', '*.ts')],
entities: [join(__dirname, '../../../../modules', '**', '*.schema.{ts,js}')],
synchronize: false,
export const getDatasourceOptions = (env: EnvVariables): DataSourceOptions => {
return {
type: 'postgres',
host: env.DB_HOST,
port: env.DB_PORT,
username: env.DB_USERNAME,
password: env.DB_PASSWORD,
database: env.DB_DATABASE,
migrations: [join(__dirname, 'migrations', '*.ts')],
entities: [
join(__dirname, '../../../../modules', '**', '*.schema.{ts,js}'),
],
synchronize: false,
};
};
14 changes: 14 additions & 0 deletions src/shared/infra/env/env.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { EnvService } from './interfaces/env.service';
import { DotEnvService } from './services/dot-env.service';

@Module({
providers: [
{
provide: EnvService,
useClass: DotEnvService,
},
],
exports: [EnvService],
})
export class EnvModule {}
19 changes: 19 additions & 0 deletions src/shared/infra/env/interfaces/env-variables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export enum EnvVariableKeys {
DB_HOST = 'DB_HOST',
DB_PORT = 'DB_PORT',
DB_USERNAME = 'DB_USERNAME',
DB_PASSWORD = 'DB_PASSWORD',
DB_DATABASE = 'DB_DATABASE',
JWT_SECRET = 'JWT_SECRET',
JWT_EXPIRES_IN = 'JWT_EXPIRES_IN',
}

export type EnvVariables = {
[EnvVariableKeys.DB_HOST]: string;
[EnvVariableKeys.DB_PORT]: number;
[EnvVariableKeys.DB_USERNAME]: string;
[EnvVariableKeys.DB_PASSWORD]: string;
[EnvVariableKeys.DB_DATABASE]: string;
[EnvVariableKeys.JWT_SECRET]: string;
[EnvVariableKeys.JWT_EXPIRES_IN]: string;
};
6 changes: 6 additions & 0 deletions src/shared/infra/env/interfaces/env.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { EnvVariableKeys, EnvVariables } from './env-variables';

export abstract class EnvService {
abstract get<K extends EnvVariableKeys>(key: K): EnvVariables[K];
abstract getAll(): EnvVariables;
}
39 changes: 39 additions & 0 deletions src/shared/infra/env/services/dot-env.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Injectable } from '@nestjs/common';
import { config as initDotEnv } from 'dotenv';
import { ValidatorSpec, cleanEnv, port, str } from 'envalid';
import { EnvVariableKeys, EnvVariables } from '../interfaces/env-variables';
import { EnvService } from '../interfaces/env.service';

type EnvSpecs = {
[K in EnvVariableKeys]: ValidatorSpec<EnvVariables[K]>;
};

@Injectable()
export class DotEnvService implements EnvService {
constructor() {
initDotEnv();
const env = cleanEnv(process.env, DotEnvService.dotEnvSpecs);

this.env = env;
}

static dotEnvSpecs: EnvSpecs = {
[EnvVariableKeys.DB_HOST]: str(),
[EnvVariableKeys.DB_PORT]: port(),
[EnvVariableKeys.DB_USERNAME]: str(),
[EnvVariableKeys.DB_PASSWORD]: str(),
[EnvVariableKeys.DB_DATABASE]: str(),
[EnvVariableKeys.JWT_SECRET]: str(),
[EnvVariableKeys.JWT_EXPIRES_IN]: str(),
};

private readonly env: EnvVariables;

get<K extends EnvVariableKeys>(key: K): EnvVariables[K] {
return this.env[key];
}

getAll(): EnvVariables {
return this.env;
}
}

0 comments on commit 35bd004

Please sign in to comment.