feat: per-tenant server access kill switch

Add Tenant.serverAccessBlocked, toggle API with appVersion bump and settings push, and expose stealth server_reachable in get_user.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-11 16:05:04 +03:30
co-authored by Cursor
parent af1e217a68
commit 51e350405a
9 changed files with 164 additions and 12 deletions
+19 -10
View File
@@ -33,16 +33,13 @@ export class SettingsProxyService {
}
try {
const { data } = await axios.get(
this.joinUrl(tenant.nodeServer, '/super/settings'),
{
headers: {
Authorization: `Bearer ${tenant.remoteApiToken}`,
Accept: 'application/json',
},
timeout: 15000,
const { data } = await axios.get(this.resolveSettingsUrl(tenant.nodeServer), {
headers: {
Authorization: `Bearer ${tenant.remoteApiToken}`,
Accept: 'application/json',
},
);
timeout: 15000,
});
return { stub: false, settings: data };
} catch (error) {
throw new BadGatewayException(
@@ -79,7 +76,7 @@ export class SettingsProxyService {
try {
const { data } = await axios.post(
this.joinUrl(tenant.nodeServer, '/super/settings'),
this.resolveSettingsUrl(tenant.nodeServer),
payload,
{
headers: {
@@ -122,6 +119,18 @@ export class SettingsProxyService {
return `${base.replace(/\/$/, '')}${path}`;
}
/**
* nodeServer may be `https://host` or `https://host/api`.
* Laravel exposes both `/super/settings` (web) and `/api/super/settings`.
*/
private resolveSettingsUrl(nodeServer: string): string {
const base = String(nodeServer || '').replace(/\/$/, '');
if (/\/api$/i.test(base)) {
return this.joinUrl(base, '/super/settings');
}
return this.joinUrl(base, '/api/super/settings');
}
private axiosMessage(error: unknown, fallback: string) {
if (axios.isAxiosError(error)) {
const status = error.response?.status;