Skip to content

Commit

Permalink
fix(nosecone)!: Change return value to Headers (#2362)
Browse files Browse the repository at this point in the history
While writing the docs for nosecone, I ran into a problem where I could not use the `res.setHeaders()` API that Node.js added in v18.15.0 because nosecone wasn't returning a `Header` or `Map` object.

The original choice for a plain object was for the SvelteKit adapter, but the implementation of it changed and that plain object is no longer needed. Changing it to an actual `Headers` object will still work in the places we expected, with the added benefits of working with the Node.js API. The only downside is that they aren't spreadable.

I've also added the nosecone usage to one of our Node.js examples.
  • Loading branch information
blaine-arcjet authored Nov 29, 2024
1 parent 1f220fc commit ff19af9
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 96 deletions.
5 changes: 4 additions & 1 deletion examples/nodejs-rate-limit/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import arcjet, { fixedWindow, shield } from "@arcjet/node";
import * as http from "node:http";
import nosecone from "nosecone";

const aj = arcjet({
// Get your site key from https://app.arcjet.com and set it as an environment
Expand All @@ -25,6 +26,8 @@ const aj = arcjet({
const server = http.createServer(async function (req, res) {
const decision = await aj.protect(req);

res.setHeaders(nosecone());

if (decision.isDenied()) {
res.writeHead(429, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Too Many Requests" }));
Expand All @@ -34,4 +37,4 @@ const server = http.createServer(async function (req, res) {
}
});

server.listen(3000);
server.listen(3000);
72 changes: 50 additions & 22 deletions examples/nodejs-rate-limit/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion examples/nodejs-rate-limit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"start": "node --env-file .env.local ./index.js"
},
"dependencies": {
"@arcjet/node": "../../arcjet-node"
"@arcjet/node": "../../arcjet-node",
"nosecone": "../../nosecone"
},
"devDependencies": {
"@types/node": "^20",
Expand Down
18 changes: 7 additions & 11 deletions nosecone-next/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,13 @@ function nonce() {
export function createMiddleware(options: NoseconeOptions = defaults) {
return async () => {
const headers = nosecone(options);
// Setting this specific header is the way that Next.js implements
// middleware. See:
// https://github.com/vercel/next.js/blob/5c45d58cd058a9683e435fd3a1a9b8fede8376c3/packages/next/src/server/web/spec-extension/response.ts#L148
// Note: we don't create the `x-middleware-override-headers` header so
// the original headers pass through
headers.set("x-middleware-next", "1");

return new Response(null, {
headers: {
...headers,
// Setting this specific header is the way that Next.js implements
// middleware. See:
// https://github.com/vercel/next.js/blob/5c45d58cd058a9683e435fd3a1a9b8fede8376c3/packages/next/src/server/web/spec-extension/response.ts#L148
// Note: we don't create the `x-middleware-override-headers` header so
// the original headers pass through
"x-middleware-next": "1",
},
});
return new Response(null, { headers });
};
}
2 changes: 1 addition & 1 deletion nosecone-sveltekit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function createHook(options: NoseconeOptions = defaults): Handle {
const response = await resolve(event);

const headers = nosecone(options);
for (const [headerName, headerValue] of Object.entries(headers)) {
for (const [headerName, headerValue] of headers.entries()) {
// Only add headers that aren't already set. For example, SvelteKit will
// likely have added `Content-Security-Policy` if configured with `csp`
if (!response.headers.has(headerName)) {
Expand Down
28 changes: 14 additions & 14 deletions nosecone/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,84 +616,84 @@ export default function nosecone({
xPermittedCrossDomainPolicies = defaults.xPermittedCrossDomainPolicies;
}

const headers: Record<string, string> = {};
const headers = new Headers();

if (contentSecurityPolicy) {
const [headerName, headerValue] = createContentSecurityPolicy(
contentSecurityPolicy,
);
headers[headerName] = headerValue;
headers.set(headerName, headerValue);
}

if (crossOriginEmbedderPolicy) {
const [headerName, headerValue] = createCrossOriginEmbedderPolicy(
crossOriginEmbedderPolicy,
);
headers[headerName] = headerValue;
headers.set(headerName, headerValue);
}

if (crossOriginOpenerPolicy) {
const [headerName, headerValue] = createCrossOriginOpenerPolicy(
crossOriginOpenerPolicy,
);
headers[headerName] = headerValue;
headers.set(headerName, headerValue);
}

if (crossOriginResourcePolicy) {
const [headerName, headerValue] = createCrossOriginResourcePolicy(
crossOriginResourcePolicy,
);
headers[headerName] = headerValue;
headers.set(headerName, headerValue);
}

if (originAgentCluster) {
const [headerName, headerValue] = createOriginAgentCluster();
headers[headerName] = headerValue;
headers.set(headerName, headerValue);
}

if (referrerPolicy) {
const [headerName, headerValue] = createReferrerPolicy(referrerPolicy);
headers[headerName] = headerValue;
headers.set(headerName, headerValue);
}

if (strictTransportSecurity) {
const [headerName, headerValue] = createStrictTransportSecurity(
strictTransportSecurity,
);
headers[headerName] = headerValue;
headers.set(headerName, headerValue);
}

if (xContentTypeOptions) {
const [headerName, headerValue] = createContentTypeOptions();
headers[headerName] = headerValue;
headers.set(headerName, headerValue);
}

if (xDnsPrefetchControl) {
const [headerName, headerValue] =
createDnsPrefetchControl(xDnsPrefetchControl);
headers[headerName] = headerValue;
headers.set(headerName, headerValue);
}

if (xDownloadOptions) {
const [headerName, headerValue] = createDownloadOptions();
headers[headerName] = headerValue;
headers.set(headerName, headerValue);
}

if (xFrameOptions) {
const [headerName, headerValue] = createFrameOptions(xFrameOptions);
headers[headerName] = headerValue;
headers.set(headerName, headerValue);
}

if (xPermittedCrossDomainPolicies) {
const [headerName, headerValue] = createPermittedCrossDomainPolicies(
xPermittedCrossDomainPolicies,
);
headers[headerName] = headerValue;
headers.set(headerName, headerValue);
}

if (xXssProtection) {
const [headerName, headerValue] = createXssProtection();
headers[headerName] = headerValue;
headers.set(headerName, headerValue);
}

return headers;
Expand Down
Loading

0 comments on commit ff19af9

Please sign in to comment.