Closed gorkemyontem closed 1 year ago
Hi @gorkemyontem thanks for writing in.
I am guessing you are getting this error because in the lib the expectation is that X-TENANT-ID
is being set in the header of the request or via the subdomain approach. If either of this isn't done then the library throws an error.
// Pull the tenant id from the subdomain
if (isTenantFromSubdomain) {
return this.getTenantFromSubdomain(isFastifyAdaptor, req);
} else {
// Validate if tenant identifier token is present
if (!tenantIdentifier) {
throw new BadRequestException(`${tenantIdentifier} is mandatory`);
}
return this.getTenantFromRequest(isFastifyAdaptor, req, tenantIdentifier);
}
private static getTenantFromRequest(
isFastifyAdaptor: boolean,
req: Request,
tenantIdentifier: string,
) {
let tenantId = '';
if (isFastifyAdaptor) {
// For Fastify
// Get the tenant id from the header
tenantId =
req.headers[`${tenantIdentifier || ''}`.toLowerCase()]?.toString() ||
'';
} else {
// For Express - Default
// Get the tenant id from the request
tenantId = req.get(`${tenantIdentifier}`) || '';
}
// Validate if tenant id is present
if (this.isEmpty(tenantId)) {
throw new BadRequestException(`${tenantIdentifier} is not supplied`);
}
return tenantId;
}
private static getTenantFromSubdomain(
isFastifyAdaptor: boolean,
req: Request,
) {
let tenantId = '';
if (isFastifyAdaptor) {
// For Fastify
const subdomains = this.getSubdomainsForFastify(req);
if (subdomains instanceof Array && subdomains.length > 0) {
tenantId = subdomains[subdomains.length - 1];
}
} else {
// For Express - Default
// Check for multi-level subdomains and return only the first name
if (req.subdomains instanceof Array && req.subdomains.length > 0) {
tenantId = req.subdomains[req.subdomains.length - 1];
}
}
// Validate if tenant identifier token is present
if (this.isEmpty(tenantId)) {
throw new BadRequestException(`Tenant ID is mandatory`);
}
return tenantId;
}
In your case if you need to get it from the JWT then the solution for fetching the tenant id will have to be modified, which is not currently an option provided by this library. Also the middleware that you have added might be getting called after the tenant check is done by the library and hence you are getting that error (just my assumption).
Once I tried this approach and it actually worked. I used it for a while and then removed, because I started to have the need for several jwt secrets, for different kinds of api key and then I needed to always validate it on the validator, and this is not a good approach.
But here is the code. Set in the tenantIdentifier the Authorization header:
const tenancyModule = TenancyModule.forRootAsync({
imports: [TenantsModule],
useFactory: async (
configService: ConfigService,
tenantValidator: TenantsValidator,
) => {
return {
tenantIdentifier: 'Authorization',
uri: (tenantId: string) => {
tenantId = tenantValidator.checkTenantId(tenantId);
return `${configService.get('database')}/tenant-${tenantId}`;
},
validator: (tenantId: string) => tenantValidator.setTenantId(tenantId),
// eslint-disable-next-line @typescript-eslint/no-empty-function
options: () => {},
};
},
inject: [ConfigService, TenantsValidator],
});
Then in your validator: PS: my tenant entity lives in a global database, it's not scoped to tenant database.
import { JwtService } from '@nestjs/jwt';
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { TenancyValidator } from '@needle-innovision/nestjs-tenancy';
import { Model } from 'mongoose';
import { Tenant } from './entities/tenant.entity';
@Injectable()
export class TenantsValidator implements TenancyValidator {
private _tenantId: string;
constructor(
@InjectModel('Tenant') private tenantModel: Model<Tenant>,
private readonly jwtService: JwtService,
) {}
checkTenantId(tenantId: string): string {
let _tenantId = tenantId;
if (tenantId.includes('Bearer')) {
const payload = this.jwtService.decode(tenantId.replace('Bearer ', ''));
if (!payload) throw new NotFoundException(`Tenant not found`);
_tenantId = payload['subdomain'];
}
return _tenantId;
}
setTenantId(tenantId: string): TenancyValidator {
this._tenantId = this.checkTenantId(tenantId);
return this;
}
async validate(): Promise<void> {
const exist = await this.tenantModel.findOne({ subdomain: this._tenantId });
if (!exist) {
throw new NotFoundException(`Workspace not found`);
}
if (!exist.active) {
throw new NotFoundException(`Your account is inactive.`);
}
}
}
Using tenantIdentifier: 'Authorization' is pretty smart @samuelpares thank you for the Idea!
Hi there, Great library, it made my task much easier. Thank you for your hard work.
I have an jwt authentication in my project. I have included tenant Id to JWT payload just in case then i thought, instead of setting req.headers ['X-TENANT-ID'] = '123'; on every request from the client side, I'm already sending jwt token back and forth, so why not using that. Therefore, I have created a middleware, which eventually will decode the JWT token, gets the tenantId and set the request headers on the fly. A small reproduction is below:
at this point, even though i set the request header, i cannot avoid getting "X-TENANT-ID is not supplied" error.
when i analyze further i see this weird behavior:
and when i checked in your code, you have fairly used the request.get() approach, therefore the library couldn't locate the tenantid.
My questions are;
Thank you in advanced!