Allow configuring allowed CORS origins for suggest (#1282)

- Support single-server testing and a local dev server (like Next) that is on a different port from the shields server
- Refactor config schema

With this change, the suggestions work locally in #1273.
This commit is contained in:
Paul Melnikow
2017-11-25 18:27:02 -05:00
committed by GitHub
parent a4bce73da6
commit 1af1f497bb
4 changed files with 80 additions and 39 deletions

View File

@@ -139,8 +139,17 @@ If you want to host the frontend on a separate server, such as cloud storage
or a CDN, you can do that. Just copy the built `index.html` there.
To help out users, you can make the Shields server redirect the server root.
Set the `FRONTEND_REDIRECT_URL` environment variable:
Set the `REDIRECT_URI` environment variable:
```sh
FRONTEND_REDIRECT_URL=http://my-custom-shields.s3.amazonaws.com/
REDIRECT_URI=http://my-custom-shields.s3.amazonaws.com/
```
If you want to use server suggestions, you should also set `ALLOWED_ORIGIN`:
```sh
ALLOWED_ORIGIN=http://my-custom-shields.s3.amazonaws.com,https://my-custom-shields.s3.amazonaws.com
```
This should be a comma-separated list of allowed origin headers. They should
not have paths or trailing slashes.

View File

@@ -4,25 +4,51 @@
// should be injected into other components needing it.
const url = require('url');
const envFlag = require('node-env-flag');
const secureServer = !!process.env.HTTPS;
const serverPort = +process.env.PORT || +process.argv[2] || (secureServer ? 443 : 80);
const bindAddress = process.env.BIND_ADDRESS || process.argv[3] || '::';
function envArray(envVar, defaultValue, delimiter) {
delimiter = delimiter || ',';
if (envVar) {
return envVar.split(delimiter);
} else {
return defaultValue;
}
}
const isSecure = envFlag(process.env.HTTPS, false);
const port = +process.env.PORT || +process.argv[2] || (isSecure ? 443 : 80);
const address = process.env.BIND_ADDRESS || process.argv[3] || '::';
const baseUri = url.format({
protocol: isSecure ? 'https' : 'http',
hostname: address,
port,
pathname: '/',
});
// The base URI provides a suitable value for development. Production should
// configure this.
const allowedOrigin = envArray(process.env.ALLOWED_ORIGIN, baseUri.replace(/\/$/, ''), ',');
const config = {
secureServer,
secureServerKey: process.env.HTTPS_KEY,
secureServerCert: process.env.HTTPS_CRT,
serverPort,
bindAddress,
githubApiUrl: process.env.GITHUB_URL || 'https://api.github.com',
frontendUri: url.format({
protocol: secureServer ? 'https' : 'http',
hostname: bindAddress,
port: serverPort,
pathname: '/',
}),
frontendRedirectUrl: process.env.FRONTEND_REDIRECT_URL || process.env.INFOSITE,
bind: {
port,
address,
},
ssl: {
isSecure,
key: process.env.HTTPS_KEY,
cert: process.env.HTTPS_CRT,
},
baseUri,
redirectUri: process.env.REDIRECT_URI || process.env.INFOSITE,
cors: {
allowedOrigin,
},
services: {
github: {
baseUri: process.env.GITHUB_URL || 'https://api.github.com',
},
},
};
module.exports = config;

View File

@@ -12,15 +12,20 @@ const githubApiUrl = process.env.GITHUB_URL || 'https://api.github.com';
// - link: target as a string URL.
// - badge: shields image URL.
// - name: string
function suggest (data, end, ask) {
const origin = ask.req.headers['origin'];
if (/^https?:\/\/shields\.io$/.test(origin)) {
ask.res.setHeader('Access-Control-Allow-Origin', origin);
} else {
ask.res.setHeader('Access-Control-Allow-Origin', 'null');
end({err: 'Disallowed'});
return;
function suggest (allowedOrigin, data, end, ask) {
// Same-host requests may be made in development or in single-server
// testing. These are legitimate, but do not have an origin header.
const origin = ask.req.headers.origin;
if (origin) {
if (allowedOrigin.includes(origin)) {
ask.res.setHeader('Access-Control-Allow-Origin', origin);
} else {
ask.res.setHeader('Access-Control-Allow-Origin', 'null');
end({err: 'Disallowed'});
return;
}
}
let url;
try {
url = nodeUrl.parse(data.url);
@@ -122,8 +127,10 @@ function githubLicense (user, repo) {
});
}
function setRoutes (camp) {
camp.ajax.on('suggest/v1', suggest);
function setRoutes (allowedOrigin, camp) {
camp.ajax.on(
'suggest/v1',
(data, end, ask) => suggest(allowedOrigin, data, end, ask));
}
module.exports = {

View File

@@ -95,15 +95,15 @@ const {
} = require('./lib/github-provider');
const serverStartTime = new Date((new Date()).toGMTString());
const { githubApiUrl } = config;
const githubApiUrl = config.services.github.baseUri;
const camp = require('camp').start({
documentRoot: path.join(__dirname, 'public'),
port: config.serverPort,
hostname: config.bindAddress,
secure: config.secureServer,
cert: config.secureServerCert,
key: config.secureServerKey,
port: config.bind.port,
hostname: config.bind.address,
secure: config.ssl.isSecure,
cert: config.ssl.cert,
key: config.ssl.key,
});
function reset() {
@@ -123,8 +123,7 @@ module.exports = {
stop
};
log('Server is starting up');
log(config.frontendUri);
log(`Server is starting up: ${config.baseUri}`);
analytics.load();
analytics.scheduleAutosaving();
@@ -135,7 +134,7 @@ if (serverSecrets && serverSecrets.gh_client_id) {
githubAuth.setRoutes(camp);
}
suggest.setRoutes(camp);
suggest.setRoutes(config.cors.allowedOrigin, camp);
camp.notfound(/\.(svg|png|gif|jpg|json)/, function(query, match, end, request) {
var format = match[1];
@@ -7518,10 +7517,10 @@ function(data, match, end, ask) {
}
});
if (config.frontendRedirectUrl) {
if (config.redirectUri) {
camp.route(/^\/$/, (data, match, end, ask) => {
ask.res.statusCode = 302;
ask.res.setHeader('Location', config.frontendRedirectUrl);
ask.res.setHeader('Location', config.redirectUri);
ask.res.end();
});
}