fix(07): replace TenantMiddleware with TenantGuard to fix tenant context
Middleware runs before guards in NestJS — req.user was always undefined when TenantMiddleware executed, so req.tenantId was never set. Convert to TenantGuard (APP_GUARD, registered after JwtAuthGuard) so it runs after JWT validation and can read req.user.tenantId correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
|
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
|
||||||
import { ScheduleModule } from '@nestjs/schedule';
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
@@ -16,7 +16,7 @@ import { DomaincheckModule } from './domaincheck/domaincheck.module';
|
|||||||
import { ModuleRegistryModule } from './module-registry/module-registry.module';
|
import { ModuleRegistryModule } from './module-registry/module-registry.module';
|
||||||
import { PrismaModule } from './prisma/prisma.module';
|
import { PrismaModule } from './prisma/prisma.module';
|
||||||
import { SettingsModule } from './settings/settings.module';
|
import { SettingsModule } from './settings/settings.module';
|
||||||
import { TenantMiddleware } from './tenant/tenant.middleware';
|
import { TenantGuard } from './tenant/tenant.guard';
|
||||||
import { TenantModule } from './tenant/tenant.module';
|
import { TenantModule } from './tenant/tenant.module';
|
||||||
import { UserModule } from './user/user.module';
|
import { UserModule } from './user/user.module';
|
||||||
|
|
||||||
@@ -44,6 +44,11 @@ import { UserModule } from './user/user.module';
|
|||||||
provide: APP_GUARD,
|
provide: APP_GUARD,
|
||||||
useClass: JwtAuthGuard,
|
useClass: JwtAuthGuard,
|
||||||
},
|
},
|
||||||
|
// Runs after JwtAuthGuard — sets req.tenantId and req.tenantPrisma from req.user
|
||||||
|
{
|
||||||
|
provide: APP_GUARD,
|
||||||
|
useClass: TenantGuard,
|
||||||
|
},
|
||||||
// Global roles guard: checks @Roles() decorator
|
// Global roles guard: checks @Roles() decorator
|
||||||
{
|
{
|
||||||
provide: APP_GUARD,
|
provide: APP_GUARD,
|
||||||
@@ -56,9 +61,4 @@ import { UserModule } from './user/user.module';
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule implements NestModule {
|
export class AppModule {}
|
||||||
configure(consumer: MiddlewareConsumer) {
|
|
||||||
// TenantMiddleware runs AFTER AuthGuard (guards run first in NestJS pipeline)
|
|
||||||
consumer.apply(TenantMiddleware).forRoutes('*');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import {
|
||||||
|
CanActivate,
|
||||||
|
ExecutionContext,
|
||||||
|
ForbiddenException,
|
||||||
|
Injectable,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { forTenant } from '../prisma/prisma-tenant.extension';
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs AFTER JwtAuthGuard (guard execution order follows APP_GUARD registration order).
|
||||||
|
* At this point req.user is populated — middleware ran too early to access it.
|
||||||
|
*
|
||||||
|
* Sets req.tenantId and req.tenantPrisma for downstream controllers.
|
||||||
|
* Super-Admin can override tenant via x-tenant-id header (D-10).
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class TenantGuard implements CanActivate {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
canActivate(context: ExecutionContext): boolean {
|
||||||
|
const req = context.switchToHttp().getRequest();
|
||||||
|
const user = req.user;
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
// Public route (login, health) — skip tenant context
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tenantId: string | undefined = user.tenantId;
|
||||||
|
|
||||||
|
if (user.role === 'SUPER_ADMIN' && req.headers['x-tenant-id']) {
|
||||||
|
tenantId = req.headers['x-tenant-id'] as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tenantId && user.role !== 'SUPER_ADMIN') {
|
||||||
|
throw new ForbiddenException('No tenant context');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tenantId) {
|
||||||
|
req.tenantPrisma = forTenant(this.prisma, tenantId);
|
||||||
|
req.tenantId = tenantId;
|
||||||
|
} else {
|
||||||
|
req.tenantPrisma = this.prisma;
|
||||||
|
req.tenantId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user