Skip to content

Commit

Permalink
updated api
Browse files Browse the repository at this point in the history
  • Loading branch information
LeeCheneler committed Jan 4, 2025
1 parent a828f9d commit 79e980f
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 29 deletions.
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,6 @@ Serve a file from the file system.
await context.serveFile("path/to/file");
```

####

### Cookies

You can read cookies from the request.
Expand Down Expand Up @@ -362,3 +360,30 @@ To run your app, you can use the `Deno.serve` function:
```tsx
Deno.serve(app.build());
```

## Header utilities

Some utility methods are available to configure common complex response headers.

### `cacheControl`

Set the `Cache-Control` header.

```tsx
cacheControl(context, {
maxAge: 60,
});
```

### `contentSecurityPolicy`

Set the `Content-Security-Policy` header.

```tsx
contentSecurityPolicy(context, {
directives: {
defaultSrc: "'self'",
scriptSrc: ["'self'", "https://example.com"],
},
});
```
75 changes: 71 additions & 4 deletions src/headers/content-security-policy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,64 @@
import type { MageContext } from "../context.ts";

type ContentSecurityPolicyOptions = Record<string, string | string[]>;
interface ContentSecurityPolicyOptions {
/**
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives
*/
directives: {
childSrc?: string | string[];
connectSrc?: string | string[];
defaultSrc?: string | string[];
fontSrc?: string | string[];
frameSrc?: string | string[];
imgSrc?: string | string[];
manifestSrc?: string | string[];
mediaSrc?: string | string[];
objectSrc?: string | string[];
prefetchSrc?: string | string[];
scriptSrc?: string | string[];
scriptSrcElem?: string | string[];
scriptSrcAttr?: string | string[];
styleSrc?: string | string[];
styleSrcElem?: string | string[];
styleSrcAttr?: string | string[];
workerSrc?: string | string[];
baseUri?: string | string[];
sandbox?: string | string[];
formAction?: string | string[];
frameAncestors?: string | string[];
reportTo?: string | string[];
upgradeInsecureRequests?: boolean;
};
}

const directiveKeyMap: Record<
keyof ContentSecurityPolicyOptions["directives"],
string
> = {
childSrc: "child-src",
connectSrc: "connect-src",
defaultSrc: "default-src",
fontSrc: "font-src",
frameSrc: "frame-src",
imgSrc: "img-src",
manifestSrc: "manifest-src",
mediaSrc: "media-src",
objectSrc: "object-src",
prefetchSrc: "prefetch-src",
scriptSrc: "script-src",
scriptSrcElem: "script-src-elem",
scriptSrcAttr: "script-src-attr",
styleSrc: "style-src",
styleSrcElem: "style-src-elem",
styleSrcAttr: "style-src-attr",
workerSrc: "worker-src",
baseUri: "base-uri",
sandbox: "sandbox",
formAction: "form-action",
frameAncestors: "frame-ancestors",
reportTo: "report-to",
upgradeInsecureRequests: "upgrade-insecure-requests",
};

/**
* Apply a Content-Security-Policy header based on the provided options.
Expand All @@ -11,10 +69,19 @@ export const contentSecurityPolicy = (
context: MageContext,
options: ContentSecurityPolicyOptions,
): void => {
const header = Object.entries(options)
const header = Object.entries(options.directives)
.map(([key, value]) => {
const values = Array.isArray(value) ? value : [value];
return `${key} ${values.join(" ")}`;
const directive = directiveKeyMap[
key as keyof ContentSecurityPolicyOptions["directives"]
];

if (typeof value === "boolean") {
return `${directive}`;
}

const directiveValue = Array.isArray(value) ? value.join(" ") : value;

return `${directive} ${directiveValue}`;
})
.join(";");

Expand Down
24 changes: 13 additions & 11 deletions src/middleware/security-headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@ import { contentSecurityPolicy } from "../headers/content-security-policy.ts";
export const useSecurityHeaders = (): MageMiddleware => {
return async (context, next) => {
contentSecurityPolicy(context, {
"default-src": "'self'",
"base-uri": "'self'",
"font-src": ["'self'", "https:", "data:"],
"form-action": "'self'",
"frame-ancestors": "'self'",
"img-src": ["'self'", "data:"],
"object-src": "'none'",
"script-src": "'self'",
"script-src-attr": "'none'",
"style-src": ["'self'", "https:", "'unsafe-inline'"],
"upgrade-insecure-requests": "",
directives: {
defaultSrc: ["'self'"],
baseUri: ["'self'"],
fontSrc: ["'self'", "https:", "data:"],
formAction: ["'self'"],
frameAncestors: ["'self'"],
imgSrc: ["'self'", "data:"],
objectSrc: ["'none'"],
scriptSrc: ["'self'"],
scriptSrcAttr: ["'none'"],
styleSrc: ["'self'", "https:", "'unsafe-inline'"],
upgradeInsecureRequests: true,
},
});

context.response.headers.set("Cross-Origin-Opener-Policy", "same-origin");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,10 @@ beforeAll(() => {

server.app.get("/", (context) => {
contentSecurityPolicy(context, {
"default-src": "'self'",
"base-uri": "'self'",
"font-src": ["'self'", "https:", "data:"],
"form-action": "'self'",
"frame-ancestors": "'self'",
"img-src": ["'self'", "data:"],
"object-src": "'none'",
"script-src": "'self'",
"script-src-attr": "'none'",
"style-src": ["'self'", "https:", "'unsafe-inline'"],
"upgrade-insecure-requests": "",
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://example.com"],
},
});

context.text(StatusCode.OK, "Hello, World!");
Expand All @@ -43,7 +36,7 @@ describe("headers - content-security-policy", () => {
await response.text();

expect(response.headers.get("Content-Security-Policy")).toEqual(
"default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests",
"default-src 'self';script-src 'self' https://example.com",
);
});
});

0 comments on commit 79e980f

Please sign in to comment.