feat(gateway-clients): add policy-based gateway client tokens and gateway client route and DNS management endpoints
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/dcrouter',
|
||||
version: '13.25.0',
|
||||
version: '13.26.0',
|
||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||
}
|
||||
|
||||
+7
-1
@@ -2506,7 +2506,12 @@ export const fetchUsersAction = usersStatePart.createAction(async (statePartArg)
|
||||
}
|
||||
});
|
||||
|
||||
export async function createApiToken(name: string, scopes: interfaces.data.TApiTokenScope[], expiresInDays?: number | null) {
|
||||
export async function createApiToken(
|
||||
name: string,
|
||||
scopes: interfaces.data.TApiTokenScope[],
|
||||
expiresInDays?: number | null,
|
||||
policy?: any,
|
||||
) {
|
||||
const context = getActionContext();
|
||||
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
||||
interfaces.requests.IReq_CreateApiToken
|
||||
@@ -2516,6 +2521,7 @@ export async function createApiToken(name: string, scopes: interfaces.data.TApiT
|
||||
identity: context.identity!,
|
||||
name,
|
||||
scopes,
|
||||
policy,
|
||||
expiresInDays,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -200,6 +200,7 @@ export class OpsViewApiTokens extends DeesElement {
|
||||
const { DeesModal } = await import('@design.estate/dees-catalog');
|
||||
|
||||
const allScopes = [
|
||||
'*',
|
||||
'routes:read',
|
||||
'routes:write',
|
||||
'config:read',
|
||||
@@ -213,6 +214,8 @@ export class OpsViewApiTokens extends DeesElement {
|
||||
'dns-records:write',
|
||||
'email-domains:read',
|
||||
'email-domains:write',
|
||||
'gateway-clients:read',
|
||||
'gateway-clients:write',
|
||||
'workhosters:read',
|
||||
'workhosters:write',
|
||||
];
|
||||
@@ -228,10 +231,15 @@ export class OpsViewApiTokens extends DeesElement {
|
||||
<dees-input-tags
|
||||
.key=${'scopes'}
|
||||
.label=${'Token Scopes'}
|
||||
.value=${['routes:read', 'routes:write']}
|
||||
.value=${['gateway-clients:read', 'gateway-clients:write']}
|
||||
.suggestions=${allScopes}
|
||||
.required=${true}
|
||||
></dees-input-tags>
|
||||
<dees-input-text .key=${'policyRole'} .label=${'Policy Role'} .description=${'admin, gatewayClient, or operator'}></dees-input-text>
|
||||
<dees-input-text .key=${'gatewayClientType'} .label=${'Gateway Client Type'} .description=${'For gatewayClient tokens: onebox, cloudly, or custom'}></dees-input-text>
|
||||
<dees-input-text .key=${'gatewayClientId'} .label=${'Gateway Client ID'} .description=${'Required for gatewayClient tokens'}></dees-input-text>
|
||||
<dees-input-text .key=${'hostnamePatterns'} .label=${'Hostname Patterns'} .description=${'Comma separated, e.g. *.apps.example.com'}></dees-input-text>
|
||||
<dees-input-text .key=${'allowedRouteTarget'} .label=${'Allowed Route Target'} .description=${'Optional host:ports, e.g. 203.0.113.10:80,443'}></dees-input-text>
|
||||
<dees-input-text .key=${'expiresInDays'} .label=${'Expires in'} .description=${'Number of days; leave blank for no expiration'}></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
@@ -257,6 +265,7 @@ export class OpsViewApiTokens extends DeesElement {
|
||||
const rawScopes: string[] = tagsInput?.getValue?.() || tagsInput?.value || formData.scopes || [];
|
||||
const scopes = rawScopes
|
||||
.filter((s: string) => allScopes.includes(s as any)) as TApiTokenScope[];
|
||||
const policy = this.buildPolicy(formData, scopes);
|
||||
|
||||
const expiresInDays = formData.expiresInDays
|
||||
? parseInt(formData.expiresInDays, 10)
|
||||
@@ -265,7 +274,7 @@ export class OpsViewApiTokens extends DeesElement {
|
||||
await modalArg.destroy();
|
||||
|
||||
try {
|
||||
const response = await appstate.createApiToken(formData.name, scopes, expiresInDays);
|
||||
const response = await appstate.createApiToken(formData.name, scopes, expiresInDays, policy);
|
||||
if (response.success && response.tokenValue) {
|
||||
// Refresh the list first so it's ready when user dismisses the modal
|
||||
await appstate.routeManagementStatePart.dispatchAction(appstate.fetchApiTokensAction, null);
|
||||
@@ -299,6 +308,42 @@ export class OpsViewApiTokens extends DeesElement {
|
||||
});
|
||||
}
|
||||
|
||||
private buildPolicy(formData: any, scopes: TApiTokenScope[]): any | undefined {
|
||||
const role = String(formData.policyRole || '').trim();
|
||||
if (!role) return undefined;
|
||||
const policy: any = {
|
||||
role,
|
||||
scopes,
|
||||
};
|
||||
if (role === 'gatewayClient') {
|
||||
const type = String(formData.gatewayClientType || 'onebox').trim() as 'onebox' | 'cloudly' | 'custom';
|
||||
const id = String(formData.gatewayClientId || '').trim();
|
||||
if (id) {
|
||||
policy.gatewayClient = { type, id };
|
||||
}
|
||||
policy.hostnamePatterns = String(formData.hostnamePatterns || '')
|
||||
.split(',')
|
||||
.map((pattern) => pattern.trim())
|
||||
.filter(Boolean);
|
||||
const target = String(formData.allowedRouteTarget || '').trim();
|
||||
if (target.includes(':')) {
|
||||
const [host, portsValue] = target.split(':');
|
||||
policy.allowedRouteTargets = [{
|
||||
host: host.trim(),
|
||||
ports: portsValue.split(',').map((port) => Number(port.trim())).filter((port) => Number.isInteger(port)),
|
||||
}];
|
||||
}
|
||||
policy.capabilities = {
|
||||
readDomains: true,
|
||||
readDnsRecords: true,
|
||||
syncRoutes: true,
|
||||
syncDnsRecords: false,
|
||||
requestCertificates: false,
|
||||
};
|
||||
}
|
||||
return policy;
|
||||
}
|
||||
|
||||
private async showRollTokenDialog(token: interfaces.data.IApiTokenInfo) {
|
||||
const { DeesModal } = await import('@design.estate/dees-catalog');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user