@aifabrix/builder 2.31.1 → 2.32.1
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/README.md +9 -9
- package/integration/hubspot/README.md +2 -2
- package/integration/hubspot/hubspot-deploy-company.json +17 -14
- package/integration/hubspot/hubspot-deploy-contact.json +19 -16
- package/integration/hubspot/hubspot-deploy-deal.json +21 -18
- package/lib/api/types/datasources.types.js +31 -5
- package/lib/api/types/wizard.types.js +142 -0
- package/lib/api/wizard.api.js +177 -0
- package/lib/{app-config.js → app/config.js} +4 -4
- package/lib/{app-deploy.js → app/deploy.js} +8 -8
- package/lib/app/display.js +90 -0
- package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
- package/lib/{app-down.js → app/down.js} +4 -4
- package/lib/app/helpers.js +218 -0
- package/lib/app/index.js +298 -0
- package/lib/{app-list.js → app/list.js} +6 -6
- package/lib/{app-push.js → app/push.js} +4 -4
- package/lib/{app-readme.js → app/readme.js} +34 -13
- package/lib/{app-register.js → app/register.js} +9 -9
- package/lib/{app-rotate-secret.js → app/rotate-secret.js} +10 -10
- package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
- package/lib/{app-run.js → app/run.js} +6 -6
- package/lib/{build.js → build/index.js} +59 -32
- package/lib/build/package.json +7 -0
- package/lib/cli.js +245 -179
- package/lib/commands/app.js +3 -3
- package/lib/commands/datasource.js +4 -4
- package/lib/commands/login-credentials.js +209 -0
- package/lib/commands/login-device.js +254 -0
- package/lib/commands/login.js +67 -378
- package/lib/commands/logout.js +1 -1
- package/lib/commands/secrets-set.js +1 -1
- package/lib/commands/secure.js +2 -2
- package/lib/commands/wizard.js +498 -0
- package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
- package/lib/{config.js → core/config.js} +28 -26
- package/lib/{diff.js → core/diff.js} +157 -72
- package/lib/{secrets.js → core/secrets.js} +86 -49
- package/lib/{templates.js → core/templates-env.js} +14 -222
- package/lib/core/templates.js +279 -0
- package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
- package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
- package/lib/datasource/list.js +223 -0
- package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
- package/lib/{deployer.js → deployment/deployer.js} +48 -18
- package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
- package/lib/{push.js → deployment/push.js} +1 -1
- package/lib/external-system/deploy-helpers.js +145 -0
- package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
- package/lib/external-system/download-helpers.js +114 -0
- package/lib/{external-system-download.js → external-system/download.js} +92 -135
- package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
- package/lib/external-system/test-auth.js +40 -0
- package/lib/external-system/test-execution.js +84 -0
- package/lib/external-system/test-helpers.js +109 -0
- package/lib/{external-system-test.js → external-system/test.js} +174 -192
- package/lib/{generator-builders.js → generator/builders.js} +87 -10
- package/lib/{generator-external.js → generator/external.js} +115 -52
- package/lib/{github-generator.js → generator/github.js} +116 -15
- package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
- package/lib/{generator.js → generator/index.js} +49 -22
- package/lib/{generator-split.js → generator/split.js} +108 -55
- package/lib/generator/wizard-prompts.js +357 -0
- package/lib/generator/wizard.js +490 -0
- package/lib/{infra.js → infrastructure/index.js} +49 -22
- package/lib/schema/external-datasource.schema.json +145 -133
- package/lib/schema/external-system.schema.json +42 -0
- package/lib/utils/api.js +9 -5
- package/lib/utils/app-register-api.js +60 -32
- package/lib/utils/app-register-auth.js +172 -47
- package/lib/utils/app-register-config.js +130 -59
- package/lib/utils/app-run-containers.js +29 -8
- package/lib/utils/build-helpers.js +1 -1
- package/lib/utils/cli-utils.js +78 -30
- package/lib/utils/compose-generator.js +145 -65
- package/lib/utils/config-paths.js +2 -0
- package/lib/utils/deployment-errors.js +1 -1
- package/lib/utils/device-code.js +99 -41
- package/lib/utils/env-config-loader.js +1 -1
- package/lib/utils/env-copy.js +21 -18
- package/lib/utils/env-endpoints.js +115 -67
- package/lib/utils/env-map.js +13 -14
- package/lib/utils/env-ports.js +45 -25
- package/lib/utils/env-template.js +84 -42
- package/lib/utils/error-formatter.js +26 -9
- package/lib/utils/error-formatters/error-parser.js +90 -4
- package/lib/utils/error-formatters/http-status-errors.js +54 -17
- package/lib/utils/error-formatters/network-errors.js +103 -26
- package/lib/utils/external-system-display.js +184 -90
- package/lib/utils/external-system-validators.js +164 -42
- package/lib/utils/file-upload.js +109 -0
- package/lib/utils/health-check.js +199 -83
- package/lib/utils/infra-containers.js +1 -1
- package/lib/utils/infra-status.js +66 -15
- package/lib/utils/local-secrets.js +45 -25
- package/lib/utils/paths.js +45 -33
- package/lib/utils/schema-loader.js +42 -25
- package/lib/utils/schema-resolver.js +123 -74
- package/lib/utils/secrets-encryption.js +62 -25
- package/lib/utils/secrets-helpers.js +126 -63
- package/lib/utils/secrets-path.js +1 -1
- package/lib/utils/secrets-url.js +1 -1
- package/lib/utils/token-manager-refresh.js +181 -0
- package/lib/utils/token-manager.js +76 -123
- package/lib/utils/variable-transformer.js +154 -77
- package/lib/utils/yaml-preserve.js +41 -47
- package/lib/{template-validator.js → validation/template.js} +54 -23
- package/lib/{validate.js → validation/validate.js} +205 -125
- package/lib/{validator.js → validation/validator.js} +58 -39
- package/package.json +31 -2
- package/templates/external-system/deploy.ps1.hbs +34 -0
- package/templates/external-system/deploy.sh.hbs +34 -0
- package/templates/external-system/external-datasource.json.hbs +31 -12
- package/lib/app.js +0 -467
- package/lib/datasource-list.js +0 -141
- /package/lib/{app-prompts.js → app/prompts.js} +0 -0
- /package/lib/{env-reader.js → core/env-reader.js} +0 -0
- /package/lib/{key-generator.js → core/key-generator.js} +0 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const yaml = require('js-yaml');
|
|
14
|
-
const config = require('../config');
|
|
14
|
+
const config = require('../core/config');
|
|
15
15
|
const { buildHostnameToServiceMap, resolveUrlPort } = require('./secrets-utils');
|
|
16
16
|
const { rewriteInfraEndpoints, getEnvHosts, getServicePort, getServiceHost, getLocalhostOverride } = require('./env-endpoints');
|
|
17
17
|
const { loadEnvConfig } = require('./env-config-loader');
|
|
@@ -130,6 +130,65 @@ function loadEnvTemplate(templatePath) {
|
|
|
130
130
|
return fs.readFileSync(templatePath, 'utf8');
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Gets port from local environment config
|
|
135
|
+
* @function getPortFromLocalEnv
|
|
136
|
+
* @param {Object} localEnv - Local environment config
|
|
137
|
+
* @returns {number|null} Port value or null
|
|
138
|
+
*/
|
|
139
|
+
function getPortFromLocalEnv(localEnv) {
|
|
140
|
+
if (localEnv.PORT === undefined || localEnv.PORT === null) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const portVal = typeof localEnv.PORT === 'number' ? localEnv.PORT : parseInt(localEnv.PORT, 10);
|
|
144
|
+
return Number.isNaN(portVal) ? null : portVal;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Gets port from variables.yaml file
|
|
149
|
+
* @function getPortFromVariablesFile
|
|
150
|
+
* @param {string} variablesPath - Path to variables.yaml
|
|
151
|
+
* @returns {number|null} Port value or null
|
|
152
|
+
*/
|
|
153
|
+
function getPortFromVariablesFile(variablesPath) {
|
|
154
|
+
if (!variablesPath || !fs.existsSync(variablesPath)) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const variablesContent = fs.readFileSync(variablesPath, 'utf8');
|
|
159
|
+
const variables = yaml.load(variablesContent);
|
|
160
|
+
const localPort = variables?.build?.localPort;
|
|
161
|
+
if (typeof localPort === 'number' && localPort > 0) {
|
|
162
|
+
return localPort;
|
|
163
|
+
}
|
|
164
|
+
return variables?.port || null;
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Gets port from environment content fallback
|
|
172
|
+
* @function getPortFromEnvContent
|
|
173
|
+
* @param {string} envContent - Environment content
|
|
174
|
+
* @returns {number} Port value (defaults to 3000)
|
|
175
|
+
*/
|
|
176
|
+
function getPortFromEnvContent(envContent) {
|
|
177
|
+
const portMatch = envContent.match(/^PORT\s*=\s*(\d+)/m);
|
|
178
|
+
return portMatch ? parseInt(portMatch[1], 10) : 3000;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Applies developer-id adjustment to port
|
|
183
|
+
* @function applyDeveloperIdAdjustment
|
|
184
|
+
* @param {number} baseAppPort - Base application port
|
|
185
|
+
* @param {number} devIdNum - Developer ID number
|
|
186
|
+
* @returns {number} Adjusted port
|
|
187
|
+
*/
|
|
188
|
+
function applyDeveloperIdAdjustment(baseAppPort, devIdNum) {
|
|
189
|
+
return devIdNum === 0 ? baseAppPort : (baseAppPort + (devIdNum * 100));
|
|
190
|
+
}
|
|
191
|
+
|
|
133
192
|
/**
|
|
134
193
|
* Calculate application port following override chain and developer-id adjustment
|
|
135
194
|
* Override chain: env-config.yaml → config.yaml → variables.yaml build.localPort → variables.yaml port
|
|
@@ -143,43 +202,21 @@ function loadEnvTemplate(templatePath) {
|
|
|
143
202
|
*/
|
|
144
203
|
async function calculateAppPort(variablesPath, localEnv, envContent, devIdNum) {
|
|
145
204
|
// Start with env-config value
|
|
146
|
-
let baseAppPort =
|
|
147
|
-
if (localEnv.PORT !== undefined && localEnv.PORT !== null) {
|
|
148
|
-
const portVal = typeof localEnv.PORT === 'number' ? localEnv.PORT : parseInt(localEnv.PORT, 10);
|
|
149
|
-
if (!Number.isNaN(portVal)) {
|
|
150
|
-
baseAppPort = portVal;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
205
|
+
let baseAppPort = getPortFromLocalEnv(localEnv);
|
|
153
206
|
|
|
154
207
|
// Override with variables.yaml → build.localPort (strongest)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// Fallback to variables.yaml → port
|
|
164
|
-
baseAppPort = variables?.port || 3000;
|
|
165
|
-
}
|
|
166
|
-
} catch {
|
|
167
|
-
// Fallback to reading from env content if variables.yaml read fails
|
|
168
|
-
if (baseAppPort === null || baseAppPort === undefined) {
|
|
169
|
-
const portMatch = envContent.match(/^PORT\s*=\s*(\d+)/m);
|
|
170
|
-
baseAppPort = portMatch ? parseInt(portMatch[1], 10) : 3000;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
} else {
|
|
174
|
-
// Fallback if variablesPath not provided
|
|
175
|
-
if (baseAppPort === null || baseAppPort === undefined) {
|
|
176
|
-
const portMatch = envContent.match(/^PORT\s*=\s*(\d+)/m);
|
|
177
|
-
baseAppPort = portMatch ? parseInt(portMatch[1], 10) : 3000;
|
|
178
|
-
}
|
|
208
|
+
const variablesPort = getPortFromVariablesFile(variablesPath);
|
|
209
|
+
if (variablesPort !== null) {
|
|
210
|
+
baseAppPort = variablesPort;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Fallback to env content if still no port
|
|
214
|
+
if (baseAppPort === null || baseAppPort === undefined) {
|
|
215
|
+
baseAppPort = getPortFromEnvContent(envContent);
|
|
179
216
|
}
|
|
180
217
|
|
|
181
218
|
// Apply developer-id adjustment
|
|
182
|
-
return
|
|
219
|
+
return applyDeveloperIdAdjustment(baseAppPort, devIdNum);
|
|
183
220
|
}
|
|
184
221
|
|
|
185
222
|
/**
|
|
@@ -211,21 +248,32 @@ function updateLocalhostUrls(content, baseAppPort, appPort) {
|
|
|
211
248
|
* @param {string} [variablesPath] - Path to variables.yaml (to read build.localPort)
|
|
212
249
|
* @returns {Promise<string>} Updated content with local ports
|
|
213
250
|
*/
|
|
214
|
-
|
|
215
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Gets developer ID number
|
|
253
|
+
* @async
|
|
254
|
+
* @function getDeveloperIdNumber
|
|
255
|
+
* @returns {Promise<number>} Developer ID number
|
|
256
|
+
*/
|
|
257
|
+
async function getDeveloperIdNumber() {
|
|
216
258
|
const devId = await config.getDeveloperId();
|
|
217
|
-
let devIdNum = 0;
|
|
218
259
|
if (devId !== null && devId !== undefined) {
|
|
219
260
|
const parsed = parseInt(devId, 10);
|
|
220
261
|
if (!Number.isNaN(parsed)) {
|
|
221
|
-
|
|
262
|
+
return parsed;
|
|
222
263
|
}
|
|
223
264
|
}
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
224
267
|
|
|
225
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Gets local environment configuration with overrides
|
|
270
|
+
* @async
|
|
271
|
+
* @function getLocalEnvWithOverrides
|
|
272
|
+
* @returns {Promise<Object>} Local environment configuration
|
|
273
|
+
*/
|
|
274
|
+
async function getLocalEnvWithOverrides() {
|
|
226
275
|
let localEnv = await getEnvHosts('local');
|
|
227
276
|
|
|
228
|
-
// Apply config.yaml → environments.local override (if exists)
|
|
229
277
|
try {
|
|
230
278
|
const os = require('os');
|
|
231
279
|
const cfgPath = path.join(os.homedir(), '.aifabrix', 'config.yaml');
|
|
@@ -237,33 +285,34 @@ async function adjustLocalEnvPortsInContent(envContent, variablesPath) {
|
|
|
237
285
|
}
|
|
238
286
|
}
|
|
239
287
|
} catch {
|
|
240
|
-
// Ignore config.yaml read errors
|
|
288
|
+
// Ignore config.yaml read errors
|
|
241
289
|
}
|
|
242
290
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// Calculate final port with developer-id adjustment
|
|
246
|
-
const appPort = await calculateAppPort(variablesPath, localEnv, envContent, devIdNum);
|
|
247
|
-
|
|
248
|
-
// Update .env content - only handle PORT variable
|
|
249
|
-
// Other port variables (DB_PORT, REDIS_PORT, etc.) are handled by interpolation
|
|
250
|
-
let updated = envContent;
|
|
291
|
+
return localEnv;
|
|
292
|
+
}
|
|
251
293
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
294
|
+
/**
|
|
295
|
+
* Updates PORT variable in content
|
|
296
|
+
* @function updatePortVariable
|
|
297
|
+
* @param {string} envContent - Environment content
|
|
298
|
+
* @param {number} appPort - Application port
|
|
299
|
+
* @returns {string} Updated content
|
|
300
|
+
*/
|
|
301
|
+
function updatePortVariable(envContent, appPort) {
|
|
302
|
+
if (/^PORT\s*=.*$/m.test(envContent)) {
|
|
303
|
+
return envContent.replace(/^PORT\s*=\s*.*$/m, `PORT=${appPort}`);
|
|
257
304
|
}
|
|
305
|
+
return `${envContent}\nPORT=${appPort}\n`;
|
|
306
|
+
}
|
|
258
307
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
308
|
+
/**
|
|
309
|
+
* Builds environment variables for interpolation
|
|
310
|
+
* @async
|
|
311
|
+
* @function buildEnvVarsForInterpolation
|
|
312
|
+
* @param {number} devIdNum - Developer ID number
|
|
313
|
+
* @returns {Promise<Object>} Environment variables object
|
|
314
|
+
*/
|
|
315
|
+
async function buildEnvVarsForInterpolation(devIdNum) {
|
|
267
316
|
const hostsForPorts = await getEnvHosts('local');
|
|
268
317
|
const redisPort = await getServicePort('REDIS_PORT', 'redis', hostsForPorts, 'local');
|
|
269
318
|
const dbPort = await getServicePort('DB_PORT', 'postgres', hostsForPorts, 'local');
|
|
@@ -271,13 +320,27 @@ async function adjustLocalEnvPortsInContent(envContent, variablesPath) {
|
|
|
271
320
|
const redisHost = getServiceHost(hostsForPorts.REDIS_HOST, 'local', 'localhost', localhostOverride);
|
|
272
321
|
const dbHost = getServiceHost(hostsForPorts.DB_HOST, 'local', 'localhost', localhostOverride);
|
|
273
322
|
|
|
274
|
-
// Build envVars map and ensure it has the correct values
|
|
275
323
|
const envVars = await buildEnvVarMap('local', null, devIdNum);
|
|
276
|
-
// Override with the actual values that were just set by rewriteInfraEndpoints
|
|
277
324
|
envVars.REDIS_HOST = redisHost;
|
|
278
325
|
envVars.REDIS_PORT = String(redisPort);
|
|
279
326
|
envVars.DB_HOST = dbHost;
|
|
280
327
|
envVars.DB_PORT = String(dbPort);
|
|
328
|
+
|
|
329
|
+
return envVars;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function adjustLocalEnvPortsInContent(envContent, variablesPath) {
|
|
333
|
+
const devIdNum = await getDeveloperIdNumber();
|
|
334
|
+
const localEnv = await getLocalEnvWithOverrides();
|
|
335
|
+
|
|
336
|
+
const baseAppPort = await calculateAppPort(variablesPath, localEnv, envContent, 0);
|
|
337
|
+
const appPort = await calculateAppPort(variablesPath, localEnv, envContent, devIdNum);
|
|
338
|
+
|
|
339
|
+
let updated = updatePortVariable(envContent, appPort);
|
|
340
|
+
updated = updateLocalhostUrls(updated, baseAppPort, appPort);
|
|
341
|
+
updated = await rewriteInfraEndpoints(updated, 'local');
|
|
342
|
+
|
|
343
|
+
const envVars = await buildEnvVarsForInterpolation(devIdNum);
|
|
281
344
|
updated = interpolateEnvVars(updated, envVars);
|
|
282
345
|
|
|
283
346
|
return updated;
|
package/lib/utils/secrets-url.js
CHANGED
|
@@ -26,7 +26,7 @@ async function resolveServicePortsInEnvContent(envContent, environment) {
|
|
|
26
26
|
const envConfig = await loadEnvConfig();
|
|
27
27
|
const dockerHosts = envConfig.environments.docker || {};
|
|
28
28
|
const hostnameToService = buildHostnameToServiceMap(dockerHosts);
|
|
29
|
-
const urlPattern = /(https?:\/\/)([a-zA-Z0-9
|
|
29
|
+
const urlPattern = /(https?:\/\/)([a-zA-Z0-9.-]+):(\d+)([^\s\n]*)?/g;
|
|
30
30
|
return envContent.replace(urlPattern, (match, protocol, hostname, port, urlPath = '') => {
|
|
31
31
|
return resolveUrlPort(protocol, hostname, port, urlPath || '', hostnameToService);
|
|
32
32
|
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Manager Refresh Utilities
|
|
3
|
+
*
|
|
4
|
+
* Token refresh functions for device and client tokens
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Token refresh utilities for AI Fabrix Builder
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const config = require('../core/config');
|
|
12
|
+
const { refreshDeviceToken: apiRefreshDeviceToken } = require('./api');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Validates refresh token parameters
|
|
16
|
+
* @function validateRefreshTokenParams
|
|
17
|
+
* @param {string} environment - Environment key
|
|
18
|
+
* @param {string} appName - Application name
|
|
19
|
+
* @param {string} controllerUrl - Controller URL
|
|
20
|
+
* @throws {Error} If validation fails
|
|
21
|
+
*/
|
|
22
|
+
function validateRefreshTokenParams(environment, appName, controllerUrl) {
|
|
23
|
+
if (!environment || typeof environment !== 'string') {
|
|
24
|
+
throw new Error('Environment is required and must be a string');
|
|
25
|
+
}
|
|
26
|
+
if (!appName || typeof appName !== 'string') {
|
|
27
|
+
throw new Error('App name is required and must be a string');
|
|
28
|
+
}
|
|
29
|
+
if (!controllerUrl || typeof controllerUrl !== 'string') {
|
|
30
|
+
throw new Error('Controller URL is required and must be a string');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Loads client credentials from parameters or secrets file
|
|
36
|
+
* @async
|
|
37
|
+
* @function loadClientCredentialsForRefresh
|
|
38
|
+
* @param {string} appName - Application name
|
|
39
|
+
* @param {string} [clientId] - Optional client ID
|
|
40
|
+
* @param {string} [clientSecret] - Optional client secret
|
|
41
|
+
* @returns {Promise<Object>} Credentials object with clientId and clientSecret
|
|
42
|
+
* @throws {Error} If credentials cannot be loaded
|
|
43
|
+
*/
|
|
44
|
+
async function loadClientCredentialsForRefresh(appName, clientId, clientSecret) {
|
|
45
|
+
const { loadClientCredentials } = require('./token-manager');
|
|
46
|
+
if (clientId && clientSecret) {
|
|
47
|
+
return { clientId, clientSecret };
|
|
48
|
+
}
|
|
49
|
+
const credentials = await loadClientCredentials(appName);
|
|
50
|
+
if (!credentials) {
|
|
51
|
+
throw new Error(`Client credentials not found for app '${appName}'. Add them to ~/.aifabrix/secrets.local.yaml as '${appName}-client-idKeyVault' and '${appName}-client-secretKeyVault'`);
|
|
52
|
+
}
|
|
53
|
+
return credentials;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Calls token API to get new token
|
|
58
|
+
* @async
|
|
59
|
+
* @function callTokenApi
|
|
60
|
+
* @param {string} controllerUrl - Controller URL
|
|
61
|
+
* @param {Object} credentials - Credentials object
|
|
62
|
+
* @returns {Promise<Object>} API response
|
|
63
|
+
* @throws {Error} If API call fails
|
|
64
|
+
*/
|
|
65
|
+
async function callTokenApi(controllerUrl, credentials) {
|
|
66
|
+
const { makeApiCall: _makeApiCall } = require('./api');
|
|
67
|
+
const response = await _makeApiCall(`${controllerUrl}/api/v1/auth/token`, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
'x-client-id': credentials.clientId,
|
|
72
|
+
'x-client-secret': credentials.clientSecret
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!response.success) {
|
|
77
|
+
throw new Error(`Failed to refresh token: ${response.error || 'Unknown error'}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const responseData = response.data;
|
|
81
|
+
if (!responseData || !responseData.token) {
|
|
82
|
+
throw new Error('Invalid response: missing token');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return responseData;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Calculates token expiration timestamp
|
|
90
|
+
* @function calculateTokenExpiration
|
|
91
|
+
* @param {Object} responseData - API response data
|
|
92
|
+
* @returns {string} ISO timestamp of expiration
|
|
93
|
+
*/
|
|
94
|
+
function calculateTokenExpiration(responseData) {
|
|
95
|
+
const expiresIn = responseData.expiresIn || 86400;
|
|
96
|
+
return responseData.expiresAt || new Date(Date.now() + expiresIn * 1000).toISOString();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Refresh client token using credentials
|
|
101
|
+
* @async
|
|
102
|
+
* @function refreshClientToken
|
|
103
|
+
* @param {string} environment - Environment key
|
|
104
|
+
* @param {string} appName - Application name
|
|
105
|
+
* @param {string} controllerUrl - Controller URL
|
|
106
|
+
* @param {string} [clientId] - Optional client ID override
|
|
107
|
+
* @param {string} [clientSecret] - Optional client secret override
|
|
108
|
+
* @returns {Promise<{token: string, expiresAt: string}>} New token info
|
|
109
|
+
* @throws {Error} If credentials are missing or token refresh fails
|
|
110
|
+
*/
|
|
111
|
+
async function refreshClientToken(environment, appName, controllerUrl, clientId, clientSecret) {
|
|
112
|
+
validateRefreshTokenParams(environment, appName, controllerUrl);
|
|
113
|
+
|
|
114
|
+
const credentials = await loadClientCredentialsForRefresh(appName, clientId, clientSecret);
|
|
115
|
+
const responseData = await callTokenApi(controllerUrl, credentials);
|
|
116
|
+
|
|
117
|
+
const token = responseData.token;
|
|
118
|
+
const expiresAt = calculateTokenExpiration(responseData);
|
|
119
|
+
|
|
120
|
+
// Save token to config.yaml (NEVER save credentials)
|
|
121
|
+
await config.saveClientToken(environment, appName, controllerUrl, token, expiresAt);
|
|
122
|
+
|
|
123
|
+
return { token, expiresAt };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Refresh device token using refresh token
|
|
128
|
+
* Calls API refresh endpoint and saves new token to config
|
|
129
|
+
* @param {string} controllerUrl - Controller URL
|
|
130
|
+
* @param {string} refreshToken - Refresh token
|
|
131
|
+
* @returns {Promise<{token: string, refreshToken: string, expiresAt: string}>} New token info
|
|
132
|
+
* @throws {Error} If refresh fails or refresh token is expired/invalid
|
|
133
|
+
*/
|
|
134
|
+
async function refreshDeviceToken(controllerUrl, refreshToken) {
|
|
135
|
+
if (!controllerUrl || typeof controllerUrl !== 'string') {
|
|
136
|
+
throw new Error('Controller URL is required');
|
|
137
|
+
}
|
|
138
|
+
if (!refreshToken || typeof refreshToken !== 'string') {
|
|
139
|
+
throw new Error('Refresh token is required');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
// Call API refresh endpoint
|
|
144
|
+
const tokenResponse = await apiRefreshDeviceToken(controllerUrl, refreshToken);
|
|
145
|
+
|
|
146
|
+
const token = tokenResponse.access_token;
|
|
147
|
+
const newRefreshToken = tokenResponse.refresh_token || refreshToken; // Use new refresh token if provided, otherwise keep old one
|
|
148
|
+
const expiresIn = tokenResponse.expires_in || 3600;
|
|
149
|
+
const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();
|
|
150
|
+
|
|
151
|
+
// Save new token and refresh token to config
|
|
152
|
+
await config.saveDeviceToken(controllerUrl, token, newRefreshToken, expiresAt);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
token,
|
|
156
|
+
refreshToken: newRefreshToken,
|
|
157
|
+
expiresAt
|
|
158
|
+
};
|
|
159
|
+
} catch (error) {
|
|
160
|
+
// Check if error indicates refresh token expiry (case-insensitive)
|
|
161
|
+
const errorMessage = (error.message || String(error)).toLowerCase();
|
|
162
|
+
if (errorMessage.includes('expired') ||
|
|
163
|
+
errorMessage.includes('invalid') ||
|
|
164
|
+
errorMessage.includes('401') ||
|
|
165
|
+
errorMessage.includes('unauthorized')) {
|
|
166
|
+
throw new Error('Refresh token has expired. Please login again using: aifabrix login');
|
|
167
|
+
}
|
|
168
|
+
// Re-throw other errors as-is
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
refreshClientToken,
|
|
175
|
+
refreshDeviceToken,
|
|
176
|
+
validateRefreshTokenParams,
|
|
177
|
+
loadClientCredentialsForRefresh,
|
|
178
|
+
callTokenApi,
|
|
179
|
+
calculateTokenExpiration
|
|
180
|
+
};
|
|
181
|
+
|