@sentio/cli 3.4.1-rc.1 → 3.4.1-rc.2
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/lib/index.js +99 -32
- package/package.json +2 -1
- package/src/commands/login-server.ts +36 -24
- package/src/commands/login.ts +76 -5
- package/lib/index.js.map +0 -7
package/lib/index.js
CHANGED
|
@@ -145841,32 +145841,46 @@ app.get("/callback", async (req, res) => {
|
|
|
145841
145841
|
fail("Failed to get authorization code");
|
|
145842
145842
|
return;
|
|
145843
145843
|
}
|
|
145844
|
-
|
|
145845
|
-
|
|
145846
|
-
|
|
145844
|
+
try {
|
|
145845
|
+
const username = await exchangeCodeAndSave(host, code, authParams.codeVerifier);
|
|
145846
|
+
res.end("Login success, please go back to CLI to continue");
|
|
145847
|
+
console.log(source_default.green(`Login success with ${username}`));
|
|
145848
|
+
} catch (e10) {
|
|
145849
|
+
fail(e10.message);
|
|
145847
145850
|
return;
|
|
145848
145851
|
}
|
|
145852
|
+
server.close();
|
|
145853
|
+
server.closeAllConnections();
|
|
145854
|
+
if (authParams.onSuccess) {
|
|
145855
|
+
authParams.onSuccess();
|
|
145856
|
+
} else {
|
|
145857
|
+
setTimeout(() => process.exit(), 1e3);
|
|
145858
|
+
}
|
|
145859
|
+
});
|
|
145860
|
+
async function exchangeCodeAndSave(host, code, codeVerifier) {
|
|
145861
|
+
const tokenResRaw = await getToken(host, code, codeVerifier);
|
|
145862
|
+
if (!tokenResRaw.ok) {
|
|
145863
|
+
throw new Error(
|
|
145864
|
+
`Failed to get access token: ${tokenResRaw.status} ${tokenResRaw.statusText}, ${await tokenResRaw.text()}`
|
|
145865
|
+
);
|
|
145866
|
+
}
|
|
145849
145867
|
const tokenRes = await tokenResRaw.json();
|
|
145850
145868
|
const accessToken = tokenRes.access_token;
|
|
145851
145869
|
const userResRaw = await getUser(host, accessToken);
|
|
145852
145870
|
if (!userResRaw.ok) {
|
|
145853
145871
|
if (userResRaw.status == 401) {
|
|
145854
|
-
|
|
145855
|
-
} else {
|
|
145856
|
-
fail(`Failed to get user info: ${userResRaw.status} ${userResRaw.statusText}`);
|
|
145872
|
+
throw new Error("The account does not exist, please sign up on sentio first");
|
|
145857
145873
|
}
|
|
145858
|
-
|
|
145874
|
+
throw new Error(`Failed to get user info: ${userResRaw.status} ${userResRaw.statusText}`);
|
|
145859
145875
|
}
|
|
145860
145876
|
const userRes = await userResRaw.json();
|
|
145861
145877
|
if (!userRes.emailVerified) {
|
|
145862
|
-
|
|
145863
|
-
return;
|
|
145878
|
+
throw new Error("Your account is not verified, please verify your email first");
|
|
145864
145879
|
}
|
|
145865
145880
|
const apiKeyName = `${os3.hostname()}-${crypto.randomBytes(4).toString("hex")}`;
|
|
145866
145881
|
const createApiKeyResRaw = await createApiKey(host, apiKeyName, "sdk_generated", accessToken);
|
|
145867
145882
|
if (!createApiKeyResRaw.ok) {
|
|
145868
|
-
|
|
145869
|
-
return;
|
|
145883
|
+
throw new Error(`Failed to create API key: ${createApiKeyResRaw.status} ${createApiKeyResRaw.statusText}`);
|
|
145870
145884
|
}
|
|
145871
145885
|
const { key, username } = await createApiKeyResRaw.json();
|
|
145872
145886
|
WriteKey(host, key);
|
|
@@ -145874,22 +145888,14 @@ app.get("/callback", async (req, res) => {
|
|
|
145874
145888
|
if (expiresAt !== void 0) {
|
|
145875
145889
|
WriteAccessToken(host, accessToken, expiresAt);
|
|
145876
145890
|
}
|
|
145877
|
-
|
|
145878
|
-
|
|
145879
|
-
|
|
145880
|
-
server.closeAllConnections();
|
|
145881
|
-
if (authParams.onSuccess) {
|
|
145882
|
-
authParams.onSuccess();
|
|
145883
|
-
} else {
|
|
145884
|
-
setTimeout(() => process.exit(), 1e3);
|
|
145885
|
-
}
|
|
145886
|
-
});
|
|
145887
|
-
async function getToken(host, code) {
|
|
145891
|
+
return username;
|
|
145892
|
+
}
|
|
145893
|
+
async function getToken(host, code, codeVerifier) {
|
|
145888
145894
|
const authConf = getAuthConfig(host);
|
|
145889
145895
|
const params = new url3.URLSearchParams({
|
|
145890
145896
|
grant_type: "authorization_code",
|
|
145891
145897
|
client_id: authConf.clientId,
|
|
145892
|
-
code_verifier:
|
|
145898
|
+
code_verifier: codeVerifier,
|
|
145893
145899
|
code,
|
|
145894
145900
|
redirect_uri: authConf.redirectUri
|
|
145895
145901
|
});
|
|
@@ -145937,6 +145943,7 @@ function decodeJwtExpiry(token) {
|
|
|
145937
145943
|
}
|
|
145938
145944
|
|
|
145939
145945
|
// src/commands/login.ts
|
|
145946
|
+
import readline from "readline";
|
|
145940
145947
|
import url4, { URL as URL2 } from "url";
|
|
145941
145948
|
import * as crypto2 from "crypto";
|
|
145942
145949
|
|
|
@@ -146564,10 +146571,43 @@ var open_default = open;
|
|
|
146564
146571
|
// src/commands/login.ts
|
|
146565
146572
|
var port = 2e4;
|
|
146566
146573
|
function createLoginCommand() {
|
|
146567
|
-
return new Command("login").description("Login to Sentio").option("--host <host>", "(Optional) Override Sentio Host name").option("--api-key <key>", "(Optional) Your API key").action((options) => {
|
|
146568
|
-
|
|
146574
|
+
return new Command("login").description("Login to Sentio").option("--host <host>", "(Optional) Override Sentio Host name").option("--api-key <key>", "(Optional) Your API key").option("--status", "Show current login status").option("--no-browser", "Print the auth URL and accept the authorization code manually (for headless environments)").action((options) => {
|
|
146575
|
+
if (options.status) {
|
|
146576
|
+
loginStatus(options);
|
|
146577
|
+
} else {
|
|
146578
|
+
login(options);
|
|
146579
|
+
}
|
|
146569
146580
|
});
|
|
146570
146581
|
}
|
|
146582
|
+
async function loginStatus(options) {
|
|
146583
|
+
const host = getFinalizedHost(options.host);
|
|
146584
|
+
console.log(source_default.blue("Host: ") + host);
|
|
146585
|
+
const apiKey = ReadKey(host);
|
|
146586
|
+
if (!apiKey) {
|
|
146587
|
+
console.log(source_default.red("Not logged in") + " (no API key stored for this host)");
|
|
146588
|
+
return;
|
|
146589
|
+
}
|
|
146590
|
+
console.log(source_default.green("API key: ") + apiKey.slice(0, 8) + "...");
|
|
146591
|
+
const tokenInfo = ReadAccessToken(host);
|
|
146592
|
+
if (tokenInfo) {
|
|
146593
|
+
const expired = isAccessTokenExpired(tokenInfo.expiresAt);
|
|
146594
|
+
const expiresDate = new Date(tokenInfo.expiresAt * 1e3).toLocaleString();
|
|
146595
|
+
if (expired) {
|
|
146596
|
+
console.log(source_default.yellow("Access token: ") + `expired (${expiresDate})`);
|
|
146597
|
+
} else {
|
|
146598
|
+
console.log(source_default.green("Access token: ") + `valid until ${expiresDate}`);
|
|
146599
|
+
}
|
|
146600
|
+
} else {
|
|
146601
|
+
console.log(source_default.yellow("Access token: ") + "none stored");
|
|
146602
|
+
}
|
|
146603
|
+
const res = await checkKey(host, apiKey);
|
|
146604
|
+
if (res.status === 200) {
|
|
146605
|
+
const { username } = await res.json();
|
|
146606
|
+
console.log(source_default.green("Logged in as: ") + username);
|
|
146607
|
+
} else {
|
|
146608
|
+
console.log(source_default.red("API key validation failed: ") + `${res.status} ${res.statusText}`);
|
|
146609
|
+
}
|
|
146610
|
+
}
|
|
146571
146611
|
function login(options) {
|
|
146572
146612
|
const host = getFinalizedHost(options.host);
|
|
146573
146613
|
if (options.apiKey) {
|
|
@@ -146591,10 +146631,15 @@ function login(options) {
|
|
|
146591
146631
|
return;
|
|
146592
146632
|
}
|
|
146593
146633
|
const authURL = buildAuthURL(conf, challenge);
|
|
146634
|
+
if (options.browser === false) {
|
|
146635
|
+
loginNoBrowser(host, authURL.toString(), verifier);
|
|
146636
|
+
return;
|
|
146637
|
+
}
|
|
146594
146638
|
console.log("Continue your authorization in the browser");
|
|
146595
146639
|
open_default(authURL.toString()).catch((reason) => {
|
|
146596
|
-
console.error(source_default.
|
|
146597
|
-
console.error(source_default.
|
|
146640
|
+
console.error(source_default.yellow("Unable to open browser: " + reason));
|
|
146641
|
+
console.error(source_default.yellow("Falling back to manual login..."));
|
|
146642
|
+
loginNoBrowser(host, authURL.toString(), verifier);
|
|
146598
146643
|
});
|
|
146599
146644
|
startServer({
|
|
146600
146645
|
serverPort: port,
|
|
@@ -146627,6 +146672,28 @@ function loginInteractiveAndWait(host) {
|
|
|
146627
146672
|
});
|
|
146628
146673
|
});
|
|
146629
146674
|
}
|
|
146675
|
+
function loginNoBrowser(host, authURL, codeVerifier) {
|
|
146676
|
+
console.log(source_default.blue("\nOpen the following URL in your browser to complete login:"));
|
|
146677
|
+
console.log(source_default.cyan(authURL));
|
|
146678
|
+
console.log(source_default.blue("\nAfter completing login, copy the authorization code from the redirect URL"));
|
|
146679
|
+
console.log(source_default.blue("(it is the value of the `code` query parameter) and paste it below.\n"));
|
|
146680
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
146681
|
+
rl.question("Authorization code: ", async (input) => {
|
|
146682
|
+
rl.close();
|
|
146683
|
+
const code = input.trim();
|
|
146684
|
+
if (!code) {
|
|
146685
|
+
console.error(source_default.red("No code provided, login aborted."));
|
|
146686
|
+
process.exit(1);
|
|
146687
|
+
}
|
|
146688
|
+
try {
|
|
146689
|
+
const username = await exchangeCodeAndSave(host, code, codeVerifier);
|
|
146690
|
+
console.log(source_default.green(`Login success with ${username}`));
|
|
146691
|
+
} catch (e10) {
|
|
146692
|
+
console.error(source_default.red("Login failed: " + e10.message));
|
|
146693
|
+
process.exit(1);
|
|
146694
|
+
}
|
|
146695
|
+
});
|
|
146696
|
+
}
|
|
146630
146697
|
function buildAuthURL(conf, challenge) {
|
|
146631
146698
|
const authURL = new URL2(conf.domain + `/authorize?`);
|
|
146632
146699
|
const params = new url4.URLSearchParams({
|
|
@@ -148996,7 +149063,7 @@ import path12 from "path";
|
|
|
148996
149063
|
import { createHash as createHash3 } from "crypto";
|
|
148997
149064
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
148998
149065
|
var import_jszip = __toESM(require_lib9(), 1);
|
|
148999
|
-
import
|
|
149066
|
+
import readline2 from "readline";
|
|
149000
149067
|
|
|
149001
149068
|
// src/uploader.ts
|
|
149002
149069
|
init_cjs_shim();
|
|
@@ -149435,7 +149502,7 @@ async function createProjectPrompt(options, auth, type) {
|
|
|
149435
149502
|
if (options.silentOverwrite) {
|
|
149436
149503
|
return await create2();
|
|
149437
149504
|
}
|
|
149438
|
-
const rl =
|
|
149505
|
+
const rl = readline2.createInterface({
|
|
149439
149506
|
input: process.stdin,
|
|
149440
149507
|
output: process.stdout
|
|
149441
149508
|
});
|
|
@@ -149457,7 +149524,7 @@ async function createProjectPrompt(options, auth, type) {
|
|
|
149457
149524
|
}
|
|
149458
149525
|
}
|
|
149459
149526
|
async function confirm(question) {
|
|
149460
|
-
const rl =
|
|
149527
|
+
const rl = readline2.createInterface({
|
|
149461
149528
|
input: process.stdin,
|
|
149462
149529
|
output: process.stdout
|
|
149463
149530
|
});
|
|
@@ -151477,7 +151544,7 @@ function asRecordArray(value) {
|
|
|
151477
151544
|
init_cjs_shim();
|
|
151478
151545
|
var import_yaml6 = __toESM(require_dist(), 1);
|
|
151479
151546
|
import process18 from "process";
|
|
151480
|
-
import
|
|
151547
|
+
import readline3 from "readline";
|
|
151481
151548
|
function createProjectCommand() {
|
|
151482
151549
|
const projectCommand = new Command("project").description("Manage Sentio projects");
|
|
151483
151550
|
projectCommand.addCommand(createProjectListCommand());
|
|
@@ -151626,7 +151693,7 @@ async function requireAccessToken(host, { strict }) {
|
|
|
151626
151693
|
return refreshed.token;
|
|
151627
151694
|
}
|
|
151628
151695
|
async function confirmDelete(projectSlug) {
|
|
151629
|
-
const rl =
|
|
151696
|
+
const rl = readline3.createInterface({ input: process18.stdin, output: process18.stdout });
|
|
151630
151697
|
return new Promise((resolve) => {
|
|
151631
151698
|
rl.question(
|
|
151632
151699
|
source_default.yellow(`Are you sure you want to delete project "${projectSlug}"? This cannot be undone. [y/N] `),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentio/cli",
|
|
3
|
-
"version": "3.4.1-rc.
|
|
3
|
+
"version": "3.4.1-rc.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"{lib,src,templates}",
|
|
14
|
+
"!lib/**/*.map",
|
|
14
15
|
"!{lib,src}/**/*.test.{js,ts}"
|
|
15
16
|
],
|
|
16
17
|
"types": "module",
|
|
@@ -49,11 +49,36 @@ app.get('/callback', async (req, res) => {
|
|
|
49
49
|
return
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
try {
|
|
53
|
+
const username = await exchangeCodeAndSave(host, code as string, authParams.codeVerifier)
|
|
54
|
+
res.end('Login success, please go back to CLI to continue')
|
|
55
|
+
console.log(chalk.green(`Login success with ${username}`))
|
|
56
|
+
} catch (e) {
|
|
57
|
+
fail((e as Error).message)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
server.close()
|
|
62
|
+
server.closeAllConnections()
|
|
63
|
+
if (authParams.onSuccess) {
|
|
64
|
+
authParams.onSuccess()
|
|
65
|
+
} else {
|
|
66
|
+
setTimeout(() => process.exit(), 1000)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Exchanges an OAuth authorization code for an access token, verifies the account,
|
|
72
|
+
* creates an API key, and saves everything to local config.
|
|
73
|
+
* Returns the username on success, throws on failure.
|
|
74
|
+
*/
|
|
75
|
+
export async function exchangeCodeAndSave(host: string, code: string, codeVerifier: string): Promise<string> {
|
|
52
76
|
// exchange token
|
|
53
|
-
const tokenResRaw = await getToken(host, code
|
|
77
|
+
const tokenResRaw = await getToken(host, code, codeVerifier)
|
|
54
78
|
if (!tokenResRaw.ok) {
|
|
55
|
-
|
|
56
|
-
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Failed to get access token: ${tokenResRaw.status} ${tokenResRaw.statusText}, ${await tokenResRaw.text()}`
|
|
81
|
+
)
|
|
57
82
|
}
|
|
58
83
|
const tokenRes = (await tokenResRaw.json()) as { access_token: string }
|
|
59
84
|
const accessToken = tokenRes.access_token
|
|
@@ -62,24 +87,20 @@ app.get('/callback', async (req, res) => {
|
|
|
62
87
|
const userResRaw = await getUser(host, accessToken)
|
|
63
88
|
if (!userResRaw.ok) {
|
|
64
89
|
if (userResRaw.status == 401) {
|
|
65
|
-
|
|
66
|
-
} else {
|
|
67
|
-
fail(`Failed to get user info: ${userResRaw.status} ${userResRaw.statusText}`)
|
|
90
|
+
throw new Error('The account does not exist, please sign up on sentio first')
|
|
68
91
|
}
|
|
69
|
-
|
|
92
|
+
throw new Error(`Failed to get user info: ${userResRaw.status} ${userResRaw.statusText}`)
|
|
70
93
|
}
|
|
71
94
|
const userRes = (await userResRaw.json()) as { emailVerified: boolean }
|
|
72
95
|
if (!userRes.emailVerified) {
|
|
73
|
-
|
|
74
|
-
return
|
|
96
|
+
throw new Error('Your account is not verified, please verify your email first')
|
|
75
97
|
}
|
|
76
98
|
|
|
77
99
|
// create API key
|
|
78
100
|
const apiKeyName = `${os.hostname()}-${crypto.randomBytes(4).toString('hex')}`
|
|
79
101
|
const createApiKeyResRaw = await createApiKey(host, apiKeyName, 'sdk_generated', accessToken)
|
|
80
102
|
if (!createApiKeyResRaw.ok) {
|
|
81
|
-
|
|
82
|
-
return
|
|
103
|
+
throw new Error(`Failed to create API key: ${createApiKeyResRaw.status} ${createApiKeyResRaw.statusText}`)
|
|
83
104
|
}
|
|
84
105
|
const { key, username } = (await createApiKeyResRaw.json()) as { key: string; username: string }
|
|
85
106
|
WriteKey(host, key)
|
|
@@ -90,24 +111,15 @@ app.get('/callback', async (req, res) => {
|
|
|
90
111
|
WriteAccessToken(host, accessToken, expiresAt)
|
|
91
112
|
}
|
|
92
113
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
server.close()
|
|
97
|
-
server.closeAllConnections()
|
|
98
|
-
if (authParams.onSuccess) {
|
|
99
|
-
authParams.onSuccess()
|
|
100
|
-
} else {
|
|
101
|
-
setTimeout(() => process.exit(), 1000)
|
|
102
|
-
}
|
|
103
|
-
})
|
|
114
|
+
return username
|
|
115
|
+
}
|
|
104
116
|
|
|
105
|
-
async function getToken(host: string, code: string) {
|
|
117
|
+
async function getToken(host: string, code: string, codeVerifier: string) {
|
|
106
118
|
const authConf = getAuthConfig(host)
|
|
107
119
|
const params = new url.URLSearchParams({
|
|
108
120
|
grant_type: 'authorization_code',
|
|
109
121
|
client_id: authConf.clientId,
|
|
110
|
-
code_verifier:
|
|
122
|
+
code_verifier: codeVerifier,
|
|
111
123
|
code: code,
|
|
112
124
|
redirect_uri: authConf.redirectUri
|
|
113
125
|
})
|
package/src/commands/login.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Command } from '@commander-js/extra-typings'
|
|
2
2
|
import { getAuthConfig, getFinalizedHost } from '../config.js'
|
|
3
|
-
import { startServer } from './login-server.js'
|
|
3
|
+
import { startServer, exchangeCodeAndSave } from './login-server.js'
|
|
4
|
+
import readline from 'readline'
|
|
4
5
|
import url, { URL } from 'url'
|
|
5
6
|
import * as crypto from 'crypto'
|
|
6
7
|
import chalk from 'chalk'
|
|
7
|
-
import { WriteKey } from '../key.js'
|
|
8
|
+
import { WriteKey, ReadKey, ReadAccessToken, isAccessTokenExpired } from '../key.js'
|
|
8
9
|
import fetch from 'node-fetch'
|
|
9
10
|
import open from 'open'
|
|
10
11
|
import { CommandOptionsType } from './types.js'
|
|
@@ -17,11 +18,51 @@ export function createLoginCommand() {
|
|
|
17
18
|
.description('Login to Sentio')
|
|
18
19
|
.option('--host <host>', '(Optional) Override Sentio Host name')
|
|
19
20
|
.option('--api-key <key>', '(Optional) Your API key')
|
|
21
|
+
.option('--status', 'Show current login status')
|
|
22
|
+
.option('--no-browser', 'Print the auth URL and accept the authorization code manually (for headless environments)')
|
|
20
23
|
.action((options) => {
|
|
21
|
-
|
|
24
|
+
if (options.status) {
|
|
25
|
+
loginStatus(options)
|
|
26
|
+
} else {
|
|
27
|
+
login(options)
|
|
28
|
+
}
|
|
22
29
|
})
|
|
23
30
|
}
|
|
24
31
|
|
|
32
|
+
async function loginStatus(options: CommandOptionsType<typeof createLoginCommand>) {
|
|
33
|
+
const host = getFinalizedHost(options.host)
|
|
34
|
+
console.log(chalk.blue('Host: ') + host)
|
|
35
|
+
|
|
36
|
+
const apiKey = ReadKey(host)
|
|
37
|
+
if (!apiKey) {
|
|
38
|
+
console.log(chalk.red('Not logged in') + ' (no API key stored for this host)')
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(chalk.green('API key: ') + apiKey.slice(0, 8) + '...')
|
|
43
|
+
|
|
44
|
+
const tokenInfo = ReadAccessToken(host)
|
|
45
|
+
if (tokenInfo) {
|
|
46
|
+
const expired = isAccessTokenExpired(tokenInfo.expiresAt)
|
|
47
|
+
const expiresDate = new Date(tokenInfo.expiresAt * 1000).toLocaleString()
|
|
48
|
+
if (expired) {
|
|
49
|
+
console.log(chalk.yellow('Access token: ') + `expired (${expiresDate})`)
|
|
50
|
+
} else {
|
|
51
|
+
console.log(chalk.green('Access token: ') + `valid until ${expiresDate}`)
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
console.log(chalk.yellow('Access token: ') + 'none stored')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const res = await checkKey(host, apiKey)
|
|
58
|
+
if (res.status === 200) {
|
|
59
|
+
const { username } = (await res.json()) as { username: string }
|
|
60
|
+
console.log(chalk.green('Logged in as: ') + username)
|
|
61
|
+
} else {
|
|
62
|
+
console.log(chalk.red('API key validation failed: ') + `${res.status} ${res.statusText}`)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
25
66
|
function login(options: CommandOptionsType<typeof createLoginCommand>) {
|
|
26
67
|
const host = getFinalizedHost(options.host)
|
|
27
68
|
|
|
@@ -48,10 +89,16 @@ function login(options: CommandOptionsType<typeof createLoginCommand>) {
|
|
|
48
89
|
}
|
|
49
90
|
const authURL = buildAuthURL(conf, challenge)
|
|
50
91
|
|
|
92
|
+
if (options.browser === false) {
|
|
93
|
+
loginNoBrowser(host, authURL.toString(), verifier)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
51
97
|
console.log('Continue your authorization in the browser')
|
|
52
98
|
open(authURL.toString()).catch((reason) => {
|
|
53
|
-
console.error(chalk.
|
|
54
|
-
console.error(chalk.
|
|
99
|
+
console.error(chalk.yellow('Unable to open browser: ' + reason))
|
|
100
|
+
console.error(chalk.yellow('Falling back to manual login...'))
|
|
101
|
+
loginNoBrowser(host, authURL.toString(), verifier)
|
|
55
102
|
})
|
|
56
103
|
|
|
57
104
|
startServer({
|
|
@@ -95,6 +142,30 @@ export function loginInteractiveAndWait(host: string): Promise<void> {
|
|
|
95
142
|
})
|
|
96
143
|
}
|
|
97
144
|
|
|
145
|
+
function loginNoBrowser(host: string, authURL: string, codeVerifier: string) {
|
|
146
|
+
console.log(chalk.blue('\nOpen the following URL in your browser to complete login:'))
|
|
147
|
+
console.log(chalk.cyan(authURL))
|
|
148
|
+
console.log(chalk.blue('\nAfter completing login, copy the authorization code from the redirect URL'))
|
|
149
|
+
console.log(chalk.blue('(it is the value of the `code` query parameter) and paste it below.\n'))
|
|
150
|
+
|
|
151
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
152
|
+
rl.question('Authorization code: ', async (input) => {
|
|
153
|
+
rl.close()
|
|
154
|
+
const code = input.trim()
|
|
155
|
+
if (!code) {
|
|
156
|
+
console.error(chalk.red('No code provided, login aborted.'))
|
|
157
|
+
process.exit(1)
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const username = await exchangeCodeAndSave(host, code, codeVerifier)
|
|
161
|
+
console.log(chalk.green(`Login success with ${username}`))
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.error(chalk.red('Login failed: ' + (e as Error).message))
|
|
164
|
+
process.exit(1)
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
98
169
|
function buildAuthURL(
|
|
99
170
|
conf: { domain: string; clientId: string; audience: string; redirectUri: string },
|
|
100
171
|
challenge: string
|