@aifabrix/builder 2.21.1 → 2.22.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/lib/app-list.js +60 -22
- package/lib/app-register.js +3 -2
- package/lib/app-rotate-secret.js +86 -48
- package/lib/commands/app.js +3 -0
- package/lib/config.js +40 -173
- package/lib/external-system-generator.js +3 -1
- package/lib/generator-external.js +229 -0
- package/lib/generator-helpers.js +205 -0
- package/lib/generator.js +2 -367
- package/lib/schema/external-system.schema.json +92 -1
- package/lib/utils/api-error-handler.js +9 -2
- package/lib/utils/app-register-api.js +39 -29
- package/lib/utils/app-register-auth.js +103 -39
- package/lib/utils/config-paths.js +112 -0
- package/lib/utils/config-tokens.js +233 -0
- package/lib/utils/device-code.js +28 -6
- package/lib/utils/error-formatters/http-status-errors.js +78 -5
- package/lib/utils/error-formatters/network-errors.js +24 -4
- package/lib/validate.js +67 -7
- package/lib/validator.js +3 -1
- package/package.json +1 -1
- package/templates/external-system/external-system.json.hbs +20 -1
package/lib/app-list.js
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const chalk = require('chalk');
|
|
12
|
-
const { getConfig } = require('./config');
|
|
12
|
+
const { getConfig, normalizeControllerUrl } = require('./config');
|
|
13
13
|
const { getOrRefreshDeviceToken } = require('./utils/token-manager');
|
|
14
14
|
const { listEnvironmentApplications } = require('./api/environments.api');
|
|
15
15
|
const { formatApiError } = require('./utils/api-error-handler');
|
|
16
|
+
const { formatAuthenticationError } = require('./utils/error-formatters/http-status-errors');
|
|
16
17
|
const logger = require('./utils/logger');
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -82,48 +83,85 @@ function displayApplications(applications, environment) {
|
|
|
82
83
|
* @async
|
|
83
84
|
* @param {Object} options - Command options
|
|
84
85
|
* @param {string} options.environment - Environment ID or key
|
|
86
|
+
* @param {string} [options.controller] - Controller URL (optional, uses configured controller if not provided)
|
|
85
87
|
* @throws {Error} If listing fails
|
|
86
88
|
*/
|
|
87
89
|
async function listApplications(options) {
|
|
88
90
|
const config = await getConfig();
|
|
89
91
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
+
// Get controller URL with priority: options.controller > device tokens
|
|
93
|
+
const controllerUrl = options.controller || null;
|
|
92
94
|
let token = null;
|
|
95
|
+
let actualControllerUrl = null;
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const deviceToken = await getOrRefreshDeviceToken(
|
|
97
|
+
// If controller URL provided, try to get device token
|
|
98
|
+
if (controllerUrl) {
|
|
99
|
+
try {
|
|
100
|
+
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
101
|
+
const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
|
|
99
102
|
if (deviceToken && deviceToken.token) {
|
|
100
103
|
token = deviceToken.token;
|
|
101
|
-
|
|
104
|
+
actualControllerUrl = deviceToken.controller || normalizedUrl;
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Show which controller URL failed
|
|
108
|
+
logger.error(chalk.red(`❌ Failed to authenticate with controller: ${controllerUrl}`));
|
|
109
|
+
logger.error(chalk.gray(`Error: ${error.message}`));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// If no token yet, try to find any device token in config
|
|
115
|
+
if (!token && config.device) {
|
|
116
|
+
const deviceUrls = Object.keys(config.device);
|
|
117
|
+
if (deviceUrls.length > 0) {
|
|
118
|
+
for (const storedUrl of deviceUrls) {
|
|
119
|
+
try {
|
|
120
|
+
const normalizedStoredUrl = normalizeControllerUrl(storedUrl);
|
|
121
|
+
const deviceToken = await getOrRefreshDeviceToken(normalizedStoredUrl);
|
|
122
|
+
if (deviceToken && deviceToken.token) {
|
|
123
|
+
token = deviceToken.token;
|
|
124
|
+
actualControllerUrl = deviceToken.controller || normalizedStoredUrl;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
// Continue to next URL
|
|
129
|
+
}
|
|
102
130
|
}
|
|
103
131
|
}
|
|
104
132
|
}
|
|
105
133
|
|
|
106
|
-
if (!token || !
|
|
107
|
-
|
|
108
|
-
|
|
134
|
+
if (!token || !actualControllerUrl) {
|
|
135
|
+
const formattedError = formatAuthenticationError({
|
|
136
|
+
controllerUrl: controllerUrl || undefined,
|
|
137
|
+
message: 'No valid authentication found'
|
|
138
|
+
});
|
|
139
|
+
logger.error(formattedError);
|
|
109
140
|
process.exit(1);
|
|
110
141
|
}
|
|
111
142
|
|
|
112
143
|
// Use centralized API client
|
|
113
144
|
const authConfig = { type: 'bearer', token: token };
|
|
114
|
-
|
|
145
|
+
try {
|
|
146
|
+
const response = await listEnvironmentApplications(actualControllerUrl, options.environment, authConfig);
|
|
147
|
+
|
|
148
|
+
if (!response.success || !response.data) {
|
|
149
|
+
const formattedError = response.formattedError || formatApiError(response, actualControllerUrl);
|
|
150
|
+
logger.error(formattedError);
|
|
151
|
+
logger.error(chalk.gray(`\nController URL: ${actualControllerUrl}`));
|
|
152
|
+
// Log full response for debugging
|
|
153
|
+
logger.error(chalk.gray('\nFull response for debugging:'));
|
|
154
|
+
logger.error(chalk.gray(JSON.stringify(response, null, 2)));
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
115
157
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
logger.error(chalk.gray(
|
|
121
|
-
logger.error(chalk.gray(JSON.stringify(response, null, 2)));
|
|
158
|
+
const applications = extractApplications(response);
|
|
159
|
+
displayApplications(applications, options.environment);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
logger.error(chalk.red(`❌ Failed to list applications from controller: ${actualControllerUrl}`));
|
|
162
|
+
logger.error(chalk.gray(`Error: ${error.message}`));
|
|
122
163
|
process.exit(1);
|
|
123
164
|
}
|
|
124
|
-
|
|
125
|
-
const applications = extractApplications(response);
|
|
126
|
-
displayApplications(applications, options.environment);
|
|
127
165
|
}
|
|
128
166
|
|
|
129
167
|
module.exports = { listApplications };
|
package/lib/app-register.js
CHANGED
|
@@ -108,6 +108,7 @@ async function saveLocalCredentials(responseData, apiUrl) {
|
|
|
108
108
|
* @param {string} appKey - Application key
|
|
109
109
|
* @param {Object} options - Registration options
|
|
110
110
|
* @param {string} options.environment - Environment ID or key
|
|
111
|
+
* @param {string} [options.controller] - Controller URL (overrides variables.yaml)
|
|
111
112
|
* @param {number} [options.port] - Application port
|
|
112
113
|
* @param {string} [options.name] - Override display name
|
|
113
114
|
* @param {string} [options.description] - Override description
|
|
@@ -126,8 +127,8 @@ async function registerApplication(appKey, options) {
|
|
|
126
127
|
const appConfig = await extractAppConfiguration(finalVariables, appKey, options);
|
|
127
128
|
await validateAppRegistrationData(appConfig, appKey);
|
|
128
129
|
|
|
129
|
-
//
|
|
130
|
-
const controllerUrl = finalVariables?.deployment?.controllerUrl;
|
|
130
|
+
// Get controller URL with priority: options.controller > variables.yaml > device tokens
|
|
131
|
+
const controllerUrl = options.controller || finalVariables?.deployment?.controllerUrl;
|
|
131
132
|
const authConfig = await checkAuthentication(controllerUrl, options.environment);
|
|
132
133
|
const environment = registerApplicationSchema.environmentId(options.environment);
|
|
133
134
|
|
package/lib/app-rotate-secret.js
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const chalk = require('chalk');
|
|
12
|
-
const { getConfig } = require('./config');
|
|
12
|
+
const { getConfig, normalizeControllerUrl } = require('./config');
|
|
13
13
|
const { getOrRefreshDeviceToken } = require('./utils/token-manager');
|
|
14
14
|
const { rotateApplicationSecret } = require('./api/applications.api');
|
|
15
15
|
const { formatApiError } = require('./utils/api-error-handler');
|
|
16
|
+
const { formatAuthenticationError } = require('./utils/error-formatters/http-status-errors');
|
|
16
17
|
const logger = require('./utils/logger');
|
|
17
18
|
const { saveLocalSecret, isLocalhost } = require('./utils/local-secrets');
|
|
18
19
|
const { updateEnvTemplate } = require('./utils/env-template');
|
|
@@ -83,6 +84,7 @@ function displayRotationResults(appKey, environment, credentials, apiUrl, messag
|
|
|
83
84
|
* @param {string} appKey - Application key
|
|
84
85
|
* @param {Object} options - Command options
|
|
85
86
|
* @param {string} options.environment - Environment ID or key
|
|
87
|
+
* @param {string} [options.controller] - Controller URL (optional, uses configured controller if not provided)
|
|
86
88
|
* @throws {Error} If rotation fails
|
|
87
89
|
*/
|
|
88
90
|
async function rotateSecret(appKey, options) {
|
|
@@ -90,25 +92,54 @@ async function rotateSecret(appKey, options) {
|
|
|
90
92
|
|
|
91
93
|
const config = await getConfig();
|
|
92
94
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
+
// Get controller URL with priority: options.controller > device tokens
|
|
96
|
+
const controllerUrl = options.controller || null;
|
|
95
97
|
let token = null;
|
|
98
|
+
let actualControllerUrl = null;
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const deviceToken = await getOrRefreshDeviceToken(
|
|
100
|
+
// If controller URL provided, try to get device token
|
|
101
|
+
if (controllerUrl) {
|
|
102
|
+
try {
|
|
103
|
+
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
104
|
+
const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
|
|
102
105
|
if (deviceToken && deviceToken.token) {
|
|
103
106
|
token = deviceToken.token;
|
|
104
|
-
|
|
107
|
+
actualControllerUrl = deviceToken.controller || normalizedUrl;
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
// Show which controller URL failed
|
|
111
|
+
logger.error(chalk.red(`❌ Failed to authenticate with controller: ${controllerUrl}`));
|
|
112
|
+
logger.error(chalk.gray(`Error: ${error.message}`));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// If no token yet, try to find any device token in config
|
|
118
|
+
if (!token && config.device) {
|
|
119
|
+
const deviceUrls = Object.keys(config.device);
|
|
120
|
+
if (deviceUrls.length > 0) {
|
|
121
|
+
for (const storedUrl of deviceUrls) {
|
|
122
|
+
try {
|
|
123
|
+
const normalizedStoredUrl = normalizeControllerUrl(storedUrl);
|
|
124
|
+
const deviceToken = await getOrRefreshDeviceToken(normalizedStoredUrl);
|
|
125
|
+
if (deviceToken && deviceToken.token) {
|
|
126
|
+
token = deviceToken.token;
|
|
127
|
+
actualControllerUrl = deviceToken.controller || normalizedStoredUrl;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
// Continue to next URL
|
|
132
|
+
}
|
|
105
133
|
}
|
|
106
134
|
}
|
|
107
135
|
}
|
|
108
136
|
|
|
109
|
-
if (!token || !
|
|
110
|
-
|
|
111
|
-
|
|
137
|
+
if (!token || !actualControllerUrl) {
|
|
138
|
+
const formattedError = formatAuthenticationError({
|
|
139
|
+
controllerUrl: controllerUrl || undefined,
|
|
140
|
+
message: 'No valid authentication found'
|
|
141
|
+
});
|
|
142
|
+
logger.error(formattedError);
|
|
112
143
|
process.exit(1);
|
|
113
144
|
}
|
|
114
145
|
|
|
@@ -117,51 +148,58 @@ async function rotateSecret(appKey, options) {
|
|
|
117
148
|
|
|
118
149
|
// Use centralized API client
|
|
119
150
|
const authConfig = { type: 'bearer', token: token };
|
|
120
|
-
|
|
151
|
+
try {
|
|
152
|
+
const response = await rotateApplicationSecret(actualControllerUrl, options.environment, appKey, authConfig);
|
|
121
153
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
154
|
+
if (!response.success) {
|
|
155
|
+
const formattedError = response.formattedError || formatApiError(response, actualControllerUrl);
|
|
156
|
+
logger.error(formattedError);
|
|
157
|
+
logger.error(chalk.gray(`\nController URL: ${actualControllerUrl}`));
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
127
160
|
|
|
128
|
-
|
|
129
|
-
|
|
161
|
+
// Validate response structure
|
|
162
|
+
validateResponse(response);
|
|
130
163
|
|
|
131
|
-
|
|
132
|
-
|
|
164
|
+
const credentials = response.data.credentials;
|
|
165
|
+
const message = response.data.message;
|
|
133
166
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
167
|
+
// Save credentials to local secrets (always save when rotating)
|
|
168
|
+
const clientIdKey = `${appKey}-client-idKeyVault`;
|
|
169
|
+
const clientSecretKey = `${appKey}-client-secretKeyVault`;
|
|
137
170
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// Regenerate .env file with updated credentials
|
|
147
|
-
try {
|
|
148
|
-
await generateEnvFile(appKey, null, 'local');
|
|
149
|
-
logger.log(chalk.green('✓ .env file updated with new credentials'));
|
|
150
|
-
} catch (error) {
|
|
151
|
-
logger.warn(chalk.yellow(`⚠️ Could not regenerate .env file: ${error.message}`));
|
|
152
|
-
}
|
|
171
|
+
try {
|
|
172
|
+
await saveLocalSecret(clientIdKey, credentials.clientId);
|
|
173
|
+
await saveLocalSecret(clientSecretKey, credentials.clientSecret);
|
|
174
|
+
|
|
175
|
+
// Update env.template if localhost
|
|
176
|
+
if (isLocalhost(actualControllerUrl)) {
|
|
177
|
+
await updateEnvTemplate(appKey, clientIdKey, clientSecretKey, actualControllerUrl);
|
|
153
178
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
179
|
+
// Regenerate .env file with updated credentials
|
|
180
|
+
try {
|
|
181
|
+
await generateEnvFile(appKey, null, 'local');
|
|
182
|
+
logger.log(chalk.green('✓ .env file updated with new credentials'));
|
|
183
|
+
} catch (error) {
|
|
184
|
+
logger.warn(chalk.yellow(`⚠️ Could not regenerate .env file: ${error.message}`));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
logger.log(chalk.green('\n✓ Credentials saved to ~/.aifabrix/secrets.local.yaml'));
|
|
188
|
+
logger.log(chalk.green('✓ env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
|
|
189
|
+
} else {
|
|
190
|
+
logger.log(chalk.green('\n✓ Credentials saved to ~/.aifabrix/secrets.local.yaml\n'));
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
logger.warn(chalk.yellow(`⚠️ Could not save credentials locally: ${error.message}`));
|
|
158
194
|
}
|
|
195
|
+
|
|
196
|
+
// Display results
|
|
197
|
+
displayRotationResults(appKey, options.environment, credentials, actualControllerUrl, message);
|
|
159
198
|
} catch (error) {
|
|
160
|
-
logger.
|
|
199
|
+
logger.error(chalk.red(`❌ Failed to rotate secret via controller: ${actualControllerUrl}`));
|
|
200
|
+
logger.error(chalk.gray(`Error: ${error.message}`));
|
|
201
|
+
process.exit(1);
|
|
161
202
|
}
|
|
162
|
-
|
|
163
|
-
// Display results
|
|
164
|
-
displayRotationResults(appKey, options.environment, credentials, controllerUrl, message);
|
|
165
203
|
}
|
|
166
204
|
|
|
167
205
|
module.exports = { rotateSecret };
|
package/lib/commands/app.js
CHANGED
|
@@ -29,6 +29,7 @@ function setupAppCommands(program) {
|
|
|
29
29
|
.command('register <appKey>')
|
|
30
30
|
.description('Register application and get pipeline credentials')
|
|
31
31
|
.requiredOption('-e, --environment <env>', 'Environment ID or key')
|
|
32
|
+
.option('-c, --controller <url>', 'Controller URL (overrides variables.yaml)')
|
|
32
33
|
.option('-p, --port <port>', 'Application port (default: from variables.yaml)')
|
|
33
34
|
.option('-n, --name <name>', 'Override display name')
|
|
34
35
|
.option('-d, --description <desc>', 'Override description')
|
|
@@ -46,6 +47,7 @@ function setupAppCommands(program) {
|
|
|
46
47
|
.command('list')
|
|
47
48
|
.description('List applications')
|
|
48
49
|
.requiredOption('-e, --environment <env>', 'Environment ID or key')
|
|
50
|
+
.option('-c, --controller <url>', 'Controller URL (optional, uses configured controller if not provided)')
|
|
49
51
|
.action(async(options) => {
|
|
50
52
|
try {
|
|
51
53
|
await listApplications(options);
|
|
@@ -60,6 +62,7 @@ function setupAppCommands(program) {
|
|
|
60
62
|
.command('rotate-secret <appKey>')
|
|
61
63
|
.description('Rotate pipeline ClientSecret for an application')
|
|
62
64
|
.requiredOption('-e, --environment <env>', 'Environment ID or key')
|
|
65
|
+
.option('-c, --controller <url>', 'Controller URL (optional, uses configured controller if not provided)')
|
|
63
66
|
.action(async(appKey, options) => {
|
|
64
67
|
try {
|
|
65
68
|
await rotateSecret(appKey, options);
|
package/lib/config.js
CHANGED
|
@@ -27,6 +27,26 @@ const RUNTIME_CONFIG_FILE = path.join(RUNTIME_CONFIG_DIR, 'config.yaml');
|
|
|
27
27
|
// Cache for developer ID - loaded when getConfig() is first called
|
|
28
28
|
let cachedDeveloperId = null;
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Normalize controller URL for consistent storage and lookup
|
|
32
|
+
* Removes trailing slashes and normalizes the URL format
|
|
33
|
+
* @param {string} url - Controller URL to normalize
|
|
34
|
+
* @returns {string} Normalized controller URL
|
|
35
|
+
*/
|
|
36
|
+
function normalizeControllerUrl(url) {
|
|
37
|
+
if (!url || typeof url !== 'string') {
|
|
38
|
+
return url;
|
|
39
|
+
}
|
|
40
|
+
// Remove trailing slashes
|
|
41
|
+
let normalized = url.trim().replace(/\/+$/, '');
|
|
42
|
+
// Ensure it starts with http:// or https://
|
|
43
|
+
if (!normalized.match(/^https?:\/\//)) {
|
|
44
|
+
// If it doesn't start with protocol, assume http://
|
|
45
|
+
normalized = `http://${normalized}`;
|
|
46
|
+
}
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
49
|
+
|
|
30
50
|
async function getConfig() {
|
|
31
51
|
try {
|
|
32
52
|
const configContent = await fs.readFile(RUNTIME_CONFIG_FILE, 'utf8');
|
|
@@ -231,131 +251,7 @@ async function decryptTokenValue(value) {
|
|
|
231
251
|
return value;
|
|
232
252
|
}
|
|
233
253
|
}
|
|
234
|
-
|
|
235
|
-
* Get device token for controller
|
|
236
|
-
* @param {string} controllerUrl - Controller URL
|
|
237
|
-
* @returns {Promise<{controller: string, token: string, refreshToken: string, expiresAt: string}|null>} Device token info or null
|
|
238
|
-
*/
|
|
239
|
-
async function getDeviceToken(controllerUrl) {
|
|
240
|
-
const config = await getConfig();
|
|
241
|
-
if (!config.device || !config.device[controllerUrl]) return null;
|
|
242
|
-
const deviceToken = config.device[controllerUrl];
|
|
243
|
-
|
|
244
|
-
// Migration: If tokens are plain text and encryption key exists, encrypt them first
|
|
245
|
-
const encryptionKey = await getSecretsEncryptionKey();
|
|
246
|
-
if (encryptionKey) {
|
|
247
|
-
let needsSave = false;
|
|
248
|
-
|
|
249
|
-
if (deviceToken.token && !isTokenEncrypted(deviceToken.token)) {
|
|
250
|
-
// Token is plain text, encrypt it
|
|
251
|
-
deviceToken.token = await encryptTokenValue(deviceToken.token);
|
|
252
|
-
needsSave = true;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (deviceToken.refreshToken && !isTokenEncrypted(deviceToken.refreshToken)) {
|
|
256
|
-
// Refresh token is plain text, encrypt it
|
|
257
|
-
deviceToken.refreshToken = await encryptTokenValue(deviceToken.refreshToken);
|
|
258
|
-
needsSave = true;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (needsSave) {
|
|
262
|
-
// Save encrypted tokens back to config
|
|
263
|
-
await saveConfig(config);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
// Decrypt tokens if encrypted (for return value)
|
|
267
|
-
const token = deviceToken.token ? await decryptTokenValue(deviceToken.token) : undefined;
|
|
268
|
-
const refreshToken = deviceToken.refreshToken ? await decryptTokenValue(deviceToken.refreshToken) : null;
|
|
269
|
-
|
|
270
|
-
return {
|
|
271
|
-
controller: controllerUrl,
|
|
272
|
-
token: token,
|
|
273
|
-
refreshToken: refreshToken,
|
|
274
|
-
expiresAt: deviceToken.expiresAt
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Get client token for environment and app
|
|
280
|
-
* @param {string} environment - Environment key
|
|
281
|
-
* @param {string} appName - Application name
|
|
282
|
-
* @returns {Promise<{controller: string, token: string, expiresAt: string}|null>} Client token info or null
|
|
283
|
-
*/
|
|
284
|
-
async function getClientToken(environment, appName) {
|
|
285
|
-
const config = await getConfig();
|
|
286
|
-
if (!config.environments || !config.environments[environment]) return null;
|
|
287
|
-
if (!config.environments[environment].clients || !config.environments[environment].clients[appName]) return null;
|
|
288
|
-
|
|
289
|
-
const clientToken = config.environments[environment].clients[appName];
|
|
290
|
-
|
|
291
|
-
// Migration: If token is plain text and encryption key exists, encrypt it first
|
|
292
|
-
const encryptionKey = await getSecretsEncryptionKey();
|
|
293
|
-
if (encryptionKey && clientToken.token && !isTokenEncrypted(clientToken.token)) {
|
|
294
|
-
// Token is plain text, encrypt it
|
|
295
|
-
clientToken.token = await encryptTokenValue(clientToken.token);
|
|
296
|
-
// Save encrypted token back to config
|
|
297
|
-
await saveConfig(config);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Decrypt token if encrypted (for return value)
|
|
301
|
-
const token = await decryptTokenValue(clientToken.token);
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
controller: clientToken.controller,
|
|
305
|
-
token: token,
|
|
306
|
-
expiresAt: clientToken.expiresAt
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Save device token for controller (root level)
|
|
312
|
-
* @param {string} controllerUrl - Controller URL (used as key)
|
|
313
|
-
* @param {string} token - Device access token
|
|
314
|
-
* @param {string} refreshToken - Refresh token for token renewal
|
|
315
|
-
* @param {string} expiresAt - ISO timestamp string
|
|
316
|
-
* @returns {Promise<void>}
|
|
317
|
-
*/
|
|
318
|
-
async function saveDeviceToken(controllerUrl, token, refreshToken, expiresAt) {
|
|
319
|
-
const config = await getConfig();
|
|
320
|
-
if (!config.device) config.device = {};
|
|
321
|
-
|
|
322
|
-
// Encrypt tokens before saving
|
|
323
|
-
const encryptedToken = await encryptTokenValue(token);
|
|
324
|
-
const encryptedRefreshToken = refreshToken ? await encryptTokenValue(refreshToken) : null;
|
|
325
|
-
|
|
326
|
-
config.device[controllerUrl] = {
|
|
327
|
-
token: encryptedToken,
|
|
328
|
-
refreshToken: encryptedRefreshToken,
|
|
329
|
-
expiresAt
|
|
330
|
-
};
|
|
331
|
-
await saveConfig(config);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Save client token for environment and app
|
|
336
|
-
* @param {string} environment - Environment key
|
|
337
|
-
* @param {string} appName - Application name
|
|
338
|
-
* @param {string} controllerUrl - Controller URL
|
|
339
|
-
* @param {string} token - Client token
|
|
340
|
-
* @param {string} expiresAt - ISO timestamp string
|
|
341
|
-
* @returns {Promise<void>}
|
|
342
|
-
*/
|
|
343
|
-
async function saveClientToken(environment, appName, controllerUrl, token, expiresAt) {
|
|
344
|
-
const config = await getConfig();
|
|
345
|
-
if (!config.environments) config.environments = {};
|
|
346
|
-
if (!config.environments[environment]) config.environments[environment] = { clients: {} };
|
|
347
|
-
if (!config.environments[environment].clients) config.environments[environment].clients = {};
|
|
348
|
-
|
|
349
|
-
// Encrypt token before saving
|
|
350
|
-
const encryptedToken = await encryptTokenValue(token);
|
|
351
|
-
|
|
352
|
-
config.environments[environment].clients[appName] = {
|
|
353
|
-
controller: controllerUrl,
|
|
354
|
-
token: encryptedToken,
|
|
355
|
-
expiresAt
|
|
356
|
-
};
|
|
357
|
-
await saveConfig(config);
|
|
358
|
-
}
|
|
254
|
+
// Token management functions moved to lib/utils/config-tokens.js to reduce file size
|
|
359
255
|
|
|
360
256
|
/**
|
|
361
257
|
* Initialize and load developer ID
|
|
@@ -412,44 +308,6 @@ async function setSecretsPath(secretsPath) {
|
|
|
412
308
|
await saveConfig(config);
|
|
413
309
|
}
|
|
414
310
|
|
|
415
|
-
async function getPathConfig(key) {
|
|
416
|
-
const config = await getConfig();
|
|
417
|
-
return config[key] || null;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
async function setPathConfig(key, value, errorMsg) {
|
|
421
|
-
if (!value || typeof value !== 'string') {
|
|
422
|
-
throw new Error(errorMsg);
|
|
423
|
-
}
|
|
424
|
-
const config = await getConfig();
|
|
425
|
-
config[key] = value;
|
|
426
|
-
await saveConfig(config);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
async function getAifabrixHomeOverride() {
|
|
430
|
-
return getPathConfig('aifabrix-home');
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
async function setAifabrixHomeOverride(homePath) {
|
|
434
|
-
await setPathConfig('aifabrix-home', homePath, 'Home path is required and must be a string');
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
async function getAifabrixSecretsPath() {
|
|
438
|
-
return getPathConfig('aifabrix-secrets');
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
async function setAifabrixSecretsPath(secretsPath) {
|
|
442
|
-
await setPathConfig('aifabrix-secrets', secretsPath, 'Secrets path is required and must be a string');
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
async function getAifabrixEnvConfigPath() {
|
|
446
|
-
return getPathConfig('aifabrix-env-config');
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
async function setAifabrixEnvConfigPath(envConfigPath) {
|
|
450
|
-
await setPathConfig('aifabrix-env-config', envConfigPath, 'Env config path is required and must be a string');
|
|
451
|
-
}
|
|
452
|
-
|
|
453
311
|
// Create exports object
|
|
454
312
|
const exportsObj = {
|
|
455
313
|
getConfig,
|
|
@@ -462,22 +320,13 @@ const exportsObj = {
|
|
|
462
320
|
setCurrentEnvironment,
|
|
463
321
|
isTokenExpired,
|
|
464
322
|
shouldRefreshToken,
|
|
465
|
-
getDeviceToken,
|
|
466
|
-
getClientToken,
|
|
467
|
-
saveDeviceToken,
|
|
468
|
-
saveClientToken,
|
|
469
323
|
encryptTokenValue,
|
|
470
324
|
decryptTokenValue,
|
|
471
325
|
getSecretsEncryptionKey,
|
|
472
326
|
setSecretsEncryptionKey,
|
|
473
327
|
getSecretsPath,
|
|
474
328
|
setSecretsPath,
|
|
475
|
-
|
|
476
|
-
setAifabrixHomeOverride,
|
|
477
|
-
getAifabrixSecretsPath,
|
|
478
|
-
setAifabrixSecretsPath,
|
|
479
|
-
getAifabrixEnvConfigPath,
|
|
480
|
-
setAifabrixEnvConfigPath,
|
|
329
|
+
normalizeControllerUrl,
|
|
481
330
|
CONFIG_DIR,
|
|
482
331
|
CONFIG_FILE
|
|
483
332
|
};
|
|
@@ -492,4 +341,22 @@ Object.defineProperty(exportsObj, 'developerId', {
|
|
|
492
341
|
enumerable: true,
|
|
493
342
|
configurable: true
|
|
494
343
|
});
|
|
344
|
+
|
|
345
|
+
// Token management functions - created after dependencies are defined
|
|
346
|
+
const { createTokenManagementFunctions } = require('./utils/config-tokens');
|
|
347
|
+
const tokenFunctions = createTokenManagementFunctions(
|
|
348
|
+
getConfig,
|
|
349
|
+
saveConfig,
|
|
350
|
+
getSecretsEncryptionKey,
|
|
351
|
+
encryptTokenValue,
|
|
352
|
+
decryptTokenValue,
|
|
353
|
+
require('./utils/token-encryption').isTokenEncrypted
|
|
354
|
+
);
|
|
355
|
+
Object.assign(exportsObj, tokenFunctions);
|
|
356
|
+
|
|
357
|
+
// Path configuration functions - created after getConfig/saveConfig are defined
|
|
358
|
+
const { createPathConfigFunctions } = require('./utils/config-paths');
|
|
359
|
+
const pathConfigFunctions = createPathConfigFunctions(getConfig, saveConfig);
|
|
360
|
+
Object.assign(exportsObj, pathConfigFunctions);
|
|
361
|
+
|
|
495
362
|
module.exports = exportsObj;
|
|
@@ -40,7 +40,9 @@ async function generateExternalSystemTemplate(appPath, systemKey, config) {
|
|
|
40
40
|
systemDisplayName: config.systemDisplayName || systemKey.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
|
|
41
41
|
systemDescription: config.systemDescription || `External system integration for ${systemKey}`,
|
|
42
42
|
systemType: config.systemType || 'openapi',
|
|
43
|
-
authType: config.authType || 'apikey'
|
|
43
|
+
authType: config.authType || 'apikey',
|
|
44
|
+
roles: config.roles || null,
|
|
45
|
+
permissions: config.permissions || null
|
|
44
46
|
};
|
|
45
47
|
|
|
46
48
|
const rendered = template(context);
|