diff --git a/.env.example b/.env.example index aedd7c2d..aecf6a1e 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,7 @@ DB_PASSWORD= DB_DATABASE_NAME= DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_DATABASE_NAME}?schema=public" SECRET_KEY= +HMAC_SECRET_KEY= HOST=::0.0.0.0 PORT=4000 \ No newline at end of file diff --git a/.env.local b/.env.local index 9cd74618..c1687c6e 100644 --- a/.env.local +++ b/.env.local @@ -8,6 +8,7 @@ DB_PASSWORD=root DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_DATABASE_NAME}?schema=public" SECRET_KEY=batata +HMAC_SECRET_KEY= HOST=::0.0.0.0 PORT=4000 \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f2806b81..c1f808ed 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -24,6 +24,7 @@ jobs: echo SECRET_KEY=${{ secrets.SECRET_KEY }} >> .env echo HOST=${{ secrets.HOST }} >> .env echo PORT=${{ secrets.PORT }} >> .env + echo HMAC_SECRET_KEY=${{ secrets.HMAC_SECRET_KEY }} >> .env echo SERVER_USER_PASSWORD=${{ secrets.SERVER_USER_PASSWORD }} >> .env cat .env diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index c8eb5c7d..37b800a8 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -24,6 +24,7 @@ jobs: echo SECRET_KEY=${{ secrets.SECRET_KEY }} >> .env echo HOST=${{ secrets.HOST }} >> .env echo PORT=${{ secrets.PORT }} >> .env + echo HMAC_SECRET_KEY=${{ secrets.HMAC_SECRET_KEY }} >> .env echo SERVER_USER_PASSWORD=${{ secrets.SERVER_USER_PASSWORD }} >> .env cat .env diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 56b875d3..818565fc 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -24,6 +24,7 @@ jobs: echo SECRET_KEY=${{ secrets.SECRET_KEY }} >> .env echo HOST=${{ secrets.HOST }} >> .env echo PORT=${{ secrets.PORT }} >> .env + echo HMAC_SECRET_KEY=${{ secrets.HMAC_SECRET_KEY }} >> .env echo SERVER_USER_PASSWORD=${{ secrets.SERVER_USER_PASSWORD }} >> .env cat .env diff --git a/src/decorators/Hmac/hmac.decorator.ts b/src/decorators/Hmac/hmac.decorator.ts new file mode 100644 index 00000000..d5ce25c0 --- /dev/null +++ b/src/decorators/Hmac/hmac.decorator.ts @@ -0,0 +1,7 @@ +import { applyDecorators, UseGuards } from '@nestjs/common'; + +import { HmacGuard } from '@/guards/hmac.guard'; + +export function Hmac() { + return applyDecorators(UseGuards(HmacGuard)); +} diff --git a/src/decorators/Hmac/index.ts b/src/decorators/Hmac/index.ts new file mode 100644 index 00000000..392c2112 --- /dev/null +++ b/src/decorators/Hmac/index.ts @@ -0,0 +1,3 @@ +import { Hmac } from './hmac.decorator'; + +export { Hmac }; diff --git a/src/guards/hmac.guard.ts b/src/guards/hmac.guard.ts new file mode 100644 index 00000000..4829c922 --- /dev/null +++ b/src/guards/hmac.guard.ts @@ -0,0 +1,42 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + UnauthorizedException, +} from '@nestjs/common'; +import * as crypto from 'crypto'; + +@Injectable() +export class HmacGuard implements CanActivate { + constructor() {} + + canActivate(context: ExecutionContext): boolean { + const request = context.switchToHttp().getRequest(); + const hmacHeader = request.headers['x-hmac-signature']; + const timestamp = request.headers['x-hmac-timestamp']; + + if (!hmacHeader || !timestamp) { + throw new UnauthorizedException(); + } + + const secretKey = process.env.HMAC_SECRET_KEY ?? ''; + const currentTimestamp = Math.floor(Date.now() / 1000); + + if (Math.abs(currentTimestamp - parseInt(timestamp)) > 10) { + throw new UnauthorizedException(); + } + + const payload = `${request.method}:${request.url}:${timestamp}:${JSON.stringify(request.body)}`; + + const hmac = crypto + .createHmac('sha256', secretKey) + .update(payload) + .digest('hex'); + + if (hmac !== hmacHeader) { + throw new UnauthorizedException(); + } + + return true; + } +}