fix(plugins): resolve remote tenant from admin and app URLs

HMAC only matched tenant.domain, so k.montzar.ir / new.gholaktala.ir missed the row. Also return an empty store when catalog tables are missing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-07 21:54:03 +03:30
co-authored by Cursor
parent a7747366f9
commit dd212d01ed
2 changed files with 44 additions and 5 deletions
+24 -5
View File
@@ -135,6 +135,9 @@ export class RemoteSmsAuthGuard implements CanActivate {
nameFa: true, nameFa: true,
domain: true, domain: true,
domains: true, domains: true,
adminUrl: true,
appUrl: true,
nodeServer: true,
product: true, product: true,
isActive: true, isActive: true,
}, },
@@ -152,15 +155,31 @@ export class RemoteSmsAuthGuard implements CanActivate {
} }
private matchesHost( private matchesHost(
tenant: { domain: string | null; domains: unknown }, tenant: {
domain: string | null;
domains: unknown;
adminUrl?: string | null;
appUrl?: string | null;
nodeServer?: string | null;
},
host: string, host: string,
) { ) {
const candidates = new Set<string>(); const hostNoWww = host.replace(/^www\./, '');
for (const value of [tenant.domain, ...this.readDomains(tenant.domains)]) { const values = [
tenant.domain,
tenant.adminUrl,
tenant.appUrl,
tenant.nodeServer,
...this.readDomains(tenant.domains),
];
for (const value of values) {
const normalized = this.normalizeHost(value); const normalized = this.normalizeHost(value);
if (normalized) candidates.add(normalized); if (!normalized) continue;
if (normalized === host || normalized.replace(/^www\./, '') === hostNoWww) {
return true;
}
} }
return candidates.has(host); return false;
} }
private readDomains(raw: unknown): string[] { private readDomains(raw: unknown): string[] {
+20
View File
@@ -470,6 +470,16 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy {
async remoteStore(tenantId: string, product: string) { async remoteStore(tenantId: string, product: string) {
await this.assertRemoteKhanegi(tenantId, product); await this.assertRemoteKhanegi(tenantId, product);
try {
return await this.buildRemoteStore(tenantId);
} catch (error: any) {
if (error instanceof ForbiddenException) throw error;
this.logger.warn(`remoteStore skipped: ${error?.message || error}`);
return { plugin_store_enabled: true, items: [] };
}
}
private async buildRemoteStore(tenantId: string) {
const items = await this.prisma.pluginCatalogItem.findMany({ const items = await this.prisma.pluginCatalogItem.findMany({
where: { inStore: true }, where: { inStore: true },
orderBy: { titleFa: 'asc' }, orderBy: { titleFa: 'asc' },
@@ -531,6 +541,16 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy {
async remoteListRequests(tenantId: string, product: string) { async remoteListRequests(tenantId: string, product: string) {
await this.assertRemoteKhanegi(tenantId, product); await this.assertRemoteKhanegi(tenantId, product);
try {
return await this.buildRemoteRequestRows(tenantId);
} catch (error: any) {
if (error instanceof ForbiddenException) throw error;
this.logger.warn(`remoteListRequests skipped: ${error?.message || error}`);
return [];
}
}
private async buildRemoteRequestRows(tenantId: string) {
const rows = await this.prisma.pluginPurchaseRequest.findMany({ const rows = await this.prisma.pluginPurchaseRequest.findMany({
where: { tenantId }, where: { tenantId },
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: 'desc' },