Skip to content

Commit f872368

Browse files
J1vvooDro.B
authored andcommitted
Add support for global and per-route security headers in PagesRouter
1 parent 73e7812 commit f872368

File tree

2 files changed

+70
-16
lines changed

2 files changed

+70
-16
lines changed

src/Options/Definitions.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,12 @@ module.exports.PagesOptions = {
779779
action: parsers.objectParser,
780780
default: {},
781781
},
782+
headers: {
783+
env: 'PARSE_SERVER_PAGES_HEADERS',
784+
help: 'Global headers applied to all PagesRouter responses.',
785+
action: parsers.objectParser,
786+
default: {},
787+
},
782788
};
783789
module.exports.PagesRoute = {
784790
handler: {
@@ -796,6 +802,12 @@ module.exports.PagesRoute = {
796802
help: 'The route path.',
797803
required: true,
798804
},
805+
headers: {
806+
env: 'PARSE_SERVER_PAGES_ROUTE_HEADERS',
807+
help: 'Headers applied only to this specific page route.',
808+
action: parsers.objectParser,
809+
default: {},
810+
},
799811
};
800812
module.exports.PagesCustomUrlsOptions = {
801813
emailVerificationLinkExpired: {

src/Routers/PagesRouter.js

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ export class PagesRouter extends PromiseRouter {
6868
*/
6969
constructor(pages = {}) {
7070
super();
71+
this._pagesOptions = pages;
72+
this.globalHeaders = pages.headers || {};
7173

7274
// Set instance properties
7375
this.pagesConfig = pages;
@@ -255,7 +257,7 @@ export class PagesRouter extends PromiseRouter {
255257
* - POST request -> redirect response (PRG pattern)
256258
* @returns {Promise<Object>} The PromiseRouter response.
257259
*/
258-
goToPage(req, page, params = {}, responseType) {
260+
goToPage(req, page, params = {}, responseType, routeHeaders = {}) {
259261
const config = req.config;
260262

261263
// Determine redirect either by force, response setting or request method
@@ -307,8 +309,8 @@ export class PagesRouter extends PromiseRouter {
307309
);
308310
} else {
309311
return redirect
310-
? this.redirectResponse(defaultUrl, params)
311-
: this.pageResponse(defaultPath, params, placeholders);
312+
? this.redirectResponse(defaultUrl, params, routeHeaders)
313+
: this.pageResponse(defaultPath, params, placeholders, routeHeaders);
312314
}
313315
}
314316

@@ -427,7 +429,7 @@ export class PagesRouter extends PromiseRouter {
427429
* These will not be included in the response header.
428430
* @returns {Object} The Promise Router response.
429431
*/
430-
async pageResponse(path, params = {}, placeholders = {}) {
432+
async pageResponse(path, params = {}, placeholders = {}, routeHeaders = {}) {
431433
// Get file content
432434
let data;
433435
try {
@@ -454,22 +456,28 @@ export class PagesRouter extends PromiseRouter {
454456

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

464-
return { text: data, headers: headers };
466+
const headers = {
467+
...this.globalHeaders,
468+
...routeHeaders,
469+
...paramHeaders,
470+
};
471+
472+
return { text: data, headers };
465473
}
466474

467475
/**
468476
* Creates a response with file content.
469477
* @param {String} path The path of the file to return.
470478
* @returns {Object} The PromiseRouter response.
471479
*/
472-
async fileResponse(path) {
480+
async fileResponse(path, routeHeaders = {}) {
473481
// Get file content
474482
let data;
475483
try {
@@ -478,7 +486,12 @@ export class PagesRouter extends PromiseRouter {
478486
return this.notFound();
479487
}
480488

481-
return { text: data };
489+
const headers = {
490+
...this.globalHeaders,
491+
...routeHeaders,
492+
};
493+
494+
return { text: data, headers };
482495
}
483496

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

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

599+
const headers = {
600+
...this.globalHeaders,
601+
...routeHeaders,
602+
...paramHeaders,
603+
};
604+
586605
return {
587606
status: 303,
588607
location: locationString,
589-
headers: headers,
608+
headers,
590609
};
591610
}
592611

@@ -698,7 +717,7 @@ export class PagesRouter extends PromiseRouter {
698717
this.setConfig(req);
699718
},
700719
async req => {
701-
const { file, query = {} } = (await route.handler(req)) || {};
720+
const { file, query = {}, headers: routeHeaders = {} } = (await route.handler(req)) || {};
702721

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

708727
// Send page response
709728
const page = new Page({ id: file, defaultFile: file });
710-
return this.goToPage(req, page, query, false);
729+
return this.goToPage(req, page, query, false, routeHeaders);
711730
}
712731
);
713732
}
@@ -728,9 +747,32 @@ export class PagesRouter extends PromiseRouter {
728747

729748
expressRouter() {
730749
const router = express.Router();
731-
router.use('/', super.expressRouter());
732-
return router;
733-
}
750+
router.use((req, res, next) => {
751+
const options = this._pagesOptions || {};
752+
753+
const enableSecureHeaders = options.secureHeaders !== false;
754+
755+
if (enableSecureHeaders) {
756+
if (!res.get('X-Frame-Options')) {
757+
res.set('X-Frame-Options', 'DENY');
758+
}
759+
if (!res.get('Content-Security-Policy')) {
760+
res.set('Content-Security-Policy', "frame-ancestors 'none'");
761+
}
762+
}
763+
764+
if (options.customHeaders) {
765+
Object.entries(options.customHeaders).forEach(([key, value]) => {
766+
res.set(key, value);
767+
});
768+
}
769+
770+
next();
771+
});
772+
773+
router.use('/', super.expressRouter());
774+
return router;
775+
}
734776
}
735777

736778
export default PagesRouter;

0 commit comments

Comments
 (0)