-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework GitHub acceptor and move to its own module #2021
Changes from 8 commits
2673372
dca054c
22a5e36
35845c9
edd7157
b0e37f8
4925255
1769b38
71f7ec4
d67f73e
b6e01ca
bde1f6e
4797e9c
45afa15
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
'use strict' | ||
|
||
const queryString = require('query-string') | ||
const request = require('request') | ||
const log = require('../../../lib/log') | ||
const githubAuth = require('../../../lib/github-auth') | ||
const serverSecrets = require('../../../lib/server-secrets') | ||
const secretIsValid = require('../../../lib/sys/secret-is-valid') | ||
|
||
function sendTokenToAllServers(token) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code is difficult to test, and enabling #1939 should obviate the need for it, so I don't think it's worth writing a new test. |
||
const { shieldsIps, shieldsSecret } = serverSecrets | ||
return Promise.all( | ||
shieldsIps.map( | ||
ip => | ||
new Promise((resolve, reject) => { | ||
const options = { | ||
url: `https://${ip}/github-auth/add-token`, | ||
method: 'POST', | ||
form: { | ||
shieldsSecret, | ||
token, | ||
}, | ||
// We target servers by IP, and we use HTTPS. Assuming that | ||
// 1. Internet routers aren't hacked, and | ||
// 2. We don't unknowingly lose our IP to someone else, | ||
// we're not leaking people's and our information. | ||
// (If we did, it would have no impact, as we only ask for a token, | ||
// no GitHub scope. The malicious entity would only be able to use | ||
// our rate limit pool.) | ||
// FIXME: use letsencrypt. | ||
strictSSL: false, | ||
} | ||
request(options, (err, res, body) => { | ||
if (err != null) { | ||
reject(err) | ||
} else { | ||
resolve() | ||
} | ||
}) | ||
}) | ||
) | ||
) | ||
} | ||
|
||
function setRoutes(server) { | ||
const baseUrl = process.env.BASE_URL || 'https://img.shields.io' | ||
|
||
server.route(/^\/github-auth$/, (data, match, end, ask) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a test. |
||
ask.res.statusCode = 302 // Found. | ||
const query = queryString.stringify({ | ||
client_id: serverSecrets.gh_client_id, | ||
redirect_uri: `${baseUrl}/github-auth/done`, | ||
}) | ||
ask.res.setHeader( | ||
'Location', | ||
`https://github.com/login/oauth/authorize?${query}` | ||
) | ||
end('') | ||
}) | ||
|
||
server.route(/^\/github-auth\/done$/, (data, match, end, ask) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a test. |
||
if (!data.code) { | ||
log(`GitHub OAuth data: ${JSON.stringify(data)}`) | ||
return end('GitHub OAuth authentication failed to provide a code.') | ||
} | ||
|
||
const options = { | ||
url: 'https://github.com/login/oauth/access_token', | ||
method: 'POST', | ||
headers: { | ||
'Content-type': 'application/x-www-form-urlencoded;charset=UTF-8', | ||
'User-Agent': 'Shields.io', | ||
}, | ||
form: queryString.stringify({ | ||
client_id: serverSecrets.gh_client_id, | ||
client_secret: serverSecrets.gh_client_secret, | ||
code: data.code, | ||
}), | ||
} | ||
request(options, (err, res, body) => { | ||
if (err != null) { | ||
return end('The connection to GitHub failed.') | ||
} | ||
|
||
let content | ||
try { | ||
content = queryString.parse(body) | ||
} catch (e) { | ||
return end('The GitHub OAuth token could not be parsed.') | ||
} | ||
|
||
const { access_token: token } = content | ||
if (!token) { | ||
return end('The GitHub OAuth process did not return a user token.') | ||
} | ||
|
||
ask.res.setHeader('Content-Type', 'text/html') | ||
end( | ||
'<p>Shields.io has received your app-specific GitHub user token. ' + | ||
'You can revoke it by going to ' + | ||
'<a href="https://github.com/settings/applications">GitHub</a>.</p>' + | ||
'<p>Until you do, you have now increased the rate limit for GitHub ' + | ||
'requests going through Shields.io. GitHub-related badges are ' + | ||
'therefore more robust.</p>' + | ||
'<p>Thanks for contributing to a smoother experience for ' + | ||
'everyone!</p>' + | ||
'<p><a href="/">Back to the website</a></p>' | ||
) | ||
|
||
sendTokenToAllServers(token).catch(e => { | ||
console.error('GitHub user token transmission failed:', e) | ||
}) | ||
}) | ||
}) | ||
|
||
server.route(/^\/github-auth\/add-token$/, (data, match, end, ask) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This endpoint is obviated when #1939 is turned on; so I'm not going to take the time to write a new test for it. |
||
if (!secretIsValid(data.shieldsSecret)) { | ||
// An unknown entity tries to connect. Let the connection linger for 10s. | ||
setTimeout(() => { | ||
end('Invalid secret.') | ||
}, 10000) | ||
return | ||
} | ||
|
||
githubAuth.addGithubToken(data.token) | ||
end('Thanks!') | ||
}) | ||
} | ||
|
||
module.exports = { | ||
sendTokenToAllServers, | ||
setRoutes, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't use
whatwg-fetch
to test redirects, because they can't be disabled.I don't have a strong preference between axios or got to supplant request and fetch. I had an easier time with
got
's documentation, so went with that for the moment. So far this is only used in test code.