Skip to content
This repository has been archived by the owner on Dec 20, 2023. It is now read-only.

Commit

Permalink
SC-598 CHORE Disable unused html routes, XSS vuln averted
Browse files Browse the repository at this point in the history
  • Loading branch information
omnikron committed Oct 30, 2023
1 parent 74f01d1 commit f41f84d
Showing 1 changed file with 75 additions and 65 deletions.
140 changes: 75 additions & 65 deletions lib/routes/index.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
var sorter = require('./sorter');
var canonical = require('./canonical');
var plan = require('./plan');
var serviceWorker = require('./service-worker');
var headers = require('./headers');
var sorter = require("./sorter");
var canonical = require("./canonical");
var plan = require("./plan");
var serviceWorker = require("./service-worker");
var headers = require("./headers");

module.exports = routes;

function title(suffix) {
var t = 'Liftie';
if(suffix && suffix.length) {
t += ' | ' + suffix;
var t = "Liftie";
if (suffix && suffix.length) {
t += " | " + suffix;
}
return t;
}


function renderResorts(req, res, next) {
var opts = req.opts || {};

req.data.get(req.requested, function(err, items) {
var template = opts.template || 'index';
req.data.get(req.requested, function (err, items) {
var template = opts.template || "index";

if (err) {
return next(err);
}
if (opts.openSingle && items.length === 1) {
items[0].open = true; // if single resort requested, always mark it as open
res.locals.title = items[0].name;
if (template !== 'widget') {
if (template !== "widget") {
res.locals.single = true;
}
} else {
Expand All @@ -53,7 +52,7 @@ function renderResorts(req, res, next) {
function index(req, res, next) {
req.requested = req.params.resort || req.query.resorts;
req.opts = {
openSingle: true
openSingle: true,
};
next();
}
Expand All @@ -63,14 +62,14 @@ function index(req, res, next) {
*/
function widget(req, res, next) {
var requested = req.params.resort;
if (req.query.style === 'naked') {
res.locals.widgetStyle = 'naked';
if (req.query.style === "naked") {
res.locals.widgetStyle = "naked";
}
req.opts = {
openSingle: true,
template: 'widget'
template: "widget",
};
req.requested = requested.split(',')[0]; // only one resort allowed
req.requested = requested.split(",")[0]; // only one resort allowed
next();
}

Expand All @@ -79,8 +78,8 @@ function widget(req, res, next) {
* /stars - display starred subset of resorts
*/
function stars(req, res, next) {
req.requested = req.cookies['resorts-starred'];
res.locals.title = 'Stars';
req.requested = req.cookies["resorts-starred"];
res.locals.title = "Stars";
next();
}

Expand All @@ -93,115 +92,126 @@ function tag(req, res, next) {
}

function about(req, res) {
res.render('about', {
title: title('About')
res.render("about", {
title: title("About"),
});
}

function sitemap(req, res) {
res.contentType('application/xml');
res.render('sitemap', {
resorts: req.data.all(true)
res.contentType("application/xml");
res.render("sitemap", {
resorts: req.data.all(true),
});
}

function absent(req, res, next) {
req.requested = req.data.filtered(function(resort) {
var hasLists = resort.lifts && resort.lifts.status && Object.keys(resort.lifts.status).length;
req.requested = req.data.filtered(function (resort) {
var hasLists =
resort.lifts &&
resort.lifts.status &&
Object.keys(resort.lifts.status).length;
return !hasLists;
});
res.locals.title = 'Absent';
res.locals.title = "Absent";
next();
}

function confused(req, res, next) {
req.requested = req.data.filtered(function(resort) {
req.requested = req.data.filtered(function (resort) {
return resort.opening && resort.lifts.stats && resort.lifts.stats.open;
});
res.locals.title = 'Confused';
res.locals.title = "Confused";
next();
}

function closed(req, res, next) {
req.requested = req.data.filtered(function(resort) {
req.requested = req.data.filtered(function (resort) {
return resort.opening;
});
res.locals.title = 'Closed';
res.locals.title = "Closed";
next();
}

function stats(req, res) {
var t = req.tag;
res.render('stats', {
title: title('Statistics'),
sectionLink: t ? '/tag/' + t.slug : '/',
sectionTitle: t ? t.label : 'All Lifts',
stats: req.data.stats(req.requested)
res.render("stats", {
title: title("Statistics"),
sectionLink: t ? "/tag/" + t.slug : "/",
sectionTitle: t ? t.label : "All Lifts",
stats: req.data.stats(req.requested),
});
}

function api(req, res, next) {
req.data.get(req.params.resort, function(err, resorts) {
if(err) {
req.data.get(req.params.resort, function (err, resorts) {
if (err) {
return next(err);
}
if(resorts.length !== 1) {
return res.send(404, 'Invalid resort name: ' + req.params.resort);
if (resorts.length !== 1) {
return res.send(404, "Invalid resort name: " + req.params.resort);
}
// do not cache API responses
res.header('Cache-Control', 'no-cache, max-age=0, must-revalidate');
res.header("Cache-Control", "no-cache, max-age=0, must-revalidate");
res.send(resorts[0]);
});
}

function meta(req, res, next) {
req.data.meta(function(err, resorts) {
if(err) {
req.data.meta(function (err, resorts) {
if (err) {
return next(err);
}
res.header('Cache-Control', 'public, max-age=86400'); // good for 24hours
res.header("Cache-Control", "public, max-age=86400"); // good for 24hours
res.send(resorts);
});
}

function routes(app) {

function reqData(req, res, next) {
req.data = app.data;
next();
}

app.param('tag', function(req, res, next, t) {
app.param("tag", function (req, res, next, t) {
var data = app.data,
tags = data.tags(),
requested = tags[t] && tags[t].members;
if(!requested) {
if (!requested) {
t = canonical(t);
if(tags[t]) {
if (tags[t]) {
// permanent redirect to canonical form of the tag
return res.redirect(301, '/tag/' + t);
return res.redirect(301, "/tag/" + t);
}
return res.send(404, 'Invalid tag name: ' + req.params.tag);
return res.send(404, "Invalid tag name: " + req.params.tag);
}
req.requested = requested;
req.tag = tags[t];
res.locals.tag = t;
next();
});

app.get('/', reqData, headers, index, renderResorts);
app.get('/resort/:resort', reqData, headers, index, renderResorts);
app.get('/widget/resort/:resort', reqData, headers, widget, renderResorts);
app.get('/tag/:tag', reqData, headers, tag, renderResorts);
app.get('/stars', reqData, headers, stars, renderResorts);
app.get('/api/resort/:resort', reqData, api);
app.get('/api/meta', reqData, meta);
app.get('/sitemap.xml', reqData, sitemap);
app.get('/about', headers, about);
app.get('/absent', reqData, headers, absent, renderResorts);
app.get('/confused', reqData, headers, confused, renderResorts);
app.get('/closed', reqData, headers, closed, renderResorts);
app.get('/stats/:tag', reqData, headers, stats);
app.get('/stats', reqData, headers, stats);
app.get('/sw.js', serviceWorker);
app.get("/api/resort/:resort", reqData, api);
app.get("/api/meta", reqData, meta);

// We (FATMAP) don't need the html version of this site, since we only use
// the API as part of our live status service. There's a theoretical XSS
// injection attack inherent in rendering html that has been scraped from
// external sources, so we're disabling the html pages entirely.
// See https://strava.atlassian.net/browse/SC-598
//
// app.get('/', reqData, headers, index, renderResorts);
// app.get('/resort/:resort', reqData, headers, index, renderResorts);
// app.get('/widget/resort/:resort', reqData, headers, widget, renderResorts);
// app.get('/tag/:tag', reqData, headers, tag, renderResorts);
// app.get('/stars', reqData, headers, stars, renderResorts);
// app.get('/absent', reqData, headers, absent, renderResorts);
// app.get('/confused', reqData, headers, confused, renderResorts);
// app.get('/closed', reqData, headers, closed, renderResorts);
//
// These are probably less risky but also unnecessary so might as well turn them off too:
// app.get("/sitemap.xml", reqData, sitemap);
// app.get("/about", headers, about);
// app.get("/stats/:tag", reqData, headers, stats);
// app.get("/stats", reqData, headers, stats);
// app.get("/sw.js", serviceWorker);
}

0 comments on commit f41f84d

Please sign in to comment.