From a83220b8a916d1643685baf2aa438624a9c17f50 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Tue, 4 Jun 2024 10:35:50 +0700 Subject: [PATCH] feat: fix interceptor --- .../src/api/routes/users.controller.ts | 9 +++++---- apps/backend/src/main.ts | 2 ++ .../src/services/auth/auth.middleware.ts | 19 +++++++++--------- .../src/components/layout/layout.context.tsx | 8 +++++++- .../src/services/exception.filter.ts | 20 +++++++++++++++++++ 5 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 libraries/nestjs-libraries/src/services/exception.filter.ts diff --git a/apps/backend/src/api/routes/users.controller.ts b/apps/backend/src/api/routes/users.controller.ts index f8623ad1..81bc575d 100644 --- a/apps/backend/src/api/routes/users.controller.ts +++ b/apps/backend/src/api/routes/users.controller.ts @@ -26,6 +26,7 @@ import { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions import { ApiTags } from '@nestjs/swagger'; import { UsersService } from '@gitroom/nestjs-libraries/database/prisma/users/users.service'; import { UserDetailDto } from '@gitroom/nestjs-libraries/dtos/users/user.details.dto'; +import { HttpForbiddenException } from '@gitroom/nestjs-libraries/services/exception.filter'; @ApiTags('User') @Controller('/user') @@ -41,10 +42,10 @@ export class UsersController { async getSelf( @GetUserFromRequest() user: User, @GetOrgFromRequest() organization: Organization, - @Req() req: Request + @Req() req: Request, ) { if (!organization) { - throw new HttpException('Organization not found', 401); + throw new HttpForbiddenException(); } return { @@ -74,7 +75,7 @@ export class UsersController { @Query('name') name: string ) { if (!user.isSuperAdmin) { - throw new HttpException('Unauthorized', 401); + throw new HttpException('Unauthorized', 400); } return this._userService.getImpersonateUser(name); @@ -87,7 +88,7 @@ export class UsersController { @Res({ passthrough: true }) response: Response ) { if (!user.isSuperAdmin) { - throw new HttpException('Unauthorized', 401); + throw new HttpException('Unauthorized', 400); } response.cookie('impersonate', id, { diff --git a/apps/backend/src/main.ts b/apps/backend/src/main.ts index 33ddda75..cbad49fd 100644 --- a/apps/backend/src/main.ts +++ b/apps/backend/src/main.ts @@ -7,6 +7,7 @@ import {Logger, ValidationPipe} from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import {SubscriptionExceptionFilter} from "@gitroom/backend/services/auth/permissions/subscription.exception"; +import { HttpExceptionFilter } from '@gitroom/nestjs-libraries/services/exception.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule, { @@ -24,6 +25,7 @@ async function bootstrap() { app.use(cookieParser()); app.useGlobalFilters(new SubscriptionExceptionFilter()); + app.useGlobalFilters(new HttpExceptionFilter()); loadSwagger(app); diff --git a/apps/backend/src/services/auth/auth.middleware.ts b/apps/backend/src/services/auth/auth.middleware.ts index 975d633c..372b40c1 100644 --- a/apps/backend/src/services/auth/auth.middleware.ts +++ b/apps/backend/src/services/auth/auth.middleware.ts @@ -5,16 +5,21 @@ import { User } from '@prisma/client'; import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service'; import { UsersService } from '@gitroom/nestjs-libraries/database/prisma/users/users.service'; import { removeSubdomain } from '@gitroom/helpers/subdomain/subdomain.management'; +import { HttpForbiddenException } from '@gitroom/nestjs-libraries/services/exception.filter'; -const removeAuth = (res: Response) => +export const removeAuth = (res: Response) => { res.cookie('auth', '', { domain: '.' + new URL(removeSubdomain(process.env.FRONTEND_URL!)).hostname, secure: true, httpOnly: true, sameSite: 'none', - expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), + expires: new Date(0), + maxAge: -1, }); + res.header('logout', 'true'); +}; + @Injectable() export class AuthMiddleware implements NestMiddleware { constructor( @@ -24,8 +29,7 @@ export class AuthMiddleware implements NestMiddleware { async use(req: Request, res: Response, next: NextFunction) { const auth = req.headers.auth || req.cookies.auth; if (!auth) { - removeAuth(res); - res.status(401).send('Unauthorized'); + throw new HttpForbiddenException(); } try { let user = AuthService.verifyJWT(auth) as User | null; @@ -71,9 +75,7 @@ export class AuthMiddleware implements NestMiddleware { organization.find((org) => org.id === orgHeader) || organization[0]; if (!organization) { - removeAuth(res); - res.status(401).send('Unauthorized'); - return ; + throw new HttpForbiddenException(); } // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -84,8 +86,7 @@ export class AuthMiddleware implements NestMiddleware { // @ts-expect-error req.org = setOrg; } catch (err) { - removeAuth(res); - res.status(401).send('Unauthorized'); + throw new HttpForbiddenException(); } next(); } diff --git a/apps/frontend/src/components/layout/layout.context.tsx b/apps/frontend/src/components/layout/layout.context.tsx index 8269c602..a44f94db 100644 --- a/apps/frontend/src/components/layout/layout.context.tsx +++ b/apps/frontend/src/components/layout/layout.context.tsx @@ -17,13 +17,19 @@ function LayoutContextInner(params: { children: ReactNode }) { const afterRequest = useCallback( async (url: string, options: RequestInit, response: Response) => { if (response?.headers?.get('onboarding')) { - window.location.href = isGeneral() ? '/launches?onboarding=true' : '/analytics?onboarding=true'; + window.location.href = isGeneral() + ? '/launches?onboarding=true' + : '/analytics?onboarding=true'; } if (response?.headers?.get('reload')) { window.location.reload(); } + if (response.status === 401) { + window.location.href = '/'; + } + if (response.status === 402) { if ( await deleteDialog( diff --git a/libraries/nestjs-libraries/src/services/exception.filter.ts b/libraries/nestjs-libraries/src/services/exception.filter.ts new file mode 100644 index 00000000..aa4c25c0 --- /dev/null +++ b/libraries/nestjs-libraries/src/services/exception.filter.ts @@ -0,0 +1,20 @@ +import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'; +import { Response } from 'express'; +import { removeAuth } from '@gitroom/backend/services/auth/auth.middleware'; + +export class HttpForbiddenException extends HttpException { + constructor() { + super('Forbidden', 403); + } +} + +@Catch(HttpForbiddenException) +export class HttpExceptionFilter implements ExceptionFilter { + catch(exception: HttpForbiddenException, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + removeAuth(response); + + return response.status(401).send(); + } +} \ No newline at end of file