@aifabrix/builder 2.32.2 → 2.33.0
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/project-rules.mdc +8 -0
- package/README.md +36 -8
- package/bin/aifabrix.js +6 -8
- package/integration/hubspot/README.md +8 -7
- package/integration/hubspot/companies.json +2048 -0
- package/integration/hubspot/create-hubspot.js +665 -0
- package/integration/hubspot/{hubspot-deploy-company.json → hubspot-datasource-company.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-contact.json → hubspot-datasource-contact.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-deal.json → hubspot-datasource-deal.json} +1 -1
- package/integration/hubspot/hubspot-deploy.json +832 -81
- package/integration/hubspot/hubspot-system.json +99 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-credential-real.yaml +20 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-env-vars.yaml +9 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-add-datasource.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-app-name.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-create.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-select.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-known-platform.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-app.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-mode.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-file.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-url.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-source.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-array-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +5 -0
- package/integration/hubspot/test-dataplane-down-helpers.js +246 -0
- package/integration/hubspot/test-dataplane-down-tests.js +419 -0
- package/integration/hubspot/test-dataplane-down.js +157 -0
- package/integration/hubspot/test.js +1517 -0
- package/integration/hubspot/variables.yaml +4 -4
- package/integration/hubspot/wizard-hubspot-e2e.yaml +16 -0
- package/integration/hubspot/wizard-hubspot-platform.yaml +8 -0
- package/lib/api/applications.api.js +1 -0
- package/lib/api/index.js +10 -5
- package/lib/api/types/wizard.types.js +176 -38
- package/lib/api/wizard.api.js +207 -38
- package/lib/app/deploy.js +116 -54
- package/lib/app/display.js +6 -5
- package/lib/app/dockerfile.js +2 -1
- package/lib/app/list.js +78 -37
- package/lib/app/prompts.js +9 -5
- package/lib/app/readme.js +41 -112
- package/lib/app/register.js +44 -9
- package/lib/app/rotate-secret.js +50 -32
- package/lib/cli.js +243 -65
- package/lib/commands/app.js +4 -9
- package/lib/commands/auth-config.js +125 -0
- package/lib/commands/auth-status.js +261 -0
- package/lib/commands/datasource.js +3 -6
- package/lib/commands/login-credentials.js +4 -4
- package/lib/commands/login-device.js +43 -29
- package/lib/commands/login.js +22 -13
- package/lib/commands/wizard-config-normalizer.js +92 -0
- package/lib/commands/wizard-core.js +515 -0
- package/lib/commands/wizard-dataplane.js +122 -0
- package/lib/commands/wizard-headless.js +115 -0
- package/lib/commands/wizard.js +129 -357
- package/lib/core/config.js +46 -0
- package/lib/core/secrets.js +3 -22
- package/lib/core/templates-env.js +1 -1
- package/lib/datasource/deploy.js +34 -23
- package/lib/datasource/list.js +8 -6
- package/lib/deployment/deployer.js +25 -0
- package/lib/deployment/environment.js +10 -13
- package/lib/external-system/delete.js +151 -0
- package/lib/external-system/deploy.js +54 -378
- package/lib/external-system/download-helpers.js +45 -65
- package/lib/external-system/download.js +34 -13
- package/lib/external-system/generator.js +11 -7
- package/lib/external-system/test-auth.js +5 -3
- package/lib/generator/builders.js +3 -1
- package/lib/generator/external-controller-manifest.js +157 -0
- package/lib/generator/external-schema-utils.js +236 -0
- package/lib/generator/external.js +55 -3
- package/lib/generator/index.js +22 -10
- package/lib/generator/wizard-prompts.js +33 -10
- package/lib/generator/wizard.js +69 -86
- package/lib/infrastructure/compose.js +100 -0
- package/lib/infrastructure/helpers.js +139 -0
- package/lib/infrastructure/index.js +52 -311
- package/lib/infrastructure/services.js +168 -0
- package/lib/schema/application-schema.json +24 -5
- package/lib/schema/external-datasource.schema.json +303 -17
- package/lib/schema/external-system.schema.json +1 -1
- package/lib/schema/wizard-config.schema.json +234 -0
- package/lib/utils/api.js +37 -42
- package/lib/utils/app-existence.js +42 -0
- package/lib/utils/app-register-config.js +7 -2
- package/lib/utils/app-register-display.js +2 -1
- package/lib/utils/auth-config-validator.js +92 -0
- package/lib/utils/cli-utils.js +3 -1
- package/lib/utils/command-header.js +43 -0
- package/lib/utils/compose-generator.js +113 -70
- package/lib/utils/controller-url.js +115 -0
- package/lib/utils/dataplane-health.js +115 -0
- package/lib/utils/dataplane-resolver.js +29 -0
- package/lib/utils/dev-config.js +6 -2
- package/lib/utils/env-copy.js +2 -1
- package/lib/utils/env-map.js +2 -1
- package/lib/utils/env-ports.js +2 -1
- package/lib/utils/env-template.js +1 -1
- package/lib/utils/error-formatter.js +149 -28
- package/lib/utils/external-readme.js +125 -0
- package/lib/utils/help-builder.js +190 -0
- package/lib/utils/infra-status.js +13 -3
- package/lib/utils/paths.js +17 -2
- package/lib/utils/port-resolver.js +111 -0
- package/lib/utils/secrets-helpers.js +3 -15
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/token-manager.js +69 -4
- package/lib/utils/variable-transformer.js +7 -2
- package/lib/validation/external-manifest-validator.js +202 -0
- package/lib/validation/validate-display.js +406 -0
- package/lib/validation/validate.js +159 -123
- package/lib/validation/validator.js +38 -4
- package/lib/validation/wizard-config-validator.js +267 -0
- package/package.json +4 -2
- package/templates/applications/README.md.hbs +19 -17
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/applications/miso-controller/rbac.yaml +7 -7
- package/templates/external-system/README.md.hbs +99 -0
- package/templates/external-system/external-system.json.hbs +1 -1
- package/templates/infra/compose.yaml.hbs +35 -0
- package/templates/python/docker-compose.hbs +26 -0
- package/templates/typescript/docker-compose.hbs +26 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder - Auth Status Command
|
|
3
|
+
*
|
|
4
|
+
* Displays authentication status for the current controller and environment
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Authentication status 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 config = require('../core/config');
|
|
14
|
+
const { getConfig } = config;
|
|
15
|
+
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
16
|
+
const { getAuthUser } = require('../api/auth.api');
|
|
17
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Format expiration date for display
|
|
21
|
+
* @param {string} expiresAt - ISO 8601 expiration timestamp
|
|
22
|
+
* @returns {string} Formatted expiration string
|
|
23
|
+
*/
|
|
24
|
+
function formatExpiration(expiresAt) {
|
|
25
|
+
if (!expiresAt) {
|
|
26
|
+
return 'Unknown';
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const date = new Date(expiresAt);
|
|
30
|
+
return date.toISOString();
|
|
31
|
+
} catch {
|
|
32
|
+
return expiresAt;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check and validate device token
|
|
38
|
+
* @async
|
|
39
|
+
* @param {string} controllerUrl - Controller URL
|
|
40
|
+
* @returns {Promise<Object|null>} Token validation result or null
|
|
41
|
+
*/
|
|
42
|
+
async function checkDeviceToken(controllerUrl) {
|
|
43
|
+
const deviceToken = await getOrRefreshDeviceToken(controllerUrl);
|
|
44
|
+
if (!deviceToken || !deviceToken.token) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const authConfig = { type: 'bearer', token: deviceToken.token };
|
|
50
|
+
// Use getAuthUser instead of validateToken - it's more reliable and tests actual API access
|
|
51
|
+
const { getAuthUser } = require('../api/auth.api');
|
|
52
|
+
const response = await getAuthUser(controllerUrl, authConfig);
|
|
53
|
+
|
|
54
|
+
if (response.success && response.data) {
|
|
55
|
+
return {
|
|
56
|
+
type: 'Device Token',
|
|
57
|
+
token: deviceToken.token,
|
|
58
|
+
authenticated: response.data.authenticated !== false,
|
|
59
|
+
user: response.data.user,
|
|
60
|
+
expiresAt: deviceToken.expiresAt
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
type: 'Device Token',
|
|
66
|
+
token: deviceToken.token,
|
|
67
|
+
authenticated: false,
|
|
68
|
+
error: response.error || response.formattedError || 'Token validation failed'
|
|
69
|
+
};
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return {
|
|
72
|
+
type: 'Device Token',
|
|
73
|
+
token: deviceToken.token,
|
|
74
|
+
authenticated: false,
|
|
75
|
+
error: error.message || 'Token validation error'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Decrypt token if encrypted
|
|
82
|
+
* @async
|
|
83
|
+
* @param {string} token - Token to decrypt
|
|
84
|
+
* @returns {Promise<string>} Decrypted token
|
|
85
|
+
*/
|
|
86
|
+
async function decryptTokenIfNeeded(token) {
|
|
87
|
+
const { decryptToken, isTokenEncrypted } = require('../utils/token-encryption');
|
|
88
|
+
const encryptionKey = await config.getSecretsEncryptionKey();
|
|
89
|
+
|
|
90
|
+
if (encryptionKey && isTokenEncrypted(token)) {
|
|
91
|
+
return await decryptToken(token, encryptionKey);
|
|
92
|
+
}
|
|
93
|
+
return token;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Validate client token and return result
|
|
98
|
+
* @async
|
|
99
|
+
* @param {string} token - Token to validate
|
|
100
|
+
* @param {string} controllerUrl - Controller URL
|
|
101
|
+
* @param {string} environment - Environment key
|
|
102
|
+
* @param {string} appName - Application name
|
|
103
|
+
* @param {string} expiresAt - Token expiration
|
|
104
|
+
* @returns {Promise<Object>} Token validation result
|
|
105
|
+
*/
|
|
106
|
+
async function validateClientToken(token, controllerUrl, environment, appName, expiresAt) {
|
|
107
|
+
try {
|
|
108
|
+
const authConfig = { type: 'bearer', token: token };
|
|
109
|
+
// Use getAuthUser instead of validateToken - it's more reliable and tests actual API access
|
|
110
|
+
const response = await getAuthUser(controllerUrl, authConfig);
|
|
111
|
+
|
|
112
|
+
if (response.success && response.data) {
|
|
113
|
+
return {
|
|
114
|
+
type: 'Client Token',
|
|
115
|
+
token: token,
|
|
116
|
+
authenticated: response.data.authenticated !== false,
|
|
117
|
+
user: response.data.user,
|
|
118
|
+
expiresAt: expiresAt,
|
|
119
|
+
appName: appName
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
type: 'Client Token',
|
|
125
|
+
token: token,
|
|
126
|
+
authenticated: false,
|
|
127
|
+
error: response.error || response.formattedError || 'Token validation failed',
|
|
128
|
+
appName: appName
|
|
129
|
+
};
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return {
|
|
132
|
+
type: 'Client Token',
|
|
133
|
+
token: '***',
|
|
134
|
+
authenticated: false,
|
|
135
|
+
error: error.message || 'Token validation error',
|
|
136
|
+
appName: appName
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Check and validate client token
|
|
143
|
+
* @async
|
|
144
|
+
* @param {string} controllerUrl - Controller URL
|
|
145
|
+
* @param {string} environment - Environment key
|
|
146
|
+
* @returns {Promise<Object|null>} Token validation result or null
|
|
147
|
+
*/
|
|
148
|
+
async function checkClientToken(controllerUrl, environment) {
|
|
149
|
+
const configData = await getConfig();
|
|
150
|
+
const environments = configData.environments || {};
|
|
151
|
+
const envConfig = environments[environment];
|
|
152
|
+
|
|
153
|
+
if (!envConfig || !envConfig.clients) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (const [appName, tokenData] of Object.entries(envConfig.clients)) {
|
|
158
|
+
if (tokenData.controller === controllerUrl && tokenData.token) {
|
|
159
|
+
const token = await decryptTokenIfNeeded(tokenData.token);
|
|
160
|
+
return await validateClientToken(token, controllerUrl, environment, appName, tokenData.expiresAt);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Display user information
|
|
169
|
+
* @param {Object} user - User object
|
|
170
|
+
*/
|
|
171
|
+
function displayUserInfo(user) {
|
|
172
|
+
if (!user) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
logger.log('');
|
|
177
|
+
logger.log(chalk.bold('User Information:'));
|
|
178
|
+
if (user.email) {
|
|
179
|
+
logger.log(` Email: ${chalk.cyan(user.email)}`);
|
|
180
|
+
}
|
|
181
|
+
if (user.username) {
|
|
182
|
+
logger.log(` Username: ${chalk.cyan(user.username)}`);
|
|
183
|
+
}
|
|
184
|
+
if (user.id) {
|
|
185
|
+
logger.log(` ID: ${chalk.gray(user.id)}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Display token information
|
|
191
|
+
* @param {Object} tokenInfo - Token information
|
|
192
|
+
*/
|
|
193
|
+
function displayTokenInfo(tokenInfo) {
|
|
194
|
+
const statusIcon = tokenInfo.authenticated ? chalk.green('✓') : chalk.red('✗');
|
|
195
|
+
const statusText = tokenInfo.authenticated ? 'Authenticated' : 'Not authenticated';
|
|
196
|
+
|
|
197
|
+
logger.log(`Status: ${statusIcon} ${statusText}`);
|
|
198
|
+
logger.log(`Token Type: ${chalk.cyan(tokenInfo.type)}`);
|
|
199
|
+
|
|
200
|
+
if (tokenInfo.appName) {
|
|
201
|
+
logger.log(`Application: ${chalk.cyan(tokenInfo.appName)}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (tokenInfo.expiresAt) {
|
|
205
|
+
logger.log(`Expires: ${chalk.gray(formatExpiration(tokenInfo.expiresAt))}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (tokenInfo.error) {
|
|
209
|
+
logger.log(`Error: ${chalk.red(tokenInfo.error)}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
displayUserInfo(tokenInfo.user);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Display authentication status
|
|
217
|
+
* @param {string} controllerUrl - Controller URL
|
|
218
|
+
* @param {string} environment - Environment key
|
|
219
|
+
* @param {Object|null} tokenInfo - Token information
|
|
220
|
+
*/
|
|
221
|
+
function displayStatus(controllerUrl, environment, tokenInfo) {
|
|
222
|
+
logger.log(chalk.bold('\n🔐 Authentication Status\n'));
|
|
223
|
+
logger.log(`Controller: ${chalk.cyan(controllerUrl)}`);
|
|
224
|
+
logger.log(`Environment: ${chalk.cyan(environment || 'Not specified')}\n`);
|
|
225
|
+
|
|
226
|
+
if (!tokenInfo) {
|
|
227
|
+
logger.log(`Status: ${chalk.red('✗ Not authenticated')}`);
|
|
228
|
+
logger.log(`Token Type: ${chalk.gray('None')}\n`);
|
|
229
|
+
logger.log(chalk.yellow('💡 Run "aifabrix login" to authenticate\n'));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
displayTokenInfo(tokenInfo);
|
|
234
|
+
logger.log('');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Handle auth status command
|
|
239
|
+
* Controller and environment come from config.yaml (set via aifabrix login or aifabrix auth config).
|
|
240
|
+
* @async
|
|
241
|
+
* @function handleAuthStatus
|
|
242
|
+
* @param {Object} _options - Command options (unused; controller/environment from config only)
|
|
243
|
+
* @returns {Promise<void>} Resolves when status is displayed
|
|
244
|
+
*/
|
|
245
|
+
async function handleAuthStatus(_options) {
|
|
246
|
+
const { resolveEnvironment } = require('../core/config');
|
|
247
|
+
const controllerUrl = await resolveControllerUrl();
|
|
248
|
+
const environment = await resolveEnvironment();
|
|
249
|
+
|
|
250
|
+
// Check device token first (preferred)
|
|
251
|
+
let tokenInfo = await checkDeviceToken(controllerUrl);
|
|
252
|
+
|
|
253
|
+
// If no device token, check client token
|
|
254
|
+
if (!tokenInfo) {
|
|
255
|
+
tokenInfo = await checkClientToken(controllerUrl, environment);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
displayStatus(controllerUrl, environment, tokenInfo);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
module.exports = { handleAuthStatus };
|
|
@@ -50,11 +50,10 @@ function setupDatasourceCommands(program) {
|
|
|
50
50
|
// List command
|
|
51
51
|
datasource
|
|
52
52
|
.command('list')
|
|
53
|
-
.description('List datasources from environment')
|
|
54
|
-
.
|
|
55
|
-
.action(async(options) => {
|
|
53
|
+
.description('List datasources from environment (uses environment from config.yaml)')
|
|
54
|
+
.action(async() => {
|
|
56
55
|
try {
|
|
57
|
-
await listDatasources(
|
|
56
|
+
await listDatasources({});
|
|
58
57
|
} catch (error) {
|
|
59
58
|
logger.error(chalk.red('❌ Failed to list datasources:'), error.message);
|
|
60
59
|
process.exit(1);
|
|
@@ -78,8 +77,6 @@ function setupDatasourceCommands(program) {
|
|
|
78
77
|
datasource
|
|
79
78
|
.command('deploy <myapp> <file>')
|
|
80
79
|
.description('Deploy datasource to dataplane')
|
|
81
|
-
.requiredOption('--controller <url>', 'Controller URL')
|
|
82
|
-
.requiredOption('-e, --environment <env>', 'Environment (miso, dev, tst, pro)')
|
|
83
80
|
.action(async(myapp, file, options) => {
|
|
84
81
|
try {
|
|
85
82
|
await deployDatasource(myapp, file, options);
|
|
@@ -54,7 +54,7 @@ async function promptForCredentials(clientId, clientSecret) {
|
|
|
54
54
|
message: 'Client ID:',
|
|
55
55
|
default: clientId || '',
|
|
56
56
|
validate: (input) => {
|
|
57
|
-
const value = input.trim();
|
|
57
|
+
const value = input ? input.trim() : '';
|
|
58
58
|
if (!value || value.length === 0) {
|
|
59
59
|
return 'Client ID is required';
|
|
60
60
|
}
|
|
@@ -68,7 +68,7 @@ async function promptForCredentials(clientId, clientSecret) {
|
|
|
68
68
|
default: clientSecret || '',
|
|
69
69
|
mask: '*',
|
|
70
70
|
validate: (input) => {
|
|
71
|
-
const value = input.trim();
|
|
71
|
+
const value = input ? input.trim() : '';
|
|
72
72
|
if (!value || value.length === 0) {
|
|
73
73
|
return 'Client Secret is required';
|
|
74
74
|
}
|
|
@@ -78,8 +78,8 @@ async function promptForCredentials(clientId, clientSecret) {
|
|
|
78
78
|
]);
|
|
79
79
|
|
|
80
80
|
return {
|
|
81
|
-
clientId: credentials.clientId.trim(),
|
|
82
|
-
clientSecret: credentials.clientSecret.trim()
|
|
81
|
+
clientId: (credentials.clientId || '').trim(),
|
|
82
|
+
clientSecret: (credentials.clientSecret || '').trim()
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const inquirer = require('inquirer');
|
|
12
12
|
const chalk = require('chalk');
|
|
13
13
|
const ora = require('ora');
|
|
14
|
-
const { setCurrentEnvironment, saveDeviceToken } = require('../core/config');
|
|
14
|
+
const { setCurrentEnvironment, saveDeviceToken, setControllerUrl } = require('../core/config');
|
|
15
15
|
const { initiateDeviceCodeFlow } = require('../api/auth.api');
|
|
16
16
|
const { pollDeviceCodeToken, displayDeviceCodeInfo } = require('../utils/api');
|
|
17
17
|
const logger = require('../utils/logger');
|
|
@@ -68,6 +68,30 @@ async function saveDeviceLoginConfig(controllerUrl, token, refreshToken, expires
|
|
|
68
68
|
await saveDeviceToken(controllerUrl, token, refreshToken, expiresAt);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Save token configuration and display success message
|
|
73
|
+
* @async
|
|
74
|
+
* @param {string} controllerUrl - Controller URL
|
|
75
|
+
* @param {string} token - Access token
|
|
76
|
+
* @param {string} refreshToken - Refresh token
|
|
77
|
+
* @param {string} expiresAt - Token expiration time
|
|
78
|
+
* @param {string} envKey - Environment key
|
|
79
|
+
* @returns {Promise<void>}
|
|
80
|
+
*/
|
|
81
|
+
async function saveTokenAndDisplaySuccess(controllerUrl, token, refreshToken, expiresAt, envKey) {
|
|
82
|
+
await saveDeviceLoginConfig(controllerUrl, token, refreshToken, expiresAt);
|
|
83
|
+
await setControllerUrl(controllerUrl);
|
|
84
|
+
if (envKey) {
|
|
85
|
+
await setCurrentEnvironment(envKey);
|
|
86
|
+
}
|
|
87
|
+
logger.log(chalk.green('\n✅ Successfully logged in!'));
|
|
88
|
+
logger.log(chalk.gray(`Controller: ${controllerUrl}`));
|
|
89
|
+
if (envKey) {
|
|
90
|
+
logger.log(chalk.gray(`Environment: ${envKey}`));
|
|
91
|
+
}
|
|
92
|
+
logger.log(chalk.gray('Token stored securely in ~/.aifabrix/config.yaml\n'));
|
|
93
|
+
}
|
|
94
|
+
|
|
71
95
|
/**
|
|
72
96
|
* Poll for device code token and save configuration
|
|
73
97
|
* @async
|
|
@@ -105,23 +129,8 @@ async function pollAndSaveDeviceCodeToken(controllerUrl, deviceCode, interval, e
|
|
|
105
129
|
const refreshToken = tokenResponse.refresh_token;
|
|
106
130
|
const expiresAt = new Date(Date.now() + (tokenResponse.expires_in * 1000)).toISOString();
|
|
107
131
|
|
|
108
|
-
|
|
109
|
-
await saveDeviceLoginConfig(controllerUrl, token, refreshToken, expiresAt);
|
|
110
|
-
|
|
111
|
-
// Still set current environment if provided (for other purposes)
|
|
112
|
-
if (envKey) {
|
|
113
|
-
await setCurrentEnvironment(envKey);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
logger.log(chalk.green('\n✅ Successfully logged in!'));
|
|
117
|
-
logger.log(chalk.gray(`Controller: ${controllerUrl}`));
|
|
118
|
-
if (envKey) {
|
|
119
|
-
logger.log(chalk.gray(`Environment: ${envKey}`));
|
|
120
|
-
}
|
|
121
|
-
logger.log(chalk.gray('Token stored securely in ~/.aifabrix/config.yaml\n'));
|
|
122
|
-
|
|
132
|
+
await saveTokenAndDisplaySuccess(controllerUrl, token, refreshToken, expiresAt, envKey);
|
|
123
133
|
return { token, environment: envKey };
|
|
124
|
-
|
|
125
134
|
} catch (pollError) {
|
|
126
135
|
spinner.fail('Authentication failed');
|
|
127
136
|
throw pollError;
|
|
@@ -130,27 +139,32 @@ async function pollAndSaveDeviceCodeToken(controllerUrl, deviceCode, interval, e
|
|
|
130
139
|
|
|
131
140
|
/**
|
|
132
141
|
* Build scope string from options
|
|
133
|
-
* @param {boolean} [
|
|
142
|
+
* @param {boolean} [online] - Whether to exclude offline_access (default: false, meaning offline tokens are default)
|
|
134
143
|
* @param {string} [customScope] - Custom scope string
|
|
135
144
|
* @returns {string} Scope string
|
|
136
145
|
*/
|
|
137
|
-
function buildScope(
|
|
146
|
+
function buildScope(online, customScope) {
|
|
138
147
|
const defaultScope = 'openid profile email';
|
|
139
148
|
|
|
140
149
|
if (customScope) {
|
|
141
|
-
// If custom scope provided, use it
|
|
142
|
-
|
|
150
|
+
// If custom scope provided, use it as-is
|
|
151
|
+
// If --online flag is used and scope contains offline_access, remove it
|
|
152
|
+
if (online && customScope.includes('offline_access')) {
|
|
153
|
+
return customScope.replace(/\s*offline_access\s*/g, ' ').trim().replace(/\s+/g, ' ');
|
|
154
|
+
}
|
|
155
|
+
// If not --online and scope doesn't have offline_access, add it (default behavior)
|
|
156
|
+
if (!online && !customScope.includes('offline_access')) {
|
|
143
157
|
return `${customScope} offline_access`;
|
|
144
158
|
}
|
|
145
159
|
return customScope;
|
|
146
160
|
}
|
|
147
161
|
|
|
148
|
-
// Default scope
|
|
149
|
-
if (
|
|
150
|
-
return
|
|
162
|
+
// Default scope: include offline_access unless --online is specified
|
|
163
|
+
if (online) {
|
|
164
|
+
return defaultScope;
|
|
151
165
|
}
|
|
152
166
|
|
|
153
|
-
return defaultScope
|
|
167
|
+
return `${defaultScope} offline_access`;
|
|
154
168
|
}
|
|
155
169
|
|
|
156
170
|
/**
|
|
@@ -200,16 +214,16 @@ function convertDeviceCodeResponse(apiResponse) {
|
|
|
200
214
|
* @async
|
|
201
215
|
* @param {string} controllerUrl - Controller URL
|
|
202
216
|
* @param {string} [environment] - Environment key from options
|
|
203
|
-
* @param {boolean} [
|
|
217
|
+
* @param {boolean} [online] - Whether to exclude offline_access scope (default: false, meaning offline tokens are default)
|
|
204
218
|
* @param {string} [scope] - Custom scope string
|
|
205
219
|
* @returns {Promise<{token: string, environment: string}>} Token and environment
|
|
206
220
|
*/
|
|
207
|
-
async function handleDeviceCodeLogin(controllerUrl, environment,
|
|
221
|
+
async function handleDeviceCodeLogin(controllerUrl, environment, online, scope) {
|
|
208
222
|
const envKey = await getEnvironmentKey(environment);
|
|
209
|
-
const requestScope = buildScope(
|
|
223
|
+
const requestScope = buildScope(online, scope);
|
|
210
224
|
|
|
211
225
|
logger.log(chalk.blue('\n📱 Initiating device code flow...\n'));
|
|
212
|
-
if (
|
|
226
|
+
if (!online && requestScope.includes('offline_access')) {
|
|
213
227
|
logger.log(chalk.gray(`Requesting offline token (scope: ${requestScope})\n`));
|
|
214
228
|
}
|
|
215
229
|
|
package/lib/commands/login.js
CHANGED
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
|
|
12
12
|
const inquirer = require('inquirer');
|
|
13
13
|
const chalk = require('chalk');
|
|
14
|
-
const { setCurrentEnvironment, saveClientToken } = require('../core/config');
|
|
14
|
+
const { setCurrentEnvironment, saveClientToken, setControllerUrl } = require('../core/config');
|
|
15
15
|
const logger = require('../utils/logger');
|
|
16
16
|
const { handleCredentialsLogin } = require('./login-credentials');
|
|
17
17
|
const { handleDeviceCodeLogin } = require('./login-device');
|
|
18
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Determine and validate authentication method
|
|
@@ -61,8 +62,8 @@ async function saveCredentialsLoginConfig(controllerUrl, token, expiresAt, envir
|
|
|
61
62
|
* @async
|
|
62
63
|
* @function handleLogin
|
|
63
64
|
* @param {Object} options - Login options
|
|
64
|
-
* @param {string} [options.controller] - Controller URL (default:
|
|
65
|
-
* @param {string} [options.method] - Authentication method ('device' or 'credentials')
|
|
65
|
+
* @param {string} [options.controller] - Controller URL (default: from config, device tokens, or developer ID)
|
|
66
|
+
* @param {string} [options.method] - Authentication method ('device' or 'credentials', default: 'device')
|
|
66
67
|
* @param {string} [options.app] - Application name (for credentials method, reads from secrets.local.yaml)
|
|
67
68
|
* @param {string} [options.clientId] - Client ID (for credentials method, overrides secrets.local.yaml)
|
|
68
69
|
* @param {string} [options.clientSecret] - Client Secret (for credentials method, overrides secrets.local.yaml)
|
|
@@ -71,13 +72,20 @@ async function saveCredentialsLoginConfig(controllerUrl, token, expiresAt, envir
|
|
|
71
72
|
* @throws {Error} If login fails
|
|
72
73
|
*/
|
|
73
74
|
/**
|
|
74
|
-
*
|
|
75
|
+
* Resolves and logs controller URL from --controller, config, device tokens, or developer-ID default
|
|
76
|
+
* @async
|
|
75
77
|
* @function normalizeControllerUrl
|
|
76
78
|
* @param {Object} options - Login options
|
|
77
|
-
* @returns {string} Normalized controller URL
|
|
79
|
+
* @returns {Promise<string>} Normalized controller URL
|
|
78
80
|
*/
|
|
79
|
-
function normalizeControllerUrl(options) {
|
|
80
|
-
|
|
81
|
+
async function normalizeControllerUrl(options) {
|
|
82
|
+
let controllerUrl = options.controller || options.url;
|
|
83
|
+
if (!controllerUrl) {
|
|
84
|
+
controllerUrl = await resolveControllerUrl();
|
|
85
|
+
}
|
|
86
|
+
controllerUrl = String(controllerUrl).replace(/\/+$/, '');
|
|
87
|
+
// Save controller URL to config
|
|
88
|
+
await setControllerUrl(controllerUrl);
|
|
81
89
|
logger.log(chalk.gray(`Controller URL: ${controllerUrl}`));
|
|
82
90
|
return controllerUrl;
|
|
83
91
|
}
|
|
@@ -108,8 +116,8 @@ async function handleEnvironmentConfig(options) {
|
|
|
108
116
|
* @param {Object} options - Login options
|
|
109
117
|
*/
|
|
110
118
|
function validateScopeOptions(method, options) {
|
|
111
|
-
if (method === 'credentials' && (options.
|
|
112
|
-
logger.log(chalk.yellow('⚠️ Warning: --
|
|
119
|
+
if (method === 'credentials' && (options.online || options.scope)) {
|
|
120
|
+
logger.log(chalk.yellow('⚠️ Warning: --online and --scope options are only available for device flow'));
|
|
113
121
|
logger.log(chalk.gray(' These options will be ignored for credentials method\n'));
|
|
114
122
|
}
|
|
115
123
|
}
|
|
@@ -137,17 +145,18 @@ async function handleCredentialsLoginFlow(controllerUrl, environment, options) {
|
|
|
137
145
|
* @async
|
|
138
146
|
* @function handleDeviceCodeLoginFlow
|
|
139
147
|
* @param {string} controllerUrl - Controller URL
|
|
148
|
+
* @param {string} environment - Resolved environment key (from config or -e/--environment)
|
|
140
149
|
* @param {Object} options - Login options
|
|
141
150
|
* @returns {Promise<{token: string, environment: string}>} Login result
|
|
142
151
|
*/
|
|
143
|
-
async function handleDeviceCodeLoginFlow(controllerUrl, options) {
|
|
144
|
-
return await handleDeviceCodeLogin(controllerUrl,
|
|
152
|
+
async function handleDeviceCodeLoginFlow(controllerUrl, environment, options) {
|
|
153
|
+
return await handleDeviceCodeLogin(controllerUrl, environment, options.online, options.scope);
|
|
145
154
|
}
|
|
146
155
|
|
|
147
156
|
async function handleLogin(options) {
|
|
148
157
|
logger.log(chalk.blue('\n🔐 Logging in to Miso Controller...\n'));
|
|
149
158
|
|
|
150
|
-
const controllerUrl = normalizeControllerUrl(options);
|
|
159
|
+
const controllerUrl = await normalizeControllerUrl(options);
|
|
151
160
|
const environment = await handleEnvironmentConfig(options);
|
|
152
161
|
const method = await determineAuthMethod(options.method);
|
|
153
162
|
|
|
@@ -156,7 +165,7 @@ async function handleLogin(options) {
|
|
|
156
165
|
if (method === 'credentials') {
|
|
157
166
|
await handleCredentialsLoginFlow(controllerUrl, environment, options);
|
|
158
167
|
} else if (method === 'device') {
|
|
159
|
-
await handleDeviceCodeLoginFlow(controllerUrl, options);
|
|
168
|
+
await handleDeviceCodeLoginFlow(controllerUrl, environment, options);
|
|
160
169
|
return; // Early return for device flow (already saved config)
|
|
161
170
|
}
|
|
162
171
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Normalize wizard-generated configs before validation
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const ENTITY_TYPE_FALLBACK = 'record-storage';
|
|
8
|
+
const VALID_ENTITY_TYPES = new Set([
|
|
9
|
+
'document-storage',
|
|
10
|
+
'documentStorage',
|
|
11
|
+
'vector-store',
|
|
12
|
+
'vectorStore',
|
|
13
|
+
'record-storage',
|
|
14
|
+
'recordStorage',
|
|
15
|
+
'message-service',
|
|
16
|
+
'messageService',
|
|
17
|
+
'none'
|
|
18
|
+
]);
|
|
19
|
+
const VALID_PORTAL_FIELDS = new Set(['text', 'textarea', 'select', 'json', 'boolean', 'number']);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Normalize system config fields to schema constraints
|
|
23
|
+
* @function normalizeSystemConfig
|
|
24
|
+
* @param {Object} systemConfig - External system config
|
|
25
|
+
* @returns {Object} Normalized config
|
|
26
|
+
*/
|
|
27
|
+
function normalizeSystemConfig(systemConfig) {
|
|
28
|
+
if (!systemConfig || typeof systemConfig !== 'object') {
|
|
29
|
+
return systemConfig;
|
|
30
|
+
}
|
|
31
|
+
if (typeof systemConfig.description === 'string' && systemConfig.description.length > 500) {
|
|
32
|
+
systemConfig.description = systemConfig.description.slice(0, 500);
|
|
33
|
+
}
|
|
34
|
+
return systemConfig;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Normalize datasource config fields to schema constraints
|
|
39
|
+
* @function normalizeDatasourceConfig
|
|
40
|
+
* @param {Object} datasourceConfig - Datasource config
|
|
41
|
+
* @returns {Object} Normalized config
|
|
42
|
+
*/
|
|
43
|
+
function normalizeDatasourceConfig(datasourceConfig) {
|
|
44
|
+
if (!datasourceConfig || typeof datasourceConfig !== 'object') {
|
|
45
|
+
return datasourceConfig;
|
|
46
|
+
}
|
|
47
|
+
if (datasourceConfig.entityType && !VALID_ENTITY_TYPES.has(datasourceConfig.entityType)) {
|
|
48
|
+
datasourceConfig.entityType = ENTITY_TYPE_FALLBACK;
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(datasourceConfig.portalInput)) {
|
|
51
|
+
datasourceConfig.portalInput = datasourceConfig.portalInput.filter(item => {
|
|
52
|
+
if (!item || typeof item !== 'object') {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (!item.name || !item.field || !item.label) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return VALID_PORTAL_FIELDS.has(item.field);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (datasourceConfig.execution?.cip?.operations) {
|
|
62
|
+
for (const operation of Object.values(datasourceConfig.execution.cip.operations)) {
|
|
63
|
+
if (!operation || !Array.isArray(operation.steps)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
for (const step of operation.steps) {
|
|
67
|
+
if (step?.output?.mode && step.output.mode !== 'records') {
|
|
68
|
+
step.output.mode = 'records';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return datasourceConfig;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Normalize system and datasource configs
|
|
78
|
+
* @function normalizeWizardConfigs
|
|
79
|
+
* @param {Object} systemConfig - System config
|
|
80
|
+
* @param {Object|Object[]} datasourceConfigs - Datasource config(s)
|
|
81
|
+
* @returns {{ systemConfig: Object, datasourceConfigs: Object[] }} Normalized configs
|
|
82
|
+
*/
|
|
83
|
+
function normalizeWizardConfigs(systemConfig, datasourceConfigs) {
|
|
84
|
+
const normalizedSystem = normalizeSystemConfig(systemConfig);
|
|
85
|
+
const configs = Array.isArray(datasourceConfigs) ? datasourceConfigs : [datasourceConfigs];
|
|
86
|
+
const normalizedDatasources = configs.map(normalizeDatasourceConfig);
|
|
87
|
+
return { systemConfig: normalizedSystem, datasourceConfigs: normalizedDatasources };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = {
|
|
91
|
+
normalizeWizardConfigs
|
|
92
|
+
};
|