mobbdev 0.0.15 → 0.0.18
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 +2 -1
- package/src/constants.mjs +3 -0
- package/src/github.mjs +20 -0
- package/src/gql.mjs +40 -29
- package/src/index.mjs +20 -4
- package/src/snyk.mjs +17 -2
- package/src/upload-file.mjs +9 -0
- package/src/web-login.mjs +17 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mobbdev",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Automated secure code remediation tool",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"scripts": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"colors": "1.4.0",
|
|
19
19
|
"configstore": "6.0.0",
|
|
20
|
+
"debug": "4.3.4",
|
|
20
21
|
"dotenv": "16.0.3",
|
|
21
22
|
"extract-zip": "2.0.1",
|
|
22
23
|
"node-fetch": "3.3.1",
|
package/src/constants.mjs
CHANGED
|
@@ -2,7 +2,9 @@ import path from 'node:path';
|
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import * as dotenv from 'dotenv';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
+
import Debug from 'debug';
|
|
5
6
|
|
|
7
|
+
const debug = Debug('mobbdev:constants');
|
|
6
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
9
|
dotenv.config({ path: path.join(__dirname, '../.env') });
|
|
8
10
|
|
|
@@ -15,6 +17,7 @@ const envVariablesSchema = z
|
|
|
15
17
|
.required();
|
|
16
18
|
|
|
17
19
|
const envVariables = envVariablesSchema.parse(process.env);
|
|
20
|
+
debug('config %o', envVariables);
|
|
18
21
|
|
|
19
22
|
export const WEB_LOGIN_URL = envVariables.WEB_LOGIN_URL;
|
|
20
23
|
export const WEB_APP_URL = envVariables.WEB_APP_URL;
|
package/src/github.mjs
CHANGED
|
@@ -4,10 +4,13 @@ import path from 'node:path';
|
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
5
|
import fetch from 'node-fetch';
|
|
6
6
|
import extract from 'extract-zip';
|
|
7
|
+
import Debug from 'debug';
|
|
7
8
|
|
|
8
9
|
const pipeline = promisify(stream.pipeline);
|
|
10
|
+
const debug = Debug('mobbdev:github');
|
|
9
11
|
|
|
10
12
|
export async function getDefaultBranch(repoUrl) {
|
|
13
|
+
debug('get default branch %s', repoUrl);
|
|
11
14
|
let slug = repoUrl.replace(/https?:\/\/github\.com\//i, '');
|
|
12
15
|
|
|
13
16
|
if (slug.endsWith('/')) {
|
|
@@ -17,6 +20,7 @@ export async function getDefaultBranch(repoUrl) {
|
|
|
17
20
|
if (slug.endsWith('.git')) {
|
|
18
21
|
slug = slug.substring(0, slug.length - '.git'.length);
|
|
19
22
|
}
|
|
23
|
+
debug('slug %s', slug);
|
|
20
24
|
|
|
21
25
|
const response = await fetch(`https://api.github.com/repos/${slug}`, {
|
|
22
26
|
method: 'GET',
|
|
@@ -27,19 +31,34 @@ export async function getDefaultBranch(repoUrl) {
|
|
|
27
31
|
});
|
|
28
32
|
|
|
29
33
|
if (!response.ok) {
|
|
34
|
+
debug('GH request failed %s %s', response.body, response.status);
|
|
30
35
|
throw new Error(
|
|
31
36
|
`Can't get default branch, make sure the repository is public: ${repoUrl}.`
|
|
32
37
|
);
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
const repoInfo = await response.json();
|
|
41
|
+
debug('GH request ok %o', repoInfo);
|
|
36
42
|
|
|
37
43
|
return repoInfo.default_branch;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
export async function downloadRepo(repoUrl, reference, dirname) {
|
|
47
|
+
debug('download repo %s %s %s', repoUrl, reference, dirname);
|
|
41
48
|
const zipFilePath = path.join(dirname, 'repo.zip');
|
|
42
49
|
const response = await fetch(`${repoUrl}/zipball/${reference}`);
|
|
50
|
+
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
debug(
|
|
53
|
+
'GH zipball request failed %s %s',
|
|
54
|
+
response.body,
|
|
55
|
+
response.status
|
|
56
|
+
);
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Can't access the repository, make it is public: ${repoUrl}.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
43
62
|
const fileWriterStream = fs.createWriteStream(zipFilePath);
|
|
44
63
|
|
|
45
64
|
await pipeline(response.body, fileWriterStream);
|
|
@@ -49,6 +68,7 @@ export async function downloadRepo(repoUrl, reference, dirname) {
|
|
|
49
68
|
.readdirSync(dirname, { withFileTypes: true })
|
|
50
69
|
.filter((dirent) => dirent.isDirectory())
|
|
51
70
|
.map((dirent) => dirent.name)[0];
|
|
71
|
+
debug('repo root %s', repoRoot);
|
|
52
72
|
|
|
53
73
|
return path.join(dirname, repoRoot);
|
|
54
74
|
}
|
package/src/gql.mjs
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
import fetch from 'node-fetch';
|
|
2
|
+
import Debug from 'debug';
|
|
2
3
|
import { API_URL } from './constants.mjs';
|
|
3
4
|
|
|
5
|
+
const debug = Debug('mobbdev:gql');
|
|
6
|
+
|
|
4
7
|
const ME = `
|
|
5
8
|
query Me {
|
|
6
9
|
user {
|
|
7
10
|
id
|
|
8
11
|
email
|
|
9
|
-
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const GET_ORG_AND_PROJECT_ID = `
|
|
17
|
+
query getOrgAndProjectId {
|
|
18
|
+
user {
|
|
19
|
+
userOrganizationsAndUserOrganizationRoles {
|
|
10
20
|
organization {
|
|
11
21
|
id
|
|
12
|
-
projects {
|
|
22
|
+
projects(order_by: {updatedAt: desc}) {
|
|
13
23
|
id
|
|
14
24
|
}
|
|
15
25
|
}
|
|
16
26
|
}
|
|
17
27
|
}
|
|
18
|
-
}
|
|
19
|
-
`;
|
|
28
|
+
}`;
|
|
20
29
|
|
|
21
30
|
const CREATE_COMMUNITY_USER = `
|
|
22
31
|
mutation CreateCommunityUser {
|
|
@@ -59,12 +68,12 @@ mutation SubmitVulnerabilityReport($vulnerabilityReportFileName: String!, $fixRe
|
|
|
59
68
|
|
|
60
69
|
export class GQLClient {
|
|
61
70
|
constructor(token) {
|
|
71
|
+
debug('init with token %s', token);
|
|
62
72
|
this._token = token;
|
|
63
|
-
this._projectId = undefined;
|
|
64
|
-
this._organizationId = undefined;
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
async _apiCall(query, variables = {}) {
|
|
76
|
+
debug('api call %o %s', variables, query);
|
|
68
77
|
const response = await fetch(API_URL, {
|
|
69
78
|
method: 'POST',
|
|
70
79
|
headers: {
|
|
@@ -77,13 +86,15 @@ export class GQLClient {
|
|
|
77
86
|
});
|
|
78
87
|
|
|
79
88
|
if (!response.ok) {
|
|
89
|
+
debug('API request failed %s %s', response.body, response.status);
|
|
80
90
|
throw new Error(`API call failed: ${response.status}`);
|
|
81
91
|
}
|
|
82
92
|
|
|
83
93
|
const data = await response.json();
|
|
94
|
+
debug('API request ok %j', data);
|
|
84
95
|
|
|
85
96
|
if (data.errors) {
|
|
86
|
-
throw new Error(`API error: ${
|
|
97
|
+
throw new Error(`API error: ${data.errors[0].message}`);
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
if (!data.data) {
|
|
@@ -93,40 +104,35 @@ export class GQLClient {
|
|
|
93
104
|
return data.data;
|
|
94
105
|
}
|
|
95
106
|
|
|
96
|
-
getOrganizationId() {
|
|
97
|
-
return this._organizationId;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
getProjectId() {
|
|
101
|
-
return this._projectId;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
107
|
async verifyToken() {
|
|
105
108
|
await this.createCommunityUser();
|
|
106
109
|
|
|
107
110
|
try {
|
|
108
|
-
|
|
109
|
-
const {
|
|
110
|
-
user: [{ userOrganizations }],
|
|
111
|
-
} = userInfo;
|
|
112
|
-
const [
|
|
113
|
-
{
|
|
114
|
-
organization: { id: organizationId, projects },
|
|
115
|
-
},
|
|
116
|
-
] = userOrganizations;
|
|
117
|
-
const [{ id: projectId }] = projects;
|
|
118
|
-
this._projectId = projectId;
|
|
119
|
-
this._organizationId = organizationId;
|
|
111
|
+
await this._apiCall(ME);
|
|
120
112
|
} catch (e) {
|
|
113
|
+
debug('verify token failed %o', e);
|
|
121
114
|
return false;
|
|
122
115
|
}
|
|
123
116
|
return true;
|
|
124
117
|
}
|
|
125
118
|
|
|
119
|
+
async getOrgAndProjectId() {
|
|
120
|
+
const data = await this._apiCall(GET_ORG_AND_PROJECT_ID);
|
|
121
|
+
const org =
|
|
122
|
+
data.user[0].userOrganizationsAndUserOrganizationRoles[0]
|
|
123
|
+
.organization;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
organizationId: org.id,
|
|
127
|
+
projectId: org.projects[0].id,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
126
131
|
async createCommunityUser() {
|
|
127
132
|
try {
|
|
128
133
|
await this._apiCall(CREATE_COMMUNITY_USER);
|
|
129
134
|
} catch (e) {
|
|
135
|
+
debug('create community user failed %o', e);
|
|
130
136
|
// Ignore errors
|
|
131
137
|
}
|
|
132
138
|
}
|
|
@@ -146,13 +152,18 @@ export class GQLClient {
|
|
|
146
152
|
};
|
|
147
153
|
}
|
|
148
154
|
|
|
149
|
-
async submitVulnerabilityReport(
|
|
155
|
+
async submitVulnerabilityReport(
|
|
156
|
+
fixReportId,
|
|
157
|
+
repoUrl,
|
|
158
|
+
reference,
|
|
159
|
+
projectId
|
|
160
|
+
) {
|
|
150
161
|
await this._apiCall(SUBMIT_VULNERABILITY_REPORT, {
|
|
151
162
|
fixReportId,
|
|
152
163
|
repoUrl,
|
|
153
164
|
reference,
|
|
154
165
|
vulnerabilityReportFileName: 'report.json',
|
|
155
|
-
projectId
|
|
166
|
+
projectId,
|
|
156
167
|
});
|
|
157
168
|
}
|
|
158
169
|
}
|
package/src/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import semver from 'semver';
|
|
5
5
|
import open from 'open';
|
|
6
6
|
import Configstore from 'configstore';
|
|
7
|
+
import Debug from 'debug';
|
|
7
8
|
import { GQLClient } from './gql.mjs';
|
|
8
9
|
import { webLogin } from './web-login.mjs';
|
|
9
10
|
import { downloadRepo, getDefaultBranch } from './github.mjs';
|
|
@@ -11,6 +12,7 @@ import { getSnykReport } from './snyk.mjs';
|
|
|
11
12
|
import { uploadFile } from './upload-file.mjs';
|
|
12
13
|
import { WEB_APP_URL } from './constants.mjs';
|
|
13
14
|
|
|
15
|
+
const debug = Debug('mobbdev:index');
|
|
14
16
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
17
|
const packageJson = JSON.parse(
|
|
16
18
|
fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8')
|
|
@@ -24,8 +26,11 @@ if (!semver.satisfies(process.version, packageJson.engines.node)) {
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
const config = new Configstore(packageJson.name, { token: '' });
|
|
29
|
+
debug('config %o', config);
|
|
27
30
|
|
|
28
31
|
export async function main(dirname, repoUrl) {
|
|
32
|
+
debug('start %s %s', dirname, repoUrl);
|
|
33
|
+
|
|
29
34
|
if (!repoUrl) {
|
|
30
35
|
console.warn(
|
|
31
36
|
'Mobb CLI usage: npx mobbdev <public GitHub repository URL>'
|
|
@@ -34,6 +39,7 @@ export async function main(dirname, repoUrl) {
|
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
let token = config.get('token');
|
|
42
|
+
debug('token %s', token);
|
|
37
43
|
let gqlClient = new GQLClient(token);
|
|
38
44
|
|
|
39
45
|
if (!token || !(await gqlClient.verifyToken())) {
|
|
@@ -47,10 +53,15 @@ export async function main(dirname, repoUrl) {
|
|
|
47
53
|
console.error('Something went wrong, API token is invalid.');
|
|
48
54
|
return;
|
|
49
55
|
}
|
|
56
|
+
debug('set token %s', token);
|
|
50
57
|
config.set('token', token);
|
|
51
58
|
}
|
|
52
59
|
|
|
60
|
+
const { projectId, organizationId } = await gqlClient.getOrgAndProjectId();
|
|
61
|
+
debug('org id %s', organizationId);
|
|
62
|
+
debug('project id %s', projectId);
|
|
53
63
|
const reference = await getDefaultBranch(repoUrl);
|
|
64
|
+
debug('default branch %s', reference);
|
|
54
65
|
const uploadData = await gqlClient.uploadS3BucketInfo();
|
|
55
66
|
|
|
56
67
|
const repositoryRoot = await downloadRepo(repoUrl, reference, dirname);
|
|
@@ -59,7 +70,11 @@ export async function main(dirname, repoUrl) {
|
|
|
59
70
|
console.log(
|
|
60
71
|
'We will run Snyk CLI to scan the repository for vulnerabilities. You may be redirected to the login page in the process.'
|
|
61
72
|
);
|
|
62
|
-
|
|
73
|
+
|
|
74
|
+
if (!(await getSnykReport(reportPath, repositoryRoot))) {
|
|
75
|
+
debug('snyk code is not enabled');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
63
78
|
|
|
64
79
|
const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
|
|
65
80
|
|
|
@@ -72,9 +87,12 @@ export async function main(dirname, repoUrl) {
|
|
|
72
87
|
await gqlClient.submitVulnerabilityReport(
|
|
73
88
|
uploadData.fixReportId,
|
|
74
89
|
repoUrl,
|
|
75
|
-
reference
|
|
90
|
+
reference,
|
|
91
|
+
projectId
|
|
76
92
|
);
|
|
77
93
|
|
|
94
|
+
debug('report %o', report);
|
|
95
|
+
|
|
78
96
|
const results = ((report.runs || [])[0] || {}).results || [];
|
|
79
97
|
if (results.length === 0) {
|
|
80
98
|
console.log('Snyk has not found any vulnerabilities — nothing to fix.');
|
|
@@ -82,8 +100,6 @@ export async function main(dirname, repoUrl) {
|
|
|
82
100
|
console.log(
|
|
83
101
|
'You will be redirected to our report page, please wait until the analysis is finished and enjoy your fixes.'
|
|
84
102
|
);
|
|
85
|
-
const projectId = gqlClient.getProjectId();
|
|
86
|
-
const organizationId = gqlClient.getOrganizationId();
|
|
87
103
|
await open(
|
|
88
104
|
`${WEB_APP_URL}/organization/${organizationId}/project/${projectId}/report/${uploadData.fixReportId}`
|
|
89
105
|
);
|
package/src/snyk.mjs
CHANGED
|
@@ -4,11 +4,17 @@ import readline from 'node:readline';
|
|
|
4
4
|
import { stdout } from 'colors/lib/system/supports-colors.js';
|
|
5
5
|
import open from 'open';
|
|
6
6
|
import * as process from 'process';
|
|
7
|
+
import Debug from 'debug';
|
|
7
8
|
|
|
9
|
+
const debug = Debug('mobbdev:snyk');
|
|
8
10
|
const require = createRequire(import.meta.url);
|
|
9
11
|
const SNYK_PATH = require.resolve('snyk/bin/snyk');
|
|
10
12
|
|
|
13
|
+
debug('snyk executable path %s', SNYK_PATH);
|
|
14
|
+
|
|
11
15
|
async function forkSnyk(args, display) {
|
|
16
|
+
debug('fork snyk with args %o %s', args, display);
|
|
17
|
+
|
|
12
18
|
return new Promise((resolve, reject) => {
|
|
13
19
|
const child = cp.fork(SNYK_PATH, args, {
|
|
14
20
|
stdio: ['inherit', 'pipe', 'pipe', 'ipc'],
|
|
@@ -16,6 +22,7 @@ async function forkSnyk(args, display) {
|
|
|
16
22
|
});
|
|
17
23
|
let out = '';
|
|
18
24
|
const onData = (chunk) => {
|
|
25
|
+
debug('chunk received from snyk std %s', chunk);
|
|
19
26
|
out += chunk;
|
|
20
27
|
};
|
|
21
28
|
|
|
@@ -28,9 +35,11 @@ async function forkSnyk(args, display) {
|
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
child.on('exit', () => {
|
|
38
|
+
debug('snyk exit');
|
|
31
39
|
resolve(out);
|
|
32
40
|
});
|
|
33
41
|
child.on('error', (err) => {
|
|
42
|
+
debug('snyk error %o', err);
|
|
34
43
|
reject(err);
|
|
35
44
|
});
|
|
36
45
|
});
|
|
@@ -51,9 +60,12 @@ async function question(questionString) {
|
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
export async function getSnykReport(reportPath, repoRoot) {
|
|
63
|
+
debug('get snyk report start %s %s', reportPath, repoRoot);
|
|
64
|
+
|
|
54
65
|
const config = await forkSnyk(['config'], false);
|
|
55
66
|
|
|
56
67
|
if (!config.includes('api: ')) {
|
|
68
|
+
debug('no token in the config %s', config);
|
|
57
69
|
await forkSnyk(['auth'], true);
|
|
58
70
|
}
|
|
59
71
|
|
|
@@ -67,16 +79,19 @@ export async function getSnykReport(reportPath, repoRoot) {
|
|
|
67
79
|
'Snyk Code is not supported for org: enable in Settings > Snyk Code'
|
|
68
80
|
)
|
|
69
81
|
) {
|
|
82
|
+
debug('snyk code is not enabled %s', out);
|
|
70
83
|
const answer = await question(
|
|
71
84
|
"Do you want to be taken to the relevant Snyk's online article? (Y/N)"
|
|
72
85
|
);
|
|
86
|
+
debug('answer %s', answer);
|
|
73
87
|
|
|
74
88
|
if (['y', 'yes', ''].includes(answer.toLowerCase())) {
|
|
89
|
+
debug('opening the browser');
|
|
75
90
|
await open(
|
|
76
91
|
'https://docs.snyk.io/scan-application-code/snyk-code/getting-started-with-snyk-code/activating-snyk-code-using-the-web-ui/step-1-enabling-the-snyk-code-option'
|
|
77
92
|
);
|
|
78
93
|
}
|
|
79
|
-
|
|
80
|
-
process.exit(0);
|
|
94
|
+
return false;
|
|
81
95
|
}
|
|
96
|
+
return true;
|
|
82
97
|
}
|
package/src/upload-file.mjs
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import fetch, { FormData, fileFrom } from 'node-fetch';
|
|
2
|
+
import Debug from 'debug';
|
|
3
|
+
|
|
4
|
+
const debug = Debug('mobbdev:upload-file');
|
|
2
5
|
|
|
3
6
|
export async function uploadFile(reportPath, url, uploadKey, uploadFields) {
|
|
7
|
+
debug('upload report file start %s %s', reportPath, url);
|
|
8
|
+
debug('upload fields %o', uploadFields);
|
|
9
|
+
debug('upload key %s', uploadKey);
|
|
10
|
+
|
|
4
11
|
const form = new FormData();
|
|
5
12
|
|
|
6
13
|
for (const key in uploadFields) {
|
|
@@ -16,6 +23,8 @@ export async function uploadFile(reportPath, url, uploadKey, uploadFields) {
|
|
|
16
23
|
});
|
|
17
24
|
|
|
18
25
|
if (!response.ok) {
|
|
26
|
+
debug('error from S3 %s %s', response.body, response.status);
|
|
19
27
|
throw new Error(`Failed to upload the report: ${response.status}`);
|
|
20
28
|
}
|
|
29
|
+
debug('upload report file done');
|
|
21
30
|
}
|
package/src/web-login.mjs
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import Debug from 'debug';
|
|
1
2
|
import { setTimeout, clearTimeout } from 'node:timers';
|
|
2
3
|
import http from 'node:http';
|
|
3
|
-
import { WEB_LOGIN_URL } from './constants.mjs';
|
|
4
4
|
import querystring from 'node:querystring';
|
|
5
5
|
import open from 'open';
|
|
6
|
+
import { WEB_LOGIN_URL } from './constants.mjs';
|
|
7
|
+
|
|
8
|
+
const debug = Debug('mobbdev:web-login');
|
|
6
9
|
|
|
7
10
|
export async function webLogin() {
|
|
11
|
+
debug('web login start');
|
|
12
|
+
|
|
8
13
|
let responseResolver;
|
|
9
14
|
let responseRejecter;
|
|
10
15
|
const responseAwaiter = new Promise((resolve, reject) => {
|
|
@@ -12,17 +17,21 @@ export async function webLogin() {
|
|
|
12
17
|
responseRejecter = reject;
|
|
13
18
|
});
|
|
14
19
|
const timerHandler = setTimeout(() => {
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
debug('timeout happened');
|
|
21
|
+
responseRejecter(new Error('No login happened in three minutes.'));
|
|
22
|
+
}, 180000);
|
|
17
23
|
|
|
18
24
|
const server = http.createServer((req, res) => {
|
|
25
|
+
debug('incoming request');
|
|
19
26
|
let body = '';
|
|
20
27
|
|
|
21
28
|
req.on('data', (chunk) => {
|
|
29
|
+
debug('http server get chunk %s', chunk);
|
|
22
30
|
body += chunk;
|
|
23
31
|
});
|
|
24
32
|
|
|
25
33
|
req.on('end', () => {
|
|
34
|
+
debug('http server end %s', body);
|
|
26
35
|
res.writeHead(301, {
|
|
27
36
|
Location: `${WEB_LOGIN_URL}?done=true`,
|
|
28
37
|
}).end();
|
|
@@ -30,17 +39,22 @@ export async function webLogin() {
|
|
|
30
39
|
});
|
|
31
40
|
});
|
|
32
41
|
|
|
42
|
+
debug('http server starting');
|
|
33
43
|
const port = await new Promise((resolve) => {
|
|
34
44
|
server.listen(0, '127.0.0.1', () => {
|
|
35
45
|
resolve(server.address().port);
|
|
36
46
|
});
|
|
37
47
|
});
|
|
48
|
+
debug('http server started on port %d', port);
|
|
38
49
|
|
|
50
|
+
debug('opening the browser on %s', `${WEB_LOGIN_URL}?port=${port}`);
|
|
39
51
|
await open(`${WEB_LOGIN_URL}?port=${port}`);
|
|
40
52
|
|
|
41
53
|
try {
|
|
54
|
+
debug('waiting for http request');
|
|
42
55
|
return await responseAwaiter;
|
|
43
56
|
} finally {
|
|
57
|
+
debug('http server close');
|
|
44
58
|
clearTimeout(timerHandler);
|
|
45
59
|
server.close();
|
|
46
60
|
}
|