Skip to content
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

Merged
merged 14 commits into from
Nov 9, 2018
Merged
132 changes: 1 addition & 131 deletions lib/github-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

const { EventEmitter } = require('events')
const crypto = require('crypto')
const log = require('./log')
const secretIsValid = require('./sys/secret-is-valid')
const queryString = require('query-string')
const request = require('request')
const serverSecrets = require('./server-secrets')
const mapKeys = require('lodash.mapkeys')

Expand All @@ -25,133 +22,6 @@ if (serverSecrets && serverSecrets.gh_token) {
addGithubToken(serverSecrets.gh_token)
}

function setRoutes(server) {
const baseUrl = process.env.BASE_URL || 'https://img.shields.io'

server.route(/^\/github-auth$/, (data, match, end, ask) => {
if (!(serverSecrets && serverSecrets.gh_client_id)) {
return end('This server is missing GitHub client secrets.')
}
const query = queryString.stringify({
client_id: serverSecrets.gh_client_id,
redirect_uri: baseUrl + '/github-auth/done',
})
ask.res.statusCode = 302 // Found.
ask.res.setHeader(
'Location',
'https://github.com/login/oauth/authorize?' + query
)
end('')
})

server.route(/^\/github-auth\/done$/, (data, match, end, ask) => {
if (
!(
serverSecrets &&
serverSecrets.gh_client_id &&
serverSecrets.gh_client_secret
)
) {
return end('This server is missing GitHub client secrets.')
}
if (!data.code) {
log(`GitHub OAuth data.code: ${JSON.stringify(data)}`)
return end('GitHub OAuth authentication failed to provide a code.')
}
const options = {
url: 'https://github.com/login/oauth/access_token',
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,
}),
method: 'POST',
}
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 token = content.access_token
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) => {
if (!secretIsValid(data.shieldsSecret)) {
// An unknown entity tries to connect. Let the connection linger for 10s.
return setTimeout(() => {
end('Invalid secret.')
}, 10000)
}
addGithubToken(data.token)
emitter.emit('token-added', data.token)
end('Thanks!')
})
}

function sendTokenToAllServers(token) {
const ips = serverSecrets.shieldsIps
return Promise.all(
ips.map(
ip =>
new Promise((resolve, reject) => {
const options = {
url: 'https://' + ip + '/github-auth/add-token',
method: 'POST',
form: {
shieldsSecret: serverSecrets.shieldsSecret,
token: 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) {
return reject(err)
}
resolve()
})
})
)
)
}

// token: client token as a string.
// reqs: number of requests remaining.
// reset: timestamp when the number of remaining requests is reset.
Expand Down Expand Up @@ -214,6 +84,7 @@ function addGithubToken(token) {
if (githubUserTokens.indexOf(token) === -1) {
githubUserTokens.push(token)
}
emitter.emit('token-added', token)
}

function rmGithubToken(token) {
Expand Down Expand Up @@ -333,7 +204,6 @@ function githubRequest(request, url, query, cb) {

module.exports = {
request: githubRequest,
setRoutes,
serializeDebugInfo,
addGithubToken,
rmGithubToken,
Expand Down
Loading