@dynamicweb/cli 1.1.0 → 2.0.0-beta.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 +404 -171
- package/bin/commands/command.js +82 -24
- package/bin/commands/env.js +143 -26
- package/bin/commands/files.js +412 -74
- package/bin/commands/install.js +98 -18
- package/bin/commands/login.js +383 -44
- package/bin/commands/query.js +96 -28
- package/bin/downloader.js +8 -5
- package/bin/index.js +37 -8
- package/package.json +1 -1
package/bin/commands/install.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import fetch from 'node-fetch';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { setupEnv, getAgent } from './env.js';
|
|
3
|
+
import { setupEnv, getAgent, createCommandError } from './env.js';
|
|
4
4
|
import { setupUser } from './login.js';
|
|
5
5
|
import { uploadFiles, resolveFilePath } from './files.js';
|
|
6
6
|
|
|
7
7
|
export function installCommand() {
|
|
8
8
|
return {
|
|
9
|
-
command: 'install [filePath]',
|
|
10
|
-
describe: 'Installs the addin on the given path, allowed file extensions are .dll, .nupkg',
|
|
9
|
+
command: 'install [filePath]',
|
|
10
|
+
describe: 'Installs the addin on the given path, allowed file extensions are .dll, .nupkg',
|
|
11
11
|
builder: (yargs) => {
|
|
12
12
|
return yargs
|
|
13
13
|
.positional('filePath', {
|
|
@@ -18,24 +18,37 @@ export function installCommand() {
|
|
|
18
18
|
type: 'boolean',
|
|
19
19
|
describe: 'Queues the install for next Dynamicweb recycle'
|
|
20
20
|
})
|
|
21
|
+
.option('output', {
|
|
22
|
+
choices: ['json'],
|
|
23
|
+
describe: 'Outputs a single JSON response for automation-friendly parsing'
|
|
24
|
+
})
|
|
21
25
|
},
|
|
22
26
|
handler: async (argv) => {
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
const output = createInstallOutput(argv);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
output.verboseLog(`Installing file located at: ${argv.filePath}`);
|
|
31
|
+
await handleInstall(argv, output);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
output.fail(err);
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
} finally {
|
|
36
|
+
output.finish();
|
|
37
|
+
}
|
|
25
38
|
}
|
|
26
39
|
}
|
|
27
40
|
}
|
|
28
41
|
|
|
29
|
-
async function handleInstall(argv) {
|
|
30
|
-
let env = await setupEnv(argv);
|
|
42
|
+
async function handleInstall(argv, output) {
|
|
43
|
+
let env = await setupEnv(argv, output);
|
|
31
44
|
let user = await setupUser(argv, env);
|
|
32
45
|
let resolvedPath = resolveFilePath(argv.filePath);
|
|
33
|
-
await uploadFiles(env, user, [ resolvedPath ], 'System/AddIns/Local', false, true);
|
|
34
|
-
await installAddin(env, user, resolvedPath, argv.queue)
|
|
46
|
+
await uploadFiles(env, user, [ resolvedPath ], 'System/AddIns/Local', false, true, output);
|
|
47
|
+
await installAddin(env, user, resolvedPath, argv.queue, output);
|
|
35
48
|
}
|
|
36
49
|
|
|
37
|
-
async function installAddin(env, user, resolvedPath, queue) {
|
|
38
|
-
|
|
50
|
+
async function installAddin(env, user, resolvedPath, queue, output) {
|
|
51
|
+
output.log('Installing addin');
|
|
39
52
|
let filename = path.basename(resolvedPath);
|
|
40
53
|
let data = {
|
|
41
54
|
'Queue': queue,
|
|
@@ -54,12 +67,79 @@ async function installAddin(env, user, resolvedPath, queue) {
|
|
|
54
67
|
});
|
|
55
68
|
|
|
56
69
|
if (res.ok) {
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
const body = await parseJsonSafe(res);
|
|
71
|
+
output.verboseLog(body);
|
|
72
|
+
output.addData({
|
|
73
|
+
type: 'install',
|
|
74
|
+
filePath: resolvedPath,
|
|
75
|
+
filename,
|
|
76
|
+
queued: Boolean(queue),
|
|
77
|
+
response: body
|
|
78
|
+
});
|
|
79
|
+
output.log('Addin installed');
|
|
80
|
+
} else {
|
|
81
|
+
const body = await parseJsonSafe(res);
|
|
82
|
+
throw createCommandError('Addin installation failed.', res.status, body);
|
|
59
83
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function createInstallOutput(argv) {
|
|
87
|
+
const response = {
|
|
88
|
+
ok: true,
|
|
89
|
+
command: 'install',
|
|
90
|
+
operation: argv.queue ? 'queue' : 'install',
|
|
91
|
+
status: 200,
|
|
92
|
+
data: [],
|
|
93
|
+
errors: [],
|
|
94
|
+
meta: {
|
|
95
|
+
filePath: argv.filePath
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
json: argv.output === 'json',
|
|
101
|
+
response,
|
|
102
|
+
log(...args) {
|
|
103
|
+
if (!this.json) {
|
|
104
|
+
console.log(...args);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
verboseLog(...args) {
|
|
108
|
+
if (argv.verbose && !this.json) {
|
|
109
|
+
console.info(...args);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
addData(entry) {
|
|
113
|
+
response.data.push(entry);
|
|
114
|
+
},
|
|
115
|
+
mergeMeta(metaOrFn) {
|
|
116
|
+
const meta = typeof metaOrFn === 'function' ? metaOrFn(response.meta) : metaOrFn;
|
|
117
|
+
response.meta = {
|
|
118
|
+
...response.meta,
|
|
119
|
+
...meta
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
fail(err) {
|
|
123
|
+
response.ok = false;
|
|
124
|
+
response.status = err?.status || 1;
|
|
125
|
+
response.errors.push({
|
|
126
|
+
message: err?.message || 'Unknown install command error.',
|
|
127
|
+
details: err?.details ?? null
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
finish() {
|
|
131
|
+
if (this.json) {
|
|
132
|
+
console.log(JSON.stringify(response, null, 2));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async function parseJsonSafe(res) {
|
|
140
|
+
try {
|
|
141
|
+
return await res.json();
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
64
144
|
}
|
|
65
|
-
}
|
|
145
|
+
}
|
package/bin/commands/login.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import fetch from 'node-fetch';
|
|
2
|
-
import { interactiveEnv, getAgent } from './env.js'
|
|
2
|
+
import { interactiveEnv, getAgent, isJsonOutput, createCommandError } from './env.js'
|
|
3
3
|
import { updateConfig, getConfig } from './config.js';
|
|
4
4
|
import { input, password } from '@inquirer/prompts';
|
|
5
5
|
|
|
6
|
+
const DEFAULT_OAUTH_TOKEN_PATH = '/Admin/OAuth/token';
|
|
7
|
+
const DEFAULT_CLIENT_ID_ENV = 'DW_CLIENT_ID';
|
|
8
|
+
const DEFAULT_CLIENT_SECRET_ENV = 'DW_CLIENT_SECRET';
|
|
9
|
+
|
|
6
10
|
export function loginCommand() {
|
|
7
11
|
return {
|
|
8
12
|
command: 'login [user]',
|
|
@@ -12,8 +16,27 @@ export function loginCommand() {
|
|
|
12
16
|
.positional('user', {
|
|
13
17
|
describe: 'user'
|
|
14
18
|
})
|
|
19
|
+
.option('oauth', {
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
describe: 'Configures OAuth client_credentials authentication for the current environment'
|
|
22
|
+
})
|
|
23
|
+
.option('output', {
|
|
24
|
+
choices: ['json'],
|
|
25
|
+
describe: 'Outputs a single JSON response for automation-friendly parsing'
|
|
26
|
+
})
|
|
15
27
|
},
|
|
16
|
-
handler: (argv) =>
|
|
28
|
+
handler: async (argv) => {
|
|
29
|
+
const output = createLoginOutput(argv);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
await handleLogin(argv, output);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
output.fail(err);
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
} finally {
|
|
37
|
+
output.finish();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
17
40
|
}
|
|
18
41
|
}
|
|
19
42
|
|
|
@@ -26,17 +49,24 @@ export async function setupUser(argv, env) {
|
|
|
26
49
|
askLogin = false;
|
|
27
50
|
}
|
|
28
51
|
|
|
52
|
+
if (!user.apiKey && shouldUseOAuth(argv, env)) {
|
|
53
|
+
return await authenticateWithOAuth(argv, env);
|
|
54
|
+
}
|
|
55
|
+
|
|
29
56
|
if (!user.apiKey && env.users && (argv.user || env.current?.user)) {
|
|
30
57
|
user = env.users[argv.user] || env.users[env.current?.user];
|
|
31
58
|
askLogin = false;
|
|
32
59
|
}
|
|
33
60
|
|
|
34
61
|
if (askLogin && argv.host) {
|
|
35
|
-
|
|
36
|
-
process.exit();
|
|
62
|
+
throw createCommandError('Please add an --apiKey, or provide OAuth client credentials when overriding the host.');
|
|
37
63
|
}
|
|
38
64
|
else if (askLogin) {
|
|
39
|
-
|
|
65
|
+
if (isJsonOutput(argv)) {
|
|
66
|
+
throw createCommandError('Current user not set, please login');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
logMessage(argv, 'Current user not set, please login');
|
|
40
70
|
await interactiveLogin(argv, {
|
|
41
71
|
environment: {
|
|
42
72
|
type: 'input',
|
|
@@ -59,27 +89,40 @@ export async function setupUser(argv, env) {
|
|
|
59
89
|
return user;
|
|
60
90
|
}
|
|
61
91
|
|
|
62
|
-
async function handleLogin(argv) {
|
|
63
|
-
|
|
92
|
+
async function handleLogin(argv, output) {
|
|
93
|
+
if (shouldUseOAuth(argv, getCurrentEnv(argv))) {
|
|
94
|
+
if (isJsonOutput(argv)) {
|
|
95
|
+
output.addData(await nonInteractiveOAuthLogin(argv));
|
|
96
|
+
} else {
|
|
97
|
+
output.addData(await interactiveOAuthLogin(argv, output));
|
|
98
|
+
}
|
|
99
|
+
} else if (argv.user) {
|
|
100
|
+
output.addData(await changeUser(argv));
|
|
101
|
+
} else {
|
|
102
|
+
if (isJsonOutput(argv)) {
|
|
103
|
+
throw createCommandError('Interactive login is not supported with --output json. Use --apiKey, or configure OAuth with --oauth --clientIdEnv/--clientSecretEnv.');
|
|
104
|
+
}
|
|
105
|
+
output.addData(await interactiveLogin(argv, {
|
|
64
106
|
environment: {
|
|
65
107
|
type: 'input',
|
|
66
108
|
default: getConfig()?.current?.env || 'dev',
|
|
67
109
|
prompt: 'if-no-arg'
|
|
68
110
|
},
|
|
69
|
-
username: {
|
|
111
|
+
username: {
|
|
70
112
|
type: 'input'
|
|
71
113
|
},
|
|
72
|
-
password: {
|
|
114
|
+
password: {
|
|
73
115
|
type: 'password'
|
|
74
116
|
},
|
|
75
117
|
interactive: {
|
|
76
118
|
default: true
|
|
77
119
|
}
|
|
78
|
-
})
|
|
120
|
+
}, output))
|
|
121
|
+
}
|
|
79
122
|
}
|
|
80
123
|
|
|
81
|
-
export async function interactiveLogin(argv, options) {
|
|
82
|
-
|
|
124
|
+
export async function interactiveLogin(argv, options, output) {
|
|
125
|
+
verboseLog(argv, 'Now logging in');
|
|
83
126
|
const result = {};
|
|
84
127
|
for (const [key, config] of Object.entries(options)) {
|
|
85
128
|
if (key === 'interactive') continue;
|
|
@@ -95,41 +138,57 @@ export async function interactiveLogin(argv, options) {
|
|
|
95
138
|
});
|
|
96
139
|
}
|
|
97
140
|
if (!getConfig().env || !getConfig().env[result.environment] || !getConfig().env[result.environment].host || !getConfig().env[result.environment].protocol) {
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
environment
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
141
|
+
if (argv.host) {
|
|
142
|
+
ensureEnvironmentFromArgs(result.environment, argv);
|
|
143
|
+
} else {
|
|
144
|
+
logMessage(argv, `The environment specified is missing parameters, please specify them`);
|
|
145
|
+
await interactiveEnv(argv, {
|
|
146
|
+
environment: {
|
|
147
|
+
type: 'input',
|
|
148
|
+
default: result.environment,
|
|
149
|
+
prompt: 'never'
|
|
150
|
+
},
|
|
151
|
+
host: {
|
|
152
|
+
describe: 'Enter your host including protocol, i.e "https://yourHost.com":',
|
|
153
|
+
type: 'input',
|
|
154
|
+
prompt: 'always'
|
|
155
|
+
},
|
|
156
|
+
interactive: {
|
|
157
|
+
default: true
|
|
158
|
+
}
|
|
159
|
+
}, output)
|
|
160
|
+
}
|
|
115
161
|
}
|
|
116
|
-
await loginInteractive(result, argv.verbose);
|
|
162
|
+
return await loginInteractive(result, argv.verbose, argv);
|
|
117
163
|
}
|
|
118
164
|
|
|
119
|
-
async function loginInteractive(result, verbose) {
|
|
165
|
+
async function loginInteractive(result, verbose, argv) {
|
|
120
166
|
var protocol = getConfig().env[result.environment].protocol;
|
|
121
167
|
var token = await login(result.username, result.password, result.environment, protocol, verbose);
|
|
122
|
-
if (!token)
|
|
168
|
+
if (!token) {
|
|
169
|
+
throw createCommandError(`Could not fetch a login token for user ${result.username}.`);
|
|
170
|
+
}
|
|
123
171
|
var apiKey = await getApiKey(token, result.environment, protocol, verbose)
|
|
124
|
-
if (!apiKey)
|
|
172
|
+
if (!apiKey) {
|
|
173
|
+
throw createCommandError(`Could not create an API Key for the logged in user ${result.username}.`);
|
|
174
|
+
}
|
|
125
175
|
getConfig().env = getConfig().env || {};
|
|
126
176
|
getConfig().env[result.environment].users = getConfig().env[result.environment].users || {};
|
|
127
177
|
getConfig().env[result.environment].users[result.username] = getConfig().env[result.environment].users[result.username] || {};
|
|
128
178
|
getConfig().env[result.environment].users[result.username].apiKey = apiKey;
|
|
129
179
|
getConfig().env[result.environment].current = getConfig().env[result.environment].current || {};
|
|
130
180
|
getConfig().env[result.environment].current.user = result.username;
|
|
131
|
-
|
|
181
|
+
getConfig().env[result.environment].current.authType = 'user';
|
|
182
|
+
logMessage(argv, "You're now logged in as " + result.username);
|
|
132
183
|
updateConfig();
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
environment: result.environment,
|
|
187
|
+
username: result.username,
|
|
188
|
+
apiKey,
|
|
189
|
+
host: getConfig().env[result.environment].host,
|
|
190
|
+
protocol
|
|
191
|
+
};
|
|
133
192
|
}
|
|
134
193
|
|
|
135
194
|
async function login(username, password, env, protocol, verbose) {
|
|
@@ -153,14 +212,13 @@ async function login(username, password, env, protocol, verbose) {
|
|
|
153
212
|
}
|
|
154
213
|
else {
|
|
155
214
|
if (verbose) console.info(res)
|
|
156
|
-
|
|
215
|
+
throw createCommandError(`Login attempt failed with username ${username}, please verify its a valid user in your Dynamicweb solution.`, res.status)
|
|
157
216
|
}
|
|
158
217
|
}
|
|
159
218
|
|
|
160
219
|
function parseCookies (cookieHeader) {
|
|
161
220
|
const list = {};
|
|
162
221
|
if (!cookieHeader) {
|
|
163
|
-
console.log(`Could not get the necessary information from the login request, please verify its a valid user in your Dynamicweb solution.`)
|
|
164
222
|
return list;
|
|
165
223
|
}
|
|
166
224
|
|
|
@@ -173,10 +231,6 @@ function parseCookies (cookieHeader) {
|
|
|
173
231
|
list[name] = decodeURIComponent(value);
|
|
174
232
|
});
|
|
175
233
|
|
|
176
|
-
if (!list.user) {
|
|
177
|
-
console.log(`Could not get the necessary information from the login request, please verify its a valid user in your Dynamicweb solution.`)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
234
|
return list;
|
|
181
235
|
}
|
|
182
236
|
|
|
@@ -193,7 +247,7 @@ async function getToken(user, env, protocol, verbose) {
|
|
|
193
247
|
}
|
|
194
248
|
else {
|
|
195
249
|
if (verbose) console.info(res)
|
|
196
|
-
|
|
250
|
+
throw createCommandError(`Could not fetch the token for the logged in user ${user}, please verify its a valid user in your Dynamicweb solution.`, res.status)
|
|
197
251
|
}
|
|
198
252
|
}
|
|
199
253
|
|
|
@@ -218,12 +272,297 @@ async function getApiKey(token, env, protocol, verbose) {
|
|
|
218
272
|
}
|
|
219
273
|
else {
|
|
220
274
|
if (verbose) console.info(res)
|
|
221
|
-
|
|
275
|
+
throw createCommandError(`Could not create an API Key for the logged in user, please verify its a valid user in your Dynamicweb solution.`, res.status)
|
|
222
276
|
}
|
|
223
277
|
}
|
|
224
278
|
|
|
225
279
|
async function changeUser(argv) {
|
|
280
|
+
if (!getConfig().current?.env || !getConfig().env?.[getConfig().current.env]) {
|
|
281
|
+
throw createCommandError('Current environment not set, please set it before changing user.');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
getConfig().env[getConfig().current.env].current = getConfig().env[getConfig().current.env].current || {};
|
|
226
285
|
getConfig().env[getConfig().current.env].current.user = argv.user;
|
|
286
|
+
getConfig().env[getConfig().current.env].current.authType = 'user';
|
|
227
287
|
updateConfig();
|
|
228
|
-
|
|
229
|
-
|
|
288
|
+
logMessage(argv, `You're now logged in as ${getConfig().env[getConfig().current.env].current.user}`);
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
environment: getConfig().current.env,
|
|
292
|
+
username: getConfig().env[getConfig().current.env].current.user
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function interactiveOAuthLogin(argv, output) {
|
|
297
|
+
verboseLog(argv, 'Configuring OAuth client credentials authentication');
|
|
298
|
+
|
|
299
|
+
const currentEnvName = getConfig()?.current?.env || 'dev';
|
|
300
|
+
const environment = await input({
|
|
301
|
+
message: 'environment',
|
|
302
|
+
default: currentEnvName
|
|
303
|
+
});
|
|
304
|
+
const existingEnv = getConfig()?.env?.[environment] || {};
|
|
305
|
+
const existingAuth = existingEnv.auth || {};
|
|
306
|
+
|
|
307
|
+
const result = {
|
|
308
|
+
environment,
|
|
309
|
+
clientIdEnv: argv.clientIdEnv || existingAuth.clientIdEnv || DEFAULT_CLIENT_ID_ENV,
|
|
310
|
+
clientSecretEnv: argv.clientSecretEnv || existingAuth.clientSecretEnv || DEFAULT_CLIENT_SECRET_ENV
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
if (!argv.clientIdEnv) {
|
|
314
|
+
result.clientIdEnv = await input({
|
|
315
|
+
message: 'clientIdEnv',
|
|
316
|
+
default: result.clientIdEnv
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (!argv.clientSecretEnv) {
|
|
321
|
+
result.clientSecretEnv = await input({
|
|
322
|
+
message: 'clientSecretEnv',
|
|
323
|
+
default: result.clientSecretEnv
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (argv.host) {
|
|
328
|
+
ensureEnvironmentFromArgs(result.environment, argv);
|
|
329
|
+
} else if (!getConfig().env || !getConfig().env[result.environment] || !getConfig().env[result.environment].host || !getConfig().env[result.environment].protocol) {
|
|
330
|
+
logMessage(argv, 'The environment specified is missing parameters, please specify them');
|
|
331
|
+
await interactiveEnv(argv, {
|
|
332
|
+
environment: {
|
|
333
|
+
type: 'input',
|
|
334
|
+
default: result.environment,
|
|
335
|
+
prompt: 'never'
|
|
336
|
+
},
|
|
337
|
+
host: {
|
|
338
|
+
describe: 'Enter your host including protocol, i.e "https://yourHost.com":',
|
|
339
|
+
type: 'input',
|
|
340
|
+
prompt: 'always'
|
|
341
|
+
},
|
|
342
|
+
interactive: {
|
|
343
|
+
default: true
|
|
344
|
+
}
|
|
345
|
+
}, output);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const oauthResult = await finalizeOAuthLogin(result.environment, result.clientIdEnv, result.clientSecretEnv, argv);
|
|
349
|
+
|
|
350
|
+
logMessage(argv, `OAuth authentication is now configured for ${result.environment}`);
|
|
351
|
+
|
|
352
|
+
return oauthResult;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function nonInteractiveOAuthLogin(argv) {
|
|
356
|
+
verboseLog(argv, 'Configuring OAuth client credentials authentication (non-interactive)');
|
|
357
|
+
|
|
358
|
+
const environment = getConfig()?.current?.env;
|
|
359
|
+
if (!environment) {
|
|
360
|
+
throw createCommandError('No environment set. Configure one with "dw env" first.');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (argv.host) {
|
|
364
|
+
ensureEnvironmentFromArgs(environment, argv);
|
|
365
|
+
} else if (!getConfig().env?.[environment]?.host) {
|
|
366
|
+
throw createCommandError(`Environment "${environment}" has no host configured. Pass --host or set it up with "dw env" first.`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const clientIdEnv = argv.clientIdEnv || getConfig().env?.[environment]?.auth?.clientIdEnv || DEFAULT_CLIENT_ID_ENV;
|
|
370
|
+
const clientSecretEnv = argv.clientSecretEnv || getConfig().env?.[environment]?.auth?.clientSecretEnv || DEFAULT_CLIENT_SECRET_ENV;
|
|
371
|
+
|
|
372
|
+
return await finalizeOAuthLogin(environment, clientIdEnv, clientSecretEnv, argv);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async function finalizeOAuthLogin(environment, clientIdEnv, clientSecretEnv, argv) {
|
|
376
|
+
const env = getConfig().env[environment];
|
|
377
|
+
const oauthConfig = resolveOAuthConfig({
|
|
378
|
+
...argv,
|
|
379
|
+
clientIdEnv,
|
|
380
|
+
clientSecretEnv,
|
|
381
|
+
oauth: true
|
|
382
|
+
}, env);
|
|
383
|
+
|
|
384
|
+
const tokenResult = await fetchOAuthToken(env, oauthConfig, argv.verbose);
|
|
385
|
+
|
|
386
|
+
getConfig().current = getConfig().current || {};
|
|
387
|
+
getConfig().current.env = environment;
|
|
388
|
+
env.auth = {
|
|
389
|
+
type: 'oauth_client_credentials',
|
|
390
|
+
clientIdEnv,
|
|
391
|
+
clientSecretEnv
|
|
392
|
+
};
|
|
393
|
+
env.current = env.current || {};
|
|
394
|
+
env.current.authType = 'oauth_client_credentials';
|
|
395
|
+
delete env.current.user;
|
|
396
|
+
updateConfig();
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
environment,
|
|
400
|
+
authType: 'oauth_client_credentials',
|
|
401
|
+
clientIdEnv,
|
|
402
|
+
clientSecretEnv,
|
|
403
|
+
expires: tokenResult.expires || null
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function authenticateWithOAuth(argv, env) {
|
|
408
|
+
const oauthConfig = resolveOAuthConfig(argv, env, true);
|
|
409
|
+
const tokenResult = await fetchOAuthToken(env, oauthConfig, argv.verbose);
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
apiKey: tokenResult.token,
|
|
413
|
+
authType: 'oauth_client_credentials',
|
|
414
|
+
expires: tokenResult.expires || null
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function shouldUseOAuth(argv, env = {}) {
|
|
419
|
+
if (argv.auth === 'user') {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (argv.oauth || argv.auth === 'oauth') {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (argv.clientId || argv.clientSecret || argv.clientIdEnv || argv.clientSecretEnv) {
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (env?.current?.authType) {
|
|
432
|
+
return env.current.authType === 'oauth_client_credentials';
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return env?.auth?.type === 'oauth_client_credentials';
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function resolveOAuthConfig(argv, env = {}, requireCredentials = true) {
|
|
439
|
+
const authConfig = env?.auth || {};
|
|
440
|
+
const clientIdEnv = argv.clientIdEnv || authConfig.clientIdEnv || DEFAULT_CLIENT_ID_ENV;
|
|
441
|
+
const clientSecretEnv = argv.clientSecretEnv || authConfig.clientSecretEnv || DEFAULT_CLIENT_SECRET_ENV;
|
|
442
|
+
const clientId = argv.clientId || process.env[clientIdEnv];
|
|
443
|
+
const clientSecret = argv.clientSecret || process.env[clientSecretEnv];
|
|
444
|
+
|
|
445
|
+
if (requireCredentials) {
|
|
446
|
+
if (!clientId) {
|
|
447
|
+
throw createCommandError(`OAuth client ID not found. Set --clientId or export ${clientIdEnv}.`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (!clientSecret) {
|
|
451
|
+
throw createCommandError(`OAuth client secret not found. Set --clientSecret or export ${clientSecretEnv}.`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
clientId,
|
|
457
|
+
clientSecret,
|
|
458
|
+
clientIdEnv,
|
|
459
|
+
clientSecretEnv
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function fetchOAuthToken(env, oauthConfig, verbose) {
|
|
464
|
+
const res = await fetch(`${env.protocol}://${env.host}${DEFAULT_OAUTH_TOKEN_PATH}`, {
|
|
465
|
+
method: 'POST',
|
|
466
|
+
headers: {
|
|
467
|
+
'Content-Type': 'application/json'
|
|
468
|
+
},
|
|
469
|
+
body: JSON.stringify({
|
|
470
|
+
grant_type: 'client_credentials',
|
|
471
|
+
client_id: oauthConfig.clientId,
|
|
472
|
+
client_secret: oauthConfig.clientSecret
|
|
473
|
+
}),
|
|
474
|
+
agent: getAgent(env.protocol)
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const body = await parseJsonSafe(res);
|
|
478
|
+
|
|
479
|
+
if (!res.ok) {
|
|
480
|
+
if (verbose) {
|
|
481
|
+
console.info(res);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
throw createCommandError(`OAuth token request failed at ${DEFAULT_OAUTH_TOKEN_PATH}.`, res.status, body);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const token = body?.token || body?.Token;
|
|
488
|
+
const expires = body?.expires || body?.Expires || null;
|
|
489
|
+
|
|
490
|
+
if (!token) {
|
|
491
|
+
throw createCommandError('OAuth token response did not include a token.', res.status, body);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return { token, expires };
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function getCurrentEnv(argv) {
|
|
498
|
+
if (argv.host) {
|
|
499
|
+
return {
|
|
500
|
+
host: argv.host,
|
|
501
|
+
protocol: argv.protocol || 'https'
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return getConfig()?.env?.[getConfig()?.current?.env] || {};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function ensureEnvironmentFromArgs(environment, argv) {
|
|
509
|
+
getConfig().env = getConfig().env || {};
|
|
510
|
+
getConfig().env[environment] = getConfig().env[environment] || {};
|
|
511
|
+
getConfig().env[environment].host = argv.host;
|
|
512
|
+
getConfig().env[environment].protocol = argv.protocol || 'https';
|
|
513
|
+
getConfig().current = getConfig().current || {};
|
|
514
|
+
getConfig().current.env = environment;
|
|
515
|
+
updateConfig();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function logMessage(argv, ...args) {
|
|
519
|
+
if (!isJsonOutput(argv)) {
|
|
520
|
+
console.log(...args);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function verboseLog(argv, ...args) {
|
|
525
|
+
if (argv?.verbose && !isJsonOutput(argv)) {
|
|
526
|
+
console.info(...args);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function createLoginOutput(argv) {
|
|
531
|
+
const response = {
|
|
532
|
+
ok: true,
|
|
533
|
+
command: 'login',
|
|
534
|
+
operation: shouldUseOAuth(argv, getCurrentEnv(argv)) ? 'oauth-login' : argv.user ? 'select-user' : 'login',
|
|
535
|
+
status: 200,
|
|
536
|
+
data: [],
|
|
537
|
+
errors: [],
|
|
538
|
+
meta: {}
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
json: isJsonOutput(argv),
|
|
543
|
+
addData(entry) {
|
|
544
|
+
response.data.push(entry);
|
|
545
|
+
},
|
|
546
|
+
fail(err) {
|
|
547
|
+
response.ok = false;
|
|
548
|
+
response.status = err?.status || 1;
|
|
549
|
+
response.errors.push({
|
|
550
|
+
message: err?.message || 'Unknown login command error.',
|
|
551
|
+
details: err?.details ?? null
|
|
552
|
+
});
|
|
553
|
+
},
|
|
554
|
+
finish() {
|
|
555
|
+
if (this.json) {
|
|
556
|
+
console.log(JSON.stringify(response, null, 2));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async function parseJsonSafe(res) {
|
|
563
|
+
try {
|
|
564
|
+
return await res.json();
|
|
565
|
+
} catch {
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
}
|