fix(external-gateway): derive gateway client identity from the dcrouter token and make the settings UI read-only

This commit is contained in:
2026-05-09 22:36:26 +00:00
parent b9c90eca3d
commit 15574b8629
7 changed files with 135 additions and 34 deletions
+68 -23
View File
@@ -10,13 +10,24 @@ type TWorkHosterType = 'onebox';
interface IExternalGatewayConfig {
url: string;
apiToken: string;
gatewayClientId: string;
gatewayClientType?: TWorkHosterType;
gatewayClientId?: string;
/** @deprecated Use gatewayClientId. */
workHosterId: string;
workHosterId?: string;
targetHost?: string;
targetPort?: number;
}
interface IGatewayClientContextResponse {
context: {
role: 'admin' | 'gatewayClient' | 'operator';
gatewayClient?: {
type: 'onebox' | 'cloudly' | 'custom';
id: string;
};
};
}
interface IWorkHosterDomain {
id?: string;
name: string;
@@ -62,8 +73,8 @@ interface IWorkAppRouteOwnership {
}
interface IGatewayClientOwnership {
gatewayClientType: TWorkHosterType;
gatewayClientId: string;
gatewayClientType?: TWorkHosterType;
gatewayClientId?: string;
appId: string;
hostname: string;
}
@@ -128,8 +139,14 @@ export class ExternalGatewayManager {
if (this.getMode() === 'disabled') {
return false;
}
const config = await this.getConfig({ requireTarget: false });
return Boolean(config);
const mode = this.getMode();
const url = mode === 'managed'
? this.oneboxRef.managedDcRouter.getGatewayUrl()
: this.normalizeUrl(this.database.getSetting('dcrouterGatewayUrl') || '');
const apiToken = mode === 'managed'
? await this.oneboxRef.managedDcRouter.getAdminToken()
: await this.database.getSecretSetting('dcrouterGatewayApiToken');
return Boolean(url && apiToken);
}
public async syncDomains(): Promise<IDomain[]> {
@@ -188,7 +205,7 @@ export class ExternalGatewayManager {
try {
const response = await this.fireDcRouterRequest<{ domains: IWorkHosterDomain[] }>(
'getGatewayClientDomains',
{ gatewayClientId: config.gatewayClientId },
config.gatewayClientId ? { gatewayClientId: config.gatewayClientId } : {},
config,
);
return response.domains.map((domain) => ({
@@ -216,7 +233,7 @@ export class ExternalGatewayManager {
try {
const response = await this.fireDcRouterRequest<{ records: IGatewayDnsRecord[] }>(
'getGatewayClientDnsRecords',
{ gatewayClientId: config.gatewayClientId },
config.gatewayClientId ? { gatewayClientId: config.gatewayClientId } : {},
config,
);
return response.records.map((record) => ({
@@ -355,16 +372,27 @@ export class ExternalGatewayManager {
return null;
}
const gatewayClientId = mode === 'managed'
? this.oneboxRef.managedDcRouter.ensureGatewayClientId()
: this.ensureGatewayClientId();
const config: IExternalGatewayConfig = {
url,
apiToken,
gatewayClientId,
workHosterId: gatewayClientId,
};
const contextClient = await this.getGatewayClientFromToken(config);
if (contextClient) {
config.gatewayClientType = contextClient.type;
config.gatewayClientId = contextClient.id;
config.workHosterId = contextClient.id;
} else {
const fallbackGatewayClientId = mode === 'managed'
? this.oneboxRef.managedDcRouter.ensureGatewayClientId()
: this.getStoredGatewayClientId();
if (fallbackGatewayClientId) {
config.gatewayClientType = 'onebox';
config.gatewayClientId = fallbackGatewayClientId;
config.workHosterId = fallbackGatewayClientId;
}
}
if (options.requireTarget !== false) {
if (mode === 'managed') {
const target = this.oneboxRef.managedDcRouter.getRouteTarget();
@@ -417,13 +445,27 @@ export class ExternalGatewayManager {
return port;
}
private ensureGatewayClientId(): string {
let gatewayClientId = this.database.getSetting('dcrouterGatewayClientId') || this.database.getSetting('dcrouterWorkHosterId');
if (!gatewayClientId) {
gatewayClientId = crypto.randomUUID();
this.database.setSetting('dcrouterGatewayClientId', gatewayClientId);
private getStoredGatewayClientId(): string {
return this.database.getSetting('dcrouterGatewayClientId') || this.database.getSetting('dcrouterWorkHosterId') || '';
}
private async getGatewayClientFromToken(config: IExternalGatewayConfig): Promise<{ type: TWorkHosterType; id: string } | null> {
try {
const response = await this.fireDcRouterRequest<IGatewayClientContextResponse>(
'getGatewayClientContext',
{},
config,
);
const gatewayClient = response.context.gatewayClient;
if (!gatewayClient) return null;
if (gatewayClient.type !== 'onebox') {
throw new Error(`dcrouter token is bound to unsupported gateway client type: ${gatewayClient.type}`);
}
return { type: gatewayClient.type, id: gatewayClient.id };
} catch (error) {
logger.debug(`dcrouter gateway client context unavailable: ${getErrorMessage(error)}`);
return null;
}
return gatewayClientId;
}
private buildOwnership(
@@ -433,7 +475,7 @@ export class ExternalGatewayManager {
): IWorkAppRouteOwnership {
return {
workHosterType: 'onebox',
workHosterId: config.gatewayClientId,
workHosterId: config.gatewayClientId || '',
workAppId: service.name || `service-${service.id}`,
hostname,
};
@@ -444,12 +486,15 @@ export class ExternalGatewayManager {
hostname: string,
config: IExternalGatewayConfig,
): IGatewayClientOwnership {
return {
gatewayClientType: 'onebox',
gatewayClientId: config.gatewayClientId,
const ownership: IGatewayClientOwnership = {
gatewayClientType: config.gatewayClientType || 'onebox',
appId: service.name || `service-${service.id}`,
hostname,
};
if (config.gatewayClientId) {
ownership.gatewayClientId = config.gatewayClientId;
}
return ownership;
}
private buildRoute(service: IService, config: IExternalGatewayConfig): IDcRouterRouteConfig {