Compare commits

..

2 Commits

Author SHA1 Message Date
9792110560 docs: add CI/CD and deployment documentation
Added a new `README.md` under `.github` to document the project's CI/CD workflow and deployment setup. Includes GitHub Actions pipeline descriptions, Docker configuration details, environment variables, and production deployment considerations.
2025-05-15 19:10:07 +02:00
e64d706274 feat: implement global JWT auth guard with public route support
Introduced `JwtAuthGuard` as a global authentication mechanism using `APP_GUARD`. Added support for public routes via a `Public` decorator with metadata. Updated `AuthController`
2025-05-15 19:09:52 +02:00
5 changed files with 126 additions and 3 deletions

75
.github/README.md vendored Normal file
View File

@ -0,0 +1,75 @@
# CI/CD and Deployment Documentation
This directory contains the CI/CD configuration for the project.
## CI/CD Workflow
The CI/CD pipeline is configured using GitHub Actions and is defined in the `.github/workflows/ci-cd.yml` file. The workflow consists of the following steps:
### Build and Test
This job runs on every push to the main branch and on pull requests:
1. Sets up Node.js and pnpm
2. Installs dependencies
3. Builds and tests the backend
4. Builds and lints the frontend
### Build and Push Docker Images
This job runs only on pushes to the main branch:
1. Sets up Docker Buildx
2. Logs in to GitHub Container Registry
3. Builds and pushes the backend Docker image
4. Builds and pushes the frontend Docker image
## Deployment
The application is containerized using Docker. Dockerfiles are provided for both the backend and frontend:
- `backend/Dockerfile`: Multi-stage build for the NestJS backend
- `frontend/Dockerfile`: Multi-stage build for the Next.js frontend
A `docker-compose.yml` file is also provided at the root of the project for local development and as a reference for deployment.
### Running Locally with Docker Compose
```bash
# Build and start all services
docker-compose up -d
# View logs
docker-compose logs -f
# Stop all services
docker-compose down
```
### Environment Variables
The following environment variables are used in the deployment:
#### Backend
- `NODE_ENV`: Environment (development, production)
- `PORT`: Port on which the backend runs
- `POSTGRES_HOST`: PostgreSQL host
- `POSTGRES_PORT`: PostgreSQL port
- `POSTGRES_DB`: PostgreSQL database name
- `POSTGRES_USER`: PostgreSQL username
- `POSTGRES_PASSWORD`: PostgreSQL password
#### Frontend
- `NODE_ENV`: Environment (development, production)
- `PORT`: Port on which the frontend runs
- `NEXT_PUBLIC_API_URL`: URL of the backend API
## Production Deployment Considerations
For production deployment, consider the following:
1. Use a proper secrets management solution for sensitive information
2. Set up proper networking and security groups
3. Configure a reverse proxy (like Nginx) for SSL termination
4. Set up monitoring and logging
5. Configure database backups

View File

@ -1,5 +1,6 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { DatabaseModule } from './database/database.module'; import { DatabaseModule } from './database/database.module';
@ -8,6 +9,7 @@ import { ProjectsModule } from './modules/projects/projects.module';
import { AuthModule } from './modules/auth/auth.module'; import { AuthModule } from './modules/auth/auth.module';
import { GroupsModule } from './modules/groups/groups.module'; import { GroupsModule } from './modules/groups/groups.module';
import { TagsModule } from './modules/tags/tags.module'; import { TagsModule } from './modules/tags/tags.module';
import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard';
@Module({ @Module({
imports: [ imports: [
@ -23,6 +25,12 @@ import { TagsModule } from './modules/tags/tags.module';
TagsModule, TagsModule,
], ],
controllers: [AppController], controllers: [AppController],
providers: [AppService], providers: [
AppService,
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
],
}) })
export class AppModule {} export class AppModule {}

View File

@ -16,6 +16,7 @@ import { GithubAuthGuard } from '../guards/github-auth.guard';
import { JwtAuthGuard } from '../guards/jwt-auth.guard'; import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { JwtRefreshGuard } from '../guards/jwt-refresh.guard'; import { JwtRefreshGuard } from '../guards/jwt-refresh.guard';
import { GetUser } from '../decorators/get-user.decorator'; import { GetUser } from '../decorators/get-user.decorator';
import { Public } from '../decorators/public.decorator';
@Controller('auth') @Controller('auth')
export class AuthController { export class AuthController {
@ -27,6 +28,7 @@ export class AuthController {
/** /**
* Initiate GitHub OAuth flow * Initiate GitHub OAuth flow
*/ */
@Public()
@Get('github') @Get('github')
@UseGuards(GithubAuthGuard) @UseGuards(GithubAuthGuard)
githubAuth() { githubAuth() {
@ -37,6 +39,7 @@ export class AuthController {
/** /**
* Handle GitHub OAuth callback * Handle GitHub OAuth callback
*/ */
@Public()
@Get('github/callback') @Get('github/callback')
@UseGuards(GithubAuthGuard) @UseGuards(GithubAuthGuard)
async githubAuthCallback(@Req() req: Request, @Res() res: Response) { async githubAuthCallback(@Req() req: Request, @Res() res: Response) {
@ -61,6 +64,7 @@ export class AuthController {
/** /**
* Refresh tokens * Refresh tokens
*/ */
@Public()
@Post('refresh') @Post('refresh')
@UseGuards(JwtRefreshGuard) @UseGuards(JwtRefreshGuard)
async refreshTokens(@GetUser() user) { async refreshTokens(@GetUser() user) {

View File

@ -0,0 +1,14 @@
import { SetMetadata } from '@nestjs/common';
/**
* Key for the public metadata
*/
export const IS_PUBLIC_KEY = 'isPublic';
/**
* Decorator to mark a route as public (not requiring authentication)
*
* Usage:
* - @Public() - Mark a route as public
*/
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

View File

@ -1,11 +1,33 @@
import { Injectable, UnauthorizedException } from '@nestjs/common'; import { Injectable, UnauthorizedException, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
/** /**
* Guard for JWT authentication * Guard for JWT authentication
*/ */
@Injectable() @Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') { export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
/**
* Check if the route is public or requires authentication
*/
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
/** /**
* Handle unauthorized errors * Handle unauthorized errors
*/ */
@ -15,4 +37,4 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
} }
return user; return user;
} }
} }