@aifabrix/builder 2.44.5 → 2.44.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/cli-layout.mdc +1 -1
- package/.cursor/rules/project-rules.mdc +1 -1
- package/.npmrc.token +1 -1
- package/README.md +15 -23
- package/integration/hubspot-test/README.md +2 -0
- package/integration/hubspot-test/test.js +5 -3
- package/jest.projects.js +48 -2
- package/lib/api/controller-health.api.js +49 -0
- package/lib/api/dimension-values.api.js +82 -0
- package/lib/api/dimensions.api.js +114 -0
- package/lib/api/external-systems.api.js +1 -0
- package/lib/api/integration-clients.api.js +168 -0
- package/lib/api/types/dimension-values.types.js +28 -0
- package/lib/api/types/dimensions.types.js +31 -0
- package/lib/api/types/integration-clients.types.js +45 -0
- package/lib/api/validation-runner.js +46 -25
- package/lib/app/deploy-config.js +11 -1
- package/lib/app/deploy-status-display.js +3 -3
- package/lib/app/deploy.js +36 -14
- package/lib/app/display.js +15 -11
- package/lib/app/push.js +46 -23
- package/lib/app/register.js +1 -1
- package/lib/app/restart-display.js +95 -0
- package/lib/app/rotate-secret.js +1 -1
- package/lib/app/run-container-start.js +12 -6
- package/lib/app/run-env-compose.js +30 -1
- package/lib/app/run-helpers.js +44 -12
- package/lib/app/run-reload-sync.js +148 -0
- package/lib/app/run-resolve-image.js +51 -1
- package/lib/app/run.js +99 -73
- package/lib/build/index.js +75 -45
- package/lib/cli/doctor-check.js +117 -0
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided.js +445 -0
- package/lib/cli/setup-app.js +20 -2
- package/lib/cli/setup-auth.js +26 -0
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra.js +134 -61
- package/lib/cli/setup-integration-client.js +182 -0
- package/lib/cli/setup-parameters.js +21 -2
- package/lib/cli/setup-platform.js +102 -0
- package/lib/cli/setup-secrets.js +18 -6
- package/lib/cli/setup-utility.js +78 -33
- package/lib/commands/datasource-capability-dimension-cli.js +128 -0
- package/lib/commands/datasource-capability-output.js +29 -0
- package/lib/commands/datasource-capability-relate-cli.js +140 -0
- package/lib/commands/datasource-capability.js +411 -0
- package/lib/commands/datasource-unified-test-cli.options.js +1 -1
- package/lib/commands/datasource.js +53 -13
- package/lib/commands/dev-down.js +3 -3
- package/lib/commands/dev-infra-gate.js +32 -0
- package/lib/commands/dev-init.js +13 -7
- package/lib/commands/dimension-value.js +179 -0
- package/lib/commands/dimension.js +330 -0
- package/lib/commands/integration-client.js +430 -0
- package/lib/commands/login-device.js +65 -30
- package/lib/commands/login.js +21 -10
- package/lib/commands/parameters-validate.js +78 -13
- package/lib/commands/repair-datasource-auto-rbac.js +166 -0
- package/lib/commands/repair-datasource-keys.js +10 -5
- package/lib/commands/repair-datasource.js +19 -7
- package/lib/commands/repair-env-template.js +4 -1
- package/lib/commands/repair-openapi-sync.js +172 -0
- package/lib/commands/repair-persist.js +102 -0
- package/lib/commands/repair-rbac-extract.js +27 -0
- package/lib/commands/repair-rbac-migrate.js +186 -0
- package/lib/commands/repair-rbac.js +214 -31
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -338
- package/lib/commands/secure.js +1 -1
- package/lib/commands/setup-modes.js +455 -0
- package/lib/commands/setup-prompts.js +388 -0
- package/lib/commands/setup.js +149 -0
- package/lib/commands/teardown.js +228 -0
- package/lib/commands/up-common.js +79 -19
- package/lib/commands/up-dataplane.js +33 -11
- package/lib/commands/up-miso.js +7 -11
- package/lib/commands/upload.js +109 -23
- package/lib/commands/wizard-core-helpers.js +14 -11
- package/lib/commands/wizard-core.js +6 -5
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +4 -3
- package/lib/commands/wizard-headless.js +2 -1
- package/lib/commands/wizard.js +2 -1
- package/lib/constants/infra-compose-service-names.js +40 -0
- package/lib/core/env-reader.js +16 -3
- package/lib/core/secrets-admin-env.js +101 -0
- package/lib/core/secrets-ensure-infra.js +34 -1
- package/lib/core/secrets-ensure.js +88 -66
- package/lib/core/secrets-env-content.js +432 -0
- package/lib/core/secrets-env-write.js +27 -1
- package/lib/core/secrets-load.js +248 -0
- package/lib/core/secrets-names.js +32 -0
- package/lib/core/secrets.js +17 -757
- package/lib/datasource/capability/basic-exposure.js +76 -0
- package/lib/datasource/capability/capability-diff-slice.js +41 -0
- package/lib/datasource/capability/capability-key.js +34 -0
- package/lib/datasource/capability/capability-resolve.js +172 -0
- package/lib/datasource/capability/capability-storage-keys.js +22 -0
- package/lib/datasource/capability/copy-operations.js +348 -0
- package/lib/datasource/capability/copy-test-payload.js +139 -0
- package/lib/datasource/capability/create-operations.js +235 -0
- package/lib/datasource/capability/dimension-operations.js +151 -0
- package/lib/datasource/capability/dimension-validate.js +219 -0
- package/lib/datasource/capability/json-pointer.js +31 -0
- package/lib/datasource/capability/reference-rewrite.js +51 -0
- package/lib/datasource/capability/relate-operations.js +325 -0
- package/lib/datasource/capability/relate-validate.js +219 -0
- package/lib/datasource/capability/remove-operations.js +275 -0
- package/lib/datasource/capability/run-capability-copy.js +152 -0
- package/lib/datasource/capability/run-capability-diff.js +135 -0
- package/lib/datasource/capability/run-capability-dimension.js +291 -0
- package/lib/datasource/capability/run-capability-edit.js +377 -0
- package/lib/datasource/capability/run-capability-relate.js +193 -0
- package/lib/datasource/capability/run-capability-remove.js +105 -0
- package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
- package/lib/datasource/capability/validate-capability-slice.js +35 -0
- package/lib/datasource/list.js +136 -23
- package/lib/datasource/log-viewer.js +2 -4
- package/lib/datasource/unified-validation-run.js +51 -16
- package/lib/datasource/validate.js +53 -1
- package/lib/deployment/deploy-poll-ui.js +60 -0
- package/lib/deployment/deployer-status.js +29 -3
- package/lib/deployment/deployer.js +48 -30
- package/lib/deployment/environment.js +7 -2
- package/lib/deployment/poll-interval.js +72 -0
- package/lib/deployment/push.js +11 -9
- package/lib/external-system/deploy.js +4 -1
- package/lib/external-system/download.js +61 -32
- package/lib/external-system/sync-deploy-manifest.js +33 -0
- package/lib/infrastructure/index.js +49 -19
- package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
- package/lib/parameters/infra-kv-discovery.js +29 -4
- package/lib/parameters/infra-parameter-catalog.js +6 -3
- package/lib/parameters/infra-parameter-validate.js +67 -19
- package/lib/resolvers/datasource-resolver.js +53 -0
- package/lib/resolvers/dimension-file.js +52 -0
- package/lib/resolvers/manifest-resolver.js +133 -0
- package/lib/schema/external-datasource.schema.json +183 -53
- package/lib/schema/external-system.schema.json +23 -10
- package/lib/schema/infra.parameter.yaml +26 -11
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/aifabrix-config-dir-walk.js +40 -0
- package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
- package/lib/utils/app-run-containers.js +2 -2
- package/lib/utils/bash-secret-env.js +59 -0
- package/lib/utils/cli-secrets-error-format.js +78 -0
- package/lib/utils/cli-test-layout-chalk.js +31 -9
- package/lib/utils/cli-utils.js +4 -36
- package/lib/utils/datasource-test-run-display.js +8 -0
- package/lib/utils/dev-hosts-helper.js +3 -2
- package/lib/utils/dev-init-ssh-merge.js +2 -1
- package/lib/utils/docker-build.js +17 -9
- package/lib/utils/docker-reload-mount.js +127 -0
- package/lib/utils/external-readme.js +71 -2
- package/lib/utils/external-system-local-test-tty.js +3 -2
- package/lib/utils/external-system-readiness-core.js +45 -12
- package/lib/utils/external-system-readiness-deploy-display.js +3 -3
- package/lib/utils/external-system-readiness-display-internals.js +33 -3
- package/lib/utils/external-system-readiness-display.js +10 -1
- package/lib/utils/file-upload.js +40 -3
- package/lib/utils/health-check-db-init.js +107 -0
- package/lib/utils/health-check-public-warn.js +69 -0
- package/lib/utils/health-check-url.js +19 -4
- package/lib/utils/health-check.js +135 -105
- package/lib/utils/help-builder.js +5 -1
- package/lib/utils/image-name.js +34 -7
- package/lib/utils/integration-file-backup.js +74 -0
- package/lib/utils/mutagen-install.js +30 -3
- package/lib/utils/paths.js +108 -25
- package/lib/utils/postgres-wipe.js +212 -0
- package/lib/utils/register-aifabrix-shell-env.js +15 -0
- package/lib/utils/remote-dev-auth.js +21 -5
- package/lib/utils/remote-docker-env.js +9 -1
- package/lib/utils/remote-secrets-loader.js +42 -3
- package/lib/utils/resolve-docker-image-ref.js +9 -3
- package/lib/utils/secrets-ancestor-paths.js +47 -0
- package/lib/utils/secrets-helpers.js +17 -10
- package/lib/utils/secrets-kv-refs.js +42 -0
- package/lib/utils/secrets-kv-scope.js +19 -2
- package/lib/utils/secrets-materialize-local.js +134 -0
- package/lib/utils/secrets-path.js +24 -10
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/system-builder-root.js +34 -0
- package/lib/utils/url-declarative-resolve-build.js +6 -1
- package/lib/utils/url-declarative-runtime-base-path.js +32 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
- package/lib/utils/urls-local-registry.js +23 -12
- package/lib/utils/validation-poll-ui.js +81 -0
- package/lib/utils/validation-run-poll.js +29 -5
- package/lib/utils/with-muted-logger.js +53 -0
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/env.template +8 -6
- package/templates/applications/miso-controller/application.yaml +7 -0
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +83 -123
- package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/lib/api/service-users.api.js +0 -150
- package/lib/api/types/service-users.types.js +0 -65
- package/lib/cli/setup-service-user.js +0 -187
- package/lib/commands/service-user.js +0 -429
|
@@ -1,429 +0,0 @@
|
|
|
1
|
-
const { formatBlockingError, formatSuccessLine } = require('../utils/cli-test-layout-chalk');
|
|
2
|
-
/**
|
|
3
|
-
* Service user create command – create service user and get one-time secret
|
|
4
|
-
* POST /api/v1/service-users. Used by `aifabrix service-user create`.
|
|
5
|
-
*
|
|
6
|
-
* @fileoverview Service user create command implementation
|
|
7
|
-
* @author AI Fabrix Team
|
|
8
|
-
* @version 2.0.0
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const chalk = require('chalk');
|
|
12
|
-
const logger = require('../utils/logger');
|
|
13
|
-
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
14
|
-
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
15
|
-
const { normalizeControllerUrl } = require('../core/config');
|
|
16
|
-
const {
|
|
17
|
-
createServiceUser,
|
|
18
|
-
listServiceUsers,
|
|
19
|
-
regenerateSecretServiceUser,
|
|
20
|
-
deleteServiceUser,
|
|
21
|
-
updateGroupsServiceUser,
|
|
22
|
-
updateRedirectUrisServiceUser
|
|
23
|
-
} = require('../api/service-users.api');
|
|
24
|
-
|
|
25
|
-
const ONE_TIME_WARNING =
|
|
26
|
-
'Save this secret now; it will not be shown again.';
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Get auth token for service-user (device token from config)
|
|
30
|
-
* @async
|
|
31
|
-
* @param {string} controllerUrl - Controller base URL
|
|
32
|
-
* @returns {Promise<{token: string, controllerUrl: string}|null>}
|
|
33
|
-
*/
|
|
34
|
-
async function getServiceUserAuth(controllerUrl) {
|
|
35
|
-
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
36
|
-
const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
|
|
37
|
-
if (deviceToken && deviceToken.token) {
|
|
38
|
-
return {
|
|
39
|
-
token: deviceToken.token,
|
|
40
|
-
controllerUrl: deviceToken.controller || normalizedUrl
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Extract clientId and clientSecret from API response.
|
|
48
|
-
* Controller returns { data: { user, clientSecret } }; API client puts body in response.data.
|
|
49
|
-
* So payload is at response.data.data. clientId may be on user.clientId or user.federatedIdentity.keycloakClientId.
|
|
50
|
-
* @param {Object} response - API response (success: true, data: body)
|
|
51
|
-
* @returns {{ clientId: string, clientSecret: string }}
|
|
52
|
-
*/
|
|
53
|
-
function extractCreateResponse(response) {
|
|
54
|
-
const payload = response?.data?.data ?? response?.data ?? response;
|
|
55
|
-
const user = payload?.user;
|
|
56
|
-
const clientId =
|
|
57
|
-
user?.clientId ??
|
|
58
|
-
user?.federatedIdentity?.keycloakClientId ??
|
|
59
|
-
payload?.clientId ??
|
|
60
|
-
'';
|
|
61
|
-
const clientSecret = payload?.clientSecret ?? '';
|
|
62
|
-
return { clientId, clientSecret };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const ID_WIDTH = 38;
|
|
66
|
-
const USERNAME_WIDTH = 22;
|
|
67
|
-
const EMAIL_WIDTH = 28;
|
|
68
|
-
const CLIENT_ID_WIDTH = 24;
|
|
69
|
-
const TABLE_SEPARATOR_LENGTH = 130;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Log error for failed create response and exit
|
|
73
|
-
* @param {Object} response - API response with success: false
|
|
74
|
-
*/
|
|
75
|
-
function handleCreateError(response) {
|
|
76
|
-
const status = response.status;
|
|
77
|
-
const msg = response.formattedError || response.error || 'Request failed';
|
|
78
|
-
if (status === 400) {
|
|
79
|
-
logger.error(formatBlockingError(`Validation error: ${msg}`));
|
|
80
|
-
} else if (status === 401) {
|
|
81
|
-
logger.error(formatBlockingError('Unauthorized. Run "aifabrix login" and try again.'));
|
|
82
|
-
} else if (status === 403) {
|
|
83
|
-
logger.error(formatBlockingError('Missing permission: service-user:create'));
|
|
84
|
-
logger.error(chalk.gray('Your account needs the service-user:create permission on the controller.'));
|
|
85
|
-
} else {
|
|
86
|
-
logger.error(formatBlockingError(`Failed to create service user: ${msg}`));
|
|
87
|
-
}
|
|
88
|
-
process.exit(1);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Log error for service-user API response and exit
|
|
93
|
-
* @param {Object} response - API response with success: false
|
|
94
|
-
* @param {string} permissionScope - Permission hint: 'read' | 'update' | 'delete'
|
|
95
|
-
*/
|
|
96
|
-
function handleServiceUserApiError(response, permissionScope) {
|
|
97
|
-
const status = response.status;
|
|
98
|
-
const msg = response.formattedError || response.error || 'Request failed';
|
|
99
|
-
if (status === 400) {
|
|
100
|
-
logger.error(formatBlockingError(`Validation error: ${msg}`));
|
|
101
|
-
} else if (status === 401) {
|
|
102
|
-
logger.error(formatBlockingError('Unauthorized. Run "aifabrix login" and try again.'));
|
|
103
|
-
} else if (status === 403) {
|
|
104
|
-
logger.error(formatBlockingError(`Missing permission: service-user:${permissionScope}`));
|
|
105
|
-
logger.error(chalk.gray(`Your account needs the service-user:${permissionScope} permission on the controller.`));
|
|
106
|
-
} else if (status === 404) {
|
|
107
|
-
logger.error(formatBlockingError('Service user not found.'));
|
|
108
|
-
const detail = response.error || '';
|
|
109
|
-
if (detail) {
|
|
110
|
-
logger.error(chalk.gray(detail));
|
|
111
|
-
}
|
|
112
|
-
} else {
|
|
113
|
-
logger.error(formatBlockingError(`Request failed: ${msg}`));
|
|
114
|
-
}
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Resolve controller URL and auth for list/rotate/delete/update (no create-specific validation)
|
|
120
|
-
* @async
|
|
121
|
-
* @param {Object} options - CLI options (controller optional)
|
|
122
|
-
* @returns {Promise<{ controllerUrl: string, authConfig: Object }>}
|
|
123
|
-
*/
|
|
124
|
-
async function resolveControllerAndAuth(options) {
|
|
125
|
-
const controllerUrl = options.controller || (await resolveControllerUrl());
|
|
126
|
-
if (!controllerUrl) {
|
|
127
|
-
logger.error(formatBlockingError('Controller URL is required. Run "aifabrix login" first.'));
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
130
|
-
const authResult = await getServiceUserAuth(controllerUrl);
|
|
131
|
-
if (!authResult || !authResult.token) {
|
|
132
|
-
logger.error(formatBlockingError(`No authentication token for controller: ${controllerUrl}`));
|
|
133
|
-
logger.error(chalk.gray('Run: aifabrix login'));
|
|
134
|
-
process.exit(1);
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
controllerUrl: authResult.controllerUrl,
|
|
138
|
-
authConfig: { type: 'bearer', token: authResult.token }
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Display service user list as a table (id, username, email, clientId, active).
|
|
144
|
-
* API shape: items have id, username, email, status, federatedIdentity.keycloakClientId.
|
|
145
|
-
* @param {Array<{ id?: string, username?: string, email?: string, status?: string, active?: boolean, clientId?: string, federatedIdentity?: { keycloakClientId?: string } }>} items - Service users
|
|
146
|
-
*/
|
|
147
|
-
function displayServiceUserList(items) {
|
|
148
|
-
logger.log(chalk.bold('\n📋 Service users:\n'));
|
|
149
|
-
if (!items || items.length === 0) {
|
|
150
|
-
logger.log(chalk.gray(' No service users found.\n'));
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
const idCol = 'Id'.padEnd(ID_WIDTH);
|
|
154
|
-
const usernameCol = 'Username'.padEnd(USERNAME_WIDTH);
|
|
155
|
-
const emailCol = 'Email'.padEnd(EMAIL_WIDTH);
|
|
156
|
-
const clientIdCol = 'ClientId'.padEnd(CLIENT_ID_WIDTH);
|
|
157
|
-
const activeCol = 'Active';
|
|
158
|
-
logger.log(chalk.gray(`${idCol}${usernameCol}${emailCol}${clientIdCol}${activeCol}`));
|
|
159
|
-
logger.log(chalk.gray('-'.repeat(TABLE_SEPARATOR_LENGTH)));
|
|
160
|
-
items.forEach((row) => {
|
|
161
|
-
const id = (row.id ?? '').toString().padEnd(ID_WIDTH);
|
|
162
|
-
const username = (row.username ?? '—').padEnd(USERNAME_WIDTH);
|
|
163
|
-
const email = (row.email ?? '—').padEnd(EMAIL_WIDTH);
|
|
164
|
-
const clientId = (row.federatedIdentity?.keycloakClientId ?? row.clientId ?? '—').padEnd(CLIENT_ID_WIDTH);
|
|
165
|
-
const active = row.status === 'active' ? 'yes' : (row.status ?? (row.active === true ? 'yes' : row.active === false ? 'no' : '—'));
|
|
166
|
-
logger.log(`${id}${username}${email}${clientId}${active}`);
|
|
167
|
-
});
|
|
168
|
-
logger.log('');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Display success output with clientId, clientSecret and one-time warning
|
|
173
|
-
* @param {string} clientId - Service user client ID
|
|
174
|
-
* @param {string} clientSecret - One-time client secret
|
|
175
|
-
*/
|
|
176
|
-
function displayCreateSuccess(clientId, clientSecret) {
|
|
177
|
-
logger.log(chalk.bold('\n✔ Service user created\n'));
|
|
178
|
-
logger.log(chalk.cyan(' clientId: ') + clientId);
|
|
179
|
-
logger.log(chalk.cyan(' clientSecret: ') + clientSecret);
|
|
180
|
-
logger.log('');
|
|
181
|
-
logger.log(chalk.yellow('⚠ ' + ONE_TIME_WARNING));
|
|
182
|
-
logger.log('');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Parse comma-separated string into non-empty trimmed array
|
|
187
|
-
* @param {string} [val] - Comma-separated value
|
|
188
|
-
* @returns {string[]}
|
|
189
|
-
*/
|
|
190
|
-
function parseList(val) {
|
|
191
|
-
if (val === undefined || val === null || String(val).trim() === '') {
|
|
192
|
-
return [];
|
|
193
|
-
}
|
|
194
|
-
return String(val)
|
|
195
|
-
.split(',')
|
|
196
|
-
.map(s => s.trim())
|
|
197
|
-
.filter(Boolean);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Validate username, email, redirectUris, groupIds; exit on failure
|
|
202
|
-
* @param {Object} options - CLI options
|
|
203
|
-
* @returns {{ username: string, email: string, redirectUris: string[], groupNames: string[], description?: string }}
|
|
204
|
-
*/
|
|
205
|
-
function validateServiceUserOptions(options) {
|
|
206
|
-
const username = options.username?.trim();
|
|
207
|
-
const email = options.email?.trim();
|
|
208
|
-
const redirectUris = parseList(options.redirectUris);
|
|
209
|
-
const groupNames = parseList(options.groupNames);
|
|
210
|
-
if (!username) {
|
|
211
|
-
logger.error(formatBlockingError('Username is required. Use --username <username>.'));
|
|
212
|
-
process.exit(1);
|
|
213
|
-
}
|
|
214
|
-
if (!email) {
|
|
215
|
-
logger.error(formatBlockingError('Email is required. Use --email <email>.'));
|
|
216
|
-
process.exit(1);
|
|
217
|
-
}
|
|
218
|
-
if (redirectUris.length === 0) {
|
|
219
|
-
logger.error(formatBlockingError('At least one redirect URI is required. Use --redirect-uris <uri1,uri2,...>.'));
|
|
220
|
-
process.exit(1);
|
|
221
|
-
}
|
|
222
|
-
if (groupNames.length === 0) {
|
|
223
|
-
logger.error(formatBlockingError('At least one group name is required. Use --group-names <name1,name2,...>.'));
|
|
224
|
-
process.exit(1);
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
username,
|
|
228
|
-
email,
|
|
229
|
-
redirectUris,
|
|
230
|
-
groupNames,
|
|
231
|
-
description: options.description?.trim() || undefined
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Resolve controller URL and auth; exit on failure
|
|
237
|
-
* @async
|
|
238
|
-
* @param {Object} options - CLI options
|
|
239
|
-
* @returns {Promise<{ username: string, email: string, redirectUris: string[], groupNames: string[], description?: string, controllerUrl: string, authConfig: Object }>}
|
|
240
|
-
*/
|
|
241
|
-
async function resolveOptionsAndAuth(options) {
|
|
242
|
-
const validated = validateServiceUserOptions(options);
|
|
243
|
-
const controllerUrl = options.controller || (await resolveControllerUrl());
|
|
244
|
-
if (!controllerUrl) {
|
|
245
|
-
logger.error(formatBlockingError('Controller URL is required. Run "aifabrix login" first.'));
|
|
246
|
-
process.exit(1);
|
|
247
|
-
}
|
|
248
|
-
const authResult = await getServiceUserAuth(controllerUrl);
|
|
249
|
-
if (!authResult || !authResult.token) {
|
|
250
|
-
logger.error(formatBlockingError(`No authentication token for controller: ${controllerUrl}`));
|
|
251
|
-
logger.error(chalk.gray('Run: aifabrix login'));
|
|
252
|
-
process.exit(1);
|
|
253
|
-
}
|
|
254
|
-
return {
|
|
255
|
-
...validated,
|
|
256
|
-
controllerUrl: authResult.controllerUrl,
|
|
257
|
-
authConfig: { type: 'bearer', token: authResult.token }
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Run service-user create: call POST /api/v1/service-users and display one-time secret with warning
|
|
263
|
-
* @async
|
|
264
|
-
* @param {Object} options - CLI options
|
|
265
|
-
* @param {string} [options.controller] - Controller URL override
|
|
266
|
-
* @param {string} options.username - Username (required)
|
|
267
|
-
* @param {string} options.email - Email (required)
|
|
268
|
-
* @param {string} options.redirectUris - Comma-separated redirect URIs (required, min 1)
|
|
269
|
-
* @param {string} options.groupNames - Comma-separated group names (required, e.g. AI-Fabrix-Developers)
|
|
270
|
-
* @param {string} [options.description] - Optional description
|
|
271
|
-
* @returns {Promise<void>}
|
|
272
|
-
*/
|
|
273
|
-
async function runServiceUserCreate(options = {}) {
|
|
274
|
-
const ctx = await resolveOptionsAndAuth(options);
|
|
275
|
-
const body = {
|
|
276
|
-
username: ctx.username,
|
|
277
|
-
email: ctx.email,
|
|
278
|
-
redirectUris: ctx.redirectUris,
|
|
279
|
-
groupNames: ctx.groupNames,
|
|
280
|
-
description: ctx.description
|
|
281
|
-
};
|
|
282
|
-
const response = await createServiceUser(ctx.controllerUrl, ctx.authConfig, body);
|
|
283
|
-
if (!response.success) {
|
|
284
|
-
handleCreateError(response);
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
const { clientId, clientSecret } = extractCreateResponse(response);
|
|
288
|
-
displayCreateSuccess(clientId, clientSecret);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Require service user id option; exit with message if missing
|
|
293
|
-
* @param {string} [id] - Service user ID from options
|
|
294
|
-
* @returns {string} Trimmed id
|
|
295
|
-
*/
|
|
296
|
-
function requireServiceUserId(id) {
|
|
297
|
-
const trimmed = (id && typeof id === 'string' ? id.trim() : '') || '';
|
|
298
|
-
if (!trimmed) {
|
|
299
|
-
logger.error(formatBlockingError('Service user ID is required. Use --id <uuid>.'));
|
|
300
|
-
process.exit(1);
|
|
301
|
-
}
|
|
302
|
-
return trimmed;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Run service-user list: call GET /api/v1/service-users and display table
|
|
307
|
-
* @async
|
|
308
|
-
* @param {Object} options - CLI options (controller, page, pageSize, sort, filter, search)
|
|
309
|
-
* @returns {Promise<void>}
|
|
310
|
-
*/
|
|
311
|
-
async function runServiceUserList(options = {}) {
|
|
312
|
-
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
313
|
-
const listOptions = {
|
|
314
|
-
page: options.page,
|
|
315
|
-
pageSize: options.pageSize,
|
|
316
|
-
sort: options.sort,
|
|
317
|
-
filter: options.filter,
|
|
318
|
-
search: options.search
|
|
319
|
-
};
|
|
320
|
-
const response = await listServiceUsers(controllerUrl, authConfig, listOptions);
|
|
321
|
-
if (response && response.success === false) {
|
|
322
|
-
handleServiceUserApiError(response, 'read');
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
const body = response?.data?.data ?? response?.data ?? response ?? {};
|
|
326
|
-
const items = Array.isArray(body) ? body : (body.data ?? []);
|
|
327
|
-
displayServiceUserList(items);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Run service-user rotate-secret: call POST .../regenerate-secret and print new secret once with warning
|
|
332
|
-
* @async
|
|
333
|
-
* @param {Object} options - CLI options (controller, id required)
|
|
334
|
-
* @returns {Promise<void>}
|
|
335
|
-
*/
|
|
336
|
-
async function runServiceUserRotateSecret(options = {}) {
|
|
337
|
-
const id = requireServiceUserId(options.id);
|
|
338
|
-
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
339
|
-
const response = await regenerateSecretServiceUser(controllerUrl, authConfig, id);
|
|
340
|
-
if (response && response.success === false) {
|
|
341
|
-
handleServiceUserApiError(response, 'update');
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
const payload = response?.data?.data ?? response?.data ?? response ?? {};
|
|
345
|
-
const clientSecret = payload?.clientSecret ?? '';
|
|
346
|
-
if (response && response.success === true) {
|
|
347
|
-
logger.log(chalk.bold('\n✔ Secret rotated\n'));
|
|
348
|
-
logger.log(chalk.cyan(' clientSecret: ') + clientSecret);
|
|
349
|
-
logger.log('');
|
|
350
|
-
logger.log(chalk.yellow('⚠ ' + ONE_TIME_WARNING));
|
|
351
|
-
logger.log('');
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Run service-user delete: call DELETE .../service-users/{id} (deactivates the user)
|
|
357
|
-
* @async
|
|
358
|
-
* @param {Object} options - CLI options (controller, id required)
|
|
359
|
-
* @returns {Promise<void>}
|
|
360
|
-
*/
|
|
361
|
-
async function runServiceUserDelete(options = {}) {
|
|
362
|
-
const id = requireServiceUserId(options.id);
|
|
363
|
-
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
364
|
-
const response = await deleteServiceUser(controllerUrl, authConfig, id);
|
|
365
|
-
if (response && response.success === false) {
|
|
366
|
-
handleServiceUserApiError(response, 'delete');
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
if (response && response.success === true) {
|
|
370
|
-
logger.log(formatSuccessLine('Service user deactivated.\n'));
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Run service-user update-groups: call PUT .../groups with groupNames
|
|
376
|
-
* @async
|
|
377
|
-
* @param {Object} options - CLI options (controller, id, groupNames required)
|
|
378
|
-
* @returns {Promise<void>}
|
|
379
|
-
*/
|
|
380
|
-
async function runServiceUserUpdateGroups(options = {}) {
|
|
381
|
-
const id = requireServiceUserId(options.id);
|
|
382
|
-
const groupNames = parseList(options.groupNames);
|
|
383
|
-
if (groupNames.length === 0) {
|
|
384
|
-
logger.error(formatBlockingError('At least one group name is required. Use --group-names <name1,name2,...>.'));
|
|
385
|
-
process.exit(1);
|
|
386
|
-
}
|
|
387
|
-
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
388
|
-
const response = await updateGroupsServiceUser(controllerUrl, authConfig, id, { groupNames });
|
|
389
|
-
if (response && response.success === false) {
|
|
390
|
-
handleServiceUserApiError(response, 'update');
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
if (response && response.success === true) {
|
|
394
|
-
logger.log(formatSuccessLine('Service user groups updated.\n'));
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Run service-user update-redirect-uris: call PUT .../redirect-uris (min 1 URI)
|
|
400
|
-
* @async
|
|
401
|
-
* @param {Object} options - CLI options (controller, id, redirectUris required, min 1)
|
|
402
|
-
* @returns {Promise<void>}
|
|
403
|
-
*/
|
|
404
|
-
async function runServiceUserUpdateRedirectUris(options = {}) {
|
|
405
|
-
const id = requireServiceUserId(options.id);
|
|
406
|
-
const redirectUris = parseList(options.redirectUris);
|
|
407
|
-
if (redirectUris.length === 0) {
|
|
408
|
-
logger.error(formatBlockingError('At least one redirect URI is required. Use --redirect-uris <uri1,uri2,...>.'));
|
|
409
|
-
process.exit(1);
|
|
410
|
-
}
|
|
411
|
-
const { controllerUrl, authConfig } = await resolveControllerAndAuth(options);
|
|
412
|
-
const response = await updateRedirectUrisServiceUser(controllerUrl, authConfig, id, { redirectUris });
|
|
413
|
-
if (response && response.success === false) {
|
|
414
|
-
handleServiceUserApiError(response, 'update');
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
if (response && response.success === true) {
|
|
418
|
-
logger.log(formatSuccessLine('Service user redirect URIs updated.\n'));
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
module.exports = {
|
|
423
|
-
runServiceUserCreate,
|
|
424
|
-
runServiceUserList,
|
|
425
|
-
runServiceUserRotateSecret,
|
|
426
|
-
runServiceUserDelete,
|
|
427
|
-
runServiceUserUpdateGroups,
|
|
428
|
-
runServiceUserUpdateRedirectUris
|
|
429
|
-
};
|