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

[TM-1271] Login endpoint #2

Merged
merged 18 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.local.sample
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
NODE_ENV=development
PHP_PROXY_TARGET=http://host.docker.internal:8080/api
USER_SERVICE_PROXY_TARGET=http://host.docker.internal:4010

DB_HOST=localhost
DB_PORT=3360
DB_DATABASE=wri_restoration_marketplace_api
DB_USERNAME=wri
DB_PASSWORD=wri

JWT_SECRET=qu3sep4GKdbg6PiVPCKLKljHukXALorq6nLHDBOCSwvs6BrgE6zb8gPmZfrNspKt
39 changes: 0 additions & 39 deletions .github/workflows/ci.yml

This file was deleted.

46 changes: 46 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Pull Request

env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

on:
pull_request:

permissions:
actions: read
contents: read

jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: KengoTODA/actions-setup-docker-compose@v1
with:
version: '2.29.1'

# This enables task distribution via Nx Cloud
# Run this command as early as possible, before dependencies are installed
# Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun
- run: npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="build"

- run: docker-compose up -d

# Cache node_modules
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'

- run: npm ci --legacy-peer-deps && (cd apps/api-gateway; npm ci) && (cd apps/api-gateway/lambda/local-proxy; npm ci)

- uses: nrwl/nx-set-shas@v4

# Distributed execution only works with cacheable builds. The Api Gateway build is not currently
# cacheable. Since the codebase is currently small, we can get away without distribution, but once
# it grows, we'll want to look into what it will take to make the api gateway build cacheable and remove
# NX_CLOUD_DISTRIBUTED_EXECUTION=false from this command.
- run: NX_CLOUD_DISTRIBUTED_EXECUTION=false npx nx affected -t lint 'test --coverage --passWithNoTests' build
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Repository for the Microservices API backend of the TerraMatch service
* [CDK CLI](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) (install globally)
* [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)
* [NX](https://nx.dev/getting-started/installation#installing-nx-globally) (install globally)
* [NestJS](https://docs.nestjs.com/) (install globally, useful for development)

# Building and starting the apps
* Copy `.env.local.sample` to `.env`
Expand All @@ -23,3 +24,24 @@ Repository for the Microservices API backend of the TerraMatch service
# Deployment
TBD. The ApiGateway has been tested to be at least functional on AWS. Tooling around deployment will be
handled in a future ticket.

# Database work
For now, Laravel is the source of truth for all things related to the DB schema. As such, TypeORM is not allowed to modify the
schema, and is expected to interface with exactly the schema that is managed by Laravel. This note is included in user.entity.ts,
and should hold true for all models created in this codebase until this codebase can take over as the source of truth for DB
schema:
```
// Note: this has some additional typing information (like width: 1 on bools and type: timestamps on
// CreateDateColumn) to make the types generated here match what is generated by Laravel exactly.
// At this time, we want TypeORM to expect exactly the same types that PHP uses by default. Tested
// by checking what schema gets generated in the test database against the real DB during unit
// test runs (the only time we let TypeORM modify the DB schema).
```

This codebase connects to the database running in the `wri-terramatch-api` docker container. The docker-compose
file included in this repo is used only for setting up the database needed for running unit tests in Github Actions.

To set up the local testing database, run these two commands in the `wri-terramatch-api` directory with the docker container running:
* `echo "grant all on terramatch_microservices_test to 'wri'@'%';" | dc exec -T mariadb mysql -h localhost -u root -proot `
* `echo "grant all on terramatch_microservices_test.* to 'wri'@'%';" | dc exec -T mariadb mysql -h localhost -u root -proot`

8 changes: 0 additions & 8 deletions apps/api-gateway/jest.config.js

This file was deleted.

17 changes: 0 additions & 17 deletions apps/api-gateway/test/api-gateway.test.ts

This file was deleted.

22 changes: 22 additions & 0 deletions apps/user-service/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Module } from '@nestjs/common';
import { AuthController } from './auth/auth.controller';
import { AuthService } from './auth/auth.service';
import { DatabaseModule } from '@terramatch-microservices/database';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
imports: [
DatabaseModule,
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET'),
}),
})
],
controllers: [AuthController],
providers: [AuthService],
})
export class AppModule {}
22 changes: 0 additions & 22 deletions apps/user-service/src/app/app.controller.spec.ts

This file was deleted.

13 changes: 0 additions & 13 deletions apps/user-service/src/app/app.controller.ts

This file was deleted.

11 changes: 0 additions & 11 deletions apps/user-service/src/app/app.module.ts

This file was deleted.

21 changes: 0 additions & 21 deletions apps/user-service/src/app/app.service.spec.ts

This file was deleted.

8 changes: 0 additions & 8 deletions apps/user-service/src/app/app.service.ts

This file was deleted.

42 changes: 42 additions & 0 deletions apps/user-service/src/auth/auth.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { UnauthorizedException } from '@nestjs/common';

describe('AuthController', () => {
let controller: AuthController;
let authService: DeepMocked<AuthService>;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthController],
providers: [
{ provide: AuthService, useValue: authService = createMock<AuthService>() },
],
}).compile();

controller = module.get<AuthController>(AuthController);
});

afterEach(() => {
jest.restoreAllMocks();
})

it('should throw if creds are invalid', async () => {
authService.login.mockResolvedValue(null);

await expect(() => controller.login({ emailAddress: '[email protected]', password: 'asdfasdfasdf' }))
.rejects
.toThrow(UnauthorizedException)
})

it('returns a token if creds are valid', async () => {
const token = 'fake jwt token';
const userId = 123;
authService.login.mockResolvedValue({ token, userId })

const result = await controller.login({ emailAddress: '[email protected]', password: 'asdfasdfasdf' });
expect(result).toEqual({ type: 'logins', token, id: `${userId}` })
})
});
32 changes: 32 additions & 0 deletions apps/user-service/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Body, Controller, HttpStatus, Post, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginRequest } from './dto/login-request.dto';
import { JsonApiResponse } from '../decorators/json-api-response.decorator';
import { LoginResponse } from './dto/login-response.dto';
import { ApiException } from '@nanogiants/nestjs-swagger-api-exception-decorator';
import { ApiOperation } from '@nestjs/swagger';

@Controller('auth')
export class AuthController {
constructor (private readonly authService: AuthService) {}

@Post('login')
@ApiOperation({ summary: 'Receive a JWT Token in exchange for login credentials' })
@JsonApiResponse({ status: HttpStatus.CREATED, dataType: LoginResponse })
@ApiException(
() => UnauthorizedException,
{ description: 'Authentication failed.', template: { statusCode: '$status', message: '$description', } }
)
async login(@Body() { emailAddress, password }: LoginRequest): Promise<LoginResponse> {
const { token, userId } = await this.authService.login(emailAddress, password) ?? {}
if (token == null) {
// there are multiple reasons for the token to be null (bad email address, wrong password),
// but we don't want to report on the specifics because it opens an attack vector: if we
// report that an email address isn't valid, that lets an attacker know which email addresses
// _are_ valid in our system.
throw new UnauthorizedException();
}

return { type: 'logins', id: `${userId}`, token };
}
}
Loading