fix(plugins): match remote HMAC tenants with the same host rules as get_user

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-08 10:50:40 +03:30
co-authored by Cursor
parent 2d43ce6c29
commit 94d79c175b
3 changed files with 124 additions and 149 deletions
+10 -69
View File
@@ -4,6 +4,10 @@ import { PrismaService } from '../prisma/prisma.service';
import { applyKhanegiNavSample } from './khanegi-nav-sample';
import { rewriteLegacyUploadUrl } from '../uploads/uploads.util';
import { PLUGIN_STORE_SETTING_KEY } from '../program-settings/runtime-setting-keys';
import {
findTenantForHost as matchTenantForHost,
normalizeTenantHost,
} from '../auth/tenant-host.util';
type UiSetting = { key: string; value: string };
@@ -28,7 +32,7 @@ export class GetUserService {
constructor(private readonly prisma: PrismaService) {}
async resolveByHost(hostHeader?: string, domainQuery?: string) {
const host = this.normalizeHost(domainQuery || hostHeader || '');
const host = normalizeTenantHost(domainQuery || hostHeader || '');
if (!host) {
throw new NotFoundException('domain required');
}
@@ -374,75 +378,12 @@ export class GetUserService {
}
/**
* اول دامنهٔ اصلی (Tenant.domain)، بعد aliases داخل domains[].
* اگر چند نسخه یک دامنه را داشته باشند، نسخهٔ primary برنده است.
* همان منطق HMAC ریموت: دامنهٔ اصلی، aliases، بعد admin/app/node URL.
*/
private findTenantForHost<
T extends {
domain: string;
domains: unknown;
},
>(tenants: T[], host: string): T | undefined {
const normalizedHost = this.normalizeHost(host);
const hostNoWww = normalizedHost.replace(/^www\./, '');
const primary = tenants.find((item) => {
const d = this.normalizeHost(item.domain);
return d === normalizedHost || d.replace(/^www\./, '') === hostNoWww;
});
if (primary) return primary;
return tenants.find((item) => this.matchesAliasHost(item, normalizedHost));
}
private matchesAliasHost(
tenant: {
domain: string;
domains: unknown;
},
private findTenantForHost<T extends { domain: string; domains: unknown }>(
tenants: T[],
host: string,
) {
const hostNoWww = host.replace(/^www\./, '');
for (const value of this.readDomains(tenant.domains)) {
const normalized = this.normalizeHost(value);
if (!normalized) continue;
if (normalized === host || normalized.replace(/^www\./, '') === hostNoWww) {
return true;
}
}
return false;
}
private matchesHost(
tenant: {
domain: string;
domains: unknown;
},
host: string,
) {
return Boolean(this.findTenantForHost([tenant], host));
}
private readDomains(raw: unknown): string[] {
if (!raw) return [];
if (Array.isArray(raw)) return raw.map(String);
if (typeof raw === 'string') {
try {
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed.map(String) : [];
} catch {
return [];
}
}
return [];
}
private normalizeHost(value: string) {
return value
.trim()
.toLowerCase()
.replace(/^https?:\/\//, '')
.split('/')[0]
.split(':')[0];
): T | undefined {
return matchTenantForHost(tenants, host);
}
}