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

Allow configuring allowed CORS origins for suggest #1282

Merged
merged 3 commits into from
Nov 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions doc/self-hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In line 20 in this file, there is an info that one should build frontend using BASE_URL=https://your-server.example.com npm run build:production
build:production runs make footer-production-transform, which sets img.shields.io as a suggestions server (var origin = 'https://img.shields.io'; in index.html). Requests from locally build frontend will go to img.shields.io.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for noticing that. It should be fixed as part of #1273, which gets rid of the makefile transform and ships the suggestions server through the bundle.


```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.
58 changes: 42 additions & 16 deletions lib/server-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
27 changes: 17 additions & 10 deletions lib/suggest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 = {
Expand Down
21 changes: 10 additions & 11 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,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() {
Expand All @@ -120,8 +120,7 @@ module.exports = {
stop
};

log('Server is starting up');
log(config.frontendUri);
log(`Server is starting up: ${config.baseUri}`);

analytics.load();
analytics.scheduleAutosaving();
Expand All @@ -132,7 +131,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];
Expand Down Expand Up @@ -7503,10 +7502,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();
});
}