@aifabrix/builder 2.40.2 → 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 +6 -4
- package/integration/hubspot/test.js +1 -1
- 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-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 +12 -3
- 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/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 +8 -0
- 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 +43 -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/validator.js +65 -0
- package/package.json +2 -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/python/docker-compose.hbs +49 -23
- package/templates/typescript/docker-compose.hbs +48 -22
package/README.md
CHANGED
|
@@ -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,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 = {};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Builder Server (dev) API type definitions – issue-cert, settings, users, SSH keys, secrets
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Issue certificate request (POST /api/dev/issue-cert). Public; no client cert.
|
|
9
|
+
* @typedef {Object} IssueCertDto
|
|
10
|
+
* @property {string} developerId - Developer ID (must match user for whom PIN was created)
|
|
11
|
+
* @property {string} pin - One-time PIN from POST /api/dev/users/:id/pin
|
|
12
|
+
* @property {string} csr - PEM-encoded Certificate Signing Request
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Issue certificate response (POST /api/dev/issue-cert)
|
|
17
|
+
* @typedef {Object} IssueCertResponseDto
|
|
18
|
+
* @property {string} certificate - PEM-encoded X.509 certificate
|
|
19
|
+
* @property {number} validDays - Validity in days
|
|
20
|
+
* @property {string} validNotAfter - ISO 8601 validity end (UTC)
|
|
21
|
+
* @property {string} [caCertificate] - Optional PEM-encoded CA certificate (for remote Docker TLS; saved as ca.pem)
|
|
22
|
+
* @property {string} [ca] - Optional alias for caCertificate
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Developer settings (GET /api/dev/settings). Cert-authenticated.
|
|
27
|
+
* @typedef {Object} SettingsResponseDto
|
|
28
|
+
* @property {string} user-mutagen-folder - Server path to workspace root (no app segment)
|
|
29
|
+
* @property {string} secrets-encryption - Encryption key (hex)
|
|
30
|
+
* @property {string} aifabrix-secrets - Path or URL for secrets
|
|
31
|
+
* @property {string} aifabrix-env-config - Env config path
|
|
32
|
+
* @property {string} remote-server - Builder-server base URL
|
|
33
|
+
* @property {string} docker-endpoint - Docker API endpoint
|
|
34
|
+
* @property {string} sync-ssh-user - SSH user for Mutagen
|
|
35
|
+
* @property {string} sync-ssh-host - SSH host for Mutagen
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* User list item (GET /api/dev/users)
|
|
40
|
+
* @typedef {Object} UserResponseDto
|
|
41
|
+
* @property {string} id - Developer ID
|
|
42
|
+
* @property {string} name - Display name
|
|
43
|
+
* @property {string} email - Email
|
|
44
|
+
* @property {string} createdAt - ISO 8601
|
|
45
|
+
* @property {boolean} certificateIssued - Whether cert was issued
|
|
46
|
+
* @property {string} [certificateValidNotAfter] - Cert validity end (optional)
|
|
47
|
+
* @property {string[]} groups - Access groups (admin, secret-manager, developer)
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create user request (POST /api/dev/users)
|
|
52
|
+
* @typedef {Object} CreateUserDto
|
|
53
|
+
* @property {string} developerId - Unique developer ID (numeric string)
|
|
54
|
+
* @property {string} name - Display name
|
|
55
|
+
* @property {string} email - Email
|
|
56
|
+
* @property {string[]} [groups] - Default [developer]
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Update user request (PATCH /api/dev/users/:id). At least one field.
|
|
61
|
+
* @typedef {Object} UpdateUserDto
|
|
62
|
+
* @property {string} [name] - Display name
|
|
63
|
+
* @property {string} [email] - Email
|
|
64
|
+
* @property {string[]} [groups] - Access groups
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create PIN response (POST /api/dev/users/:id/pin)
|
|
69
|
+
* @typedef {Object} CreatePinResponseDto
|
|
70
|
+
* @property {string} pin - One-time PIN
|
|
71
|
+
* @property {string} expiresAt - ISO 8601
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Add SSH key request (POST /api/dev/users/:id/ssh-keys)
|
|
76
|
+
* @typedef {Object} AddSshKeyDto
|
|
77
|
+
* @property {string} publicKey - SSH public key line
|
|
78
|
+
* @property {string} [label] - Optional label
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* SSH key item (list/add response)
|
|
83
|
+
* @typedef {Object} SshKeyItemDto
|
|
84
|
+
* @property {string} fingerprint - Key fingerprint
|
|
85
|
+
* @property {string} [label] - Optional label
|
|
86
|
+
* @property {string} [createdAt] - ISO 8601
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Deleted response (DELETE endpoints)
|
|
91
|
+
* @typedef {Object} DeletedResponseDto
|
|
92
|
+
* @property {string} deleted - ID or key of deleted resource
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Secret item (GET /api/dev/secrets)
|
|
97
|
+
* @typedef {Object} SecretItemDto
|
|
98
|
+
* @property {string} name - Secret key
|
|
99
|
+
* @property {string} value - Decrypted value
|
|
100
|
+
*/
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Add secret request (POST /api/dev/secrets)
|
|
104
|
+
* @typedef {Object} AddSecretDto
|
|
105
|
+
* @property {string} key - Secret key
|
|
106
|
+
* @property {string} value - Secret value
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Add secret response
|
|
111
|
+
* @typedef {Object} AddSecretResponseDto
|
|
112
|
+
* @property {string} key - Key that was added/updated
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Delete secret response
|
|
117
|
+
* @typedef {Object} DeleteSecretResponseDto
|
|
118
|
+
* @property {string} deleted - Key that was removed
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Health response (GET /health)
|
|
123
|
+
* @typedef {Object} HealthResponseDto
|
|
124
|
+
* @property {string} status - Overall status, e.g. "ok"
|
|
125
|
+
* @property {Object} checks - Per-component health checks
|
|
126
|
+
* @property {string} checks.dataDir - Data directory check ("ok" or error)
|
|
127
|
+
* @property {string} checks.encryptionKey - Encryption key check ("ok" or error)
|
|
128
|
+
* @property {string} checks.ca - CA certificate check ("ok" or error)
|
|
129
|
+
* @property {string} checks.users - Users store check ("ok" or error)
|
|
130
|
+
* @property {string} checks.tokens - Tokens store check ("ok" or error)
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Error response (all error responses)
|
|
135
|
+
* @typedef {Object} ErrorResponseDto
|
|
136
|
+
* @property {number} statusCode - HTTP status
|
|
137
|
+
* @property {string} error - Short error type
|
|
138
|
+
* @property {string} message - Human-readable message
|
|
139
|
+
* @property {string} [code] - Optional machine-readable code
|
|
140
|
+
*/
|