While Next.js can handle static sites, we've had a few issues with it, notably a performance hit at runtime and some bugginess around routing and SSR. Gatsby being fully intended for high-performance static sites makes it a great technical fit for the Shields frontend. The `createPages()` API should be a really nice way to add a page for each service family, for example. This migrates the frontend from Next.js to Gatsby. Gatsby is a powerful tool, which has a bit of downside as there's a lot to dig through. Overall I found configuration easier than Next.js. There are a lot of plugins and for the most part they worked out of the box. The documentation is good. Links are cleaner now: there is no #. This will break old links though perhaps we could add some redirection to help with that. The only one I’m really concerned about `/#/endpoint`. I’m not sure if folks are deep-linking to the category pages. There are a lot of enhancements we could add, in order to speed up the site even more. In particular we could think about inlining the SVGs rather than making separate requests for each one. While Gatsby recommends GraphQL, it's not required. To keep things simple and reduce the learning curve, I did not use it here. Close #1943 Fix #2837 Fix #2616
135 lines
4.1 KiB
JavaScript
135 lines
4.1 KiB
JavaScript
'use strict'
|
|
|
|
const queryString = require('query-string')
|
|
const request = require('request')
|
|
const log = require('../../../core/server/log')
|
|
const secretIsValid = require('../../../core/server/secret-is-valid')
|
|
const serverSecrets = require('../../../lib/server-secrets')
|
|
|
|
function sendTokenToAllServers(token) {
|
|
const {
|
|
shields_ips: shieldsIps,
|
|
shields_secret: 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, onTokenAccepted }) {
|
|
const baseUrl = process.env.GATSBY_BASE_URL || 'https://img.shields.io'
|
|
|
|
server.route(/^\/github-auth$/, (data, match, end, ask) => {
|
|
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) => {
|
|
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) => {
|
|
if (!secretIsValid(data.shieldsSecret)) {
|
|
// An unknown entity tries to connect. Let the connection linger for 10s.
|
|
setTimeout(() => {
|
|
end('Invalid secret.')
|
|
}, 10000)
|
|
return
|
|
}
|
|
|
|
onTokenAccepted(data.token)
|
|
end('Thanks!')
|
|
})
|
|
}
|
|
|
|
module.exports = {
|
|
setRoutes,
|
|
}
|