Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -779,13 +779,19 @@
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: {

Check failure on line 790 in src/Options/Definitions.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 4 spaces but found 2
env: 'PARSE_SERVER_PAGES_ROUTE_HANDLER',

Check failure on line 791 in src/Options/Definitions.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 4 spaces but found 2
help: 'The route handler that is an async function.',

Check failure on line 792 in src/Options/Definitions.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 4 spaces but found 2
required: true,

Check failure on line 793 in src/Options/Definitions.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 4 spaces but found 2
},

Check failure on line 794 in src/Options/Definitions.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 2 spaces but found 0
method: {
env: 'PARSE_SERVER_PAGES_ROUTE_METHOD',
help: "The route method, e.g. 'GET' or 'POST'.",
Expand All @@ -796,13 +802,19 @@
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: {

Check failure on line 813 in src/Options/Definitions.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 4 spaces but found 2
env: 'PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_LINK_EXPIRED',

Check failure on line 814 in src/Options/Definitions.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 4 spaces but found 2
help: 'The URL to the custom page for email verification -> link expired.',

Check failure on line 815 in src/Options/Definitions.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 4 spaces but found 2
},

Check failure on line 816 in src/Options/Definitions.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 4 spaces but found 2
emailVerificationLinkInvalid: {

Check failure on line 817 in src/Options/Definitions.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 2 spaces but found 0
env: 'PARSE_SERVER_PAGES_CUSTOM_URL_EMAIL_VERIFICATION_LINK_INVALID',
help: 'The URL to the custom page for email verification -> link invalid.',
},
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
Loading