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

Add Content-Security-Policy and other headers to vercel.json #846

Open
wants to merge 14 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/layouts/barebones.astro
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,10 @@ lang ??= "en";
</head>
<body>
<slot />

<script>
import { enableHrefContainers } from "utils/href-container-script";
window.addEventListener("load", enableHrefContainers);
</script>
</body>
</html>
11 changes: 11 additions & 0 deletions src/pages/collections/framework-field-guide/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,14 @@ import { unicorns } from "utils/data";
import { enableParallaxScrolling } from "../../../views/collections/framework-field-guide/scripts/parallax-scrolling";
enableParallaxScrolling();
</script>
<script>
document.querySelectorAll("[data-scroll-to]").forEach((element) => {
element.addEventListener("click", (e) => {
e.preventDefault();
const scrollToElement = document.querySelector(
element.getAttribute("data-scroll-to"),
);
scrollToElement.scrollIntoView({ behavior: "smooth", block: "start" });
});
});
</script>
31 changes: 18 additions & 13 deletions src/utils/href-container-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function isNestedElement(e: MouseEvent) {
return false;
}

globalThis.handleHrefContainerMouseDown = (e: MouseEvent) => {
const handleHrefContainerMouseDown = (e: MouseEvent) => {
const isMiddleClick = e.button === 1;
if (!isMiddleClick) return;
if (e.defaultPrevented) return;
Expand All @@ -44,17 +44,17 @@ globalThis.handleHrefContainerMouseDown = (e: MouseEvent) => {
};

// implement the AuxClick event using MouseUp (only on browsers that don't support auxclick; i.e. safari)
globalThis.handleHrefContainerMouseUp = (e: MouseEvent) => {
const handleHrefContainerMouseUp = (e: MouseEvent) => {
// if auxclick is supported, do nothing
if ("onauxclick" in e.currentTarget) return;
// otherwise, pass mouseup events to auxclick
globalThis.handleHrefContainerAuxClick(e);
handleHrefContainerAuxClick(e);
};

// Handle middle click button - should open a new tab (cannot be detected via "click" event)
// - prefer the "auxclick" event for this, since it ensures that mousedown/mouseup both occur within the same element
// otherwise, using "mouseup" would activate on mouseup even when dragging between elements, which should not trigger a click
globalThis.handleHrefContainerAuxClick = (e: MouseEvent) => {
const handleHrefContainerAuxClick = (e: MouseEvent) => {
const href = (e.currentTarget as HTMLElement).dataset.href;

// only handle middle click events
Expand All @@ -68,7 +68,7 @@ globalThis.handleHrefContainerAuxClick = (e: MouseEvent) => {
return false;
};

globalThis.handleHrefContainerClick = (e: MouseEvent) => {
const handleHrefContainerClick = (e: MouseEvent) => {
const href = (e.currentTarget as HTMLElement).dataset.href;

if (e.defaultPrevented) return;
Expand Down Expand Up @@ -98,6 +98,15 @@ globalThis.handleHrefContainerClick = (e: MouseEvent) => {
window.location.href = href;
};

export function enableHrefContainers() {
document.querySelectorAll<HTMLElement>("[data-href]").forEach((el) => {
el.addEventListener("mousedown", handleHrefContainerMouseDown);
el.addEventListener("mouseup", handleHrefContainerMouseUp);
el.addEventListener("auxclick", handleHrefContainerAuxClick);
el.addEventListener("click", handleHrefContainerClick);
});
}

export function getHrefContainerProps(href: string) {
// If the href is null or empty, no props should be added
if (!href) return {};
Expand All @@ -111,19 +120,15 @@ export function getHrefContainerProps(href: string) {
) {
// if running in NodeJS (Astro), return string props
return {
onmousedown: "handleHrefContainerMouseDown(event)",
onmouseup: "handleHrefContainerMouseUp(event)",
onauxclick: "handleHrefContainerAuxClick(event)",
onclick: "handleHrefContainerClick(event)",
"data-href": href,
};
} else {
// otherwise, need to return client-side functions
return {
onMouseDown: globalThis.handleHrefContainerMouseDown,
onMouseUp: globalThis.handleHrefContainerMouseUp,
onAuxClick: globalThis.handleHrefContainerAuxClick,
onClick: globalThis.handleHrefContainerClick,
onMouseDown: handleHrefContainerMouseDown,
onMouseUp: handleHrefContainerMouseUp,
onAuxClick: handleHrefContainerAuxClick,
onClick: handleHrefContainerClick,
"data-href": href,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ const tags: TagList = [
performant, stable, and easier to maintain.
</p>
<div class="book-ctas">
<BigFilledButton
href="#signup-form"
onclick="event.preventDefault();var title=document.getElementById('signup-form');title.scrollIntoView({behavior: 'smooth', block: 'start'})"
>
<BigFilledButton href="#signup-form" data-scroll-to="#signup-form">
Sign up for updates
</BigFilledButton>
<!-- <BigOutlinedButton>Learn more</BigOutlinedButton> -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,7 @@ const tags: TagList = [
using these frameworks.
</p>
<div class="book-ctas">
<BigFilledButton
href="#signup-form"
onclick="event.preventDefault();var title=document.getElementById('signup-form');title.scrollIntoView({behavior: 'smooth', block: 'start'})"
>
<BigFilledButton href="#signup-form" data-scroll-to="#signup-form">
Notify me at launch
</BigFilledButton>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import Sun from "../cover-layers/fundamentals/sun.astro";
</p>
<SmallFilledButton
href="#fundamentals-title"
onclick="event.preventDefault();var title=document.getElementById('fundamentals-title');title.scrollIntoView({behavior: 'smooth', block: 'center'})"
data-scroll-to="#fundamentals-title"
>
Learn more
</SmallFilledButton>
Expand All @@ -59,7 +59,7 @@ import Sun from "../cover-layers/fundamentals/sun.astro";
</p>
<SmallOutlinedButton
href="#ecosystem-title"
onclick="event.preventDefault();var title=document.getElementById('ecosystem-title');title.scrollIntoView({behavior: 'smooth', block: 'center'})"
data-scroll-to="#ecosystem-title"
>
Learn more
</SmallOutlinedButton>
Expand All @@ -76,7 +76,7 @@ import Sun from "../cover-layers/fundamentals/sun.astro";
</p>
<SmallOutlinedButton
href="#internals-title"
onclick="event.preventDefault();var title=document.getElementById('internals-title');title.scrollIntoView({behavior: 'smooth', block: 'center'})"
data-scroll-to="#internals-title"
>
Learn more
</SmallOutlinedButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ const tags: TagList = [
yourself?
</p>
<div class="book-ctas">
<BigFilledButton
href="#signup-form"
onclick="event.preventDefault();var title=document.getElementById('signup-form');title.scrollIntoView({behavior: 'smooth', block: 'start'})"
>
<BigFilledButton href="#signup-form" data-scroll-to="#signup-form">
Sign up for updates
</BigFilledButton>
<!-- <BigOutlinedButton>Learn more</BigOutlinedButton> -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Learn the core concepts behind your favorite frameworks and develop skillsets to
<SmallFilledButton
slot="button"
href="#signup-form"
onclick="event.preventDefault();var title=document.getElementById('signup-form');title.scrollIntoView({behavior: 'smooth', block: 'start'})"
data-scroll-to="#signup-form"
>
Notify me at launch
</SmallFilledButton>
Expand Down Expand Up @@ -82,7 +82,7 @@ Build out production-ready applications with all of the bells and whistles your
<SmallOutlinedButton
slot="button"
href="#signup-form"
onclick="event.preventDefault();var title=document.getElementById('signup-form');title.scrollIntoView({behavior: 'smooth', block: 'start'})"
data-scroll-to="#signup-form"
>
Sign up for updates
</SmallOutlinedButton>
Expand Down Expand Up @@ -118,7 +118,7 @@ Take a deeper dive into understanding how your apps work under-the-hood, includi
<SmallOutlinedButton
slot="button"
href="#signup-form"
onclick="event.preventDefault();var title=document.getElementById('signup-form');title.scrollIntoView({behavior: 'smooth', block: 'start'})"
data-scroll-to="#signup-form"
>
Sign up for updates
</SmallOutlinedButton>
Expand Down
49 changes: 49 additions & 0 deletions vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,55 @@
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "/(.*)",
"headers": [
{
"key": "Access-Control-Allow-Origin",
"value": "https://unicorn-utterances.com"
},
{
"key": "Content-Security-Policy",
"value": "default-src 'self';base-uri 'self';form-action 'self' https://unicorn-utterances.com/ https://app.convertkit.com/;frame-ancestors 'none';frame-src https:;img-src 'self' https: data:;object-src 'none';script-src 'self' https://vercel.live/;connect-src 'self' https://vercel.live/;style-src 'self' 'unsafe-inline';upgrade-insecure-requests"
},
{
"key": "Cross-Origin-Opener-Policy",
"value": "same-origin-allow-popups"
},
{
"key": "Cross-Origin-Resource-Policy",
"value": "same-origin"
},
{
"key": "Permissions-Policy",
"value": "camera=(), display-capture=(), document-domain=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), usb=(), clipboard-read=(), clipboard-write=(self)"
},
{
"key": "Referrer-Policy",
"value": "no-referrer"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Download-Options",
"value": "noopen"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "X-Permitted-Cross-Domain-Policies",
"value": "none"
},
{
"key": "X-XSS-Protection",
"value": "0"
evelynhathaway marked this conversation as resolved.
Show resolved Hide resolved
}
]
}
]
}
46 changes: 46 additions & 0 deletions vercel.json.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# vercel.json

This documents our settings used in the [vercel.json configuration](https://vercel.com/docs/projects/project-configuration) and the reasoning behind them.

## Headers

Most headers are based on [express helmet](https://github.com/helmetjs/helmet) defaults and are adapted for UU.

See also: Google's [Content Security Policy explainer](https://csp.withgoogle.com/docs/index.html).

- **Access-Control-Allow-Origin:** Prevents cross-origin requests to site content. This has no effect on same-origin requests, so its value does not break preview builds.

- **Content-Security-Policy:** Helps prevent XSS/injection attacks.
* `default-src 'self'` - only allows resources from the current origin by default, unless otherwise specified:
* `base-uri 'self'` - restricts the use of the `<base>` element in the page
* `form-action 'self' https://unicorn-utterances.com/ https://app.convertkit.com/` - restricts URLs that can be used as the target of form submissions
* `https://app.convertkit.com/` is used for newsletter signups
* `https://unicorn-utterances.com/` is needed on preview builds because ConvertKit redirects the form submission back to UU
* `frame-ancestors 'none'` - prevents embedding the document in any iframe
* `frame-src https:` - allows any https URL to be used as an iframe source in the page
* `img-src 'self' https: data:` - allows any https or data image sources
* `object-src 'none'` - prevents use of the `<object>` and `<embed>` elements, which are considered legacy HTML
* `script-src 'self' https://vercel.live/` - restricts the sources that can be used by `<script>` elements or attributes
* `https://vercel.live/` is used for Vercel's comments widget on preview builds
* `connect-src 'self' https://vercel.live/` - restricts the usage of `fetch()` calls and other script APIs
* `style-src 'self' 'unsafe-inline'` - restricts the usage of `<style>` elements and attributes
* `unsafe-inline` is needed for Vercel's comments widget, the [medium-zoom](https://github.com/francoischalifour/medium-zoom) script, and other elements that use `style=` attributes

- **Cross-Origin-Opener-Policy:** Ensures the document does not share its global object if opened in a popup from another website.
* Uses `same-origin-allow-popups` for compatibility with Vercel's preview comments.

- **Cross-Origin-Resource-Policy:** blocks no-cors cross-origin/cross-site requests.

- **Permissions-Policy:** Allows/denies the use of browser features in the document and any `<iframe>` elements within it.

- **Referrer-Policy:** Controls how much referrer information should be included with requests. `no-referrer` provides no information. This is equivalent to setting `rel="noreferrer"` on every link.

- **X-Content-Type-Options:** `nosniff` mitigates [MIME type sniffing](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#mime_sniffing) which can cause security issues.

- **X-Download-Options:** `noopen` is specific to Internet Explorer 8.

- **X-Frame-Options:** `DENY`

- **X-Permitted-Cross-Domain-Policies:** `none` tells some clients (mostly Adobe products) not to load cross-domain content.

- **X-XSS-Protection:** `0` turns off XSS protection in older browsers, which can create XSS vulnerabilities in otherwise safe websites.