Skip to content
Open
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
12 changes: 12 additions & 0 deletions src/Options/Definitions.js
Copy link
Member

Choose a reason for hiding this comment

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

Check the contribution guide for how to add a new option.

Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,12 @@ module.exports.PagesOptions = {
action: parsers.objectParser,
default: {},
},
headers: {
env: 'PARSE_SERVER_PAGES_HEADERS',
help: 'Global headers applied to all PagesRouter responses.',
action: parsers.objectParser,
default: {},
},
};
module.exports.PagesRoute = {
handler: {
Expand All @@ -803,6 +809,12 @@ module.exports.PagesRoute = {
help: 'The route path.',
required: true,
},
headers: {
env: 'PARSE_SERVER_PAGES_ROUTE_HEADERS',
help: 'Headers applied only to this specific page route.',
action: parsers.objectParser,
default: {},
},
};
module.exports.PagesCustomUrlsOptions = {
emailVerificationLinkExpired: {
Expand Down
74 changes: 58 additions & 16 deletions src/Routers/PagesRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ export class PagesRouter extends PromiseRouter {
*/
constructor(pages = {}) {
super();
this._pagesOptions = pages;
this.globalHeaders = pages.headers || {};

// Set instance properties
this.pagesConfig = pages;
Expand Down Expand Up @@ -255,7 +257,7 @@ export class PagesRouter extends PromiseRouter {
* - POST request -> redirect response (PRG pattern)
* @returns {Promise<Object>} The PromiseRouter response.
*/
goToPage(req, page, params = {}, responseType) {
goToPage(req, page, params = {}, responseType, routeHeaders = {}) {
const config = req.config;

// Determine redirect either by force, response setting or request method
Expand Down Expand Up @@ -307,8 +309,8 @@ export class PagesRouter extends PromiseRouter {
);
} else {
return redirect
? this.redirectResponse(defaultUrl, params)
: this.pageResponse(defaultPath, params, placeholders);
? this.redirectResponse(defaultUrl, params, routeHeaders)
: this.pageResponse(defaultPath, params, placeholders, routeHeaders);
}
}

Expand Down Expand Up @@ -427,7 +429,7 @@ export class PagesRouter extends PromiseRouter {
* These will not be included in the response header.
* @returns {Object} The Promise Router response.
*/
async pageResponse(path, params = {}, placeholders = {}) {
async pageResponse(path, params = {}, placeholders = {}, routeHeaders = {}) {
// Get file content
let data;
try {
Expand All @@ -454,22 +456,28 @@ export class PagesRouter extends PromiseRouter {

// Add placeholders in header to allow parsing for programmatic use
// of response, instead of having to parse the HTML content.
const headers = Object.entries(params).reduce((m, p) => {
const paramHeaders = Object.entries(params).reduce((m, p) => {
if (p[1] !== undefined) {
m[`${pageParamHeaderPrefix}${p[0].toLowerCase()}`] = p[1];
}
return m;
}, {});

return { text: data, headers: headers };
const headers = {
...this.globalHeaders,
...routeHeaders,
...paramHeaders,
};

return { text: data, headers };
}

/**
* Creates a response with file content.
* @param {String} path The path of the file to return.
* @returns {Object} The PromiseRouter response.
*/
async fileResponse(path) {
async fileResponse(path, routeHeaders = {}) {
// Get file content
let data;
try {
Expand All @@ -478,7 +486,12 @@ export class PagesRouter extends PromiseRouter {
return this.notFound();
}

return { text: data };
const headers = {
...this.globalHeaders,
...routeHeaders,
};

return { text: data, headers };
}

/**
Expand Down Expand Up @@ -560,7 +573,7 @@ export class PagesRouter extends PromiseRouter {
* @param {Object} params The query parameters to include.
* @returns {Object} The Promise Router response.
*/
async redirectResponse(url, params) {
async redirectResponse(url, params, routeHeaders = {}) {
// Remove any parameters with undefined value
params = Object.entries(params).reduce((m, p) => {
if (p[1] !== undefined) {
Expand All @@ -576,17 +589,23 @@ export class PagesRouter extends PromiseRouter {

// Add parameters to header to allow parsing for programmatic use
// of response, instead of having to parse the HTML content.
const headers = Object.entries(params).reduce((m, p) => {
const paramHeaders = Object.entries(params).reduce((m, p) => {
if (p[1] !== undefined) {
m[`${pageParamHeaderPrefix}${p[0].toLowerCase()}`] = p[1];
}
return m;
}, {});

const headers = {
...this.globalHeaders,
...routeHeaders,
...paramHeaders,
};

return {
status: 303,
location: locationString,
headers: headers,
headers,
};
}

Expand Down Expand Up @@ -698,7 +717,7 @@ export class PagesRouter extends PromiseRouter {
this.setConfig(req);
},
async req => {
const { file, query = {} } = (await route.handler(req)) || {};
const { file, query = {}, headers: routeHeaders = {} } = (await route.handler(req)) || {};

// If route handler did not return a page send 404 response
if (!file) {
Expand All @@ -707,7 +726,7 @@ export class PagesRouter extends PromiseRouter {

// Send page response
const page = new Page({ id: file, defaultFile: file });
return this.goToPage(req, page, query, false);
return this.goToPage(req, page, query, false, routeHeaders);
}
);
}
Expand All @@ -728,9 +747,32 @@ export class PagesRouter extends PromiseRouter {

expressRouter() {
const router = express.Router();
router.use('/', super.expressRouter());
return router;
}
router.use((req, res, next) => {
const options = this._pagesOptions || {};

const enableSecureHeaders = options.secureHeaders !== false;

if (enableSecureHeaders) {
if (!res.get('X-Frame-Options')) {
res.set('X-Frame-Options', 'DENY');
}
if (!res.get('Content-Security-Policy')) {
res.set('Content-Security-Policy', "frame-ancestors 'none'");
}
}

if (options.customHeaders) {
Object.entries(options.customHeaders).forEach(([key, value]) => {
res.set(key, value);
});
}

next();
});

router.use('/', super.expressRouter());
return router;
}
}

export default PagesRouter;
Expand Down