Skip to content

Commit

Permalink
merge conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
phertyameen committed Feb 27, 2025
2 parents 3e2cfd9 + b46e522 commit 4319292
Show file tree
Hide file tree
Showing 33 changed files with 946 additions and 43 deletions.
7 changes: 7 additions & 0 deletions backend/security/decorators/auth.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { SetMetadata } from '@nestjs/common';
import { AuthType } from '../../src/common/enums/auth-type.enum';

export const AUTH_KEY = 'authType';

export const Auth = (type: AuthType = AuthType.Bearer) =>
SetMetadata(AUTH_KEY, type);
6 changes: 6 additions & 0 deletions backend/security/decorators/roles.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { SetMetadata } from '@nestjs/common';
import { UserRole } from '../../src/common/enums/users-roles.enum';

export const ROLES_KEY = 'roles';
export const RoleDecorator = (...roles: [UserRole, ...UserRole[]]) =>
SetMetadata(ROLES_KEY, roles);
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
return super.canActivate(context);
}

handleRequest(err, user) {
handleRequest<TUser = any>(
err: any,
user: TUser,
info: any,
context: ExecutionContext,
status?: any,
): TUser {
console.log('Extracted User:', user);
if (err || !user) {
throw new UnauthorizedException('Invalid token');
throw new UnauthorizedException('you dont have any access ');
}
return user;
}
Expand Down
39 changes: 39 additions & 0 deletions backend/security/guards/rolesGuard/roles.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../../decorators/roles.decorator';
import { UserRole } from 'src/common/enums/users-roles.enum';

@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {
// Get required roles from metadata
const requiredRoles = this.reflector.getAllAndOverride<UserRole[]>(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);

// Get user from request
const request = context.switchToHttp().getRequest();
const user = request.user; // Ensure user is attached to request (e.g., via JWT auth)
console.log('User from request:', user);

if (!user || !user.role) {
throw new ForbiddenException('Access denied: No role assigned');
}

// Check if the user has at least one required role
const hasRequiredRoles = requiredRoles.includes(user.role);
if (!hasRequiredRoles) {
throw new ForbiddenException('Access denied: role not found');
}

return hasRequiredRoles;
}
}
3 changes: 0 additions & 3 deletions backend/security/roles.decorator.ts

This file was deleted.

31 changes: 0 additions & 31 deletions backend/security/roles.guard.ts

This file was deleted.

File renamed without changes.
4 changes: 2 additions & 2 deletions backend/src/admin/admin.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
import { AdminService } from './providers/admin.service';
import { CreateAdminDto } from './dto/create-admin.dto';
import { UpdateAdminDto } from './dto/update-admin.dto';
import { RolesGuard } from 'security/roles.guard';
import { JwtAuthGuard } from 'security/jwt-auth.guard';
import { RolesGuard } from 'security/guards/rolesGuard/roles.guard';
import { JwtAuthGuard } from 'security/guards/jwt-auth.guard';

@Controller('/api/v1/admin')
@UseGuards(JwtAuthGuard, RolesGuard)
Expand Down
4 changes: 2 additions & 2 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { MailModule } from './mail/mail.module';
username: process.env.DB_USERNAME,
password: String(process.env.DB_PASSWORD),
database: process.env.DB_NAME,
autoLoadEntities: true, // Automatically loads entities from entities folder
autoLoadEntities: true,
entities: [User, Result, Leaderboard, Admin, SubAdmin],
migrations: ['src/migrations/*.ts'],
synchronize: true,
Expand All @@ -60,6 +60,6 @@ import { MailModule } from './mail/mail.module';
MailModule,
],
controllers: [AppController, GuestUserController],
providers: [AppService, GuestGuard, RedisService, GuestUserService], // Provide RedisService & GuestGuard globally
providers: [AppService, GuestGuard, RedisService, GuestUserService],
})
export class AppModule {}
2 changes: 1 addition & 1 deletion backend/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ConfigModule } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { RefreshTokenProvider } from './providers/refresh-token.provider';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from '../../security/jwt.strategy';
import { JwtStrategy } from '../../security/strategies/jwt.strategy';
import { SubAdminModule } from 'src/sub-admin/sub-admin.module';
import { GoogleAuthenticationController } from './social/google-authtication.controller';
import { GoogleAuthenticationService } from './social/providers/google-authtication';
Expand Down
4 changes: 4 additions & 0 deletions backend/src/common/enums/auth-type.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum AuthType {
Bearer = 'Bearer',
None = 'None'
}
5 changes: 5 additions & 0 deletions backend/src/common/enums/users-roles.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum UserRole {
Admin = 'Admin',
User = 'User',
Guest = 'Guest'
}
40 changes: 40 additions & 0 deletions backend/src/common/example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { HttpStatus, Injectable } from '@nestjs/common';
import {
DatabaseException,
BlockchainException,
SessionException,
} from './exceptions';

