|
@ -57,220 +57,219 @@ const serverListen = ({ server, port }) => { |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function login(ctx) { |
|
|
const login = ctx => new Promise(async resolve => { |
|
|
return new Promise(async resolve => { |
|
|
let credentialsIndex = ctx.authConfig.credentials.findIndex( |
|
|
let credentialsIndex = ctx.authConfig.credentials.findIndex( |
|
|
cred => cred.provider === 'gcp' |
|
|
cred => cred.provider === 'gcp' |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if (credentialsIndex !== -1) { |
|
|
if (credentialsIndex !== -1) { |
|
|
// the user is already logged into gcp
|
|
|
// the user is already logged into gcp
|
|
|
let yes |
|
|
let yes |
|
|
try { |
|
|
try { |
|
|
yes = await promptBool( |
|
|
yes = await promptBool( |
|
|
info( |
|
|
info( |
|
|
`You already have GCP credentials – this will replace them.`, |
|
|
`You already have GCP credentials – this will replace them.`, |
|
|
` Do you want to continue?` |
|
|
` Do you want to continue?` |
|
|
) |
|
|
|
|
|
) |
|
|
) |
|
|
} catch (err) { |
|
|
) |
|
|
// promptBool only `reject`s upon user abort
|
|
|
} catch (err) { |
|
|
// let's set it to false just to make it clear
|
|
|
// promptBool only `reject`s upon user abort
|
|
|
yes = false |
|
|
// let's set it to false just to make it clear
|
|
|
} |
|
|
yes = false |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (!yes) { |
|
|
if (!yes) { |
|
|
console.log(aborted('No changes made.')) |
|
|
console.log(aborted('No changes made.')) |
|
|
resolve(0) |
|
|
return |
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const ports = [...PORTS] |
|
|
const ports = [...PORTS] |
|
|
const server = createServer(async function handleRequest(req, res) { |
|
|
const server = createServer(async function handleRequest(req, res) { |
|
|
const { query: { error: _error, code } } = parseUrl(req.url, true) |
|
|
const { query: { error: _error, code } } = parseUrl(req.url, true) |
|
|
|
|
|
|
|
|
if (!_error && !code) { |
|
|
if (!_error && !code) { |
|
|
// the browser requesting the favicon etc
|
|
|
// the browser requesting the favicon etc
|
|
|
res.end('') |
|
|
res.end('') |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
res.setHeader('content-type', 'text/html') |
|
|
|
|
|
res.end( |
|
|
|
|
|
`<meta charset="UTF-8">` + |
|
|
|
|
|
`<h2>That's it – you can now return to your terminal!</h2>` |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if (_error) { |
|
|
res.setHeader('content-type', 'text/html') |
|
|
// the user didn't give us permission
|
|
|
res.end( |
|
|
console.log(aborted(`No changes made.`)) |
|
|
`<meta charset="UTF-8">` + |
|
|
return resolve(1) |
|
|
`<h2>That's it – you can now return to your terminal!</h2>` |
|
|
} |
|
|
) |
|
|
|
|
|
|
|
|
if (code) { |
|
|
if (_error) { |
|
|
// that's right after the user gave us permission
|
|
|
// the user didn't give us permission
|
|
|
// let's exchange the authorization code for an access + refresh codes
|
|
|
console.log(aborted(`No changes made.`)) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const body = formUrlEncode({ |
|
|
if (code) { |
|
|
code, |
|
|
// that's right after the user gave us permission
|
|
|
client_id: CLIENT_ID, |
|
|
// let's exchange the authorization code for an access + refresh codes
|
|
|
client_secret: CLIENT_SECRET, |
|
|
|
|
|
redirect_uri: `http://${req.headers.host}`, |
|
|
|
|
|
grant_type: GRANT_TYPE |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
const opts = { |
|
|
const body = formUrlEncode({ |
|
|
method: 'POST', |
|
|
code, |
|
|
headers: { |
|
|
client_id: CLIENT_ID, |
|
|
'content-type': 'application/x-www-form-urlencoded', |
|
|
client_secret: CLIENT_SECRET, |
|
|
'content-length': body.length // just in case
|
|
|
redirect_uri: `http://${req.headers.host}`, |
|
|
}, |
|
|
grant_type: GRANT_TYPE |
|
|
body: body |
|
|
}) |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let accessToken |
|
|
const opts = { |
|
|
let expiresIn |
|
|
method: 'POST', |
|
|
let refreshToken |
|
|
headers: { |
|
|
let response |
|
|
'content-type': 'application/x-www-form-urlencoded', |
|
|
|
|
|
'content-length': body.length // just in case
|
|
|
|
|
|
}, |
|
|
|
|
|
body: body |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
let accessToken |
|
|
response = await fetch(TOKEN_URL, opts) |
|
|
let expiresIn |
|
|
if (response.status !== 200) { |
|
|
let refreshToken |
|
|
debug( |
|
|
let response |
|
|
`HTTP ${response.status} when trying to exchange the authorization code`, |
|
|
|
|
|
await response.text() |
|
|
|
|
|
) |
|
|
|
|
|
console.log( |
|
|
|
|
|
error( |
|
|
|
|
|
`Got unexpected status code from Google: ${response.status}` |
|
|
|
|
|
) |
|
|
|
|
|
) |
|
|
|
|
|
return resolve(1) |
|
|
|
|
|
} |
|
|
|
|
|
} catch (err) { |
|
|
|
|
|
debug( |
|
|
|
|
|
'unexpected error occurred while making the request to exchange the authorization code', |
|
|
|
|
|
err.message |
|
|
|
|
|
) |
|
|
|
|
|
console.log( |
|
|
|
|
|
error( |
|
|
|
|
|
'Unexpected error occurred while authenthing with Google', |
|
|
|
|
|
err.stack |
|
|
|
|
|
) |
|
|
|
|
|
) |
|
|
|
|
|
return resolve(1) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
const json = await response.json() |
|
|
response = await fetch(TOKEN_URL, opts) |
|
|
accessToken = json.access_token |
|
|
if (response.status !== 200) { |
|
|
expiresIn = json.expires_in |
|
|
|
|
|
refreshToken = json.refresh_token |
|
|
|
|
|
} catch (err) { |
|
|
|
|
|
debug( |
|
|
debug( |
|
|
'unexpected error occurred while parsing the JSON from the exchange request', |
|
|
`HTTP ${response.status} when trying to exchange the authorization code`, |
|
|
err.stack, |
|
|
|
|
|
'got', |
|
|
|
|
|
await response.text() |
|
|
await response.text() |
|
|
) |
|
|
) |
|
|
console.log( |
|
|
console.log( |
|
|
error( |
|
|
error( |
|
|
'Unexpected error occurred while parsing the JSON response from Google', |
|
|
`Got unexpected status code from Google: ${response.status}` |
|
|
err.message |
|
|
|
|
|
) |
|
|
) |
|
|
) |
|
|
) |
|
|
resolve(1) |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
} catch (err) { |
|
|
const now = new Date() |
|
|
debug( |
|
|
// `expires_in` is 3600 seconds
|
|
|
'unexpected error occurred while making the request to exchange the authorization code', |
|
|
const expiresAt = now.setSeconds(now.getSeconds() + expiresIn) |
|
|
err.message |
|
|
ctx = saveCredentials({ |
|
|
|
|
|
ctx, |
|
|
|
|
|
accessToken, |
|
|
|
|
|
expiresAt, |
|
|
|
|
|
refreshToken, |
|
|
|
|
|
credentialsIndex |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
const projects = await listProjects(ctx) |
|
|
|
|
|
const message = 'Select a project:' |
|
|
|
|
|
const choices = projects.map(project => { |
|
|
|
|
|
return { |
|
|
|
|
|
name: `${project.name} (${project.projectId})`, |
|
|
|
|
|
value: project.projectId, |
|
|
|
|
|
short: project.name |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
const projectId = await promptList({ |
|
|
|
|
|
message, |
|
|
|
|
|
choices, |
|
|
|
|
|
separator: false |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
const { projectId: id, name } = projects.find( |
|
|
|
|
|
p => p.projectId === projectId |
|
|
|
|
|
) |
|
|
) |
|
|
|
|
|
console.log( |
|
|
credentialsIndex = ctx.authConfig.credentials.findIndex( |
|
|
error( |
|
|
cred => cred.provider === 'gcp' |
|
|
'Unexpected error occurred while authenthing with Google', |
|
|
|
|
|
err.stack |
|
|
|
|
|
) |
|
|
) |
|
|
) |
|
|
ctx.authConfig.credentials[credentialsIndex].project = { |
|
|
return |
|
|
id, |
|
|
} |
|
|
name |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
writeToAuthConfigFile(ctx.authConfig) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
const json = await response.json() |
|
|
|
|
|
accessToken = json.access_token |
|
|
|
|
|
expiresIn = json.expires_in |
|
|
|
|
|
refreshToken = json.refresh_token |
|
|
|
|
|
} catch (err) { |
|
|
|
|
|
debug( |
|
|
|
|
|
'unexpected error occurred while parsing the JSON from the exchange request', |
|
|
|
|
|
err.stack, |
|
|
|
|
|
'got', |
|
|
|
|
|
await response.text() |
|
|
|
|
|
) |
|
|
console.log( |
|
|
console.log( |
|
|
ready( |
|
|
error( |
|
|
`Credentials and project saved in ${param(humanize(getNowDir()))}.` |
|
|
'Unexpected error occurred while parsing the JSON response from Google', |
|
|
|
|
|
err.message |
|
|
) |
|
|
) |
|
|
) |
|
|
) |
|
|
resolve(1) |
|
|
return |
|
|
} |
|
|
} |
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
let shouldRetry = true |
|
|
const now = new Date() |
|
|
let portToTry = ports.shift() |
|
|
// `expires_in` is 3600 seconds
|
|
|
|
|
|
const expiresAt = now.setSeconds(now.getSeconds() + expiresIn) |
|
|
|
|
|
ctx = saveCredentials({ |
|
|
|
|
|
ctx, |
|
|
|
|
|
accessToken, |
|
|
|
|
|
expiresAt, |
|
|
|
|
|
refreshToken, |
|
|
|
|
|
credentialsIndex |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
while (shouldRetry) { |
|
|
const projects = await listProjects(ctx) |
|
|
try { |
|
|
const message = 'Select a project:' |
|
|
await serverListen({ server, port: portToTry }) |
|
|
const choices = projects.map(project => { |
|
|
shouldRetry = false // done, listening
|
|
|
return { |
|
|
} catch (err) { |
|
|
name: `${project.name} (${project.projectId})`, |
|
|
if (ports.length) { |
|
|
value: project.projectId, |
|
|
// let's try again
|
|
|
short: project.name |
|
|
portToTry = ports.shift() |
|
|
|
|
|
} else { |
|
|
|
|
|
// we're out of ports to try
|
|
|
|
|
|
shouldRetry = false |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
const projectId = await promptList({ |
|
|
|
|
|
message, |
|
|
|
|
|
choices, |
|
|
|
|
|
separator: false |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
const { projectId: id, name } = projects.find( |
|
|
|
|
|
p => p.projectId === projectId |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
credentialsIndex = ctx.authConfig.credentials.findIndex( |
|
|
|
|
|
cred => cred.provider === 'gcp' |
|
|
|
|
|
) |
|
|
|
|
|
ctx.authConfig.credentials[credentialsIndex].project = { |
|
|
|
|
|
id, |
|
|
|
|
|
name |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!server.listening) { |
|
|
writeToAuthConfigFile(ctx.authConfig) |
|
|
|
|
|
|
|
|
console.log( |
|
|
console.log( |
|
|
error( |
|
|
ready( |
|
|
`Make sure you have one of the following TCP ports available:`, |
|
|
`Credentials and project saved in ${param(humanize(getNowDir()))}.` |
|
|
` ${PORTS.join(', ').replace()}` |
|
|
|
|
|
) |
|
|
) |
|
|
) |
|
|
) |
|
|
return resolve(1) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const query = { |
|
|
resolve() |
|
|
client_id: CLIENT_ID, |
|
|
}) |
|
|
redirect_uri: `http://localhost:${portToTry}`, |
|
|
|
|
|
response_type: RESPONSE_TYPE, |
|
|
let shouldRetry = true |
|
|
scope: SCOPES.join(' '), |
|
|
let portToTry = ports.shift() |
|
|
access_type: ACCESS_TYPE, |
|
|
|
|
|
prompt: PROMPT_CONSENT |
|
|
while (shouldRetry) { |
|
|
|
|
|
try { |
|
|
|
|
|
await serverListen({ server, port: portToTry }) |
|
|
|
|
|
shouldRetry = false // done, listening
|
|
|
|
|
|
} catch (err) { |
|
|
|
|
|
if (ports.length) { |
|
|
|
|
|
// let's try again
|
|
|
|
|
|
portToTry = ports.shift() |
|
|
|
|
|
} else { |
|
|
|
|
|
// we're out of ports to try
|
|
|
|
|
|
shouldRetry = false |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
opn(USER_URL + '?' + encodeQuery(query)) |
|
|
if (!server.listening) { |
|
|
}) |
|
|
console.log( |
|
|
} |
|
|
error( |
|
|
|
|
|
`Make sure you have one of the following TCP ports available:`, |
|
|
|
|
|
` ${PORTS.join(', ').replace()}` |
|
|
|
|
|
) |
|
|
|
|
|
) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const query = { |
|
|
|
|
|
client_id: CLIENT_ID, |
|
|
|
|
|
redirect_uri: `http://localhost:${portToTry}`, |
|
|
|
|
|
response_type: RESPONSE_TYPE, |
|
|
|
|
|
scope: SCOPES.join(' '), |
|
|
|
|
|
access_type: ACCESS_TYPE, |
|
|
|
|
|
prompt: PROMPT_CONSENT |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
opn(USER_URL + '?' + encodeQuery(query)) |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
module.exports = login |
|
|
module.exports = login |
|
|