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

feat(gatsby-plugin-fastify): add customizable headers with sensible defaults for security #392

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
74b9009
add initial work from PR #98
tsdexter Jan 23, 2023
f82a85f
work on headers for static files
tsdexter Mar 7, 2023
8053ccf
add custom headers via options config
tsdexter Mar 7, 2023
f50252f
fix static test
tsdexter Mar 8, 2023
21b785f
Update yarn.lock
tsdexter Mar 8, 2023
4797817
add changeset
tsdexter Mar 8, 2023
4099220
add headers documentation
tsdexter Mar 8, 2023
41ceb84
undo husky change
tsdexter Mar 8, 2023
2e99ba7
add SSR specifics to documentation
tsdexter Mar 8, 2023
3e84682
Update .all-contributorsrc
tsdexter Mar 8, 2023
233b2ae
manually run lint-staged scripts
tsdexter Mar 8, 2023
dfe706f
fix linting issues
tsdexter Mar 8, 2023
568e8ad
fix validate types issues
tsdexter Mar 8, 2023
70e5443
refactor into headers plugin with hooks/decorators
tsdexter Mar 13, 2023
f91a536
Update packages/gatsby-plugin-fastify/README.md
tsdexter Mar 14, 2023
67e1817
refactor array reduce to for loop
tsdexter Mar 14, 2023
f0fbeb5
Merge branch 'feat/fastify/default-custom-headers' of https://github.…
tsdexter Mar 14, 2023
63ea4bb
Update packages/gatsby-plugin-fastify/README.md
tsdexter Mar 14, 2023
1f016a7
fix dependency regression
tsdexter Mar 14, 2023
42005d2
update dependencies
tsdexter Mar 14, 2023
0b31e91
inline superfluous constants
tsdexter Mar 14, 2023
9fcd9b1
refactor decorators, configurations to more suitable places
tsdexter Mar 14, 2023
8fe0bc0
add capability to unset previously configured headers
tsdexter Mar 14, 2023
9a328fb
add support for functions on gatsby hosting to test site
tsdexter Mar 14, 2023
0883b8f
use `@fastify/reply-from` to serve non-wildcard rev proxy redirects
tsdexter Mar 16, 2023
abd1fe5
minor tweaks to get benchmarks running
tsdexter Mar 17, 2023
897e7d5
refactor to build pages->headers map during `onPostBuild`
tsdexter Mar 17, 2023
9bb9be1
Update packages/gatsby-plugin-fastify/README.md
tsdexter Mar 21, 2023
266d421
Update packages/gatsby-plugin-fastify/README.md
tsdexter Mar 21, 2023
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 .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,18 @@
"contributions": [
"maintenance"
]
},
{
"login": "tsdexter",
"name": "Thomas Dexter",
"avatar_url": "https://avatars.githubusercontent.com/u/4126987?v=4",
"profile": "https://github.com/tsdexter",
"contributions": [
"maintenance",
"code",
"doc",
"bug"
]
}
],
"contributorsPerLine": 7
Expand Down
5 changes: 5 additions & 0 deletions .changeset/neat-pans-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"gatsby-plugin-fastify": minor
---

Added to `options.features.headers` for configuring `customHeaders`, `useDefaultCaching`, and `useDefaultSecurity`. Please see the [Headers section](https://github.com/gatsby-uc/plugins/blob/main/packages/gatsby-plugin-fastify/README.md#headers) in the docs for more information.
19 changes: 10 additions & 9 deletions integration-tests/plugin-fastify/benchmark.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const StatusCodes = require("http-status-codes");
const Benchmark = require("benchmark");
const {
getServerConfig,
setConfig,
ConfigKeyEnum,
getConfig,
} = require("gatsby-plugin-fastify/utils/config");
const { serveGatsby } = require("gatsby-plugin-fastify/plugins/gatsby");
} = require("gatsby-plugin-fastify/dist/utils/config");
const { serveGatsby } = require("gatsby-plugin-fastify/dist/plugins/gatsby");
Comment on lines +8 to +9
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this change needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was getting gatsby-plugin-fastify module not found (iirc on the exact error)...I tried a few things and only this worked. I'll try again while I'm making changes and put in the exact error, maybe you have another fix.

Copy link
Contributor

Choose a reason for hiding this comment

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

weird...it kinda makes sense...but why has it been working is just as strange.

Copy link
Contributor

