@aifabrix/builder 2.40.0 → 2.41.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/README.md +7 -5
- package/integration/hubspot/test.js +1 -1
- package/jest.config.manual.js +29 -0
- package/lib/api/credential.api.js +40 -0
- package/lib/api/dev.api.js +423 -0
- package/lib/api/types/credential.types.js +23 -0
- package/lib/api/types/dev.types.js +140 -0
- package/lib/app/config.js +21 -0
- package/lib/app/down.js +2 -1
- package/lib/app/index.js +9 -0
- package/lib/app/push.js +36 -12
- package/lib/app/readme.js +1 -3
- package/lib/app/run-env-compose.js +201 -0
- package/lib/app/run-helpers.js +121 -118
- package/lib/app/run.js +148 -28
- package/lib/app/show.js +5 -2
- package/lib/build/index.js +11 -3
- package/lib/cli/setup-app.js +140 -14
- package/lib/cli/setup-auth.js +1 -0
- package/lib/cli/setup-dev.js +180 -17
- package/lib/cli/setup-environment.js +4 -2
- package/lib/cli/setup-external-system.js +71 -21
- package/lib/cli/setup-infra.js +29 -2
- package/lib/cli/setup-secrets.js +52 -5
- package/lib/cli/setup-utility.js +19 -4
- package/lib/commands/app-install.js +172 -0
- package/lib/commands/app-shell.js +75 -0
- package/lib/commands/app-test.js +282 -0
- package/lib/commands/app.js +1 -1
- package/lib/commands/auth-status.js +36 -3
- package/lib/commands/dev-cli-handlers.js +141 -0
- package/lib/commands/dev-down.js +114 -0
- package/lib/commands/dev-init.js +309 -0
- package/lib/commands/secrets-list.js +118 -0
- package/lib/commands/secrets-remove.js +97 -0
- package/lib/commands/secrets-set.js +30 -17
- package/lib/commands/secrets-validate.js +50 -0
- package/lib/commands/up-dataplane.js +2 -2
- package/lib/commands/up-miso.js +0 -25
- package/lib/commands/upload.js +26 -1
- package/lib/core/admin-secrets.js +96 -0
- package/lib/core/secrets-ensure.js +378 -0
- package/lib/core/secrets-env-write.js +157 -0
- package/lib/core/secrets.js +147 -81
- package/lib/datasource/field-reference-validator.js +91 -0
- package/lib/datasource/validate.js +21 -3
- package/lib/deployment/environment-config.js +137 -0
- package/lib/deployment/environment.js +21 -98
- package/lib/deployment/push.js +32 -2
- package/lib/external-system/download.js +7 -0
- package/lib/external-system/test-auth.js +7 -3
- package/lib/external-system/test.js +5 -1
- package/lib/generator/index.js +174 -25
- package/lib/generator/wizard.js +13 -1
- package/lib/infrastructure/helpers.js +103 -20
- package/lib/infrastructure/index.js +88 -10
- package/lib/infrastructure/services.js +70 -15
- package/lib/schema/application-schema.json +24 -3
- package/lib/schema/external-system.schema.json +435 -413
- package/lib/utils/api.js +3 -3
- package/lib/utils/app-register-auth.js +25 -3
- package/lib/utils/cli-utils.js +20 -0
- package/lib/utils/compose-generator.js +76 -75
- package/lib/utils/compose-handlebars-helpers.js +43 -0
- package/lib/utils/compose-vector-helper.js +18 -0
- package/lib/utils/config-paths.js +127 -2
- package/lib/utils/credential-secrets-env.js +267 -0
- package/lib/utils/dev-cert-helper.js +122 -0
- package/lib/utils/device-code-helpers.js +224 -0
- package/lib/utils/device-code.js +37 -336
- package/lib/utils/docker-build.js +40 -8
- package/lib/utils/env-copy.js +83 -13
- package/lib/utils/env-map.js +35 -5
- package/lib/utils/env-template.js +6 -5
- package/lib/utils/error-formatters/http-status-errors.js +20 -1
- package/lib/utils/help-builder.js +15 -2
- package/lib/utils/infra-status.js +30 -1
- package/lib/utils/local-secrets.js +7 -52
- package/lib/utils/mutagen-install.js +195 -0
- package/lib/utils/mutagen.js +146 -0
- package/lib/utils/paths.js +49 -33
- package/lib/utils/port-resolver.js +28 -16
- package/lib/utils/remote-dev-auth.js +38 -0
- package/lib/utils/remote-docker-env.js +43 -0
- package/lib/utils/remote-secrets-loader.js +60 -0
- package/lib/utils/secrets-generator.js +94 -6
- package/lib/utils/secrets-helpers.js +33 -25
- package/lib/utils/secrets-path.js +2 -2
- package/lib/utils/secrets-utils.js +52 -1
- package/lib/utils/secrets-validation.js +84 -0
- package/lib/utils/ssh-key-helper.js +116 -0
- package/lib/utils/token-manager-messages.js +90 -0
- package/lib/utils/token-manager.js +5 -4
- package/lib/utils/variable-transformer.js +3 -3
- package/lib/validation/validate.js +1 -1
- package/lib/validation/validator.js +65 -0
- package/package.json +4 -2
- package/scripts/install-local.js +34 -15
- package/templates/README.md +0 -1
- package/templates/applications/README.md.hbs +4 -4
- package/templates/applications/dataplane/application.yaml +5 -4
- package/templates/applications/dataplane/env.template +12 -7
- package/templates/applications/keycloak/env.template +2 -0
- package/templates/applications/miso-controller/application.yaml +1 -0
- package/templates/applications/miso-controller/env.template +11 -9
- package/templates/external-system/external-system.json.hbs +1 -16
- package/templates/python/docker-compose.hbs +49 -23
- package/templates/typescript/docker-compose.hbs +48 -22
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Install the AI Fabrix platform and test it locally. Then add external integratio
|
|
|
16
16
|
- **Full lifecycle in your version control:** Configuration, apps, and integrations live in your own VCS (GitHub, GitLab, Azure DevOps).
|
|
17
17
|
- **One tool from day one:** Single CLI for local infra, app and integration creation, build, run, and deploy—same workflow for apps and integrations.
|
|
18
18
|
- **Consistency and production readiness:** Schema-driven; deploy apps and integrations to the same controller/dataplane; production-ready secrets with `kv://` and Azure Key Vault.
|
|
19
|
-
- **Application development:** Use **[miso-client](https://github.com/esystemsdev/aifabrix-miso-client)**
|
|
19
|
+
- **Application development:** Use **[miso-client](https://github.com/esystemsdev/aifabrix-miso-client)** (TypeScript and Python) to talk to the dataplane and controller. The repo includes both TypeScript and Python SDKs; see [templates/applications/dataplane/README.md](templates/applications/dataplane/README.md) and the repo for usage.
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -34,7 +34,7 @@ Install the AI Fabrix platform and test it locally. Then add external integratio
|
|
|
34
34
|
npm install -g @aifabrix/builder
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
**Alias:** You can use `
|
|
37
|
+
**Alias:** You can use `af` instead of `aifabrix` in any command.
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
@@ -48,6 +48,8 @@ Get the platform running locally so you can try it.
|
|
|
48
48
|
aifabrix up-infra
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
First-time run creates required infra secrets automatically. Use `aifabrix up-infra --adminPwd <password>` to set a custom admin password for Postgres, pgAdmin, and Redis Commander.
|
|
52
|
+
|
|
51
53
|
2. **Start the platform** (Keycloak, Miso Controller, Dataplane) from community images:
|
|
52
54
|
|
|
53
55
|
```bash
|
|
@@ -60,12 +62,12 @@ Get the platform running locally so you can try it.
|
|
|
60
62
|
|
|
61
63
|
- **OpenAI:** set your API key:
|
|
62
64
|
```bash
|
|
63
|
-
aifabrix
|
|
65
|
+
aifabrix secret set secrets-openaiApiKeyVault <your-openai-secret-key>
|
|
64
66
|
```
|
|
65
67
|
- **Azure OpenAI:** set endpoint and API key:
|
|
66
68
|
```bash
|
|
67
|
-
aifabrix
|
|
68
|
-
aifabrix
|
|
69
|
+
aifabrix secret set azure-openaiapi-urlKeyVault <your-azure-openai-endpoint-url>
|
|
70
|
+
aifabrix secret set secrets-azureOpenaiApiKeyVault <your-azure-openai-secret-key>
|
|
69
71
|
```
|
|
70
72
|
|
|
71
73
|
Secrets are stored in `~/.aifabrix/secrets.local.yaml` or the file from `aifabrix-secrets` in your config (e.g. `builder/secrets.local.yaml`).
|
|
@@ -281,7 +281,7 @@ async function loadEnvFile(envPath, options) {
|
|
|
281
281
|
/**
|
|
282
282
|
* Load test config (controller, environment, dataplane, openapi file).
|
|
283
283
|
* Reads integration/hubspot/.env; missing CONTROLLER_URL/ENVIRONMENT fall back to
|
|
284
|
-
* the same resolution as the CLI (
|
|
284
|
+
* the same resolution as the CLI (af auth status) so tests use the same controller.
|
|
285
285
|
* @async
|
|
286
286
|
* @function loadTestConfigFromEnv
|
|
287
287
|
* @returns {Promise<Object>} Context with controllerUrl, environment, dataplaneUrl, openapiFile
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest Configuration for Manual Tests (real API calls)
|
|
3
|
+
* Runs only tests/manual; requires user to be logged in (validated before run).
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Jest config for manual tests that call real Controller/Dataplane APIs
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const baseProject = require('./jest.config').projects[0];
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
projects: [
|
|
14
|
+
{
|
|
15
|
+
...baseProject,
|
|
16
|
+
displayName: 'manual',
|
|
17
|
+
testMatch: [
|
|
18
|
+
'**/tests/manual/**/*.test.js'
|
|
19
|
+
],
|
|
20
|
+
testPathIgnorePatterns: [
|
|
21
|
+
'/node_modules/',
|
|
22
|
+
'\\\\node_modules\\\\'
|
|
23
|
+
],
|
|
24
|
+
setupFilesAfterEnv: ['<rootDir>/tests/manual/setup.js'],
|
|
25
|
+
testTimeout: 60000,
|
|
26
|
+
maxWorkers: 1
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Credential API functions (Dataplane secret store)
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { ApiClient } = require('./index');
|
|
8
|
+
|
|
9
|
+
const CREDENTIAL_SECRET_ENDPOINT = '/api/v1/credential/secret';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Store credential secrets in the dataplane secret store.
|
|
13
|
+
* Values are encrypted at rest by the dataplane; send plain values only (no kv:// as value).
|
|
14
|
+
*
|
|
15
|
+
* POST /api/v1/credential/secret
|
|
16
|
+
* @requiresPermission {Dataplane} credential:create
|
|
17
|
+
* @async
|
|
18
|
+
* @function storeCredentialSecrets
|
|
19
|
+
* @param {string} dataplaneUrl - Dataplane base URL
|
|
20
|
+
* @param {Object} authConfig - Authentication configuration (Bearer token required)
|
|
21
|
+
* @param {Array<{ key: string, value: string }>} items - Secret items (key = kv path, value = plain)
|
|
22
|
+
* @returns {Promise<{ stored?: number, success?: boolean, error?: string }>} Secret store response
|
|
23
|
+
* @throws {Error} If request fails (non-2xx) and caller may handle 403/401 as warning
|
|
24
|
+
*/
|
|
25
|
+
async function storeCredentialSecrets(dataplaneUrl, authConfig, items) {
|
|
26
|
+
if (!dataplaneUrl || typeof dataplaneUrl !== 'string') {
|
|
27
|
+
throw new Error('dataplaneUrl is required and must be a string');
|
|
28
|
+
}
|
|
29
|
+
if (!items || !Array.isArray(items) || items.length === 0) {
|
|
30
|
+
return { stored: 0 };
|
|
31
|
+
}
|
|
32
|
+
const client = new ApiClient(dataplaneUrl, authConfig);
|
|
33
|
+
return await client.post(CREDENTIAL_SECRET_ENDPOINT, {
|
|
34
|
+
body: items
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
storeCredentialSecrets
|
|
40
|
+
};
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Builder Server (dev) API – issue-cert, settings, users, SSH keys, secrets.
|
|
3
|
+
* First call: issue-cert is public (no client cert). All other routes require client certificate:
|
|
4
|
+
* when clientKeyPem is provided, requests use mTLS (TLS client cert); otherwise X-Client-Cert header only.
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const https = require('https');
|
|
10
|
+
const { makeApiCall } = require('../utils/api');
|
|
11
|
+
|
|
12
|
+
const DEFAULT_TIMEOUT_MS = 15000;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Encode PEM for use in X-Client-Cert header. HTTP header values must not contain newlines;
|
|
16
|
+
* we send certPem as base64: Buffer.from(pem, 'utf8').toString('base64').
|
|
17
|
+
* Server should decode with Buffer.from(headerVal, 'base64').toString('utf8').
|
|
18
|
+
* @param {string} clientCertPem - PEM-encoded client certificate
|
|
19
|
+
* @returns {string} Base64-encoded PEM for header
|
|
20
|
+
*/
|
|
21
|
+
function encodeCertForHeader(clientCertPem) {
|
|
22
|
+
if (!clientCertPem || typeof clientCertPem !== 'string') return '';
|
|
23
|
+
return Buffer.from(clientCertPem, 'utf8').toString('base64');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Normalize base URL (no trailing slash)
|
|
28
|
+
* @param {string} serverUrl - Base URL of Builder Server
|
|
29
|
+
* @returns {string} Normalized URL
|
|
30
|
+
*/
|
|
31
|
+
function normalizeBaseUrl(serverUrl) {
|
|
32
|
+
if (!serverUrl || typeof serverUrl !== 'string') {
|
|
33
|
+
throw new Error('remote-server URL is required and must be a string');
|
|
34
|
+
}
|
|
35
|
+
return serverUrl.trim().replace(/\/+$/, '');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build full URL for an endpoint path
|
|
40
|
+
* @param {string} baseUrl - Normalized base URL
|
|
41
|
+
* @param {string} path - Path (e.g. /api/dev/issue-cert)
|
|
42
|
+
* @returns {string} Full URL
|
|
43
|
+
*/
|
|
44
|
+
function buildUrl(baseUrl, path) {
|
|
45
|
+
const p = path.startsWith('/') ? path : `/${path}`;
|
|
46
|
+
return `${baseUrl}${p}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Make request to Builder Server. Throws on !success with message from response or error.
|
|
51
|
+
* @param {string} url - Full URL
|
|
52
|
+
* @param {Object} options - Fetch options (method, headers, body)
|
|
53
|
+
* @returns {Promise<Object>} result.data when success
|
|
54
|
+
*/
|
|
55
|
+
async function request(url, options = {}) {
|
|
56
|
+
const fetchOptions = {
|
|
57
|
+
method: options.method || 'GET',
|
|
58
|
+
headers: { 'Content-Type': 'application/json', ...options.headers },
|
|
59
|
+
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
|
|
60
|
+
};
|
|
61
|
+
if (options.body !== undefined) {
|
|
62
|
+
fetchOptions.body = typeof options.body === 'string' ? options.body : JSON.stringify(options.body);
|
|
63
|
+
}
|
|
64
|
+
const result = await makeApiCall(url, fetchOptions);
|
|
65
|
+
if (!result.success) {
|
|
66
|
+
const msg = result.formattedError || result.error || result.message || `Request failed (${result.status})`;
|
|
67
|
+
const err = new Error(msg);
|
|
68
|
+
err.status = result.status;
|
|
69
|
+
err.errorData = result.errorData;
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
return result.data;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build request options and TLS agent for mTLS request.
|
|
77
|
+
* @param {string} url - Full URL
|
|
78
|
+
* @param {Object} options - { method, headers, body }
|
|
79
|
+
* @param {string} certPem - PEM client certificate
|
|
80
|
+
* @param {string} keyPem - PEM client key
|
|
81
|
+
* @returns {{ urlObj: URL, method: string, headers: Object, body: string|undefined, agent: https.Agent }}
|
|
82
|
+
*/
|
|
83
|
+
function buildMtlsRequestOptions(url, options, certPem, keyPem) {
|
|
84
|
+
const urlObj = new URL(url);
|
|
85
|
+
const method = (options.method || 'GET').toUpperCase();
|
|
86
|
+
const headers = { 'Content-Type': 'application/json', ...options.headers };
|
|
87
|
+
let body = options.body;
|
|
88
|
+
if (body !== undefined && typeof body !== 'string') {
|
|
89
|
+
body = JSON.stringify(body);
|
|
90
|
+
}
|
|
91
|
+
if (body) {
|
|
92
|
+
headers['Content-Length'] = Buffer.byteLength(body, 'utf8');
|
|
93
|
+
}
|
|
94
|
+
const tlsOptions = { cert: certPem, key: keyPem, rejectUnauthorized: true };
|
|
95
|
+
const agent = new https.Agent(tlsOptions);
|
|
96
|
+
return { urlObj, method, headers, body, agent, tlsOptions };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Handle mTLS response: collect body, parse JSON, resolve or reject.
|
|
101
|
+
* @param {import('http').IncomingMessage} res - HTTP response
|
|
102
|
+
* @param {Function} resolve - Promise resolve
|
|
103
|
+
* @param {Function} reject - Promise reject
|
|
104
|
+
*/
|
|
105
|
+
function handleMtlsResponse(res, resolve, reject) {
|
|
106
|
+
const chunks = [];
|
|
107
|
+
res.on('data', (c) => chunks.push(c));
|
|
108
|
+
res.on('end', () => {
|
|
109
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
110
|
+
let data;
|
|
111
|
+
try {
|
|
112
|
+
data = raw ? JSON.parse(raw) : {};
|
|
113
|
+
} catch {
|
|
114
|
+
data = raw;
|
|
115
|
+
}
|
|
116
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
117
|
+
const msg = (data && (data.message || data.error)) || res.statusMessage || `Request failed (${res.statusCode})`;
|
|
118
|
+
const err = new Error(msg);
|
|
119
|
+
err.status = res.statusCode;
|
|
120
|
+
err.errorData = data;
|
|
121
|
+
reject(err);
|
|
122
|
+
} else {
|
|
123
|
+
resolve(data);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Make request with mTLS (TLS client certificate). Uses Node https module so the client cert
|
|
130
|
+
* is presented in the TLS handshake. Also sends X-Client-Cert header for backends that read it.
|
|
131
|
+
* @param {string} url - Full URL (https only)
|
|
132
|
+
* @param {Object} options - { method, headers, body }
|
|
133
|
+
* @param {string} certPem - PEM-encoded client certificate
|
|
134
|
+
* @param {string} keyPem - PEM-encoded client private key
|
|
135
|
+
* @returns {Promise<Object>} response data when success
|
|
136
|
+
*/
|
|
137
|
+
function requestWithCertImpl(url, options, certPem, keyPem) {
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
const urlObj = new URL(url);
|
|
140
|
+
if (urlObj.protocol !== 'https:') {
|
|
141
|
+
reject(new Error('mTLS request requires https URL'));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const { method, headers, body, agent, tlsOptions } = buildMtlsRequestOptions(url, options, certPem, keyPem);
|
|
145
|
+
const req = https.request(
|
|
146
|
+
{
|
|
147
|
+
hostname: urlObj.hostname,
|
|
148
|
+
port: urlObj.port || 443,
|
|
149
|
+
path: urlObj.pathname + urlObj.search,
|
|
150
|
+
method,
|
|
151
|
+
headers,
|
|
152
|
+
agent,
|
|
153
|
+
...tlsOptions
|
|
154
|
+
},
|
|
155
|
+
(res) => handleMtlsResponse(res, resolve, reject)
|
|
156
|
+
);
|
|
157
|
+
req.on('error', reject);
|
|
158
|
+
req.setTimeout(DEFAULT_TIMEOUT_MS, () => {
|
|
159
|
+
req.destroy();
|
|
160
|
+
reject(new Error(`Request timed out after ${DEFAULT_TIMEOUT_MS}ms`));
|
|
161
|
+
});
|
|
162
|
+
if (body) {
|
|
163
|
+
req.write(body, 'utf8');
|
|
164
|
+
}
|
|
165
|
+
req.end();
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Issue developer certificate (public; no client cert). POST /api/dev/issue-cert
|
|
171
|
+
* @requiresPermission {BuilderServer} Public (no auth required)
|
|
172
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
173
|
+
* @param {Object} body - IssueCertDto: developerId, pin, csr
|
|
174
|
+
* @returns {Promise<Object>} IssueCertResponseDto: certificate, validDays, validNotAfter
|
|
175
|
+
*/
|
|
176
|
+
async function issueCert(serverUrl, body) {
|
|
177
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
178
|
+
return request(buildUrl(base, '/api/dev/issue-cert'), { method: 'POST', body });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get health. GET /health (public)
|
|
183
|
+
* @requiresPermission {BuilderServer} Public (no auth required)
|
|
184
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
185
|
+
* @returns {Promise<Object>} HealthResponseDto: status, checks (dataDir, encryptionKey, ca, users, tokens)
|
|
186
|
+
*/
|
|
187
|
+
async function getHealth(serverUrl) {
|
|
188
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
189
|
+
return request(buildUrl(base, '/health'));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get developer settings (cert-authenticated). GET /api/dev/settings
|
|
194
|
+
* When clientKeyPem is provided, uses mTLS (TLS client cert); otherwise X-Client-Cert header only.
|
|
195
|
+
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert or mTLS)
|
|
196
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
197
|
+
* @param {string} clientCertPem - PEM-encoded client certificate
|
|
198
|
+
* @param {string} [clientKeyPem] - PEM-encoded client private key (enables mTLS when provided)
|
|
199
|
+
* @returns {Promise<Object>} SettingsResponseDto
|
|
200
|
+
*/
|
|
201
|
+
async function getSettings(serverUrl, clientCertPem, clientKeyPem) {
|
|
202
|
+
if (!clientCertPem || typeof clientCertPem !== 'string') {
|
|
203
|
+
throw new Error('Client certificate PEM is required for getSettings');
|
|
204
|
+
}
|
|
205
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
206
|
+
const url = buildUrl(base, '/api/dev/settings');
|
|
207
|
+
const reqOptions = { method: 'GET', headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) } };
|
|
208
|
+
if (clientKeyPem && typeof clientKeyPem === 'string') {
|
|
209
|
+
return requestWithCertImpl(url, reqOptions, clientCertPem, clientKeyPem);
|
|
210
|
+
}
|
|
211
|
+
return request(url, reqOptions);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* List developers. GET /api/dev/users
|
|
216
|
+
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
|
|
217
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
218
|
+
* @param {string} clientCertPem - PEM client certificate
|
|
219
|
+
* @returns {Promise<Object[]>} Array of UserResponseDto (empty when none)
|
|
220
|
+
*/
|
|
221
|
+
async function listUsers(serverUrl, clientCertPem) {
|
|
222
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
223
|
+
const data = await request(buildUrl(base, '/api/dev/users'), {
|
|
224
|
+
method: 'GET',
|
|
225
|
+
headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
|
|
226
|
+
});
|
|
227
|
+
return Array.isArray(data) ? data : [];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Create developer. POST /api/dev/users
|
|
232
|
+
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
|
|
233
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
234
|
+
* @param {string} clientCertPem - PEM client certificate
|
|
235
|
+
* @param {Object} body - CreateUserDto: developerId, name, email, optional groups
|
|
236
|
+
* @returns {Promise<Object>} UserResponseDto
|
|
237
|
+
*/
|
|
238
|
+
async function createUser(serverUrl, clientCertPem, body) {
|
|
239
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
240
|
+
return request(buildUrl(base, '/api/dev/users'), {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) },
|
|
243
|
+
body
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Update developer. PATCH /api/dev/users/:id
|
|
249
|
+
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
|
|
250
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
251
|
+
* @param {string} clientCertPem - PEM client certificate
|
|
252
|
+
* @param {string} id - Developer ID
|
|
253
|
+
* @param {Object} body - UpdateUserDto: at least one of name, email, groups
|
|
254
|
+
* @returns {Promise<Object>} UserResponseDto
|
|
255
|
+
*/
|
|
256
|
+
async function updateUser(serverUrl, clientCertPem, id, body) {
|
|
257
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
258
|
+
return request(buildUrl(base, `/api/dev/users/${encodeURIComponent(id)}`), {
|
|
259
|
+
method: 'PATCH',
|
|
260
|
+
headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) },
|
|
261
|
+
body
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Delete developer. DELETE /api/dev/users/:id
|
|
267
|
+
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
|
|
268
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
269
|
+
* @param {string} clientCertPem - PEM client certificate
|
|
270
|
+
* @param {string} id - Developer ID
|
|
271
|
+
* @returns {Promise<Object>} DeletedResponseDto
|
|
272
|
+
*/
|
|
273
|
+
async function deleteUser(serverUrl, clientCertPem, id) {
|
|
274
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
275
|
+
return request(buildUrl(base, `/api/dev/users/${encodeURIComponent(id)}`), {
|
|
276
|
+
method: 'DELETE',
|
|
277
|
+
headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Create or regenerate one-time PIN. POST /api/dev/users/:id/pin
|
|
283
|
+
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
|
|
284
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
285
|
+
* @param {string} clientCertPem - PEM client certificate
|
|
286
|
+
* @param {string} id - Developer ID
|
|
287
|
+
* @returns {Promise<Object>} CreatePinResponseDto: pin, expiresAt
|
|
288
|
+
*/
|
|
289
|
+
async function createPin(serverUrl, clientCertPem, id) {
|
|
290
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
291
|
+
return request(buildUrl(base, `/api/dev/users/${encodeURIComponent(id)}/pin`), {
|
|
292
|
+
method: 'POST',
|
|
293
|
+
headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* List SSH keys for developer. GET /api/dev/users/:id/ssh-keys
|
|
299
|
+
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
|
|
300
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
301
|
+
* @param {string} clientCertPem - PEM client certificate
|
|
302
|
+
* @param {string} id - Developer ID
|
|
303
|
+
* @returns {Promise<Object[]>} Array of SshKeyItemDto
|
|
304
|
+
*/
|
|
305
|
+
async function listSshKeys(serverUrl, clientCertPem, id) {
|
|
306
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
307
|
+
const data = await request(buildUrl(base, `/api/dev/users/${encodeURIComponent(id)}/ssh-keys`), {
|
|
308
|
+
method: 'GET',
|
|
309
|
+
headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
|
|
310
|
+
});
|
|
311
|
+
return Array.isArray(data) ? data : [];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Add SSH public key for developer. POST /api/dev/users/:id/ssh-keys
|
|
316
|
+
* When clientKeyPem is provided, uses mTLS (TLS client cert); otherwise X-Client-Cert header only.
|
|
317
|
+
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert or mTLS)
|
|
318
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
319
|
+
* @param {string} clientCertPem - PEM client certificate
|
|
320
|
+
* @param {string} id - Developer ID
|
|
321
|
+
* @param {Object} body - AddSshKeyDto: publicKey, optional label
|
|
322
|
+
* @param {string} [clientKeyPem] - PEM-encoded client private key (enables mTLS when provided)
|
|
323
|
+
* @returns {Promise<Object>} SshKeyItemDto
|
|
324
|
+
*/
|
|
325
|
+
async function addSshKey(serverUrl, clientCertPem, id, body, clientKeyPem) {
|
|
326
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
327
|
+
const url = buildUrl(base, `/api/dev/users/${encodeURIComponent(id)}/ssh-keys`);
|
|
328
|
+
const reqOptions = {
|
|
329
|
+
method: 'POST',
|
|
330
|
+
headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) },
|
|
331
|
+
body
|
|
332
|
+
};
|
|
333
|
+
if (clientKeyPem && typeof clientKeyPem === 'string') {
|
|
334
|
+
return requestWithCertImpl(url, reqOptions, clientCertPem, clientKeyPem);
|
|
335
|
+
}
|
|
336
|
+
return request(url, reqOptions);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Remove SSH key by fingerprint. DELETE /api/dev/users/:id/ssh-keys/:fingerprint
|
|
341
|
+
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
|
|
342
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
343
|
+
* @param {string} clientCertPem - PEM client certificate
|
|
344
|
+
* @param {string} id - Developer ID
|
|
345
|
+
* @param {string} fingerprint - Key fingerprint
|
|
346
|
+
* @returns {Promise<Object>} DeletedResponseDto
|
|
347
|
+
*/
|
|
348
|
+
async function removeSshKey(serverUrl, clientCertPem, id, fingerprint) {
|
|
349
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
350
|
+
return request(buildUrl(base, `/api/dev/users/${encodeURIComponent(id)}/ssh-keys/${encodeURIComponent(fingerprint)}`), {
|
|
351
|
+
method: 'DELETE',
|
|
352
|
+
headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* List secrets. GET /api/dev/secrets
|
|
358
|
+
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
|
|
359
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
360
|
+
* @param {string} clientCertPem - PEM client certificate
|
|
361
|
+
* @returns {Promise<Object[]>} Array of SecretItemDto: name, value
|
|
362
|
+
*/
|
|
363
|
+
async function listSecrets(serverUrl, clientCertPem) {
|
|
364
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
365
|
+
const data = await request(buildUrl(base, '/api/dev/secrets'), {
|
|
366
|
+
method: 'GET',
|
|
367
|
+
headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
|
|
368
|
+
});
|
|
369
|
+
return Array.isArray(data) ? data : [];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Add or update secret. POST /api/dev/secrets
|
|
374
|
+
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
|
|
375
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
376
|
+
* @param {string} clientCertPem - PEM client certificate
|
|
377
|
+
* @param {Object} body - AddSecretDto: key, value
|
|
378
|
+
* @returns {Promise<Object>} AddSecretResponseDto
|
|
379
|
+
*/
|
|
380
|
+
async function addSecret(serverUrl, clientCertPem, body) {
|
|
381
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
382
|
+
return request(buildUrl(base, '/api/dev/secrets'), {
|
|
383
|
+
method: 'POST',
|
|
384
|
+
headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) },
|
|
385
|
+
body
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Delete secret by key. DELETE /api/dev/secrets/:key
|
|
391
|
+
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
|
|
392
|
+
* @param {string} serverUrl - Builder Server base URL
|
|
393
|
+
* @param {string} clientCertPem - PEM client certificate
|
|
394
|
+
* @param {string} key - Secret key
|
|
395
|
+
* @returns {Promise<Object>} DeleteSecretResponseDto
|
|
396
|
+
*/
|
|
397
|
+
async function deleteSecret(serverUrl, clientCertPem, key) {
|
|
398
|
+
const base = normalizeBaseUrl(serverUrl);
|
|
399
|
+
return request(buildUrl(base, `/api/dev/secrets/${encodeURIComponent(key)}`), {
|
|
400
|
+
method: 'DELETE',
|
|
401
|
+
headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
module.exports = {
|
|
406
|
+
issueCert,
|
|
407
|
+
getHealth,
|
|
408
|
+
getSettings,
|
|
409
|
+
listUsers,
|
|
410
|
+
createUser,
|
|
411
|
+
updateUser,
|
|
412
|
+
deleteUser,
|
|
413
|
+
createPin,
|
|
414
|
+
listSshKeys,
|
|
415
|
+
addSshKey,
|
|
416
|
+
removeSshKey,
|
|
417
|
+
listSecrets,
|
|
418
|
+
addSecret,
|
|
419
|
+
deleteSecret,
|
|
420
|
+
normalizeBaseUrl,
|
|
421
|
+
buildUrl,
|
|
422
|
+
encodeCertForHeader
|
|
423
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Credential API type definitions (Dataplane secret store)
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Single secret item for Dataplane credential secret store.
|
|
9
|
+
* Key is the kv:// path; value must be plain (resolved), never a kv:// reference.
|
|
10
|
+
* @typedef {Object} SecretStoreItem
|
|
11
|
+
* @property {string} key - kv:// path (e.g. kv://secrets/client-secret)
|
|
12
|
+
* @property {string} value - Plain secret value (encrypted at rest by dataplane)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Response from POST /api/v1/credential/secret (Dataplane).
|
|
17
|
+
* @typedef {Object} SecretStoreResponse
|
|
18
|
+
* @property {number} [stored] - Number of secrets stored
|
|
19
|
+
* @property {boolean} [success] - Request success flag
|
|
20
|
+
* @property {string} [error] - Error message when success is false
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
module.exports = {};
|