@geolonia/geonicdb-cli 0.7.0 → 0.9.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 +91 -26
- package/dist/index.js +495 -118
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -186,6 +186,15 @@ function printInfo(message) {
|
|
|
186
186
|
function printWarning(message) {
|
|
187
187
|
console.error(chalk.yellow(message));
|
|
188
188
|
}
|
|
189
|
+
function printApiKeyBox(key) {
|
|
190
|
+
const border = "\u2500".repeat(key.length + 4);
|
|
191
|
+
console.error("");
|
|
192
|
+
console.error(chalk.green(` \u250C${border}\u2510`));
|
|
193
|
+
console.error(chalk.green(` \u2502 ${chalk.bold(key)} \u2502`));
|
|
194
|
+
console.error(chalk.green(` \u2514${border}\u2518`));
|
|
195
|
+
console.error("");
|
|
196
|
+
console.error(chalk.yellow("\u26A0 \u3053\u306E API \u30AD\u30FC\u5024\u3092\u5B89\u5168\u306B\u4FDD\u5B58\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u4E8C\u5EA6\u3068\u8868\u793A\u3055\u308C\u307E\u305B\u3093\u3002"));
|
|
197
|
+
}
|
|
189
198
|
function printCount(count) {
|
|
190
199
|
console.log(chalk.dim(`Count: ${count}`));
|
|
191
200
|
}
|
|
@@ -785,6 +794,15 @@ var GdbClient = class _GdbClient {
|
|
|
785
794
|
canRefresh() {
|
|
786
795
|
return (!!this.refreshToken || !!this.clientId && !!this.clientSecret) && !this.apiKey;
|
|
787
796
|
}
|
|
797
|
+
/** Check whether an error indicates an authentication/token problem that may be resolved by refreshing. */
|
|
798
|
+
static isTokenError(err) {
|
|
799
|
+
if (err.status === 401) return true;
|
|
800
|
+
if (err.status === 403) {
|
|
801
|
+
const msg = (err.message ?? "").toLowerCase();
|
|
802
|
+
return msg.includes("not assigned to any tenant") || msg.includes("invalid token");
|
|
803
|
+
}
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
788
806
|
async performTokenRefresh() {
|
|
789
807
|
if (this.refreshPromise) return this.refreshPromise;
|
|
790
808
|
this.refreshPromise = this.doRefresh();
|
|
@@ -888,7 +906,7 @@ var GdbClient = class _GdbClient {
|
|
|
888
906
|
try {
|
|
889
907
|
return await this.executeRequest(method, path, options);
|
|
890
908
|
} catch (err) {
|
|
891
|
-
if (err instanceof GdbClientError && err
|
|
909
|
+
if (err instanceof GdbClientError && _GdbClient.isTokenError(err) && this.canRefresh()) {
|
|
892
910
|
const refreshed = await this.performTokenRefresh();
|
|
893
911
|
if (refreshed) {
|
|
894
912
|
return await this.executeRequest(method, path, options);
|
|
@@ -917,7 +935,7 @@ var GdbClient = class _GdbClient {
|
|
|
917
935
|
try {
|
|
918
936
|
return await this.executeRawRequest(method, path, options);
|
|
919
937
|
} catch (err) {
|
|
920
|
-
if (err instanceof GdbClientError && err
|
|
938
|
+
if (err instanceof GdbClientError && _GdbClient.isTokenError(err) && this.canRefresh()) {
|
|
921
939
|
const refreshed = await this.performTokenRefresh();
|
|
922
940
|
if (refreshed) {
|
|
923
941
|
return await this.executeRawRequest(method, path, options);
|
|
@@ -937,31 +955,6 @@ var GdbClientError = class extends Error {
|
|
|
937
955
|
};
|
|
938
956
|
|
|
939
957
|
// src/helpers.ts
|
|
940
|
-
var SCOPES_HELP_NOTES = [
|
|
941
|
-
"Valid scopes:",
|
|
942
|
-
" read:entities, write:entities, read:subscriptions, write:subscriptions,",
|
|
943
|
-
" read:registrations, write:registrations, read:rules, write:rules,",
|
|
944
|
-
" read:custom-data-models, write:custom-data-models,",
|
|
945
|
-
" admin:users, admin:tenants, admin:policies, admin:oauth-clients,",
|
|
946
|
-
" admin:api-keys, admin:metrics",
|
|
947
|
-
"",
|
|
948
|
-
"admin:X implies both read:X and write:X.",
|
|
949
|
-
"write:X does NOT imply read:X \u2014 specify both if needed."
|
|
950
|
-
];
|
|
951
|
-
var API_KEY_SCOPES_HELP_NOTES = [
|
|
952
|
-
"Valid scopes:",
|
|
953
|
-
" read:entities, write:entities, read:subscriptions, write:subscriptions,",
|
|
954
|
-
" read:registrations, write:registrations"
|
|
955
|
-
];
|
|
956
|
-
var VALID_PERMISSIONS = /* @__PURE__ */ new Set(["read", "write", "create", "update", "delete"]);
|
|
957
|
-
function parsePermissions(raw) {
|
|
958
|
-
const permissions = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
959
|
-
if (permissions.length === 0 || permissions.some((p) => !VALID_PERMISSIONS.has(p))) {
|
|
960
|
-
printError("--permissions must be a comma-separated list of: read, write, create, update, delete");
|
|
961
|
-
process.exit(1);
|
|
962
|
-
}
|
|
963
|
-
return permissions;
|
|
964
|
-
}
|
|
965
958
|
function resolveOptions(cmd) {
|
|
966
959
|
const opts = cmd.optsWithGlobals();
|
|
967
960
|
const config = loadConfig(opts.profile);
|
|
@@ -1027,6 +1020,8 @@ function withErrorHandler(fn) {
|
|
|
1027
1020
|
}
|
|
1028
1021
|
if (err instanceof GdbClientError && err.status === 401) {
|
|
1029
1022
|
printError("Authentication failed. Please re-authenticate (e.g., `geonic auth login` or check your API key).");
|
|
1023
|
+
} else if (err instanceof GdbClientError && err.status === 403 && /not assigned to any tenant|invalid token/i.test(err.message)) {
|
|
1024
|
+
printError("Authentication failed. Please re-authenticate (e.g., `geonic auth login` or check your API key).");
|
|
1030
1025
|
} else if (err instanceof GdbClientError && err.status === 403) {
|
|
1031
1026
|
const detail = (err.ngsiError?.detail ?? err.ngsiError?.description ?? "").toLowerCase();
|
|
1032
1027
|
if (detail.includes("entity type") || detail.includes("allowedentitytypes")) {
|
|
@@ -1335,16 +1330,16 @@ function addMeOAuthClientsSubcommand(me) {
|
|
|
1335
1330
|
command: "geonic me oauth-clients list"
|
|
1336
1331
|
}
|
|
1337
1332
|
]);
|
|
1338
|
-
const create = oauthClients.command("create [json]").description("Create a new OAuth client").option("--name <name>", "Client name").option("--
|
|
1333
|
+
const create = oauthClients.command("create [json]").description("Create a new OAuth client").option("--name <name>", "Client name").option("--policy <policyId>", "Policy ID to attach").option("--save", "Save credentials to config for automatic re-authentication").action(
|
|
1339
1334
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
1340
1335
|
const opts = cmd.opts();
|
|
1341
1336
|
let body;
|
|
1342
1337
|
if (json) {
|
|
1343
1338
|
body = await parseJsonInput(json);
|
|
1344
|
-
} else if (opts.name || opts.
|
|
1339
|
+
} else if (opts.name || opts.policy) {
|
|
1345
1340
|
const payload = {};
|
|
1346
|
-
if (opts.name) payload.
|
|
1347
|
-
if (opts.
|
|
1341
|
+
if (opts.name) payload.name = opts.name;
|
|
1342
|
+
if (opts.policy) payload.policyId = opts.policy;
|
|
1348
1343
|
body = payload;
|
|
1349
1344
|
} else {
|
|
1350
1345
|
body = await parseJsonInput();
|
|
@@ -1367,8 +1362,7 @@ function addMeOAuthClientsSubcommand(me) {
|
|
|
1367
1362
|
const tokenResult = await clientCredentialsGrant({
|
|
1368
1363
|
baseUrl,
|
|
1369
1364
|
clientId,
|
|
1370
|
-
clientSecret
|
|
1371
|
-
scope: data.allowedScopes?.join(" ")
|
|
1365
|
+
clientSecret
|
|
1372
1366
|
});
|
|
1373
1367
|
const config = loadConfig(globalOpts.profile);
|
|
1374
1368
|
config.clientId = clientId;
|
|
@@ -1386,11 +1380,18 @@ function addMeOAuthClientsSubcommand(me) {
|
|
|
1386
1380
|
printSuccess("OAuth client created.");
|
|
1387
1381
|
})
|
|
1388
1382
|
);
|
|
1389
|
-
addNotes(create,
|
|
1383
|
+
addNotes(create, [
|
|
1384
|
+
"Use --policy to attach an existing XACML policy to the OAuth client.",
|
|
1385
|
+
"Manage policies with `geonic admin policies` commands."
|
|
1386
|
+
]);
|
|
1390
1387
|
addExamples(create, [
|
|
1391
1388
|
{
|
|
1392
1389
|
description: "Create an OAuth client with flags",
|
|
1393
|
-
command: "geonic me oauth-clients create --name my-ci-bot
|
|
1390
|
+
command: "geonic me oauth-clients create --name my-ci-bot"
|
|
1391
|
+
},
|
|
1392
|
+
{
|
|
1393
|
+
description: "Create with a policy attached",
|
|
1394
|
+
command: "geonic me oauth-clients create --name my-ci-bot --policy <policy-id>"
|
|
1394
1395
|
},
|
|
1395
1396
|
{
|
|
1396
1397
|
description: "Create and save credentials for auto-reauth",
|
|
@@ -1398,7 +1399,53 @@ function addMeOAuthClientsSubcommand(me) {
|
|
|
1398
1399
|
},
|
|
1399
1400
|
{
|
|
1400
1401
|
description: "Create an OAuth client from JSON",
|
|
1401
|
-
command: `geonic me oauth-clients create '{"
|
|
1402
|
+
command: `geonic me oauth-clients create '{"name":"my-bot","policyId":"<policy-id>"}'`
|
|
1403
|
+
}
|
|
1404
|
+
]);
|
|
1405
|
+
const update = oauthClients.command("update <clientId> [json]").description("Update an OAuth client").option("--name <name>", "Client name").option("--description <desc>", "Client description").option("--policy-id <policyId>", "Policy ID to attach (use 'null' to unbind)").option("--active", "Activate the OAuth client").option("--inactive", "Deactivate the OAuth client").action(
|
|
1406
|
+
withErrorHandler(async (clientId, json, _opts, cmd) => {
|
|
1407
|
+
const opts = cmd.opts();
|
|
1408
|
+
let body;
|
|
1409
|
+
if (json) {
|
|
1410
|
+
body = await parseJsonInput(json);
|
|
1411
|
+
} else if (opts.name || opts.description || opts.policyId !== void 0 || opts.active || opts.inactive) {
|
|
1412
|
+
const payload = {};
|
|
1413
|
+
if (opts.name) payload.name = opts.name;
|
|
1414
|
+
if (opts.description) payload.description = opts.description;
|
|
1415
|
+
if (opts.policyId !== void 0) payload.policyId = opts.policyId === "null" ? null : opts.policyId;
|
|
1416
|
+
if (opts.active) payload.isActive = true;
|
|
1417
|
+
if (opts.inactive) payload.isActive = false;
|
|
1418
|
+
body = payload;
|
|
1419
|
+
} else {
|
|
1420
|
+
body = await parseJsonInput();
|
|
1421
|
+
}
|
|
1422
|
+
const client = createClient(cmd);
|
|
1423
|
+
const format = getFormat(cmd);
|
|
1424
|
+
const response = await client.rawRequest(
|
|
1425
|
+
"PATCH",
|
|
1426
|
+
`/me/oauth-clients/${encodeURIComponent(String(clientId))}`,
|
|
1427
|
+
{ body }
|
|
1428
|
+
);
|
|
1429
|
+
outputResponse(response, format);
|
|
1430
|
+
printSuccess("OAuth client updated.");
|
|
1431
|
+
})
|
|
1432
|
+
);
|
|
1433
|
+
addExamples(update, [
|
|
1434
|
+
{
|
|
1435
|
+
description: "Rename an OAuth client",
|
|
1436
|
+
command: "geonic me oauth-clients update <client-id> --name new-name"
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
description: "Attach a policy",
|
|
1440
|
+
command: "geonic me oauth-clients update <client-id> --policy-id <policy-id>"
|
|
1441
|
+
},
|
|
1442
|
+
{
|
|
1443
|
+
description: "Unbind policy",
|
|
1444
|
+
command: "geonic me oauth-clients update <client-id> --policy-id null"
|
|
1445
|
+
},
|
|
1446
|
+
{
|
|
1447
|
+
description: "Deactivate an OAuth client",
|
|
1448
|
+
command: "geonic me oauth-clients update <client-id> --inactive"
|
|
1402
1449
|
}
|
|
1403
1450
|
]);
|
|
1404
1451
|
const del = oauthClients.command("delete <id>").description("Delete an OAuth client").action(
|
|
@@ -1417,9 +1464,67 @@ function addMeOAuthClientsSubcommand(me) {
|
|
|
1417
1464
|
command: "geonic me oauth-clients delete <client-id>"
|
|
1418
1465
|
}
|
|
1419
1466
|
]);
|
|
1467
|
+
const regenerateSecret = oauthClients.command("regenerate-secret <clientId>").description("Regenerate the client secret of an OAuth client").action(
|
|
1468
|
+
withErrorHandler(async (clientId, _opts, cmd) => {
|
|
1469
|
+
const client = createClient(cmd);
|
|
1470
|
+
const format = getFormat(cmd);
|
|
1471
|
+
const response = await client.rawRequest(
|
|
1472
|
+
"POST",
|
|
1473
|
+
`/me/oauth-clients/${encodeURIComponent(String(clientId))}/regenerate-secret`
|
|
1474
|
+
);
|
|
1475
|
+
printWarning("Save the new clientSecret now \u2014 it will not be shown again.");
|
|
1476
|
+
outputResponse(response, format);
|
|
1477
|
+
printSuccess("OAuth client secret regenerated.");
|
|
1478
|
+
})
|
|
1479
|
+
);
|
|
1480
|
+
addExamples(regenerateSecret, [
|
|
1481
|
+
{
|
|
1482
|
+
description: "Regenerate client secret",
|
|
1483
|
+
command: "geonic me oauth-clients regenerate-secret <client-id>"
|
|
1484
|
+
}
|
|
1485
|
+
]);
|
|
1420
1486
|
}
|
|
1421
1487
|
|
|
1422
1488
|
// src/commands/me-api-keys.ts
|
|
1489
|
+
function cleanApiKeyData(data) {
|
|
1490
|
+
if (Array.isArray(data)) return data.map(cleanApiKeyData);
|
|
1491
|
+
if (typeof data !== "object" || data === null) return data;
|
|
1492
|
+
const obj = { ...data };
|
|
1493
|
+
if (obj.key === "******") delete obj.key;
|
|
1494
|
+
return obj;
|
|
1495
|
+
}
|
|
1496
|
+
function handleSaveKey(data, cmd) {
|
|
1497
|
+
const globalOpts = resolveOptions(cmd);
|
|
1498
|
+
const key = data.key;
|
|
1499
|
+
if (!key) {
|
|
1500
|
+
printError("Response missing key. API key was created, but it could not be saved.");
|
|
1501
|
+
process.exitCode = 1;
|
|
1502
|
+
return false;
|
|
1503
|
+
}
|
|
1504
|
+
try {
|
|
1505
|
+
const config = loadConfig(globalOpts.profile);
|
|
1506
|
+
config.apiKey = key;
|
|
1507
|
+
saveConfig(config, globalOpts.profile);
|
|
1508
|
+
console.error("API key saved to config. X-Api-Key header will be sent automatically.");
|
|
1509
|
+
return true;
|
|
1510
|
+
} catch (err) {
|
|
1511
|
+
printError(`Failed to save API key to config: ${err instanceof Error ? err.message : String(err)}`);
|
|
1512
|
+
printApiKeyBox(key);
|
|
1513
|
+
process.exitCode = 1;
|
|
1514
|
+
return false;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
function showKeyResult(data, save, cmd) {
|
|
1518
|
+
const key = data.key;
|
|
1519
|
+
if (!key) {
|
|
1520
|
+
printError("Response missing key. The new API key value was not returned.");
|
|
1521
|
+
process.exitCode = 1;
|
|
1522
|
+
return false;
|
|
1523
|
+
}
|
|
1524
|
+
if (save) return handleSaveKey(data, cmd);
|
|
1525
|
+
printApiKeyBox(key);
|
|
1526
|
+
return true;
|
|
1527
|
+
}
|
|
1423
1528
|
function addMeApiKeysSubcommand(me) {
|
|
1424
1529
|
const apiKeys = me.command("api-keys").description("Manage your API keys");
|
|
1425
1530
|
const list = apiKeys.command("list").description("List your API keys").action(
|
|
@@ -1427,7 +1532,9 @@ function addMeApiKeysSubcommand(me) {
|
|
|
1427
1532
|
const client = createClient(cmd);
|
|
1428
1533
|
const format = getFormat(cmd);
|
|
1429
1534
|
const response = await client.rawRequest("GET", "/me/api-keys");
|
|
1535
|
+
response.data = cleanApiKeyData(response.data);
|
|
1430
1536
|
outputResponse(response, format);
|
|
1537
|
+
console.error("\u203B API \u30AD\u30FC\u5024\u306F\u4F5C\u6210\u6642 (create) \u307E\u305F\u306F\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u6642 (refresh) \u306B\u306E\u307F\u8868\u793A\u3055\u308C\u307E\u3059\u3002");
|
|
1431
1538
|
})
|
|
1432
1539
|
);
|
|
1433
1540
|
addExamples(list, [
|
|
@@ -1436,7 +1543,7 @@ function addMeApiKeysSubcommand(me) {
|
|
|
1436
1543
|
command: "geonic me api-keys list"
|
|
1437
1544
|
}
|
|
1438
1545
|
]);
|
|
1439
|
-
const create = apiKeys.command("create [json]").description("Create a new API key").option("--name <name>", "Key name").option("--
|
|
1546
|
+
const create = apiKeys.command("create [json]").description("Create a new API key").option("--name <name>", "Key name").option("--policy <policyId>", "Policy ID to attach").option("--origins <origins>", "Allowed origins (comma-separated)").option("--rate-limit <n>", "Rate limit per minute").option("--dpop-required", "Require DPoP token binding").option("--save", "Save the API key to config for automatic use").action(
|
|
1440
1547
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
1441
1548
|
const opts = cmd.opts();
|
|
1442
1549
|
if (opts.origins !== void 0) {
|
|
@@ -1449,14 +1556,12 @@ function addMeApiKeysSubcommand(me) {
|
|
|
1449
1556
|
let body;
|
|
1450
1557
|
if (json) {
|
|
1451
1558
|
body = await parseJsonInput(json);
|
|
1452
|
-
} else if (opts.name || opts.
|
|
1559
|
+
} else if (opts.name || opts.policy || opts.origins || opts.rateLimit || opts.dpopRequired !== void 0) {
|
|
1453
1560
|
const payload = {};
|
|
1454
1561
|
if (opts.name) payload.name = opts.name;
|
|
1455
|
-
if (opts.
|
|
1562
|
+
if (opts.policy) payload.policyId = opts.policy;
|
|
1456
1563
|
if (opts.origins) payload.allowedOrigins = opts.origins.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1457
|
-
if (opts.entityTypes) payload.allowedEntityTypes = opts.entityTypes.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1458
1564
|
if (opts.dpopRequired !== void 0) payload.dpopRequired = opts.dpopRequired;
|
|
1459
|
-
if (opts.permissions) payload.permissions = parsePermissions(opts.permissions);
|
|
1460
1565
|
if (opts.rateLimit) {
|
|
1461
1566
|
const raw = opts.rateLimit.trim();
|
|
1462
1567
|
if (!/^\d+$/.test(raw)) {
|
|
@@ -1485,41 +1590,19 @@ function addMeApiKeysSubcommand(me) {
|
|
|
1485
1590
|
const format = getFormat(cmd);
|
|
1486
1591
|
const response = await client.rawRequest("POST", "/me/api-keys", { body });
|
|
1487
1592
|
const data = response.data;
|
|
1488
|
-
|
|
1489
|
-
const globalOpts = resolveOptions(cmd);
|
|
1490
|
-
const key = data.key;
|
|
1491
|
-
if (!key) {
|
|
1492
|
-
printError("Response missing key. API key was created, but it could not be saved.");
|
|
1493
|
-
outputResponse(response, format);
|
|
1494
|
-
process.exitCode = 1;
|
|
1495
|
-
return;
|
|
1496
|
-
}
|
|
1497
|
-
const config = loadConfig(globalOpts.profile);
|
|
1498
|
-
config.apiKey = key;
|
|
1499
|
-
saveConfig(config, globalOpts.profile);
|
|
1500
|
-
console.error("API key saved to config. X-Api-Key header will be sent automatically.");
|
|
1501
|
-
} else {
|
|
1502
|
-
printWarning("Save the API key now \u2014 it will not be shown again. Use --save to store it automatically.");
|
|
1503
|
-
}
|
|
1593
|
+
const ok = showKeyResult(data, !!opts.save, cmd);
|
|
1504
1594
|
outputResponse(response, format);
|
|
1505
|
-
console.error("API key created.");
|
|
1595
|
+
if (ok) console.error("API key created.");
|
|
1506
1596
|
})
|
|
1507
1597
|
);
|
|
1508
1598
|
addNotes(create, [
|
|
1509
|
-
|
|
1510
|
-
""
|
|
1511
|
-
"Valid permissions: read, write, create, update, delete",
|
|
1512
|
-
" write = create + update + delete",
|
|
1513
|
-
" Permissions auto-generate XACML policies (allowedEntityTypes respected)."
|
|
1599
|
+
"Use --policy to attach an existing XACML policy to the API key.",
|
|
1600
|
+
"Manage policies with `geonic admin policies` commands."
|
|
1514
1601
|
]);
|
|
1515
1602
|
addExamples(create, [
|
|
1516
1603
|
{
|
|
1517
|
-
description: "Create an API key with
|
|
1518
|
-
command: "geonic me api-keys create --name my-app --
|
|
1519
|
-
},
|
|
1520
|
-
{
|
|
1521
|
-
description: "Create with permissions (auto-generates XACML policy)",
|
|
1522
|
-
command: "geonic me api-keys create --name my-app --permissions read,write --save"
|
|
1604
|
+
description: "Create an API key with a policy",
|
|
1605
|
+
command: "geonic me api-keys create --name my-app --policy <policy-id>"
|
|
1523
1606
|
},
|
|
1524
1607
|
{
|
|
1525
1608
|
description: "Create and save API key to config",
|
|
@@ -1527,7 +1610,7 @@ function addMeApiKeysSubcommand(me) {
|
|
|
1527
1610
|
},
|
|
1528
1611
|
{
|
|
1529
1612
|
description: "Create an API key from JSON",
|
|
1530
|
-
command: `geonic me api-keys create '{"name":"my-app","
|
|
1613
|
+
command: `geonic me api-keys create '{"name":"my-app","policyId":"<policy-id>"}'`
|
|
1531
1614
|
},
|
|
1532
1615
|
{
|
|
1533
1616
|
description: "Create an API key with rate limiting",
|
|
@@ -1538,6 +1621,106 @@ function addMeApiKeysSubcommand(me) {
|
|
|
1538
1621
|
command: "geonic me api-keys create --name my-app --dpop-required"
|
|
1539
1622
|
}
|
|
1540
1623
|
]);
|
|
1624
|
+
const refresh = apiKeys.command("refresh <keyId>").description("Refresh (rotate) an API key \u2014 generates a new key value").option("--save", "Save the new API key to config for automatic use").action(
|
|
1625
|
+
withErrorHandler(async (keyId, _opts, cmd) => {
|
|
1626
|
+
const opts = cmd.opts();
|
|
1627
|
+
const client = createClient(cmd);
|
|
1628
|
+
const format = getFormat(cmd);
|
|
1629
|
+
const response = await client.rawRequest(
|
|
1630
|
+
"POST",
|
|
1631
|
+
`/me/api-keys/${encodeURIComponent(String(keyId))}/refresh`
|
|
1632
|
+
);
|
|
1633
|
+
const data = response.data;
|
|
1634
|
+
const ok = showKeyResult(data, !!opts.save, cmd);
|
|
1635
|
+
outputResponse(response, format);
|
|
1636
|
+
if (ok) console.error("API key refreshed.");
|
|
1637
|
+
})
|
|
1638
|
+
);
|
|
1639
|
+
addNotes(refresh, [
|
|
1640
|
+
"Refreshing generates a new key value while keeping keyId, name, and policy settings.",
|
|
1641
|
+
"The previous key value is immediately invalidated."
|
|
1642
|
+
]);
|
|
1643
|
+
addExamples(refresh, [
|
|
1644
|
+
{
|
|
1645
|
+
description: "Refresh an API key",
|
|
1646
|
+
command: "geonic me api-keys refresh <key-id>"
|
|
1647
|
+
},
|
|
1648
|
+
{
|
|
1649
|
+
description: "Refresh and save new key to config",
|
|
1650
|
+
command: "geonic me api-keys refresh <key-id> --save"
|
|
1651
|
+
}
|
|
1652
|
+
]);
|
|
1653
|
+
const update = apiKeys.command("update <keyId> [json]").description("Update an API key").option("--name <name>", "Key name").option("--policy-id <policyId>", "Policy ID to attach (use 'null' to unbind)").option("--origins <origins>", "Allowed origins (comma-separated)").option("--rate-limit <n>", "Rate limit (requests per minute)").option("--dpop-required", "Require DPoP token binding").option("--no-dpop-required", "Disable DPoP requirement").option("--active", "Activate the API key").option("--inactive", "Deactivate the API key").action(
|
|
1654
|
+
withErrorHandler(async (keyId, json, _opts, cmd) => {
|
|
1655
|
+
const opts = cmd.opts();
|
|
1656
|
+
if (opts.origins !== void 0) {
|
|
1657
|
+
const parsed = opts.origins.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1658
|
+
if (parsed.length === 0) {
|
|
1659
|
+
printError("allowedOrigins must contain at least 1 item. Use '*' to allow all origins.");
|
|
1660
|
+
process.exit(1);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
let body;
|
|
1664
|
+
if (json) {
|
|
1665
|
+
body = await parseJsonInput(json);
|
|
1666
|
+
} else if (opts.name || opts.policyId !== void 0 || opts.origins !== void 0 || opts.rateLimit || opts.dpopRequired !== void 0 || opts.active || opts.inactive) {
|
|
1667
|
+
const payload = {};
|
|
1668
|
+
if (opts.name) payload.name = opts.name;
|
|
1669
|
+
if (opts.policyId !== void 0) payload.policyId = opts.policyId === "null" ? null : opts.policyId;
|
|
1670
|
+
if (opts.origins !== void 0) payload.allowedOrigins = opts.origins.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1671
|
+
if (opts.dpopRequired !== void 0) payload.dpopRequired = opts.dpopRequired;
|
|
1672
|
+
if (opts.rateLimit) {
|
|
1673
|
+
const raw = opts.rateLimit.trim();
|
|
1674
|
+
if (!/^\d+$/.test(raw)) {
|
|
1675
|
+
printError("--rate-limit must be a positive integer.");
|
|
1676
|
+
process.exit(1);
|
|
1677
|
+
}
|
|
1678
|
+
const perMinute = Number(raw);
|
|
1679
|
+
if (perMinute <= 0) {
|
|
1680
|
+
printError("--rate-limit must be a positive integer.");
|
|
1681
|
+
process.exit(1);
|
|
1682
|
+
}
|
|
1683
|
+
payload.rateLimit = { perMinute };
|
|
1684
|
+
}
|
|
1685
|
+
if (opts.active) payload.isActive = true;
|
|
1686
|
+
if (opts.inactive) payload.isActive = false;
|
|
1687
|
+
body = payload;
|
|
1688
|
+
} else {
|
|
1689
|
+
body = await parseJsonInput();
|
|
1690
|
+
}
|
|
1691
|
+
const client = createClient(cmd);
|
|
1692
|
+
const format = getFormat(cmd);
|
|
1693
|
+
const response = await client.rawRequest(
|
|
1694
|
+
"PATCH",
|
|
1695
|
+
`/me/api-keys/${encodeURIComponent(String(keyId))}`,
|
|
1696
|
+
{ body }
|
|
1697
|
+
);
|
|
1698
|
+
outputResponse(response, format);
|
|
1699
|
+
console.error("API key updated.");
|
|
1700
|
+
})
|
|
1701
|
+
);
|
|
1702
|
+
addExamples(update, [
|
|
1703
|
+
{
|
|
1704
|
+
description: "Rename an API key",
|
|
1705
|
+
command: "geonic me api-keys update <key-id> --name new-name"
|
|
1706
|
+
},
|
|
1707
|
+
{
|
|
1708
|
+
description: "Attach a policy",
|
|
1709
|
+
command: "geonic me api-keys update <key-id> --policy-id <policy-id>"
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
description: "Unbind policy",
|
|
1713
|
+
command: "geonic me api-keys update <key-id> --policy-id null"
|
|
1714
|
+
},
|
|
1715
|
+
{
|
|
1716
|
+
description: "Deactivate an API key",
|
|
1717
|
+
command: "geonic me api-keys update <key-id> --inactive"
|
|
1718
|
+
},
|
|
1719
|
+
{
|
|
1720
|
+
description: "Update from JSON",
|
|
1721
|
+
command: `geonic me api-keys update <key-id> '{"name":"new-name","rateLimit":{"perMinute":60}}'`
|
|
1722
|
+
}
|
|
1723
|
+
]);
|
|
1541
1724
|
const del = apiKeys.command("delete <keyId>").description("Delete an API key").action(
|
|
1542
1725
|
withErrorHandler(async (keyId, _opts, cmd) => {
|
|
1543
1726
|
const client = createClient(cmd);
|
|
@@ -1556,6 +1739,149 @@ function addMeApiKeysSubcommand(me) {
|
|
|
1556
1739
|
]);
|
|
1557
1740
|
}
|
|
1558
1741
|
|
|
1742
|
+
// src/commands/me-policies.ts
|
|
1743
|
+
function addMePoliciesSubcommand(me) {
|
|
1744
|
+
const policies = me.command("policies").description("Manage your personal XACML policies");
|
|
1745
|
+
const list = policies.command("list").description("List your personal policies").action(
|
|
1746
|
+
withErrorHandler(async (_opts, cmd) => {
|
|
1747
|
+
const client = createClient(cmd);
|
|
1748
|
+
const format = getFormat(cmd);
|
|
1749
|
+
const response = await client.rawRequest("GET", "/me/policies");
|
|
1750
|
+
outputResponse(response, format);
|
|
1751
|
+
})
|
|
1752
|
+
);
|
|
1753
|
+
addExamples(list, [
|
|
1754
|
+
{
|
|
1755
|
+
description: "List your personal policies",
|
|
1756
|
+
command: "geonic me policies list"
|
|
1757
|
+
}
|
|
1758
|
+
]);
|
|
1759
|
+
const get = policies.command("get <policyId>").description("Get a personal policy by ID").action(
|
|
1760
|
+
withErrorHandler(async (policyId, _opts, cmd) => {
|
|
1761
|
+
const client = createClient(cmd);
|
|
1762
|
+
const format = getFormat(cmd);
|
|
1763
|
+
const response = await client.rawRequest(
|
|
1764
|
+
"GET",
|
|
1765
|
+
`/me/policies/${encodeURIComponent(String(policyId))}`
|
|
1766
|
+
);
|
|
1767
|
+
outputResponse(response, format);
|
|
1768
|
+
})
|
|
1769
|
+
);
|
|
1770
|
+
addExamples(get, [
|
|
1771
|
+
{
|
|
1772
|
+
description: "Get a personal policy by ID",
|
|
1773
|
+
command: "geonic me policies get <policy-id>"
|
|
1774
|
+
}
|
|
1775
|
+
]);
|
|
1776
|
+
const create = policies.command("create [json]").description(
|
|
1777
|
+
`Create a personal XACML policy
|
|
1778
|
+
|
|
1779
|
+
Constraints (enforced server-side):
|
|
1780
|
+
- priority is fixed at 100 (user role minimum)
|
|
1781
|
+
- scope is 'personal' \u2014 not applied tenant-wide
|
|
1782
|
+
- target is required
|
|
1783
|
+
- data API paths only (/v2/**, /ngsi-ld/** etc.)
|
|
1784
|
+
|
|
1785
|
+
Example \u2014 GET-only policy for /v2/**:
|
|
1786
|
+
{
|
|
1787
|
+
"policyId": "my-readonly",
|
|
1788
|
+
"target": {
|
|
1789
|
+
"resources": [{"attributeId": "path", "matchValue": "/v2/**", "matchFunction": "glob"}]
|
|
1790
|
+
},
|
|
1791
|
+
"rules": [
|
|
1792
|
+
{"ruleId": "allow-get", "effect": "Permit", "target": {"actions": [{"attributeId": "method", "matchValue": "GET"}]}},
|
|
1793
|
+
{"ruleId": "deny-others", "effect": "Deny"}
|
|
1794
|
+
]
|
|
1795
|
+
}`
|
|
1796
|
+
).option("--policy-id <id>", "Policy ID (auto-generated UUID if omitted)").option("--description <text>", "Policy description").action(
|
|
1797
|
+
withErrorHandler(async (json, _opts, cmd) => {
|
|
1798
|
+
const opts = cmd.opts();
|
|
1799
|
+
let body;
|
|
1800
|
+
if (json) {
|
|
1801
|
+
body = await parseJsonInput(json);
|
|
1802
|
+
} else if (opts.policyId || opts.description) {
|
|
1803
|
+
const payload = {};
|
|
1804
|
+
if (opts.policyId) payload.policyId = opts.policyId;
|
|
1805
|
+
if (opts.description) payload.description = opts.description;
|
|
1806
|
+
body = payload;
|
|
1807
|
+
} else {
|
|
1808
|
+
body = await parseJsonInput();
|
|
1809
|
+
}
|
|
1810
|
+
const client = createClient(cmd);
|
|
1811
|
+
const format = getFormat(cmd);
|
|
1812
|
+
const response = await client.rawRequest("POST", "/me/policies", { body });
|
|
1813
|
+
outputResponse(response, format);
|
|
1814
|
+
printSuccess("Policy created.");
|
|
1815
|
+
})
|
|
1816
|
+
);
|
|
1817
|
+
addNotes(create, [
|
|
1818
|
+
"priority is always set to 100 by the server regardless of the value you specify.",
|
|
1819
|
+
"Bind the policy to an API key or OAuth client with `geonic me api-keys update --policy-id` or `geonic me oauth-clients update --policy-id`."
|
|
1820
|
+
]);
|
|
1821
|
+
addExamples(create, [
|
|
1822
|
+
{
|
|
1823
|
+
description: "Create a GET-only policy from inline JSON",
|
|
1824
|
+
command: `geonic me policies create '{"policyId":"my-readonly","target":{"resources":[{"attributeId":"path","matchValue":"/v2/**","matchFunction":"glob"}]},"rules":[{"ruleId":"allow-get","effect":"Permit","target":{"actions":[{"attributeId":"method","matchValue":"GET"}]}},{"ruleId":"deny-others","effect":"Deny"}]}'`
|
|
1825
|
+
},
|
|
1826
|
+
{
|
|
1827
|
+
description: "Create from a JSON file",
|
|
1828
|
+
command: "geonic me policies create @policy.json"
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
description: "Create from stdin",
|
|
1832
|
+
command: "cat policy.json | geonic me policies create"
|
|
1833
|
+
}
|
|
1834
|
+
]);
|
|
1835
|
+
const update = policies.command("update <policyId> [json]").description("Update a personal policy (partial update)").option("--description <text>", "Policy description").action(
|
|
1836
|
+
withErrorHandler(async (policyId, json, _opts, cmd) => {
|
|
1837
|
+
const opts = cmd.opts();
|
|
1838
|
+
let body;
|
|
1839
|
+
if (json) {
|
|
1840
|
+
body = await parseJsonInput(json);
|
|
1841
|
+
} else if (opts.description) {
|
|
1842
|
+
body = { description: opts.description };
|
|
1843
|
+
} else {
|
|
1844
|
+
body = await parseJsonInput();
|
|
1845
|
+
}
|
|
1846
|
+
const client = createClient(cmd);
|
|
1847
|
+
const format = getFormat(cmd);
|
|
1848
|
+
const response = await client.rawRequest(
|
|
1849
|
+
"PATCH",
|
|
1850
|
+
`/me/policies/${encodeURIComponent(String(policyId))}`,
|
|
1851
|
+
{ body }
|
|
1852
|
+
);
|
|
1853
|
+
outputResponse(response, format);
|
|
1854
|
+
printSuccess("Policy updated.");
|
|
1855
|
+
})
|
|
1856
|
+
);
|
|
1857
|
+
addExamples(update, [
|
|
1858
|
+
{
|
|
1859
|
+
description: "Update policy rules",
|
|
1860
|
+
command: `geonic me policies update <policy-id> '{"rules":[{"ruleId":"allow-get","effect":"Permit"}]}'`
|
|
1861
|
+
},
|
|
1862
|
+
{
|
|
1863
|
+
description: "Update from a JSON file",
|
|
1864
|
+
command: "geonic me policies update <policy-id> @patch.json"
|
|
1865
|
+
}
|
|
1866
|
+
]);
|
|
1867
|
+
const del = policies.command("delete <policyId>").description("Delete a personal policy").action(
|
|
1868
|
+
withErrorHandler(async (policyId, _opts, cmd) => {
|
|
1869
|
+
const client = createClient(cmd);
|
|
1870
|
+
await client.rawRequest(
|
|
1871
|
+
"DELETE",
|
|
1872
|
+
`/me/policies/${encodeURIComponent(String(policyId))}`
|
|
1873
|
+
);
|
|
1874
|
+
printSuccess("Policy deleted.");
|
|
1875
|
+
})
|
|
1876
|
+
);
|
|
1877
|
+
addExamples(del, [
|
|
1878
|
+
{
|
|
1879
|
+
description: "Delete a personal policy",
|
|
1880
|
+
command: "geonic me policies delete <policy-id>"
|
|
1881
|
+
}
|
|
1882
|
+
]);
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1559
1885
|
// src/commands/auth.ts
|
|
1560
1886
|
function createLoginCommand() {
|
|
1561
1887
|
return new Command("login").description("Authenticate and save token").option("--client-credentials", "Use OAuth 2.0 Client Credentials flow").option("--client-id <id>", "OAuth client ID").option("--client-secret <secret>", "OAuth client secret").option("--scope <scopes>", "OAuth scopes (space-separated)").option("--tenant-id <id>", "Tenant ID for scoped authentication").action(
|
|
@@ -1624,10 +1950,10 @@ function createLoginCommand() {
|
|
|
1624
1950
|
process.exit(1);
|
|
1625
1951
|
}
|
|
1626
1952
|
const availableTenants = data.availableTenants;
|
|
1627
|
-
|
|
1953
|
+
let finalTenantId = data.tenantId;
|
|
1628
1954
|
if (availableTenants && availableTenants.length > 1 && !loginOpts.tenantId) {
|
|
1629
|
-
const selectedTenantId = await promptTenantSelection(availableTenants,
|
|
1630
|
-
if (selectedTenantId && selectedTenantId !==
|
|
1955
|
+
const selectedTenantId = await promptTenantSelection(availableTenants, finalTenantId);
|
|
1956
|
+
if (selectedTenantId && selectedTenantId !== finalTenantId) {
|
|
1631
1957
|
const reloginResponse = await client.rawRequest("POST", "/auth/login", {
|
|
1632
1958
|
body: { email, password, tenantId: selectedTenantId },
|
|
1633
1959
|
skipTenantHeader: true
|
|
@@ -1640,6 +1966,7 @@ function createLoginCommand() {
|
|
|
1640
1966
|
}
|
|
1641
1967
|
token = newToken;
|
|
1642
1968
|
refreshToken = reloginData.refreshToken;
|
|
1969
|
+
finalTenantId = selectedTenantId;
|
|
1643
1970
|
}
|
|
1644
1971
|
}
|
|
1645
1972
|
const config = loadConfig(globalOpts.profile);
|
|
@@ -1649,6 +1976,11 @@ function createLoginCommand() {
|
|
|
1649
1976
|
} else {
|
|
1650
1977
|
delete config.refreshToken;
|
|
1651
1978
|
}
|
|
1979
|
+
if (finalTenantId) {
|
|
1980
|
+
config.service = finalTenantId;
|
|
1981
|
+
} else {
|
|
1982
|
+
delete config.service;
|
|
1983
|
+
}
|
|
1652
1984
|
saveConfig(config, globalOpts.profile);
|
|
1653
1985
|
printSuccess("Login successful. Token saved to config.");
|
|
1654
1986
|
})
|
|
@@ -1887,6 +2219,7 @@ function registerAuthCommands(program2) {
|
|
|
1887
2219
|
]);
|
|
1888
2220
|
addMeOAuthClientsSubcommand(me);
|
|
1889
2221
|
addMeApiKeysSubcommand(me);
|
|
2222
|
+
addMePoliciesSubcommand(me);
|
|
1890
2223
|
program2.addCommand(createLoginCommand(), { hidden: true });
|
|
1891
2224
|
program2.addCommand(createLogoutCommand(), { hidden: true });
|
|
1892
2225
|
const hiddenWhoami = new Command("whoami").description("Display current authenticated user").action(createMeAction());
|
|
@@ -3386,7 +3719,7 @@ function registerPoliciesCommand(parent) {
|
|
|
3386
3719
|
}
|
|
3387
3720
|
]);
|
|
3388
3721
|
const create = policies.command("create [json]").description(
|
|
3389
|
-
'Create a new policy\n\nJSON payload examples:\n\n Allow all entities:\n {\n "description": "Allow all entities",\n "rules": [{"ruleId": "allow-all", "effect": "Permit"}]\n }\n\n Allow GET access to a specific entity type:\n {\n "description": "Allow GET access to Landmark entities",\n "target": {\n "resources": [{"attributeId": "entityType", "matchValue": "Landmark"}],\n "actions": [{"attributeId": "method", "matchValue": "GET"}]\n },\n "rules": [{"ruleId": "permit-get", "effect": "Permit"}]\n }\n\nTarget fields:\n subjects \u2014 attributeId: role, userId, email, tenantId\n resources \u2014 attributeId: path, entityType, entityId, entityOwner, tenantService, servicePath\n actions \u2014 attributeId: method (GET, POST, PATCH, DELETE)\n\nEach element: {attributeId, matchValue, matchFunction?}\n matchFunction: "string-equal" (default) | "string-regexp" | "glob"\n\nPriority:
|
|
3722
|
+
'Create a new policy\n\nJSON payload examples:\n\n Allow all entities:\n {\n "description": "Allow all entities",\n "rules": [{"ruleId": "allow-all", "effect": "Permit"}]\n }\n\n Allow GET access to a specific entity type:\n {\n "description": "Allow GET access to Landmark entities",\n "target": {\n "resources": [{"attributeId": "entityType", "matchValue": "Landmark"}],\n "actions": [{"attributeId": "method", "matchValue": "GET"}]\n },\n "rules": [{"ruleId": "permit-get", "effect": "Permit"}]\n }\n\nTarget fields:\n subjects \u2014 attributeId: role, userId, email, tenantId\n resources \u2014 attributeId: path, entityType, entityId, entityOwner, tenantService, servicePath\n actions \u2014 attributeId: method (GET, POST, PATCH, DELETE)\n\nEach element: {attributeId, matchValue, matchFunction?}\n matchFunction: "string-equal" (default) | "string-regexp" | "glob"\n\nPriority: smaller value = higher precedence (e.g. priority 10 overrides user default at 100).\n tenant_admin: minimum priority 10. user self-service (/me/policies): fixed at 100.\n\nDefault role policies (priority 100):\n user \u2192 /v2/** and /ngsi-ld/** all methods Permit; other data APIs GET only\n api_key \u2192 all Deny, anonymous \u2192 all Deny'
|
|
3390
3723
|
).action(
|
|
3391
3724
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
3392
3725
|
const body = await parseJsonInput(json);
|
|
@@ -3542,7 +3875,7 @@ function registerOAuthClientsCommand(parent) {
|
|
|
3542
3875
|
}
|
|
3543
3876
|
]);
|
|
3544
3877
|
const create = oauthClients.command("create [json]").description(
|
|
3545
|
-
'Create a new OAuth client\n\nJSON payload example:\n {\n "
|
|
3878
|
+
'Create a new OAuth client\n\nJSON payload example:\n {\n "name": "my-app",\n "policyId": "<policy-id>"\n }'
|
|
3546
3879
|
).action(
|
|
3547
3880
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
3548
3881
|
const body = await parseJsonInput(json);
|
|
@@ -3558,7 +3891,7 @@ function registerOAuthClientsCommand(parent) {
|
|
|
3558
3891
|
addExamples(create, [
|
|
3559
3892
|
{
|
|
3560
3893
|
description: "Create with inline JSON",
|
|
3561
|
-
command: `geonic admin oauth-clients create '{"
|
|
3894
|
+
command: `geonic admin oauth-clients create '{"name":"my-app","policyId":"<policy-id>"}'`
|
|
3562
3895
|
},
|
|
3563
3896
|
{
|
|
3564
3897
|
description: "Create from a JSON file",
|
|
@@ -3678,6 +4011,13 @@ function registerCaddeCommand(parent) {
|
|
|
3678
4011
|
}
|
|
3679
4012
|
|
|
3680
4013
|
// src/commands/admin/api-keys.ts
|
|
4014
|
+
function cleanApiKeyData2(data) {
|
|
4015
|
+
if (Array.isArray(data)) return data.map(cleanApiKeyData2);
|
|
4016
|
+
if (typeof data !== "object" || data === null) return data;
|
|
4017
|
+
const obj = { ...data };
|
|
4018
|
+
if (obj.key === "******") delete obj.key;
|
|
4019
|
+
return obj;
|
|
4020
|
+
}
|
|
3681
4021
|
function validateOrigins(body, opts) {
|
|
3682
4022
|
if (opts.origins !== void 0) {
|
|
3683
4023
|
const origins = String(opts.origins).split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -3697,9 +4037,8 @@ function validateOrigins(body, opts) {
|
|
|
3697
4037
|
function buildBodyFromFlags(opts) {
|
|
3698
4038
|
const payload = {};
|
|
3699
4039
|
if (opts.name) payload.name = opts.name;
|
|
3700
|
-
if (opts.
|
|
4040
|
+
if (opts.policy) payload.policyId = opts.policy;
|
|
3701
4041
|
if (opts.origins) payload.allowedOrigins = opts.origins.split(",").map((s) => s.trim()).filter(Boolean);
|
|
3702
|
-
if (opts.entityTypes) payload.allowedEntityTypes = opts.entityTypes.split(",").map((s) => s.trim()).filter(Boolean);
|
|
3703
4042
|
if (opts.rateLimit) {
|
|
3704
4043
|
const raw = String(opts.rateLimit).trim();
|
|
3705
4044
|
if (!/^\d+$/.test(raw)) {
|
|
@@ -3714,10 +4053,41 @@ function buildBodyFromFlags(opts) {
|
|
|
3714
4053
|
payload.rateLimit = { perMinute };
|
|
3715
4054
|
}
|
|
3716
4055
|
if (opts.dpopRequired !== void 0) payload.dpopRequired = opts.dpopRequired;
|
|
3717
|
-
if (opts.permissions) payload.permissions = parsePermissions(opts.permissions);
|
|
3718
4056
|
if (opts.tenantId) payload.tenantId = opts.tenantId;
|
|
3719
4057
|
return payload;
|
|
3720
4058
|
}
|
|
4059
|
+
function handleSaveKey2(data, cmd) {
|
|
4060
|
+
const globalOpts = resolveOptions(cmd);
|
|
4061
|
+
const key = data.key;
|
|
4062
|
+
if (!key) {
|
|
4063
|
+
printError("Response missing key. API key was created, but it could not be saved.");
|
|
4064
|
+
process.exitCode = 1;
|
|
4065
|
+
return false;
|
|
4066
|
+
}
|
|
4067
|
+
try {
|
|
4068
|
+
const config = loadConfig(globalOpts.profile);
|
|
4069
|
+
config.apiKey = key;
|
|
4070
|
+
saveConfig(config, globalOpts.profile);
|
|
4071
|
+
console.error("API key saved to config. X-Api-Key header will be sent automatically.");
|
|
4072
|
+
return true;
|
|
4073
|
+
} catch (err) {
|
|
4074
|
+
printError(`Failed to save API key to config: ${err instanceof Error ? err.message : String(err)}`);
|
|
4075
|
+
printApiKeyBox(key);
|
|
4076
|
+
process.exitCode = 1;
|
|
4077
|
+
return false;
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
function showKeyResult2(data, save, cmd) {
|
|
4081
|
+
const key = data.key;
|
|
4082
|
+
if (!key) {
|
|
4083
|
+
printError("Response missing key. The new API key value was not returned.");
|
|
4084
|
+
process.exitCode = 1;
|
|
4085
|
+
return false;
|
|
4086
|
+
}
|
|
4087
|
+
if (save) return handleSaveKey2(data, cmd);
|
|
4088
|
+
printApiKeyBox(key);
|
|
4089
|
+
return true;
|
|
4090
|
+
}
|
|
3721
4091
|
function registerApiKeysCommand(parent) {
|
|
3722
4092
|
const apiKeys = parent.command("api-keys").description("Manage API keys");
|
|
3723
4093
|
const list = apiKeys.command("list").description("List all API keys").option("--tenant-id <id>", "Filter by tenant ID").action(
|
|
@@ -3730,7 +4100,9 @@ function registerApiKeysCommand(parent) {
|
|
|
3730
4100
|
const response = await client.rawRequest("GET", "/admin/api-keys", {
|
|
3731
4101
|
params
|
|
3732
4102
|
});
|
|
4103
|
+
response.data = cleanApiKeyData2(response.data);
|
|
3733
4104
|
outputResponse(response, format);
|
|
4105
|
+
console.error("\u203B API \u30AD\u30FC\u5024\u306F\u4F5C\u6210\u6642 (create) \u307E\u305F\u306F\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5\u6642 (refresh) \u306B\u306E\u307F\u8868\u793A\u3055\u308C\u307E\u3059\u3002");
|
|
3734
4106
|
})
|
|
3735
4107
|
);
|
|
3736
4108
|
addExamples(list, [
|
|
@@ -3751,6 +4123,7 @@ function registerApiKeysCommand(parent) {
|
|
|
3751
4123
|
"GET",
|
|
3752
4124
|
`/admin/api-keys/${encodeURIComponent(String(keyId))}`
|
|
3753
4125
|
);
|
|
4126
|
+
response.data = cleanApiKeyData2(response.data);
|
|
3754
4127
|
outputResponse(response, format);
|
|
3755
4128
|
})
|
|
3756
4129
|
);
|
|
@@ -3760,14 +4133,14 @@ function registerApiKeysCommand(parent) {
|
|
|
3760
4133
|
command: "geonic admin api-keys get <key-id>"
|
|
3761
4134
|
}
|
|
3762
4135
|
]);
|
|
3763
|
-
const create = apiKeys.command("create [json]").description("Create a new API key").option("--name <name>", "Key name").option("--
|
|
4136
|
+
const create = apiKeys.command("create [json]").description("Create a new API key").option("--name <name>", "Key name").option("--policy <policyId>", "Policy ID to attach").option("--origins <origins>", "Comma-separated origins").option("--rate-limit <n>", "Rate limit per minute").option("--dpop-required", "Require DPoP token binding").option("--tenant-id <id>", "Tenant ID").option("--save", "Save the API key to profile config").action(
|
|
3764
4137
|
withErrorHandler(async (json, _opts, cmd) => {
|
|
3765
4138
|
const opts = cmd.opts();
|
|
3766
4139
|
validateOrigins(void 0, opts);
|
|
3767
4140
|
let body;
|
|
3768
4141
|
if (json) {
|
|
3769
4142
|
body = await parseJsonInput(json);
|
|
3770
|
-
} else if (opts.name || opts.
|
|
4143
|
+
} else if (opts.name || opts.policy || opts.origins || opts.rateLimit || opts.dpopRequired !== void 0 || opts.tenantId) {
|
|
3771
4144
|
body = buildBodyFromFlags(opts);
|
|
3772
4145
|
} else {
|
|
3773
4146
|
body = await parseJsonInput();
|
|
@@ -3779,41 +4152,19 @@ function registerApiKeysCommand(parent) {
|
|
|
3779
4152
|
body
|
|
3780
4153
|
});
|
|
3781
4154
|
const data = response.data;
|
|
3782
|
-
|
|
3783
|
-
const globalOpts = resolveOptions(cmd);
|
|
3784
|
-
const key = data.key;
|
|
3785
|
-
if (!key) {
|
|
3786
|
-
printError("Response missing key. API key was created, but it could not be saved.");
|
|
3787
|
-
outputResponse(response, format);
|
|
3788
|
-
process.exitCode = 1;
|
|
3789
|
-
return;
|
|
3790
|
-
}
|
|
3791
|
-
const config = loadConfig(globalOpts.profile);
|
|
3792
|
-
config.apiKey = key;
|
|
3793
|
-
saveConfig(config, globalOpts.profile);
|
|
3794
|
-
console.error("API key saved to config. X-Api-Key header will be sent automatically.");
|
|
3795
|
-
} else {
|
|
3796
|
-
printWarning("Save the API key now \u2014 it will not be shown again. Use --save to store it automatically.");
|
|
3797
|
-
}
|
|
4155
|
+
const ok = showKeyResult2(data, !!opts.save, cmd);
|
|
3798
4156
|
outputResponse(response, format);
|
|
3799
|
-
console.error("API key created.");
|
|
4157
|
+
if (ok) console.error("API key created.");
|
|
3800
4158
|
})
|
|
3801
4159
|
);
|
|
3802
4160
|
addNotes(create, [
|
|
3803
|
-
|
|
3804
|
-
""
|
|
3805
|
-
"Valid permissions: read, write, create, update, delete",
|
|
3806
|
-
" write = create + update + delete",
|
|
3807
|
-
" Permissions auto-generate XACML policies (allowedEntityTypes respected)."
|
|
4161
|
+
"Use --policy to attach an existing XACML policy to the API key.",
|
|
4162
|
+
"Manage policies with `geonic admin policies` commands."
|
|
3808
4163
|
]);
|
|
3809
4164
|
addExamples(create, [
|
|
3810
4165
|
{
|
|
3811
|
-
description: "Create an API key with
|
|
3812
|
-
command: "geonic admin api-keys create --name my-key --
|
|
3813
|
-
},
|
|
3814
|
-
{
|
|
3815
|
-
description: "Create with permissions (auto-generates XACML policy)",
|
|
3816
|
-
command: "geonic admin api-keys create --name my-key --permissions read,write --origins '*'"
|
|
4166
|
+
description: "Create an API key with a policy",
|
|
4167
|
+
command: "geonic admin api-keys create --name my-key --policy <policy-id> --origins '*'"
|
|
3817
4168
|
},
|
|
3818
4169
|
{
|
|
3819
4170
|
description: "Create an API key with DPoP required",
|
|
@@ -3824,7 +4175,36 @@ function registerApiKeysCommand(parent) {
|
|
|
3824
4175
|
command: "geonic admin api-keys create @key.json --save"
|
|
3825
4176
|
}
|
|
3826
4177
|
]);
|
|
3827
|
-
const
|
|
4178
|
+
const refresh = apiKeys.command("refresh <keyId>").description("Refresh (rotate) an API key \u2014 generates a new key value").option("--save", "Save the new API key to profile config").action(
|
|
4179
|
+
withErrorHandler(async (keyId, _opts, cmd) => {
|
|
4180
|
+
const opts = cmd.opts();
|
|
4181
|
+
const client = createClient(cmd);
|
|
4182
|
+
const format = getFormat(cmd);
|
|
4183
|
+
const response = await client.rawRequest(
|
|
4184
|
+
"POST",
|
|
4185
|
+
`/admin/api-keys/${encodeURIComponent(String(keyId))}/refresh`
|
|
4186
|
+
);
|
|
4187
|
+
const data = response.data;
|
|
4188
|
+
const ok = showKeyResult2(data, !!opts.save, cmd);
|
|
4189
|
+
outputResponse(response, format);
|
|
4190
|
+
if (ok) console.error("API key refreshed.");
|
|
4191
|
+
})
|
|
4192
|
+
);
|
|
4193
|
+
addNotes(refresh, [
|
|
4194
|
+
"Refreshing generates a new key value while keeping keyId, name, and policy settings.",
|
|
4195
|
+
"The previous key value is immediately invalidated."
|
|
4196
|
+
]);
|
|
4197
|
+
addExamples(refresh, [
|
|
4198
|
+
{
|
|
4199
|
+
description: "Refresh an API key",
|
|
4200
|
+
command: "geonic admin api-keys refresh <key-id>"
|
|
4201
|
+
},
|
|
4202
|
+
{
|
|
4203
|
+
description: "Refresh and save new key to config",
|
|
4204
|
+
command: "geonic admin api-keys refresh <key-id> --save"
|
|
4205
|
+
}
|
|
4206
|
+
]);
|
|
4207
|
+
const update = apiKeys.command("update <keyId> [json]").description("Update an API key").option("--name <name>", "Key name").option("--policy <policyId>", "Policy ID to attach").option("--origins <origins>", "Comma-separated origins").option("--rate-limit <n>", "Rate limit per minute").option("--dpop-required", "Require DPoP token binding").option("--no-dpop-required", "Disable DPoP token binding").action(
|
|
3828
4208
|
withErrorHandler(
|
|
3829
4209
|
async (keyId, json, _opts, cmd) => {
|
|
3830
4210
|
const opts = cmd.opts();
|
|
@@ -3832,7 +4212,7 @@ function registerApiKeysCommand(parent) {
|
|
|
3832
4212
|
let body;
|
|
3833
4213
|
if (json) {
|
|
3834
4214
|
body = await parseJsonInput(json);
|
|
3835
|
-
} else if (opts.name || opts.
|
|
4215
|
+
} else if (opts.name || opts.policy || opts.origins || opts.rateLimit || opts.dpopRequired !== void 0) {
|
|
3836
4216
|
body = buildBodyFromFlags(opts);
|
|
3837
4217
|
} else {
|
|
3838
4218
|
body = await parseJsonInput();
|
|
@@ -3851,11 +4231,8 @@ function registerApiKeysCommand(parent) {
|
|
|
3851
4231
|
)
|
|
3852
4232
|
);
|
|
3853
4233
|
addNotes(update, [
|
|
3854
|
-
|
|
3855
|
-
""
|
|
3856
|
-
"Valid permissions: read, write, create, update, delete",
|
|
3857
|
-
" write = create + update + delete",
|
|
3858
|
-
" Permissions auto-generate XACML policies (allowedEntityTypes respected)."
|
|
4234
|
+
"Use --policy to attach an existing XACML policy to the API key.",
|
|
4235
|
+
"Manage policies with `geonic admin policies` commands."
|
|
3859
4236
|
]);
|
|
3860
4237
|
addExamples(update, [
|
|
3861
4238
|
{
|
|
@@ -3863,8 +4240,8 @@ function registerApiKeysCommand(parent) {
|
|
|
3863
4240
|
command: "geonic admin api-keys update <key-id> --name new-name"
|
|
3864
4241
|
},
|
|
3865
4242
|
{
|
|
3866
|
-
description: "
|
|
3867
|
-
command: "geonic admin api-keys update <key-id> --
|
|
4243
|
+
description: "Attach a policy",
|
|
4244
|
+
command: "geonic admin api-keys update <key-id> --policy <policy-id>"
|
|
3868
4245
|
},
|
|
3869
4246
|
{
|
|
3870
4247
|
description: "Enable DPoP requirement",
|