Choose a reason for hiding this comment

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

ya know what...this isn't automated...it probably hasn't been run since I made this change.

const Fastify = require("fastify");

Benchmark.options.minSamples = 500;
Expand Down Expand Up @@ -37,7 +38,7 @@ setConfig(
const serverConfig = getServerConfig();
setConfig(ConfigKeyEnum.SERVER, serverConfig);

function expectResp(def, path, code = 200) {
function expectResp(def, path, code = StatusCodes.OK) {
return (res) => {
if (res.statusCode !== code) {
console.log(`Expected status code ${code}, got ${res.statusCode} from ${path}`);
Expand Down Expand Up @@ -158,7 +159,7 @@ function expectResp(def, path, code = 200) {
method: "GET",
url: "/nonExistentRoute",
})
.then(expectResp(def, "/nonExistentRoute", 404));
.then(expectResp(def, "/nonExistentRoute", StatusCodes.NOT_FOUND));
},
})
.add("Serve 500", {
Expand All @@ -169,7 +170,7 @@ function expectResp(def, path, code = 200) {
method: "GET",
url: "/ssrBad/",
})
.then(expectResp(def, "/ssrBad/", 500));
.then(expectResp(def, "/ssrBad/", StatusCodes.INTERNAL_SERVER_ERROR));
},
})
.add("Serve Redirect", {
Expand All @@ -180,7 +181,7 @@ function expectResp(def, path, code = 200) {
method: "GET",
url: "/perm-redirect/",
})
.then(expectResp(def, "/perm-redirect/", 301));
.then(expectResp(def, "/perm-redirect/", StatusCodes.PERMANENT_REDIRECT));
},
})
.add("Serve Reverse Proxy", {
Expand All @@ -191,7 +192,7 @@ function expectResp(def, path, code = 200) {
method: "GET",
url: "/example-proxy/",
})
.then(expectResp(def, "/example-proxy/", 200));
.then(expectResp(def, "/example-proxy/", StatusCodes.OK));
},
})
.add("Serve Function", {
Expand All @@ -202,7 +203,7 @@ function expectResp(def, path, code = 200) {
method: "GET",
url: "/api/test",
})
.then(expectResp(def, "/api/test", 200));
.then(expectResp(def, "/api/test", StatusCodes.OK));
},
})
.add("Serve Splat Function", {
Expand All @@ -213,7 +214,7 @@ function expectResp(def, path, code = 200) {
method: "GET",
url: "/api/test1/thisShouldWork",
})
.then(expectResp(def, "/api/test1/thisShouldWork", 200));
.then(expectResp(def, "/api/test1/thisShouldWork", StatusCodes.OK));
},
})
.on("cycle", function (event) {
Expand Down
45 changes: 44 additions & 1 deletion integration-tests/plugin-fastify/gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,50 @@ module.exports = {
},
{
resolve: `gatsby-plugin-fastify`,
options: {},
options: {
features: {
headers: {
customHeaders: {
"/posts/page-1*": {
"x-test-page-specific": "shows on /posts/page-1",
},
"/posts/page-2*": {
"X-Content-Type-Options": "nosniff by default, overwritten for this page",
},
"/**": {
"x-test-all-pages": "shows on every page/file",
},
"/icon.png": {
"cache-control": "max-age=60000",
},
"/component*.js": {
"x-test-js": "root js file",
"x-cache-control": "overwrite cache-control for all root js files",
"cache-control": "max-age=60000",
},
"/ssr/**": {
"x-test-ssr-kept": "ssr page",
"x-test-ssr-overwrite": "ssr page",
},
"/api/test": {
"x-test-function-kept": "function page",
"x-test-function-overwrite": "function page",
},
"/generated/page-6": {
"x-test-dsg-kept": "dsg page",
},
"/page-data/generated/page-6/page-data.json": {
"x-test-dsg-kept": "dsg page data",
},
"/posts/page-1/index.html": {
"x-test-page-specific": "shows on /posts/page-1 and its page-data (overwritten)",
},
},
useDefaultCaching: true,
useDefaultSecurity: true,
},
},
},
},
"gatsby-plugin-sitemap",
{
Expand Down
2 changes: 2 additions & 0 deletions integration-tests/plugin-fastify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"benchmark": "node benchmark.js",
"develop": "gatsby develop",
"start": "gserve",
"start:devserver": "yarn build:deps && yarn start",
"build": "gatsby build",
"serve": "gatsby serve",
"clean": "gatsby clean",
Expand All @@ -29,6 +30,7 @@
"gatsby-source-faker": "^5.7.0",
"gatsby-source-filesystem": "^5.7.0",
"gatsby-transformer-sharp": "^5.7.0",
"pino-pretty": "^10.0.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

Not seeing this used anywhere... also http-status-codes is being used now but it hasn't been added to deps.

"postcss": "^8.4.21",
"react": "^18.2.0",
"react-dom": "^18.2.0"
Expand Down
8 changes: 7 additions & 1 deletion integration-tests/plugin-fastify/src/api/splat/[splat].js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import { isGatsbyHosting } from "../../utils/functions";

export default function handler(req, res) {
res.code(200).send({ splat: req.params.splat });
if (isGatsbyHosting(res)) {
res.status(200).send({ splat: req.params.splat });
} else {
res.code(200).send({ splat: req.params.splat });
}
}
12 changes: 11 additions & 1 deletion integration-tests/plugin-fastify/src/api/test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isGatsbyHosting } from "../utils/functions";

const responseData = [
{
_id: "612d652f43f0c05240ed09b1",
Expand Down Expand Up @@ -268,5 +270,13 @@ const responseData = [
];

export default function handler(req, res) {
res.code(200).send(responseData);
if (isGatsbyHosting(res)) {
res.setHeader("x-test-function-overwrite", "Overwritten by FUNCTION");
res.status(200).send(responseData);
} else {
res.headers({
"x-test-function-overwrite": "Overwritten by FUNCTION",
});
res.code(200).send(responseData);
}
}
8 changes: 7 additions & 1 deletion integration-tests/plugin-fastify/src/api/test1/[splat].js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import { isGatsbyHosting } from "../../utils/functions";

export default function handler(req, res) {
res.code(200).send(req.params);
if (isGatsbyHosting(res)) {
res.status(200).send(req.params);
} else {
res.code(200).send(req.params);
}
}
8 changes: 7 additions & 1 deletion integration-tests/plugin-fastify/src/api/wildcard/[...].js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import { isGatsbyHosting } from "../../utils/functions";

export default function handler(req, res) {
res.code(200).send(req.params);
if (isGatsbyHosting(res)) {
res.status(200).send(req.params);
} else {
res.code(200).send(req.params);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as React from "react";

export default function LazyComponent() {
return (
<div>
<h1 data-lazy="true">Lazy Component</h1>
</div>
);
}
6 changes: 6 additions & 0 deletions integration-tests/plugin-fastify/src/components/Post.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import * as React from "react";
import { Link } from "gatsby";

const LazyComponent = React.lazy(() => import(`./LazyComponent`));

export default function PostPage({ title, content }) {
return (
<article>
<Link to="/">Return to Home</Link>

<h1>{title}</h1>
<div dangerouslySetInnerHTML={{ __html: content }} />

<React.Suspense fallback={<div>Loading</div>}>
<LazyComponent />
</React.Suspense>
</article>
);
}
6 changes: 6 additions & 0 deletions integration-tests/plugin-fastify/src/pages/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import * as React from "react";
import { Link, withPrefix } from "gatsby";
import { StaticImage } from "gatsby-plugin-image";

import "../../src/styles/test.css";

// styles
const pageStyles = {
color: "#232129",
Expand Down Expand Up @@ -153,6 +157,8 @@ const IndexPage = () => {
<a href={withPrefix("/ssr_named_splat/test/path")}>Named Splat routed SSR page</a>
</li>
</ul>

<StaticImage src="../images/icon.png" alt="Icon" layout="fixed" width={165} />
</main>
);
};
Expand Down
1 change: 1 addition & 0 deletions integration-tests/plugin-fastify/src/pages/ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export async function getServerData() {
return {
headers: {
"x-test": "Custom Headers Work!",
"x-test-ssr-overwrite": "Overwritten by SSR",
},
props: await res.json(),
};
Expand Down
3 changes: 3 additions & 0 deletions integration-tests/plugin-fastify/src/styles/test.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
outline: 2px solid red;
}
3 changes: 3 additions & 0 deletions integration-tests/plugin-fastify/src/utils/functions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function isGatsbyHosting(response) {
return response.setHeader !== undefined;
}
Binary file added integration-tests/plugin-fastify/static/test.pdf
Binary file not shown.
58 changes: 58 additions & 0 deletions packages/gatsby-plugin-fastify/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,61 @@ export default function handler(req: FastifyRequest, res: FastifyReply) {
### Gatsby Routing

We have implemented a compatability layer to support the Gatsby flavor of routing for [Gatsby Functions](https://www.gatsbyjs.com/docs/reference/functions/routing/) and [File System Routing API](https://www.gatsbyjs.com/docs/reference/routing/file-system-route-api/#syntax-client-only-routes). This should be transparent and if you follow the Gatsby docs for routing we should now support all those modes. This very well might not be perfect, if you have issues with routing please file a bug with a reproduction.

### Headers

Sensible default security headers are added to all files/paths. These headers include:

- X-Frame-Options: DENY
- X-XSS-Protection: 1; mode=block
- X-Content-Type-Options: nosniff
- Referrer-Policy: same-origin

Headers for user-defined path patterns can be added/overwritten via `options.features.headers.customHeaders`.

We use [picomatch](https://www.npmjs.com/package/picomatch) for pattern matching. [Globbing](https://www.npmjs.com/package/picomatch#basic-globbing) can be used to match paths. For example, to add headers to all posts with a URL structure such as `/posts/category-name/post-name` you would use a pattern like `/posts/**`. `/posts/*`, with a single asterisk, would only match the second level sub-directory after `/posts/` (in this case the `category-name`), not the third level where the posts reside.

```
tsdexter marked this conversation as resolved.
Show resolved Hide resolved
{
resolve: `gatsby-plugin-fastify`,
options: {
features: {
headers: {
useDefaultCaching: true, // default: true
useDefaultSecurity: true, // default: true
customHeaders: {
"/posts/**": { // all categories and posts
"x-test": "post",
},
"/posts/fun-stuff/trampolines": { // just the trampoline post
"x-test": "trampoline post",
},
},
},
},
},
},
```

As in the example above, successive matching entries in `customHeaders` will overwrite previous matches. This successive overwriting includes overwriting the default caching and default security headers if you are including them via `options.features.headers.useDefaultCaching` and/or `options.features.headers.useDefaultSecurity` which are both `true` by default.

For SSR pages, headers configured in `options.features.headers.customHeaders` will be added to the matching routes alongside headers returned from `getServerData`. If both places set the same header the value in `getServerData` will take precedence.

You can unset a previously configured headers value, either custom or defaults, by setting its value to `"undefined"` (type `string`). For example:

```
tsdexter marked this conversation as resolved.
Show resolved Hide resolved
{
resolve: `gatsby-plugin-fastify`,
options: {
features: {
headers: {
customHeaders: {
"/no-cache-header": {
"cache-control": "undefined",
},
},
},
},
},
},
```
10 changes: 9 additions & 1 deletion packages/gatsby-plugin-fastify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,22 @@
"@fastify/accepts": "^4.1.0",
"@fastify/http-proxy": "^8.4.0",
"@fastify/middie": "^8.1.0",
"@fastify/reply-from": "^9.0.1",
"@fastify/static": "^6.9.0",
"fastify-plugin": "^4.5.0",
"fs-extra": "^11.1.0",
"gatsby-core-utils": "^4.7.0",
"gatsby-plugin-utils": "^4.7.0",
"http-status-codes": "^2.2.0",
"just-compose": "^2.3.0",
"just-map-object": "^2.3.0",
"just-map-values": "^3.2.0",
"just-merge": "^3.2.0",
"just-typeof": "^3.2.0",
"mime": "^3.0.0",
"open": "^8.4.2",
"picomatch": "^2.3.1",
"webpack-assets-manifest": "^5.1.0",
"yargs": "^17.7.1"
},
"devDependencies": {
Expand All @@ -53,8 +60,9 @@
"@types/mime": "^3.0.1",
"@types/node": "^18.14.6",
"@types/picomatch": "^2.3.0",
"@types/webpack-assets-manifest": "^5.1.0",
"@types/yargs": "^17.0.22",
"babel-jest": "^29.4.3",
"babel-jest": "^29.5.0",
"babel-preset-gatsby-package": "^3.7.0",
"cross-env": "^7.0.3",
"jest": "^29.4.3"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("Hello from app-hash.js");
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("Hellow from component-fake-hash.js");
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
body { border: 1px solid red; }
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("Hello from fake-chunk.js");
Loading