impel-cli 0.14.2 → 0.14.3
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/package.json +1 -1
- package/src/gateway/index.js +33 -5
package/package.json
CHANGED
package/src/gateway/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const SAFE_NAMESPACE = /^[a-z][a-z0-9_-]{1,31}$/u;
|
|
|
11
11
|
const SAFE_PREFIX = /^[a-z][a-z0-9_]{2,31}_$/u;
|
|
12
12
|
const SAFE_ENVIRONMENT_PREFIX = /^[A-Z][A-Z0-9_]{1,31}$/u;
|
|
13
13
|
const SAFE_ROUTE = /^\/[A-Za-z0-9._~!$&'()*+,;=:@%/-]*$/u;
|
|
14
|
+
const SAFE_TENANT_ID = /^[A-Za-z0-9_.-]{1,128}$/u;
|
|
14
15
|
const ANSI_ESCAPE_RE = /\u001B(?:\][^\u0007\u001B]*(?:\u0007|\u001B\\)|\[[0-?]*[ -/]*[@-~]|[@-_])/gu;
|
|
15
16
|
const CONTROL_TEST_RE = /[\u0000-\u001F\u007F-\u009F]/u;
|
|
16
17
|
const CONTROL_RE = /[\u0000-\u001F\u007F-\u009F]/gu;
|
|
@@ -29,6 +30,15 @@ function validateRoute(name, value) {
|
|
|
29
30
|
return value;
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
function validateTenantID(value) {
|
|
34
|
+
if (value == null) return null;
|
|
35
|
+
const tenantID = String(value || "").trim();
|
|
36
|
+
if (!SAFE_TENANT_ID.test(tenantID) || tenantID === "." || tenantID === ".." || CONTROL_TEST_RE.test(tenantID)) {
|
|
37
|
+
throw new Error("gateway CLI tenant.defaultId is invalid");
|
|
38
|
+
}
|
|
39
|
+
return tenantID;
|
|
40
|
+
}
|
|
41
|
+
|
|
32
42
|
function validateBrand(input) {
|
|
33
43
|
if (!input || Array.isArray(input) || typeof input !== "object" || input.schemaVersion !== 1) {
|
|
34
44
|
throw new Error("gateway CLI brand schemaVersion must be 1");
|
|
@@ -40,6 +50,8 @@ function validateBrand(input) {
|
|
|
40
50
|
const managedMarker = String(input.cli?.managedMarker || "");
|
|
41
51
|
const environmentPrefix = String(input.cli?.environmentPrefix || command.toUpperCase().replace(/-/gu, "_"));
|
|
42
52
|
const patPrefix = String(input.auth?.patPrefix || "");
|
|
53
|
+
const tenantPrefix = input.auth?.tenantPrefix == null ? null : String(input.auth.tenantPrefix);
|
|
54
|
+
const defaultTenantID = validateTenantID(input.tenant?.defaultId);
|
|
43
55
|
if (!SAFE_ID.test(productID)) throw new Error("gateway CLI product.id is invalid");
|
|
44
56
|
if (!SAFE_ID.test(command)) throw new Error("gateway CLI command is invalid");
|
|
45
57
|
if (!SAFE_NAMESPACE.test(configNamespace)) throw new Error("gateway CLI configNamespace is invalid");
|
|
@@ -47,13 +59,16 @@ function validateBrand(input) {
|
|
|
47
59
|
if (!SAFE_NAMESPACE.test(managedMarker)) throw new Error("gateway CLI managedMarker is invalid");
|
|
48
60
|
if (!SAFE_ENVIRONMENT_PREFIX.test(environmentPrefix)) throw new Error("gateway CLI environmentPrefix is invalid");
|
|
49
61
|
if (!SAFE_PREFIX.test(patPrefix)) throw new Error("gateway CLI PAT prefix is invalid");
|
|
62
|
+
if (tenantPrefix != null && !SAFE_PREFIX.test(tenantPrefix)) throw new Error("gateway CLI tenant prefix is invalid");
|
|
63
|
+
if (defaultTenantID && !tenantPrefix) throw new Error("gateway CLI tenant prefix is required when tenant.defaultId is set");
|
|
50
64
|
|
|
51
65
|
const defaultOrigin = input.gateway?.defaultOrigin == null ? null : normalizeGatewayUrl(input.gateway.defaultOrigin);
|
|
52
66
|
return Object.freeze({
|
|
53
67
|
schemaVersion: 1,
|
|
54
68
|
product: Object.freeze({ id: productID, displayName: validateText("product.displayName", input.product?.displayName) }),
|
|
55
69
|
cli: Object.freeze({ command, configNamespace, providerId: providerID, managedMarker, environmentPrefix }),
|
|
56
|
-
auth: Object.freeze({ patPrefix }),
|
|
70
|
+
auth: Object.freeze({ patPrefix, tenantPrefix }),
|
|
71
|
+
tenant: Object.freeze({ defaultId: defaultTenantID }),
|
|
57
72
|
gateway: Object.freeze({
|
|
58
73
|
defaultOrigin,
|
|
59
74
|
healthPath: validateRoute("healthPath", input.gateway?.healthPath),
|
|
@@ -183,9 +198,21 @@ export function createGatewayCli(options) {
|
|
|
183
198
|
return `${secret.slice(0, brand.auth.patPrefix.length + 4)}...${secret.slice(-4)}`;
|
|
184
199
|
}
|
|
185
200
|
|
|
201
|
+
function bearerCredential(pat) {
|
|
202
|
+
if (!brand.tenant.defaultId) return pat;
|
|
203
|
+
const encodedTenant = Buffer.from(brand.tenant.defaultId, "utf8").toString("base64url");
|
|
204
|
+
return `${brand.auth.tenantPrefix}${encodedTenant}.${pat}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
186
207
|
function redactSecretText(value) {
|
|
187
208
|
const escapedPrefix = brand.auth.patPrefix.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
188
|
-
const
|
|
209
|
+
const escapedTenantPrefix = brand.auth.tenantPrefix?.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
210
|
+
const tokenPattern = new RegExp(
|
|
211
|
+
escapedTenantPrefix
|
|
212
|
+
? `(?:${escapedTenantPrefix}[A-Za-z0-9_-]+\\.)?${escapedPrefix}[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)?`
|
|
213
|
+
: `${escapedPrefix}[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)?`,
|
|
214
|
+
"gu",
|
|
215
|
+
);
|
|
189
216
|
return String(value ?? "")
|
|
190
217
|
.replace(tokenPattern, "[REDACTED GATEWAY CREDENTIAL]")
|
|
191
218
|
.replace(ANSI_ESCAPE_RE, "")
|
|
@@ -244,7 +271,7 @@ export function createGatewayCli(options) {
|
|
|
244
271
|
if (!health.ok) throw new Error(`gateway health returned HTTP ${health.status}`);
|
|
245
272
|
const models = await fetch(gatewayRoutes.models(gatewayUrl), {
|
|
246
273
|
...requestOptions,
|
|
247
|
-
headers: { authorization: `Bearer ${pat}` },
|
|
274
|
+
headers: { authorization: `Bearer ${bearerCredential(pat)}` },
|
|
248
275
|
});
|
|
249
276
|
if (!models.ok) throw new Error(`gateway authentication returned HTTP ${models.status}`);
|
|
250
277
|
const body = await models.json();
|
|
@@ -353,6 +380,7 @@ export function createGatewayCli(options) {
|
|
|
353
380
|
const config = loadConfig();
|
|
354
381
|
if (!config?.pat || !config?.gatewayUrl) throw new Error(`not configured; run \`${brand.cli.command} setup\``);
|
|
355
382
|
print(`Gateway: ${config.gatewayUrl}`);
|
|
383
|
+
if (brand.tenant.defaultId) print(`Tenant: ${brand.tenant.defaultId}`);
|
|
356
384
|
print(`PAT: ${maskSecret(config.pat)}`);
|
|
357
385
|
print(`Profiles: ${profileRoot}`);
|
|
358
386
|
try {
|
|
@@ -367,7 +395,7 @@ export function createGatewayCli(options) {
|
|
|
367
395
|
async function cmdToken() {
|
|
368
396
|
const config = loadConfig();
|
|
369
397
|
if (!config?.pat) throw new Error(`not authenticated; run \`${brand.cli.command} setup\` or \`${brand.cli.command} auth\``);
|
|
370
|
-
output.write(`${config.pat}\n`);
|
|
398
|
+
output.write(`${bearerCredential(config.pat)}\n`);
|
|
371
399
|
return 0;
|
|
372
400
|
}
|
|
373
401
|
|
|
@@ -408,7 +436,7 @@ export function createGatewayCli(options) {
|
|
|
408
436
|
const profile = ensureClaudeProfile(config.gatewayUrl);
|
|
409
437
|
childEnvironment.CLAUDE_CONFIG_DIR = profile.configDir;
|
|
410
438
|
childEnvironment.ANTHROPIC_BASE_URL = gatewayRoutes.anthropic(config.gatewayUrl);
|
|
411
|
-
childEnvironment.ANTHROPIC_AUTH_TOKEN = config.pat;
|
|
439
|
+
childEnvironment.ANTHROPIC_AUTH_TOKEN = bearerCredential(config.pat);
|
|
412
440
|
} else if (tool === "codex") {
|
|
413
441
|
const profile = ensureCodexProfile(config.gatewayUrl);
|
|
414
442
|
childEnvironment.CODEX_HOME = profile.codexHome;
|