Skip to content

Commit

Permalink
feat: create session middleware
Browse files Browse the repository at this point in the history
* configure eslint
  • Loading branch information
LwveMike committed Jun 7, 2024
1 parent e59a51e commit 72b82d9
Show file tree
Hide file tree
Showing 45 changed files with 2,469 additions and 300 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,5 @@ pids
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

apps/backend/keys

12 changes: 10 additions & 2 deletions apps/backend/.env.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,13 @@ DB_NAME=
PORT=

# Relative to the project root
CERT_KEY_PATH='certs/key.key'
CERT_CRT_PATH='certs/crt.crt'
ACCESS_PUB_KEY_PATH='keys/access-pub-key.pem'
ACCESS_PRIV_KEY_PATH='keys/access-priv-key.pem'
REFRESH_PUB_KEY_PATH='keys/refresh-pub-key.pem'
REFRESH_PRIV_KEY_PATH='keys/refresh-priv-key.pem'

# Expressed in seconds
ACCESS_EXPIRY=3600
REFRESH_EXPIRY=1209600

DEBUG=true
2 changes: 1 addition & 1 deletion apps/backend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ module.exports = {
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
}
11 changes: 0 additions & 11 deletions apps/backend/certs/crt.crt

This file was deleted.

5 changes: 0 additions & 5 deletions apps/backend/certs/key.key

This file was deleted.

8 changes: 4 additions & 4 deletions apps/backend/database/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ services:
restart: always
container_name: nfa-database
environment:
MYSQL_DATABASE: 'nfa'
MYSQL_USER: 'user'
MYSQL_PASSWORD: 'user'
MYSQL_ROOT_PASSWORD: 'root'
MYSQL_DATABASE: nfa
MYSQL_USER: user
MYSQL_PASSWORD: user
MYSQL_ROOT_PASSWORD: root
ports:
- '3306:3306'
15 changes: 8 additions & 7 deletions apps/backend/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { defineConfig } from 'drizzle-kit';
import { defineConfig } from 'drizzle-kit'

export default defineConfig({
schema: './src/modules/drizzle/schema.ts',
out: './migrations',
dialect: 'mysql',
dialect: 'mysql',
// TODO(lwvemike): take this from configService
dbCredentials: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
host: process.env.DB_HOST!,
user: process.env.DB_USER!,
password: process.env.DB_PASSWORD!,
database: process.env.DB_NAME!,
},
});
})
9 changes: 9 additions & 0 deletions apps/backend/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import antfu from '@antfu/eslint-config'

export default antfu({
typescript: {
overrides: {
'ts/consistent-type-imports': 'off',
},
},
})
9 changes: 9 additions & 0 deletions apps/backend/generate-keys
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash

TOKEN_PREFIX="keys/$1"

set -e

openssl ecparam -name prime256v1 -genkey -noout -out "$TOKEN_PREFIX-priv-key.pem"
openssl ec -in "$TOKEN_PREFIX-priv-key.pem" -pubout > "$TOKEN_PREFIX-pub-key.pem"

2 changes: 1 addition & 1 deletion apps/backend/migrations/meta/_journal.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"7","dialect":"mysql","entries":[]}
{ "version": "7", "dialect": "mysql", "entries": [] }
15 changes: 11 additions & 4 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
@@ -1,46 +1,53 @@
{
"name": "backend",
"version": "0.0.1",
"private": true,
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"dev": "nest start --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"database:up": "docker compose -f ./database/docker-compose.yml up -d",
"database:down": "docker compose -f ./database/docker-compose.yml down",
"database:push": "pnpm drizzle-kit push",
"generate:cert": "openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -days 3650 -nodes -keyout certs/key.key -out certs/crt.crt -subj '/CN=example.com' -addext 'subjectAltName=DNS:example.com,DNS:*.example.com'"
"generate:keys": "./generate-keys access && ./generate-keys refresh"
},
"dependencies": {
"@knaadh/nestjs-drizzle-mysql2": "^1.0.1",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.2",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"cookie-parser": "^1.4.6",
"date-fns": "^3.6.0",
"drizzle-orm": "^0.31.0",
"jsonwebtoken": "^9.0.2",
"mysql2": "^3.10.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"ua-parser-js": "^1.0.38",
"zod": "^3.23.8"
},
"devDependencies": {
"@antfu/eslint-config": "^2.20.0",
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/cookie-parser": "^1.4.7",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@types/ua-parser-js": "^0.7.39",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"drizzle-kit": "^0.22.1",
"eslint": "^8.42.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
Expand Down
28 changes: 22 additions & 6 deletions apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import { Module } from '@nestjs/common';
import { AuthenticationModule } from './modules/authentication/authentication.module';
import { DrizzleModule } from './modules/drizzle/drizzle.module';
import { ConfigurationModule } from './modules/configuration/configuration.module';
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'
import { AuthenticationModule } from './modules/authentication/authentication.module'
import { DrizzleModule } from './modules/drizzle/drizzle.module'
import { ConfigurationModule } from './modules/configuration/configuration.module'
import { ProtectedModule } from './modules/protected/protected.module'
import { SessionMiddleware } from './modules/session/session.middleware'
import { JwtModule } from './modules/jwt/jwt.module'
import { SessionModule } from './modules/session/session.module'

@Module({
imports: [
ConfigurationModule,
DrizzleModule,
AuthenticationModule
JwtModule,
SessionModule,
AuthenticationModule,
ProtectedModule,
],
})
export class AppModule {}
export class AppModule implements NestModule {
static readonly GLOBAL_PREFIX = '/api'

configure(consumer: MiddlewareConsumer) {
consumer
.apply(SessionMiddleware)
.exclude(...SessionMiddleware.EXCLUDE_ROUTES)
.forRoutes('*')
}
}
22 changes: 12 additions & 10 deletions apps/backend/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ConfigModule, ConfigService} from '@nestjs/config';
import { Config } from './modules/configuration/parse-config';
import { INestApplication } from '@nestjs/common';
import { NestFactory } from '@nestjs/core'
import { ConfigService } from '@nestjs/config'
import * as cookieParser from 'cookie-parser'
import type { Config } from './modules/configuration/parse-config'
import { AppModule } from './app.module'

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
const app = await NestFactory.create(AppModule)
app.setGlobalPrefix('/api')
/**
* @description Express is exposing the ip on the request object
*/
// @ts-expect-error
app.set('trust proxy', true)

