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

Fix header forwarding #28

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/node_modules
.DS_Store
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## UNRELEASED

### Changes
* The configuration array for headers passed from Apostrophe to the browser has been changed from `forwardHeaders` to `includeResponseHeaders` with BC maintained.
* A new configuration option `excludeResponseHeaders` has been added to allow exclusion of headers like `host` being sent from the browser to Apostrophe.
* The `README.MD` has been updated with the new configuration options.

## 1.1.0 (2024-11-20)

### Adds
Expand Down
28 changes: 21 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,17 @@ export default defineConfig({
widgetsMapping: './src/widgets',
templatesMapping: './src/templates',
viewTransitionWorkaround: false,
forwardHeaders: [
includeResponseHeaders: [
'content-security-policy',
'strict-transport-security',
'x-frame-options',
'referrer-policy',
'cache-control',
'host'
'cache-control'
],
excludeRequestHeaders: [
// For single-site setups or hosting on multiple servers, block the host header
'host'
Copy link
Member

Choose a reason for hiding this comment

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

Right so it is critical to include this setting in the project level PRs.

]
proxyRoutes: [
// Custom URLs that should be proxied to Apostrophe.
// Note that all of `/api/v1` is already proxied, so
Expand Down Expand Up @@ -153,17 +156,28 @@ improve performance for editors. Ordinary website visitors are
not impacted in any case. We are seeking an alternative solution to
eliminate this option.

### `forwardHeaders`
### `includeResponseHeaders`

An array of HTTP headers that you want to include from Apostrophe to the final response sent to the browser - useful if you want to use an Apostrophe module like `@apostrophecms/security-headers` and want to keep those headers as configured in Apostrophe.
Copy link
Member

Choose a reason for hiding this comment

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

Also good for respecting apostrophe's caching headers.


An array of HTTP headers that you want to forward from Apostrophe to the final response sent to the browser - useful if you want to use an Apostrophe module like `@apostrophecms/security-headers` and want to keep those headers as configured in Apostrophe.
At the present time, Astro is not compatible with the `nonce` property of `content-security-policy` `script-src` value. So this is automatically removed with that integration. The rest of the CSP header remains unchanged.

### `excludeRequestHeaders`

An array of HTTP headers that you want to prevent from being forwarded from the browser to Apostrophe. This is particularly useful in single-site setups where you want to block the `host` header to allow Astro and Apostrophe to run on different domains or ports.
Copy link
Member

Choose a reason for hiding this comment

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

Just say different hostnames. Different ports aren't really a problem.


By default, all headers are forwarded except those specified in this array.

### `forwardHeaders` (deprecated)

This option has been replaced by `includeResponseHeaders` which provides clearer naming for its purpose. If both options are provided, `includeResponseHeaders` takes precedence. `forwardHeaders` will be removed in a future version.

### Mapping Apostrophe templates to Astro components

Since the front end of our project is entirely Astro, we'll need to create Astro components corresponding to each
template that Apostrophe would normally render with Nunjucks.
Create your template mapping in `src/templates/index.js` file.

Create your template mapping in `src/templates/index.js` file.
As shown above, this file path must then be added to your `astro.config.mjs` file,
in the `templatesMapping` option of the `apostrophe` integration.

Expand Down
10 changes: 8 additions & 2 deletions components/layouts/AposLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ import config from 'virtual:apostrophe-config';
const { aposData } = Astro.props;

// Forward Apostrophe response headers to Astro based on the config
if (config.forwardHeaders && Array.isArray(config.forwardHeaders)) {
let headersToInclude = config.includeResponseHeaders;
if (!headersToInclude && config.forwardHeaders) {
console.warn('forwardHeaders is deprecated. Please use includeResponseHeaders instead.');
headersToInclude = config.forwardHeaders;
}

if (headersToInclude && Array.isArray(headersToInclude)) {
const headers = aposData.aposResponseHeaders;
if (headers) {
for (const header of config.forwardHeaders) {
for (const header of headersToInclude) {
let aposHeader = headers.get(header);
// Astro is not compatible with nonce in CSP
if (aposHeader && header === 'content-security-policy') {
Expand Down
4 changes: 3 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ export default function apostropheIntegration(options) {
vitePluginApostropheConfig(
options.aposHost,
options.forwardHeaders,
options.viewTransitionWorkaround
options.viewTransitionWorkaround,
options.includeResponseHeaders,
options.excludeRequestHeaders
),
],
},
Expand Down
12 changes: 8 additions & 4 deletions lib/aposResponse.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ export default async function aposResponse(req) {
const aposUrl = new URL(url.pathname, aposHost);
aposUrl.search = url.search;

const requestHeaders = {}
for (const header of req.headers) {
requestHeaders[header[0]] = header[1];
const requestHeaders = {};
for (const [name, value] of req.headers) {
const headerLower = name.toLowerCase();
if (!config.excludeRequestHeaders?.map(h => h.toLowerCase()).includes(headerLower)) {
Copy link
Member

Choose a reason for hiding this comment

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

  • Shouldn't this be an equality comparison rather than "includes" which would match a header that happens to be a substring of some unrelated header?
  • Because it is on each request, it is worth making it more efficient by converting all of the excluded request headers to lowercase at startup rather than doing it over and over like this.
  • So you should be able to do a simple "includes" test on config.excludeRequestHeaders after you've normalized it.

requestHeaders[name] = value;
}
}

const res = await request(aposUrl.href, { headers: requestHeaders, method: req.method, body: req.body });
const responseHeaders = new Headers();
Object.entries(res.headers).forEach(([key, value]) => {
Expand All @@ -21,7 +25,7 @@ export default async function aposResponse(req) {
});
const { headers, statusCode, ...rest } = res;
const body = [204, 304].includes(statusCode) ? null : res.body;
const response = new Response(body, { ...rest , status: res.statusCode, headers: responseHeaders });
const response = new Response(body, { ...rest, status: res.statusCode, headers: responseHeaders });
return response;
};

37 changes: 22 additions & 15 deletions vite/vite-plugin-apostrophe-config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
export function vitePluginApostropheConfig(
aposHost,
forwardHeaders,
viewTransitionWorkaround
forwardHeaders = null,
viewTransitionWorkaround,
includeResponseHeaders = null,
excludeRequestHeaders = null
) {

const virtualModuleId = "virtual:apostrophe-config";
const resolvedVirtualModuleId = "\0" + virtualModuleId;

// Use includeResponseHeaders if provided, fallback to forwardHeaders for BC
const headersToInclude = includeResponseHeaders || forwardHeaders;

return {
name: "vite-plugin-apostrophe-config",
async resolveId(id) {
Expand All @@ -16,18 +20,21 @@ export function vitePluginApostropheConfig(
},
async load(id) {
if (id === resolvedVirtualModuleId) {
return `
export default {
aposHost: "${aposHost}"
${forwardHeaders ? `,
forwardHeaders: ${JSON.stringify(forwardHeaders)}` : ''
}
${viewTransitionWorkaround ? `,
viewTransitionWorkaround: true` : ''
}
}`
;
return `
export default {
aposHost: "${aposHost}"
${headersToInclude ? `,
includeResponseHeaders: ${JSON.stringify(headersToInclude)}` : ''
}
${excludeRequestHeaders ? `,
excludeRequestHeaders: ${JSON.stringify(excludeRequestHeaders)}` : ''
}
${viewTransitionWorkaround ? `,
viewTransitionWorkaround: true` : ''
}
}`
;
}
},
};
};
};