@Injectable()
export class SomeService {
async performDatabaseOperation() {
try {
// Database operations...
} catch (error) {
if (error.code === '23505') {
throw new DatabaseException(
'A record with the same ID already exists',
HttpStatus.CONFLICT,
);
}
throw new DatabaseException('Failed to perform database operation');
}
}

async executeBlockchainTransaction() {
try {
// Blockchain operations...
} catch (error) {
throw new BlockchainException('Smart contract execution failed');
}
}

async validateSession(sessionId: string) {
const isValid = /* check session validity */ false;
if (!isValid) {
throw new SessionException(
'Your session has expired, please log in again',
);
}
}
}
49 changes: 49 additions & 0 deletions backend/src/common/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { HttpException, HttpStatus } from '@nestjs/common';

export class ValidationException extends HttpException {
constructor(message: string, errors?: any) {
super(
{
message,
errors,
},
HttpStatus.BAD_REQUEST,
);
}
}

export class SessionException extends HttpException {
constructor(message: string = 'Session expired') {
super(
{
message,
},
HttpStatus.FORBIDDEN,
);
}
}

export class DatabaseException extends HttpException {
constructor(
message: string,
statusCode: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR,
) {
super(
{
message,
},
statusCode,
);
}
}

export class BlockchainException extends HttpException {
constructor(message: string) {
super(
{
message,
},
HttpStatus.UNPROCESSABLE_ENTITY,
);
}
}
78 changes: 78 additions & 0 deletions backend/src/common/filters/all-exceptions.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { randomUUID } from 'crypto';
import { ErrorResponse } from '../interfaces/error-response.interface';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
private readonly logger = new Logger(AllExceptionsFilter.name);

catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();

const requestId = randomUUID();

let statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
let message = 'Internal server error';
let error = 'Internal Server Error';

if (exception instanceof HttpException) {
statusCode = exception.getStatus();
const exceptionResponse = exception.getResponse();

if (typeof exceptionResponse === 'string') {
message = exceptionResponse;
} else if (
typeof exceptionResponse === 'object' &&
exceptionResponse !== null
) {
message = (exceptionResponse as any).message || message;
error =
(exceptionResponse as any).error ||
this.getErrorNameFromStatus(statusCode);
}
}

const errorResponse: ErrorResponse = {
statusCode,
message,
error,
timestamp: new Date().toISOString(),
path: request.url,
requestId,
};

this.logger.error(
`${requestId} - ${request.method} ${request.url} - ${statusCode} - ${message}`,
exception instanceof Error ? exception.stack : '',
);

response.status(statusCode).json(errorResponse);
}

private getErrorNameFromStatus(status: number): string {
switch (status) {
case HttpStatus.BAD_REQUEST:
return 'Bad Request';
case HttpStatus.UNAUTHORIZED:
return 'Unauthorized';
case HttpStatus.FORBIDDEN:
return 'Forbidden';
case HttpStatus.NOT_FOUND:
return 'Not Found';
case HttpStatus.UNPROCESSABLE_ENTITY:
return 'Unprocessable Entity';
default:
return 'Internal Server Error';
}
}
}
62 changes: 62 additions & 0 deletions backend/src/common/filters/auth-exception.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
ExceptionFilter,
Catch,
ArgumentsHost,
UnauthorizedException,
ForbiddenException,
Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { randomUUID } from 'crypto';
import { ErrorResponse } from '../interfaces/error-response.interface';

@Catch(UnauthorizedException, ForbiddenException)
export class AuthExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(AuthExceptionFilter.name);

catch(
exception: UnauthorizedException | ForbiddenException,
host: ArgumentsHost,
) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();

const requestId = randomUUID();
const statusCode = exception.getStatus();

let message: string;
let error: string;

if (exception instanceof UnauthorizedException) {
message = 'Authentication required';
error = 'Unauthorized';
} else {
message = 'Access denied';
error = 'Forbidden';
}

// Override with custom message if provided
const exceptionResponse = exception.getResponse();
if (typeof exceptionResponse === 'object' && exceptionResponse !== null) {
if ((exceptionResponse as any).message) {
message = (exceptionResponse as any).message;
}
}

const errorResponse: ErrorResponse = {
statusCode,
message,
error,
timestamp: new Date().toISOString(),
path: request.url,
requestId,
};

this.logger.error(
`${requestId} - ${error}: ${request.method} ${request.url} - ${message}`,
);

response.status(statusCode).json(errorResponse);
}
}
Loading

0 comments on commit 4319292

Please sign in to comment.