const configService = app.get(ConfigService);
app.use(cookieParser())

await app.listen(configService.get<Config['port']>('port'));
const configService = app.get(ConfigService)

await app.listen(configService.get<Config['port']>('port')!)
}
bootstrap();
bootstrap()
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { z } from "zod";
import { BadRequestException, Body, Controller, Get, HttpCode, HttpStatus, Post, Req } from "@nestjs/common";
import { AuthenticationService } from "./authentication.service";
import * as UaParser from 'ua-parser-js';
import { Request } from "express";
import { z } from 'zod'
import { BadRequestException, Body, Controller, Get, HttpCode, HttpStatus, Post, Req, Res } from '@nestjs/common'
import type { Request as ExpressRequest, Response as ExpressResponse } from 'express'
import { JwtService } from '../jwt/jwt.service'
import { AuthenticationService } from './authentication.service'

@Controller()
export class AuthenticationController {
constructor(
private readonly authenticationService: AuthenticationService
private readonly authenticationService: AuthenticationService,
) { }

/**
* @description I refused to use class-validator for dto validation, if you want you can use them
*/
@Post('/sign-up')
@HttpCode(HttpStatus.OK)
async signUp (@Body() body: unknown) {
async signUp(@Body() body: unknown) {
const schema = z.object({
username: z.string().min(3).max(64),
password: z.string().min(8).max(64),
Expand All @@ -34,7 +34,7 @@ export class AuthenticationController {

@Post('/sign-in')
@HttpCode(HttpStatus.OK)
async signIn (@Body() body: unknown, @Req() req: Request) {
async signIn(@Body() body: unknown, @Req() req: ExpressRequest, @Res({ passthrough: true }) res: ExpressResponse) {
const schema = z.object({
username: z.string().min(3).max(64),
password: z.string().min(8).max(64),
Expand All @@ -48,6 +48,18 @@ export class AuthenticationController {

const { username, password } = result.data

return await this.authenticationService.signIn(username, password, req)
return await this.authenticationService.signIn(username, password, req, res)
}

@Get('/verify')
@HttpCode(HttpStatus.OK)
async verify(@Req() req: ExpressRequest) {
const result = z.string().safeParse(req.cookies?.[JwtService.ACCESS_TOKEN_NAME] ?? null)

if (result.success === false) {
throw new BadRequestException('Bad token')
}

return this.authenticationService.verify(result.data)
}
}
31 changes: 31 additions & 0 deletions apps/backend/src/modules/authentication/authentication.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { CanActivate, ExecutionContext } from '@nestjs/common'
import { Injectable, Logger } from '@nestjs/common'
import type { Observable } from 'rxjs'
import { z } from 'zod'
import { SessionService } from '../session/session.service'
import { JwtService } from '../jwt/jwt.service'

@Injectable()
export class AuthenticationGuard implements CanActivate {
readonly #logger = new Logger(AuthenticationGuard.name)
constructor(
private readonly sessionService: SessionService,
) { }

canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest()

const result = z.string().safeParse(request.cookies?.[JwtService.ACCESS_TOKEN_NAME])

if (result.success === false) {
this.#logger.error('No token found in request')
return false
}

const { data: token } = result

return this.sessionService.tieSession(token)
}
}
13 changes: 7 additions & 6 deletions apps/backend/src/modules/authentication/authentication.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Module } from "@nestjs/common";
import { AuthenticationController } from "./authentication.controller";
import { DrizzleModule } from "../drizzle/drizzle.module";
import { AuthenticationService } from "./authentication.service";
import { SessionModule } from "../session/session.module";
import { Module } from '@nestjs/common'
import { DrizzleModule } from '../drizzle/drizzle.module'
import { SessionModule } from '../session/session.module'
import { JwtModule } from '../jwt/jwt.module'
import { AuthenticationService } from './authentication.service'
import { AuthenticationController } from './authentication.controller'

@Module({
imports: [DrizzleModule, SessionModule],
imports: [DrizzleModule, SessionModule, JwtModule],
controllers: [AuthenticationController],
providers: [AuthenticationService],
})
Expand Down
Loading

0 comments on commit 72b82d9

Please sign in to comment.