@geolonia/geonicdb-cli 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -1
- package/dist/index.js +70 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,7 +121,47 @@ geonic help [<command>] [<subcommand>]
|
|
|
121
121
|
| `auth nonce` | Get a nonce and PoW challenge for API key authentication |
|
|
122
122
|
| `auth token-exchange` | Exchange API key for a session JWT via nonce + PoW |
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
#### Email/Password Login
|
|
125
|
+
|
|
126
|
+
`auth login` uses interactive prompts for email and password. A TTY is required — credentials are never accepted via environment variables or command-line arguments to prevent leaking secrets in shell history.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
geonic auth login
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
| Option | Description |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `--tenant-id <id>` | Log in to a specific tenant |
|
|
135
|
+
|
|
136
|
+
**Multi-tenant support**: When you belong to multiple tenants, `auth login` displays the list and lets you select one interactively. Use `--tenant-id` to skip the prompt.
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
$ geonic auth login
|
|
140
|
+
Email: user@example.com
|
|
141
|
+
Password: ********
|
|
142
|
+
Login successful. Token saved to config.
|
|
143
|
+
|
|
144
|
+
Available tenants:
|
|
145
|
+
* 1) my_city (tenant_admin) ← current
|
|
146
|
+
2) another_city (user)
|
|
147
|
+
|
|
148
|
+
Select tenant number (Enter to keep current):
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### OAuth Client Credentials
|
|
152
|
+
|
|
153
|
+
For machine-to-machine authentication (CI/CD, scripts), use the OAuth Client Credentials flow:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
geonic auth login --client-credentials --client-id MY_ID --client-secret MY_SECRET
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
| Option | Description |
|
|
160
|
+
|---|---|
|
|
161
|
+
| `--client-credentials` | Use OAuth 2.0 Client Credentials flow |
|
|
162
|
+
| `--client-id <id>` | OAuth client ID (or `GDB_OAUTH_CLIENT_ID` env var) |
|
|
163
|
+
| `--client-secret <secret>` | OAuth client secret (or `GDB_OAUTH_CLIENT_SECRET` env var) |
|
|
164
|
+
| `--scope <scopes>` | OAuth scopes (space-separated) |
|
|
125
165
|
|
|
126
166
|
#### API Key Token Exchange
|
|
127
167
|
|
package/dist/index.js
CHANGED
|
@@ -848,6 +848,9 @@ var GdbClient = class _GdbClient {
|
|
|
848
848
|
async executeRawRequest(method, path, options) {
|
|
849
849
|
const url = this.buildUrl(path, options?.params);
|
|
850
850
|
const headers = this.buildHeaders(options?.headers);
|
|
851
|
+
if (options?.skipTenantHeader) {
|
|
852
|
+
delete headers["NGSILD-Tenant"];
|
|
853
|
+
}
|
|
851
854
|
const body = options?.body ? JSON.stringify(options.body) : void 0;
|
|
852
855
|
this.logRequest(method, url, headers, body);
|
|
853
856
|
this.handleDryRun(method, url, headers, body);
|
|
@@ -985,7 +988,7 @@ function withErrorHandler(fn) {
|
|
|
985
988
|
return;
|
|
986
989
|
}
|
|
987
990
|
if (err instanceof GdbClientError && err.status === 401) {
|
|
988
|
-
printError("Authentication failed. Please
|
|
991
|
+
printError("Authentication failed. Please re-authenticate (e.g., `geonic auth login` or check your API key).");
|
|
989
992
|
} else if (err instanceof GdbClientError && err.status === 403) {
|
|
990
993
|
const detail = (err.ngsiError?.detail ?? err.ngsiError?.description ?? "").toLowerCase();
|
|
991
994
|
if (detail.includes("entity type") || detail.includes("allowedentitytypes")) {
|
|
@@ -1097,6 +1100,30 @@ async function promptPassword() {
|
|
|
1097
1100
|
stdin.on("error", onError);
|
|
1098
1101
|
});
|
|
1099
1102
|
}
|
|
1103
|
+
async function promptTenantSelection(tenants, currentTenantId) {
|
|
1104
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1105
|
+
try {
|
|
1106
|
+
console.log("\nAvailable tenants:");
|
|
1107
|
+
for (let i = 0; i < tenants.length; i++) {
|
|
1108
|
+
const t = tenants[i];
|
|
1109
|
+
const current = t.tenantId === currentTenantId ? " \u2190 current" : "";
|
|
1110
|
+
const marker = t.tenantId === currentTenantId ? " *" : " ";
|
|
1111
|
+
console.log(`${marker} ${i + 1}) ${t.tenantId} (${t.role})${current}`);
|
|
1112
|
+
}
|
|
1113
|
+
for (; ; ) {
|
|
1114
|
+
const answer = await rl.question("\nSelect tenant number (Enter to keep current): ");
|
|
1115
|
+
const trimmed = answer.trim();
|
|
1116
|
+
if (!trimmed) return void 0;
|
|
1117
|
+
const index = parseInt(trimmed, 10) - 1;
|
|
1118
|
+
if (index >= 0 && index < tenants.length) {
|
|
1119
|
+
return tenants[index].tenantId;
|
|
1120
|
+
}
|
|
1121
|
+
console.log(`Invalid selection. Please enter a number between 1 and ${tenants.length}.`);
|
|
1122
|
+
}
|
|
1123
|
+
} finally {
|
|
1124
|
+
rl.close();
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1100
1127
|
|
|
1101
1128
|
// src/token.ts
|
|
1102
1129
|
function decodeJwtPayload(token) {
|
|
@@ -1511,32 +1538,59 @@ function createLoginCommand() {
|
|
|
1511
1538
|
printSuccess("Login successful (OAuth Client Credentials). Token saved to config.");
|
|
1512
1539
|
return;
|
|
1513
1540
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1541
|
+
if (!globalOpts.url) {
|
|
1542
|
+
printError("No URL configured. Use `geonic config set url <url>` or pass --url.");
|
|
1543
|
+
process.exit(1);
|
|
1544
|
+
}
|
|
1545
|
+
try {
|
|
1546
|
+
validateUrl(globalOpts.url);
|
|
1547
|
+
} catch (err) {
|
|
1548
|
+
printError(err.message);
|
|
1549
|
+
process.exit(1);
|
|
1550
|
+
}
|
|
1551
|
+
if (!isInteractive()) {
|
|
1552
|
+
printError(
|
|
1553
|
+
"Interactive terminal required. Run `geonic auth login` in a terminal with TTY."
|
|
1554
|
+
);
|
|
1555
|
+
process.exit(1);
|
|
1526
1556
|
}
|
|
1557
|
+
const email = await promptEmail();
|
|
1558
|
+
const password = await promptPassword();
|
|
1527
1559
|
const client = createClient(cmd);
|
|
1528
1560
|
const body = { email, password };
|
|
1529
1561
|
if (loginOpts.tenantId) {
|
|
1530
1562
|
body.tenantId = loginOpts.tenantId;
|
|
1531
1563
|
}
|
|
1532
|
-
const response = await client.rawRequest("POST", "/auth/login", {
|
|
1564
|
+
const response = await client.rawRequest("POST", "/auth/login", {
|
|
1565
|
+
body,
|
|
1566
|
+
skipTenantHeader: true
|
|
1567
|
+
});
|
|
1533
1568
|
const data = response.data;
|
|
1534
|
-
|
|
1535
|
-
|
|
1569
|
+
let token = data.accessToken ?? data.token;
|
|
1570
|
+
let refreshToken = data.refreshToken;
|
|
1536
1571
|
if (!token) {
|
|
1537
1572
|
printError("No token received from server.");
|
|
1538
1573
|
process.exit(1);
|
|
1539
1574
|
}
|
|
1575
|
+
const availableTenants = data.availableTenants;
|
|
1576
|
+
const currentTenantId = data.tenantId;
|
|
1577
|
+
if (availableTenants && availableTenants.length > 1 && !loginOpts.tenantId) {
|
|
1578
|
+
const selectedTenantId = await promptTenantSelection(availableTenants, currentTenantId);
|
|
1579
|
+
if (selectedTenantId && selectedTenantId !== currentTenantId) {
|
|
1580
|
+
const reloginResponse = await client.rawRequest("POST", "/auth/login", {
|
|
1581
|
+
body: { email, password, tenantId: selectedTenantId },
|
|
1582
|
+
skipTenantHeader: true
|
|
1583
|
+
});
|
|
1584
|
+
const reloginData = reloginResponse.data;
|
|
1585
|
+
const newToken = reloginData.accessToken ?? reloginData.token;
|
|
1586
|
+
if (!newToken) {
|
|
1587
|
+
printError("Re-login failed: no token received for selected tenant.");
|
|
1588
|
+
process.exit(1);
|
|
1589
|
+
}
|
|
1590
|
+
token = newToken;
|
|
1591
|
+
refreshToken = reloginData.refreshToken;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1540
1594
|
const config = loadConfig(globalOpts.profile);
|
|
1541
1595
|
config.token = token;
|
|
1542
1596
|
if (refreshToken) {
|
|
@@ -1728,10 +1782,6 @@ function registerAuthCommands(program2) {
|
|
|
1728
1782
|
description: "Login with OAuth client credentials",
|
|
1729
1783
|
command: "geonic auth login --client-credentials --client-id MY_ID --client-secret MY_SECRET"
|
|
1730
1784
|
},
|
|
1731
|
-
{
|
|
1732
|
-
description: "Login with environment variables",
|
|
1733
|
-
command: "GDB_EMAIL=user@example.com GDB_PASSWORD=pass geonic auth login"
|
|
1734
|
-
},
|
|
1735
1785
|
{
|
|
1736
1786
|
description: "Login to a specific tenant",
|
|
1737
1787
|
command: "geonic auth login --tenant-id my-tenant"
|