unbound-cli 0.8.1 → 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/package.json +1 -1
- package/src/api.js +38 -3
- package/src/auth.js +13 -6
- package/src/commands/login.js +13 -8
- package/src/commands/oacb.js +931 -0
- package/src/commands/onboard.js +24 -15
- package/src/commands/setup.js +22 -16
- package/src/index.js +1 -0
- package/test/config-normalize-url.test.js +43 -0
- package/test/oacb.test.js +307 -0
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -29,15 +29,18 @@ class ApiError extends Error {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
function request(method, path, { body, query, apiKey } = {}) {
|
|
33
|
-
|
|
32
|
+
function request(method, path, { body, query, apiKey, baseUrl } = {}) {
|
|
33
|
+
// Explicit baseUrl wins over env-var-aware getBaseUrl(). Used by login /
|
|
34
|
+
// setup / onboard so a user's just-passed --backend-url isn't shadowed by a
|
|
35
|
+
// stale UNBOUND_API_URL env var from a prior shell session.
|
|
36
|
+
const resolvedBaseUrl = baseUrl || config.getBaseUrl();
|
|
34
37
|
const key = apiKey || config.getApiKey();
|
|
35
38
|
|
|
36
39
|
if (!key) {
|
|
37
40
|
return Promise.reject(new Error('Not logged in. Run `unbound login` first.'));
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
const url = new URL(path,
|
|
43
|
+
const url = new URL(path, resolvedBaseUrl);
|
|
41
44
|
if (query) {
|
|
42
45
|
for (const [k, v] of Object.entries(query)) {
|
|
43
46
|
if (v !== undefined && v !== null) {
|
|
@@ -101,10 +104,42 @@ function request(method, path, { body, query, apiKey } = {}) {
|
|
|
101
104
|
});
|
|
102
105
|
}
|
|
103
106
|
|
|
107
|
+
// Plain unauthenticated GET to an arbitrary URL. Returns the raw response body
|
|
108
|
+
// as a UTF-8 string. Callers that want JSON must do JSON.parse() themselves.
|
|
109
|
+
// Used by `unbound oacb` to fetch baseline JSONs and hook scripts from GitHub.
|
|
110
|
+
function getRaw(url) {
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
const parsed = new URL(url);
|
|
113
|
+
const transport = parsed.protocol === 'https:' ? https : http;
|
|
114
|
+
const req = transport.get(url, { headers: { 'User-Agent': USER_AGENT } }, (res) => {
|
|
115
|
+
const chunks = [];
|
|
116
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
117
|
+
res.on('end', () => {
|
|
118
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
119
|
+
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
120
|
+
} else {
|
|
121
|
+
reject(new ApiError(res.statusCode, { error: `HTTP ${res.statusCode} fetching ${url}` }));
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
res.on('error', (err) => {
|
|
125
|
+
reject(new Error(`Network error fetching ${parsed.host}: ${err.message}`));
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
req.setTimeout(30000, () => {
|
|
129
|
+
req.destroy();
|
|
130
|
+
reject(new Error(`Request timed out fetching ${parsed.host}`));
|
|
131
|
+
});
|
|
132
|
+
req.on('error', (err) => {
|
|
133
|
+
reject(new Error(`Network error fetching ${parsed.host}: ${err.message}`));
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
104
138
|
module.exports = {
|
|
105
139
|
ApiError,
|
|
106
140
|
get: (path, opts) => request('GET', path, opts),
|
|
107
141
|
post: (path, opts) => request('POST', path, opts),
|
|
108
142
|
put: (path, opts) => request('PUT', path, opts),
|
|
109
143
|
del: (path, opts) => request('DELETE', path, opts),
|
|
144
|
+
getRaw,
|
|
110
145
|
};
|
package/src/auth.js
CHANGED
|
@@ -107,13 +107,17 @@ async function loginWithBrowser(frontendUrl) {
|
|
|
107
107
|
* Shows a spinner during validation and a success message with user info.
|
|
108
108
|
*
|
|
109
109
|
* @param {string} apiKey - The API key to validate and store
|
|
110
|
+
* @param {object} [opts]
|
|
111
|
+
* @param {string} [opts.baseUrl] - Explicit backend URL override. Used when the
|
|
112
|
+
* caller just persisted a tenant URL via setUrls and wants validation to hit
|
|
113
|
+
* that exact backend, regardless of any stale UNBOUND_API_URL env var.
|
|
110
114
|
* @returns {Promise<true>}
|
|
111
115
|
*/
|
|
112
|
-
async function loginWithApiKey(apiKey) {
|
|
116
|
+
async function loginWithApiKey(apiKey, { baseUrl } = {}) {
|
|
113
117
|
const spin = output.spinner('Authenticating...');
|
|
114
118
|
let response;
|
|
115
119
|
try {
|
|
116
|
-
response = await api.get('/api/v1/users/privileges/', { apiKey });
|
|
120
|
+
response = await api.get('/api/v1/users/privileges/', { apiKey, baseUrl });
|
|
117
121
|
} catch (err) {
|
|
118
122
|
let msg;
|
|
119
123
|
if (err.statusCode === 401 || err.statusCode === 403) {
|
|
@@ -143,10 +147,14 @@ async function loginWithApiKey(apiKey) {
|
|
|
143
147
|
* Ensures the user is logged in. If an apiKey is provided, validates and
|
|
144
148
|
* stores it first. If not logged in, triggers browser-based login.
|
|
145
149
|
* Returns true if logged in (or just logged in), false on failure.
|
|
150
|
+
*
|
|
151
|
+
* `baseUrl` and `frontendUrl` are explicit overrides for the URLs to validate
|
|
152
|
+
* against / open in the browser. Used by login/setup/onboard so a just-passed
|
|
153
|
+
* --backend-url / --frontend-url isn't shadowed by a stale env var.
|
|
146
154
|
*/
|
|
147
|
-
async function ensureLoggedIn({ apiKey } = {}) {
|
|
155
|
+
async function ensureLoggedIn({ apiKey, baseUrl, frontendUrl } = {}) {
|
|
148
156
|
if (apiKey) {
|
|
149
|
-
await loginWithApiKey(apiKey);
|
|
157
|
+
await loginWithApiKey(apiKey, { baseUrl });
|
|
150
158
|
return true;
|
|
151
159
|
}
|
|
152
160
|
|
|
@@ -155,8 +163,7 @@ async function ensureLoggedIn({ apiKey } = {}) {
|
|
|
155
163
|
}
|
|
156
164
|
|
|
157
165
|
output.warn('Not logged in. Opening browser to authenticate...');
|
|
158
|
-
const
|
|
159
|
-
const result = await loginWithBrowser(frontendUrl);
|
|
166
|
+
const result = await loginWithBrowser(frontendUrl || config.getFrontendUrl());
|
|
160
167
|
|
|
161
168
|
const parts = [];
|
|
162
169
|
if (result.email) parts.push(`as ${result.email}`);
|
package/src/commands/login.js
CHANGED
|
@@ -47,17 +47,21 @@ Examples:
|
|
|
47
47
|
// --backend-url is the canonical visible flag; --base-url is a hidden
|
|
48
48
|
// back-compat alias. setUrls writes all provided URLs atomically so
|
|
49
49
|
// a malformed --frontend-url can't leave a fresh --backend-url half-applied.
|
|
50
|
-
config.setUrls({
|
|
50
|
+
const written = config.setUrls({
|
|
51
51
|
backend: opts.backendUrl || opts.baseUrl,
|
|
52
52
|
frontend: opts.frontendUrl,
|
|
53
53
|
gateway: opts.gatewayUrl,
|
|
54
54
|
});
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// Prefer just-persisted values over env-var-aware getters so a stale
|
|
56
|
+
// UNBOUND_*_URL from a prior shell session can't shadow the user's
|
|
57
|
+
// explicit --backend-url / --frontend-url and route validation at the
|
|
58
|
+
// wrong tenant.
|
|
59
|
+
const explicitBaseUrl = written.base_url;
|
|
60
|
+
const explicitFrontendUrl = written.frontend_url;
|
|
57
61
|
|
|
58
62
|
if (opts.apiKey) {
|
|
59
63
|
try {
|
|
60
|
-
await loginWithApiKey(opts.apiKey);
|
|
64
|
+
await loginWithApiKey(opts.apiKey, { baseUrl: explicitBaseUrl });
|
|
61
65
|
} catch {
|
|
62
66
|
process.exitCode = 1;
|
|
63
67
|
return;
|
|
@@ -69,12 +73,13 @@ Examples:
|
|
|
69
73
|
if (opts.domain) {
|
|
70
74
|
frontendUrl = opts.domain.startsWith('http') ? opts.domain : `https://${opts.domain}`;
|
|
71
75
|
} else {
|
|
72
|
-
frontendUrl = config.getFrontendUrl();
|
|
76
|
+
frontendUrl = explicitFrontendUrl || config.getFrontendUrl();
|
|
73
77
|
}
|
|
74
78
|
await loginWithBrowser(frontendUrl);
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
|
|
79
|
+
// Validate the just-stored key against the explicit backend if one
|
|
80
|
+
// was just persisted; otherwise fall back to the env-var-aware
|
|
81
|
+
// getter. api.get reads the stored key from config internally.
|
|
82
|
+
await api.get('/api/v1/users/privileges/', { baseUrl: explicitBaseUrl });
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
const cfg = config.readConfig();
|