Merge pull request #1094 from bigcapitalhq/fix/tenant-bypass-organization-id-header
fix(server): prevent cross-tenant access via organization-id header
This commit is contained in:
@@ -18,7 +18,7 @@ import { createBullBoardAuthMiddleware } from '@/middleware/bull-board-auth.midd
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { ClsModule, ClsService } from 'nestjs-cls';
|
||||
import { ClsModule } from 'nestjs-cls';
|
||||
import { AppController } from './App.controller';
|
||||
import { AppService } from './App.service';
|
||||
import { ItemsModule } from '../Items/Items.module';
|
||||
@@ -169,9 +169,6 @@ import { AppThrottleModule } from './AppThrottle.module';
|
||||
global: true,
|
||||
middleware: {
|
||||
mount: true,
|
||||
setup: (cls: ClsService, req: Request, res: Response) => {
|
||||
cls.set('organizationId', req.headers['organization-id']);
|
||||
},
|
||||
generateId: true,
|
||||
saveReq: true,
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ClsService } from 'nestjs-cls';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { SystemUser } from '@/modules/System/models/SystemUser';
|
||||
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||
import { ModelObject } from 'objection';
|
||||
import { JwtPayload } from '../Auth.interfaces';
|
||||
import { InvalidEmailPasswordException } from '../exceptions/InvalidEmailPassword.exception';
|
||||
@@ -12,6 +13,10 @@ export class AuthSigninService {
|
||||
constructor(
|
||||
@Inject(SystemUser.name)
|
||||
private readonly systemUserModel: typeof SystemUser,
|
||||
|
||||
@Inject(TenantModel.name)
|
||||
private readonly tenantModel: typeof TenantModel,
|
||||
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly clsService: ClsService,
|
||||
) { }
|
||||
@@ -49,6 +54,7 @@ export class AuthSigninService {
|
||||
*/
|
||||
async verifyPayload(payload: JwtPayload): Promise<any> {
|
||||
let user: SystemUser;
|
||||
let tenant: TenantModel | undefined;
|
||||
|
||||
try {
|
||||
user = await this.systemUserModel
|
||||
@@ -56,8 +62,14 @@ export class AuthSigninService {
|
||||
.findOne({ email: payload.sub })
|
||||
.throwIfNotFound();
|
||||
|
||||
tenant = await this.tenantModel
|
||||
.query()
|
||||
.findById(user.tenantId)
|
||||
.throwIfNotFound();
|
||||
|
||||
this.clsService.set('tenantId', user.tenantId);
|
||||
this.clsService.set('userId', user.id);
|
||||
this.clsService.set('organizationId', tenant.organizationId);
|
||||
} catch (error) {
|
||||
throw new UserNotFoundException(String(payload.sub));
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
SetMetadata,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants';
|
||||
import { getAuthApiKey } from '../Auth/Auth.utils';
|
||||
|
||||
@@ -16,7 +17,10 @@ export const TenantAgnosticRoute = () => SetMetadata(IS_TENANT_AGNOSTIC, true);
|
||||
|
||||
@Injectable()
|
||||
export class TenancyGlobalGuard implements CanActivate {
|
||||
constructor(private reflector: Reflector) {}
|
||||
constructor(
|
||||
private readonly reflector: Reflector,
|
||||
private readonly clsService: ClsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Validates the organization ID in the request headers.
|
||||
@@ -43,6 +47,14 @@ export class TenancyGlobalGuard implements CanActivate {
|
||||
if (!organizationId) {
|
||||
throw new UnauthorizedException('Organization ID is required.');
|
||||
}
|
||||
const authenticatedOrganizationId =
|
||||
this.clsService.get('organizationId');
|
||||
if (
|
||||
authenticatedOrganizationId &&
|
||||
authenticatedOrganizationId !== organizationId
|
||||
) {
|
||||
throw new UnauthorizedException('Organization mismatch.');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user