@aifabrix/builder 2.45.0 → 2.45.5
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/docs-rules.mdc +3 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
- package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
- package/jest.projects.js +44 -1
- package/lib/api/agent-metadata-validation.api.js +93 -0
- package/lib/api/audit-api-helpers.js +68 -0
- package/lib/api/audit.api.js +119 -0
- package/lib/api/auth-cache.api.js +44 -0
- package/lib/api/certificates.api.js +4 -4
- package/lib/api/credential.api.js +2 -2
- package/lib/api/dataplane-health.api.js +137 -0
- package/lib/api/dataplane-sync.api.js +27 -0
- package/lib/api/datasources-core.api.js +21 -18
- package/lib/api/datasources-extended.api.js +22 -22
- package/lib/api/dimensions.api.js +3 -0
- package/lib/api/external-systems.api.js +13 -13
- package/lib/api/governance-scenarios.api.js +39 -0
- package/lib/api/groups.api.js +79 -0
- package/lib/api/index.js +45 -2
- package/lib/api/pipeline.api.js +4 -4
- package/lib/api/protection.api.js +157 -0
- package/lib/api/records-bulk.api.js +30 -0
- package/lib/api/records-search-parse.js +49 -0
- package/lib/api/records-search.api.js +29 -0
- package/lib/api/types/agent-metadata-validation.types.js +67 -0
- package/lib/api/types/audit.types.js +72 -0
- package/lib/api/types/dataplane-health.types.js +27 -0
- package/lib/api/types/dimensions.types.js +2 -0
- package/lib/api/types/governance-scenarios.types.js +39 -0
- package/lib/api/types/protection.types.js +69 -0
- package/lib/api/types/records-bulk.types.js +35 -0
- package/lib/api/types/records-search.types.js +28 -0
- package/lib/api/user-groups.api.js +50 -0
- package/lib/api/users.api.js +95 -0
- package/lib/api/validation-run.api.js +3 -3
- package/lib/api/validation-runner.js +3 -3
- package/lib/api/wizard-platform.api.js +3 -3
- package/lib/api/wizard.api.js +21 -21
- package/lib/app/deploy.js +34 -19
- package/lib/cli/index.js +4 -2
- package/lib/cli/infra-guided.js +22 -3
- package/lib/cli/setup-app.help.js +50 -2
- package/lib/cli/setup-app.js +33 -24
- package/lib/cli/setup-app.test-commands.js +80 -21
- package/lib/cli/setup-auth.js +30 -3
- package/lib/cli/setup-external-system.js +37 -3
- package/lib/cli/setup-utility-help-after.js +45 -0
- package/lib/cli/setup-utility-resolve.js +2 -2
- package/lib/cli/setup-utility.js +99 -81
- package/lib/commands/auth-status-dataplane-version.js +231 -0
- package/lib/commands/auth-status-display.js +94 -0
- package/lib/commands/auth-status.js +70 -6
- package/lib/commands/credential-push.js +10 -5
- package/lib/commands/datasource-load-export-cli.js +178 -0
- package/lib/commands/datasource-test-trust-cli.js +114 -0
- package/lib/commands/datasource-unified-test-cli.js +25 -17
- package/lib/commands/datasource-unified-test-cli.options.js +18 -3
- package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
- package/lib/commands/datasource-verify-audit-cli.js +96 -0
- package/lib/commands/datasource.js +68 -1
- package/lib/commands/dimension-value.js +43 -13
- package/lib/commands/dimension.js +38 -7
- package/lib/commands/identity-apply-sync.js +131 -0
- package/lib/commands/identity-group-cli.js +146 -0
- package/lib/commands/identity-membership-role-cache.js +175 -0
- package/lib/commands/identity-shared.js +94 -0
- package/lib/commands/identity-user-cli.js +158 -0
- package/lib/commands/identity.js +43 -0
- package/lib/commands/login.js +46 -16
- package/lib/commands/protection-cli-leaves.js +286 -0
- package/lib/commands/protection.js +50 -0
- package/lib/commands/repair.js +13 -3
- package/lib/commands/test-e2e-external.js +40 -9
- package/lib/commands/test-governance-cli-options.js +47 -0
- package/lib/commands/test-governance-command-action.js +55 -0
- package/lib/commands/test-governance-external.js +88 -0
- package/lib/commands/test-trust-cli-options.js +32 -0
- package/lib/commands/test-trust-command-action.js +118 -0
- package/lib/commands/test-trust-external.js +127 -0
- package/lib/commands/upload.js +34 -13
- package/lib/commands/wizard-core.js +3 -0
- package/lib/core/config-device-dataplane.js +185 -0
- package/lib/datasource/agent-trust-debug-log.js +45 -0
- package/lib/datasource/agent-trust-fetch.js +115 -0
- package/lib/datasource/agent-trust-map.js +101 -0
- package/lib/datasource/agent-trust-mock.js +44 -0
- package/lib/datasource/agent-trust-run.js +93 -0
- package/lib/datasource/agent-trust-timeout.js +25 -0
- package/lib/datasource/audit-evidence-display.js +80 -0
- package/lib/datasource/audit-evidence-extract-debug.js +103 -0
- package/lib/datasource/audit-evidence-extract.js +177 -0
- package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
- package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
- package/lib/datasource/audit-evidence-poll.js +109 -0
- package/lib/datasource/audit-evidence-run.js +177 -0
- package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
- package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
- package/lib/datasource/audit-evidence-verify.js +108 -0
- package/lib/datasource/bulk-loader-run-batches.js +88 -0
- package/lib/datasource/bulk-loader-service-retry.js +52 -0
- package/lib/datasource/bulk-loader-service.js +127 -0
- package/lib/datasource/capability/run-capability-dimension.js +2 -1
- package/lib/datasource/datasource-exporter-resolve.js +82 -0
- package/lib/datasource/datasource-exporter-service.js +144 -0
- package/lib/datasource/datasource-identifier.js +148 -0
- package/lib/datasource/datasource-load-export-context.js +92 -0
- package/lib/datasource/integration-context.js +6 -4
- package/lib/datasource/local-data-paths.js +145 -0
- package/lib/datasource/log-cleaner.js +241 -0
- package/lib/datasource/log-viewer.js +111 -6
- package/lib/datasource/record-file-parser.js +85 -0
- package/lib/datasource/record-mapper.js +129 -0
- package/lib/datasource/resolve-app.js +74 -55
- package/lib/datasource/test-e2e.js +26 -2
- package/lib/datasource/unified-validation-run-resolve.js +45 -12
- package/lib/datasource/unified-validation-run.js +43 -17
- package/lib/datasource/validate.js +37 -20
- package/lib/external-system/test-system-level.js +25 -4
- package/lib/external-system/test.js +3 -1
- package/lib/governance/governance-pack-loader.js +70 -0
- package/lib/governance/governance-run-display.js +119 -0
- package/lib/identity/identity-apply-core.js +108 -0
- package/lib/identity/identity-apply-phases.js +244 -0
- package/lib/identity/identity-apply-service.js +77 -0
- package/lib/identity/identity-csv-parser.js +114 -0
- package/lib/internal/fs-real-sync.js +29 -1
- package/lib/protection/auth-context.js +45 -0
- package/lib/protection/convert-batch.js +90 -0
- package/lib/protection/load.js +41 -0
- package/lib/protection/paths.js +158 -0
- package/lib/protection/preflight-datasource-ready.js +46 -0
- package/lib/protection/protection-create-probe-lines.js +142 -0
- package/lib/protection/protection-create-probes.js +95 -0
- package/lib/protection/protection-create-scaffold.js +281 -0
- package/lib/protection/protection-display-helpers.js +445 -0
- package/lib/protection/protection-display.js +215 -0
- package/lib/protection/protection-preset-registry.js +97 -0
- package/lib/protection/report-exit.js +62 -0
- package/lib/protection/resolve.js +202 -0
- package/lib/protection/run-commands-validate.js +130 -0
- package/lib/protection/run-commands.js +216 -0
- package/lib/protection/run-protection-create-helpers.js +134 -0
- package/lib/protection/run-protection-create-probes-flow.js +43 -0
- package/lib/protection/run-protection-create.js +191 -0
- package/lib/protection/scope.js +22 -0
- package/lib/protection/sync-after-upload.js +64 -0
- package/lib/protection/upload-batch.js +127 -0
- package/lib/protection/validate-batch.js +113 -0
- package/lib/protection/validate-local.js +65 -0
- package/lib/resolvers/dimension-file.js +29 -4
- package/lib/schema/external-datasource.schema.json +47 -13
- package/lib/schema/flag-map-validation-run.json +2 -2
- package/lib/schema/protection.schema.json +374 -0
- package/lib/schema/type/credential-field-registry.json +636 -0
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/agent-trust-run-display.js +231 -0
- package/lib/utils/agent-trust-run-exit.js +59 -0
- package/lib/utils/agent-trust-wait-ui.js +94 -0
- package/lib/utils/cli-exit-codes.js +30 -0
- package/lib/utils/cli-sync-options.js +33 -0
- package/lib/utils/credential-push-ui.js +90 -0
- package/lib/utils/dataplane-cli-version-gate.js +202 -0
- package/lib/utils/dataplane-cli-version-help.js +74 -0
- package/lib/utils/datasource-export-display.js +137 -0
- package/lib/utils/datasource-load-display.js +174 -0
- package/lib/utils/datasource-load-export-ui.js +120 -0
- package/lib/utils/datasource-test-run-capability-scope.js +3 -3
- package/lib/utils/datasource-test-run-display.js +3 -3
- package/lib/utils/datasource-test-run-tty-log.js +5 -1
- package/lib/utils/help-builder.js +30 -24
- package/lib/utils/manifest-source-emit.js +22 -0
- package/lib/utils/semver-compare.js +127 -0
- package/lib/utils/upload-pipeline-ui.js +91 -0
- package/lib/utils/validation-poll-ui.js +14 -7
- package/lib/validation/wizard-config-validator.js +58 -19
- package/package.json +1 -1
- package/templates/applications/dataplane/env.template +13 -1
- package/templates/applications/dataplane/rbac.yaml +4 -0
- package/templates/external-system/README.md.hbs +56 -8
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Identity membership, role, and cache subcommands
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const logger = require('../utils/logger');
|
|
10
|
+
const { formatBlockingError, formatSuccessLine } = require('../utils/cli-layout-chalk');
|
|
11
|
+
const {
|
|
12
|
+
resolveControllerAndAuth,
|
|
13
|
+
resolveEnvKey,
|
|
14
|
+
requireNonEmpty,
|
|
15
|
+
parseGroupsList,
|
|
16
|
+
unwrapControllerData,
|
|
17
|
+
throwIfApiFailed
|
|
18
|
+
} = require('./identity-shared');
|
|
19
|
+
const { addUserToGroup, removeUserFromGroup } = require('../api/user-groups.api');
|
|
20
|
+
const { listEnvironmentRoles, updateRoleGroups } = require('../api/environments.api');
|
|
21
|
+
const { clearAuthCache, invalidateAuthCache } = require('../api/auth-cache.api');
|
|
22
|
+
|
|
23
|
+
function printJson(data) {
|
|
24
|
+
logger.log(JSON.stringify(data, null, 2));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {import('commander').Command} memCmd
|
|
29
|
+
*/
|
|
30
|
+
function setupIdentityMembershipCommands(memCmd) {
|
|
31
|
+
memCmd
|
|
32
|
+
.command('add <userId> <groupId>')
|
|
33
|
+
.description('Add user to group (controller ids)')
|
|
34
|
+
.action(async(userId, groupId) => {
|
|
35
|
+
try {
|
|
36
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
37
|
+
const res = await addUserToGroup(
|
|
38
|
+
controllerUrl,
|
|
39
|
+
authConfig,
|
|
40
|
+
requireNonEmpty(userId, 'userId'),
|
|
41
|
+
requireNonEmpty(groupId, 'groupId'),
|
|
42
|
+
{}
|
|
43
|
+
);
|
|
44
|
+
throwIfApiFailed(res);
|
|
45
|
+
logger.log(formatSuccessLine(`Membership created: ${userId} → ${groupId}`));
|
|
46
|
+
} catch (e) {
|
|
47
|
+
logger.error(formatBlockingError(e.message));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
memCmd
|
|
53
|
+
.command('remove <userId> <groupId>')
|
|
54
|
+
.description('Remove user from group')
|
|
55
|
+
.action(async(userId, groupId) => {
|
|
56
|
+
try {
|
|
57
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
58
|
+
const res = await removeUserFromGroup(
|
|
59
|
+
controllerUrl,
|
|
60
|
+
authConfig,
|
|
61
|
+
requireNonEmpty(userId, 'userId'),
|
|
62
|
+
requireNonEmpty(groupId, 'groupId')
|
|
63
|
+
);
|
|
64
|
+
throwIfApiFailed(res);
|
|
65
|
+
logger.log(formatSuccessLine(`Membership removed: ${userId} → ${groupId}`));
|
|
66
|
+
} catch (e) {
|
|
67
|
+
logger.error(formatBlockingError(e.message));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {import('commander').Command} roleCmd
|
|
75
|
+
*/
|
|
76
|
+
function setupIdentityRoleCommands(roleCmd) {
|
|
77
|
+
roleCmd
|
|
78
|
+
.command('list')
|
|
79
|
+
.description('List roles and group mappings for an environment')
|
|
80
|
+
.requiredOption('-e, --env <key>', 'Environment key (dev, tst, pro)')
|
|
81
|
+
.option('--json', 'JSON output')
|
|
82
|
+
.action(async(options) => {
|
|
83
|
+
try {
|
|
84
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
85
|
+
const envKey = await resolveEnvKey(options);
|
|
86
|
+
const res = await listEnvironmentRoles(controllerUrl, envKey, authConfig);
|
|
87
|
+
throwIfApiFailed(res);
|
|
88
|
+
const data = unwrapControllerData(res);
|
|
89
|
+
if (options.json) {
|
|
90
|
+
printJson(data);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const items = Array.isArray(data) ? data : data?.data || data?.roles || [];
|
|
94
|
+
(items || []).forEach((r) => {
|
|
95
|
+
const groups = Array.isArray(r.groups) ? r.groups.join(', ') : '';
|
|
96
|
+
logger.log(` ${r.value || r.role || r.name} → ${groups || '—'}`);
|
|
97
|
+
});
|
|
98
|
+
logger.log('');
|
|
99
|
+
} catch (e) {
|
|
100
|
+
logger.error(formatBlockingError(e.message));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
roleCmd
|
|
106
|
+
.command('set-groups <roleValue>')
|
|
107
|
+
.description('Map role to groups in an environment')
|
|
108
|
+
.requiredOption('-e, --env <key>', 'Environment key')
|
|
109
|
+
.requiredOption('--groups <names>', 'Comma-separated group names')
|
|
110
|
+
.action(async(roleValue, options) => {
|
|
111
|
+
try {
|
|
112
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
113
|
+
const envKey = await resolveEnvKey(options);
|
|
114
|
+
const groups = parseGroupsList(options.groups);
|
|
115
|
+
const res = await updateRoleGroups(
|
|
116
|
+
controllerUrl,
|
|
117
|
+
envKey,
|
|
118
|
+
requireNonEmpty(roleValue, 'roleValue'),
|
|
119
|
+
authConfig,
|
|
120
|
+
groups
|
|
121
|
+
);
|
|
122
|
+
throwIfApiFailed(res);
|
|
123
|
+
logger.log(formatSuccessLine(`Role ${roleValue} mapped to ${groups.length} group(s) in ${envKey}`));
|
|
124
|
+
} catch (e) {
|
|
125
|
+
logger.error(formatBlockingError(e.message));
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {import('commander').Command} cacheCmd
|
|
133
|
+
*/
|
|
134
|
+
function setupIdentityCacheCommands(cacheCmd) {
|
|
135
|
+
cacheCmd
|
|
136
|
+
.command('clear')
|
|
137
|
+
.description('Clear all controller auth/RBAC cache entries')
|
|
138
|
+
.action(async() => {
|
|
139
|
+
try {
|
|
140
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
141
|
+
const res = await clearAuthCache(controllerUrl, authConfig);
|
|
142
|
+
throwIfApiFailed(res);
|
|
143
|
+
logger.log(formatSuccessLine('Controller cache cleared'));
|
|
144
|
+
} catch (e) {
|
|
145
|
+
logger.error(formatBlockingError(e.message));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
cacheCmd
|
|
151
|
+
.command('invalidate')
|
|
152
|
+
.description('Invalidate controller cache entries by pattern')
|
|
153
|
+
.requiredOption('--pattern <pattern>', 'Cache key pattern (e.g. permissions:*)')
|
|
154
|
+
.action(async(options) => {
|
|
155
|
+
try {
|
|
156
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
157
|
+
const res = await invalidateAuthCache(
|
|
158
|
+
controllerUrl,
|
|
159
|
+
authConfig,
|
|
160
|
+
requireNonEmpty(options.pattern, 'pattern')
|
|
161
|
+
);
|
|
162
|
+
throwIfApiFailed(res);
|
|
163
|
+
logger.log(formatSuccessLine(`Cache invalidated for pattern: ${options.pattern}`));
|
|
164
|
+
} catch (e) {
|
|
165
|
+
logger.error(formatBlockingError(e.message));
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = {
|
|
172
|
+
setupIdentityMembershipCommands,
|
|
173
|
+
setupIdentityRoleCommands,
|
|
174
|
+
setupIdentityCacheCommands
|
|
175
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared auth/helpers for identity CLI commands
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
10
|
+
const { normalizeControllerUrl, resolveEnvironment } = require('../core/config');
|
|
11
|
+
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
12
|
+
const {
|
|
13
|
+
unwrapControllerData,
|
|
14
|
+
throwIfApiFailed,
|
|
15
|
+
assertSyncStatsOk,
|
|
16
|
+
apiErrorMessage
|
|
17
|
+
} = require('../identity/identity-apply-core');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @returns {Promise<{ controllerUrl: string, authConfig: Object }>}
|
|
21
|
+
*/
|
|
22
|
+
async function resolveControllerAndAuth() {
|
|
23
|
+
const controllerUrl = await resolveControllerUrl();
|
|
24
|
+
if (!controllerUrl) {
|
|
25
|
+
throw new Error('Controller URL is required. Run "aifabrix login" first.');
|
|
26
|
+
}
|
|
27
|
+
const normalized = normalizeControllerUrl(controllerUrl);
|
|
28
|
+
const deviceToken = await getOrRefreshDeviceToken(normalized);
|
|
29
|
+
if (!deviceToken || !deviceToken.token) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Not authenticated for controller: ${controllerUrl}. Run "aifabrix login" and try again.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
controllerUrl: deviceToken.controller || normalized,
|
|
36
|
+
authConfig: { type: 'bearer', token: deviceToken.token }
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {Object} [options]
|
|
42
|
+
* @returns {Promise<string>}
|
|
43
|
+
*/
|
|
44
|
+
async function resolveEnvKey(options = {}) {
|
|
45
|
+
const fromFlag = options.env || options.environment;
|
|
46
|
+
if (fromFlag) {
|
|
47
|
+
return String(fromFlag).trim();
|
|
48
|
+
}
|
|
49
|
+
const env = await resolveEnvironment();
|
|
50
|
+
return env || 'dev';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} raw
|
|
55
|
+
* @param {string} label
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
function requireNonEmpty(raw, label) {
|
|
59
|
+
const s = String(raw || '').trim();
|
|
60
|
+
if (!s) {
|
|
61
|
+
throw new Error(`${label} is required.`);
|
|
62
|
+
}
|
|
63
|
+
return s;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} groupsArg
|
|
68
|
+
* @returns {string[]}
|
|
69
|
+
*/
|
|
70
|
+
function parseGroupsList(groupsArg) {
|
|
71
|
+
const raw = String(groupsArg || '').trim();
|
|
72
|
+
if (!raw) {
|
|
73
|
+
throw new Error('--groups is required (comma-separated group names).');
|
|
74
|
+
}
|
|
75
|
+
const parts = raw
|
|
76
|
+
.split(',')
|
|
77
|
+
.map((g) => g.trim())
|
|
78
|
+
.filter(Boolean);
|
|
79
|
+
if (parts.length === 0) {
|
|
80
|
+
throw new Error('At least one group name is required in --groups.');
|
|
81
|
+
}
|
|
82
|
+
return parts;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
resolveControllerAndAuth,
|
|
87
|
+
resolveEnvKey,
|
|
88
|
+
requireNonEmpty,
|
|
89
|
+
parseGroupsList,
|
|
90
|
+
unwrapControllerData,
|
|
91
|
+
throwIfApiFailed,
|
|
92
|
+
assertSyncStatsOk,
|
|
93
|
+
apiErrorMessage
|
|
94
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Identity user subcommands
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const logger = require('../utils/logger');
|
|
11
|
+
const { formatBlockingError, formatSuccessLine, headerKeyValue } = require('../utils/cli-layout-chalk');
|
|
12
|
+
const {
|
|
13
|
+
resolveControllerAndAuth,
|
|
14
|
+
requireNonEmpty,
|
|
15
|
+
unwrapControllerData,
|
|
16
|
+
throwIfApiFailed
|
|
17
|
+
} = require('./identity-shared');
|
|
18
|
+
const {
|
|
19
|
+
listUsers,
|
|
20
|
+
getUser,
|
|
21
|
+
createUser,
|
|
22
|
+
listUserGroups
|
|
23
|
+
} = require('../api/users.api');
|
|
24
|
+
|
|
25
|
+
function printJson(data) {
|
|
26
|
+
logger.log(JSON.stringify(data, null, 2));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function registerUserCreate(userCmd) {
|
|
30
|
+
userCmd
|
|
31
|
+
.command('create')
|
|
32
|
+
.description('Create a user on the controller')
|
|
33
|
+
.requiredOption('--email <email>', 'User email')
|
|
34
|
+
.option('--first-name <name>', 'First name')
|
|
35
|
+
.option('--last-name <name>', 'Last name')
|
|
36
|
+
.option('--display-name <name>', 'Display name')
|
|
37
|
+
.option('--username <name>', 'Username')
|
|
38
|
+
.action(async(options) => {
|
|
39
|
+
try {
|
|
40
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
41
|
+
const res = await createUser(controllerUrl, authConfig, {
|
|
42
|
+
email: requireNonEmpty(options.email, 'email'),
|
|
43
|
+
firstName: options.firstName,
|
|
44
|
+
lastName: options.lastName,
|
|
45
|
+
displayName: options.displayName,
|
|
46
|
+
username: options.username,
|
|
47
|
+
status: 'active'
|
|
48
|
+
});
|
|
49
|
+
throwIfApiFailed(res);
|
|
50
|
+
const row = unwrapControllerData(res);
|
|
51
|
+
logger.log(formatSuccessLine(`User created: ${row.id} (${row.email})`));
|
|
52
|
+
} catch (e) {
|
|
53
|
+
logger.error(formatBlockingError(e.message));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function registerUserList(userCmd) {
|
|
60
|
+
userCmd
|
|
61
|
+
.command('list')
|
|
62
|
+
.description('List users')
|
|
63
|
+
.option('--page <n>', 'Page', (v) => parseInt(v, 10))
|
|
64
|
+
.option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
|
|
65
|
+
.option('--search <text>', 'Search users')
|
|
66
|
+
.option('--json', 'JSON output')
|
|
67
|
+
.action(async(options) => {
|
|
68
|
+
try {
|
|
69
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
70
|
+
const res = await listUsers(controllerUrl, authConfig, {
|
|
71
|
+
page: options.page,
|
|
72
|
+
pageSize: options.pageSize,
|
|
73
|
+
search: options.search
|
|
74
|
+
});
|
|
75
|
+
throwIfApiFailed(res);
|
|
76
|
+
const data = unwrapControllerData(res);
|
|
77
|
+
if (options.json) {
|
|
78
|
+
printJson(data);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const items = Array.isArray(data) ? data : data?.data || [];
|
|
82
|
+
logger.log(chalk.bold(`\nUsers (${items.length} shown):\n`));
|
|
83
|
+
items.forEach((u) => logger.log(` ${u.id} ${u.email}`));
|
|
84
|
+
logger.log('');
|
|
85
|
+
} catch (e) {
|
|
86
|
+
logger.error(formatBlockingError(e.message));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function registerUserGet(userCmd) {
|
|
93
|
+
userCmd
|
|
94
|
+
.command('get <idOrEmail>')
|
|
95
|
+
.description('Get user by id or email')
|
|
96
|
+
.option('--json', 'JSON output')
|
|
97
|
+
.action(async(idOrEmail, options) => {
|
|
98
|
+
try {
|
|
99
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
100
|
+
const res = await getUser(controllerUrl, authConfig, requireNonEmpty(idOrEmail, 'idOrEmail'));
|
|
101
|
+
throwIfApiFailed(res);
|
|
102
|
+
const row = unwrapControllerData(res);
|
|
103
|
+
if (options.json) {
|
|
104
|
+
printJson(row);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
logger.log(headerKeyValue('Id:', row.id));
|
|
108
|
+
logger.log(headerKeyValue('Email:', row.email));
|
|
109
|
+
logger.log('');
|
|
110
|
+
} catch (e) {
|
|
111
|
+
logger.error(formatBlockingError(e.message));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function registerUserGroups(userCmd) {
|
|
118
|
+
userCmd
|
|
119
|
+
.command('groups <idOrEmail>')
|
|
120
|
+
.description('List groups for a user')
|
|
121
|
+
.option('--json', 'JSON output')
|
|
122
|
+
.action(async(idOrEmail, options) => {
|
|
123
|
+
try {
|
|
124
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
125
|
+
const res = await listUserGroups(
|
|
126
|
+
controllerUrl,
|
|
127
|
+
authConfig,
|
|
128
|
+
requireNonEmpty(idOrEmail, 'idOrEmail')
|
|
129
|
+
);
|
|
130
|
+
throwIfApiFailed(res);
|
|
131
|
+
const data = unwrapControllerData(res);
|
|
132
|
+
if (options.json) {
|
|
133
|
+
printJson(data);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const items = Array.isArray(data) ? data : data?.data || [];
|
|
137
|
+
items.forEach((g) => logger.log(` ${g.id || g.groupId} ${g.name || ''}`));
|
|
138
|
+
logger.log('');
|
|
139
|
+
} catch (e) {
|
|
140
|
+
logger.error(formatBlockingError(e.message));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {import('commander').Command} userCmd
|
|
148
|
+
*/
|
|
149
|
+
function setupIdentityUserCommands(userCmd) {
|
|
150
|
+
registerUserCreate(userCmd);
|
|
151
|
+
registerUserList(userCmd);
|
|
152
|
+
registerUserGet(userCmd);
|
|
153
|
+
registerUserGroups(userCmd);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = {
|
|
157
|
+
setupIdentityUserCommands
|
|
158
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity and RBAC commands (Controller users, groups, sync, cache).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview `aifabrix identity` command group
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { setupIdentityUserCommands } = require('./identity-user-cli');
|
|
12
|
+
const { setupIdentityGroupCommands } = require('./identity-group-cli');
|
|
13
|
+
const {
|
|
14
|
+
setupIdentityMembershipCommands,
|
|
15
|
+
setupIdentityRoleCommands,
|
|
16
|
+
setupIdentityCacheCommands
|
|
17
|
+
} = require('./identity-membership-role-cache');
|
|
18
|
+
const { setupIdentityApplyCommand, setupIdentitySyncCommand } = require('./identity-apply-sync');
|
|
19
|
+
const { IDENTITY_HELP_AFTER } = require('../cli/setup-app.help');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {import('commander').Command} program
|
|
23
|
+
*/
|
|
24
|
+
function setupIdentityCommands(program) {
|
|
25
|
+
const identityCmd = program
|
|
26
|
+
.command('identity')
|
|
27
|
+
.description('Manage controller users, groups, RBAC mappings, cache, and dataplane identity sync')
|
|
28
|
+
.addHelpText('after', IDENTITY_HELP_AFTER);
|
|
29
|
+
|
|
30
|
+
setupIdentityUserCommands(identityCmd.command('user').description('User commands'));
|
|
31
|
+
setupIdentityGroupCommands(identityCmd.command('group').description('Group commands'));
|
|
32
|
+
setupIdentityMembershipCommands(
|
|
33
|
+
identityCmd.command('membership').description('User–group membership commands')
|
|
34
|
+
);
|
|
35
|
+
setupIdentityRoleCommands(identityCmd.command('role').description('Environment role commands'));
|
|
36
|
+
setupIdentityCacheCommands(identityCmd.command('cache').description('Controller cache commands'));
|
|
37
|
+
setupIdentityApplyCommand(identityCmd);
|
|
38
|
+
setupIdentitySyncCommand(identityCmd);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
setupIdentityCommands
|
|
43
|
+
};
|
package/lib/commands/login.js
CHANGED
|
@@ -17,6 +17,7 @@ const logger = require('../utils/logger');
|
|
|
17
17
|
const { handleCredentialsLogin } = require('./login-credentials');
|
|
18
18
|
const { handleDeviceCodeLogin } = require('./login-device');
|
|
19
19
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
20
|
+
const { tryRefreshDataplaneVersionAfterLogin } = require('./auth-status-dataplane-version');
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Determine and validate authentication method
|
|
@@ -161,6 +162,47 @@ async function handleDeviceCodeLoginFlow(controllerUrl, environment, options) {
|
|
|
161
162
|
});
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
/**
|
|
166
|
+
* @param {string} controllerUrl
|
|
167
|
+
* @param {Object} options
|
|
168
|
+
* @param {boolean} skipRefresh
|
|
169
|
+
* @returns {Promise<void>}
|
|
170
|
+
*/
|
|
171
|
+
async function refreshDataplaneVersionIfNeeded(controllerUrl, options, skipRefresh) {
|
|
172
|
+
if (skipRefresh) return;
|
|
173
|
+
await tryRefreshDataplaneVersionAfterLogin(controllerUrl, options);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @returns {Promise<'credentials'|'device'>}
|
|
178
|
+
*/
|
|
179
|
+
async function executeLoginMethod(method, controllerUrl, environment, options, skipRefresh) {
|
|
180
|
+
if (method === 'credentials') {
|
|
181
|
+
await handleCredentialsLoginFlow(controllerUrl, environment, options);
|
|
182
|
+
await refreshDataplaneVersionIfNeeded(controllerUrl, options, skipRefresh);
|
|
183
|
+
return 'credentials';
|
|
184
|
+
}
|
|
185
|
+
await handleDeviceCodeLoginFlow(controllerUrl, environment, options);
|
|
186
|
+
await refreshDataplaneVersionIfNeeded(controllerUrl, options, skipRefresh);
|
|
187
|
+
return 'device';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @param {Object} options
|
|
192
|
+
* @param {string} controllerUrl
|
|
193
|
+
* @param {string} environment
|
|
194
|
+
*/
|
|
195
|
+
function logLoginSuccessFooter(options, controllerUrl, environment) {
|
|
196
|
+
if (options.compact) return;
|
|
197
|
+
logger.log(formatSuccessParagraph('Successfully logged in!'));
|
|
198
|
+
logger.log(chalk.gray(`Controller: ${controllerUrl}`));
|
|
199
|
+
logger.log(chalk.gray(`Environment: ${environment}`));
|
|
200
|
+
if (options.app) {
|
|
201
|
+
logger.log(chalk.gray(`App: ${options.app}`));
|
|
202
|
+
}
|
|
203
|
+
logger.log(chalk.gray('Token stored securely in ~/.aifabrix/config.yaml\n'));
|
|
204
|
+
}
|
|
205
|
+
|
|
164
206
|
async function handleLogin(options) {
|
|
165
207
|
if (!options.compact) {
|
|
166
208
|
logger.log(chalk.blue('\n🔐 Logging in to Miso Controller...\n'));
|
|
@@ -169,25 +211,13 @@ async function handleLogin(options) {
|
|
|
169
211
|
const controllerUrl = await normalizeControllerUrl(options);
|
|
170
212
|
const environment = await handleEnvironmentConfig(options);
|
|
171
213
|
const method = await determineAuthMethod(options.method);
|
|
172
|
-
|
|
173
214
|
validateScopeOptions(method, options);
|
|
174
215
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
await handleDeviceCodeLoginFlow(controllerUrl, environment, options);
|
|
179
|
-
return; // Early return for device flow (already saved config)
|
|
180
|
-
}
|
|
216
|
+
const skipRefresh = options.skipDataplaneVersionRefresh === true;
|
|
217
|
+
const flow = await executeLoginMethod(method, controllerUrl, environment, options, skipRefresh);
|
|
218
|
+
if (flow === 'device') return;
|
|
181
219
|
|
|
182
|
-
|
|
183
|
-
logger.log(formatSuccessParagraph('Successfully logged in!'));
|
|
184
|
-
logger.log(chalk.gray(`Controller: ${controllerUrl}`));
|
|
185
|
-
logger.log(chalk.gray(`Environment: ${environment}`));
|
|
186
|
-
if (options.app) {
|
|
187
|
-
logger.log(chalk.gray(`App: ${options.app}`));
|
|
188
|
-
}
|
|
189
|
-
logger.log(chalk.gray('Token stored securely in ~/.aifabrix/config.yaml\n'));
|
|
190
|
-
}
|
|
220
|
+
logLoginSuccessFooter(options, controllerUrl, environment);
|
|
191
221
|
}
|
|
192
222
|
|
|
193
223
|
module.exports = { handleLogin };
|