Skip to content
Merged
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
184 changes: 155 additions & 29 deletions examples/pnpm-lock.yaml

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions examples/with-strict-csp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
dist
.wrangler
.output
.vercel
.netlify
.vinxi
app.config.timestamp_*.js

# Environment
.env
.env*.local

# dependencies
/node_modules

# IDEs and editors
/.idea
.project
.classpath
*.launch
.settings/

# Temp
gitignore

# System Files
.DS_Store
Thumbs.db
37 changes: 37 additions & 0 deletions examples/with-strict-csp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Content Security Policy with Nonce

This example demonstrates how to implement a strict Content Security Policy (CSP) with a nonce in a SolidStart application.

## How to Use

You can use the Solid CLI to bootstrap the example with one of the following commands based on your package manager preference:

**npm:**

```bash
npm init solid@latest --solidstart --ts --template with-strict-csp my-app-with-strict-csp
```

**pnpm:**

```bash
pnpm create solid --solidstart --ts --template with-strict-csp my-app-with-strict-csp
```

**yarn:**

```bash
yarn create solid@latest --solidstart --ts --template with-strict-csp my-app-with-strict-csp
```

**bun:**

```bash
bun create solid@latest --solidstart --ts --template with-strict-csp my-app-with-strict-csp
```

**deno:**

```bash
deno init --npm solid@latest --solidstart --ts --template with-strict-csp my-app-with-strict-csp
```
5 changes: 5 additions & 0 deletions examples/with-strict-csp/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineConfig } from "@solidjs/start/config";

export default defineConfig({
middleware: "src/middleware.ts"
});
17 changes: 17 additions & 0 deletions examples/with-strict-csp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "example-bare",
"type": "module",
"scripts": {
"dev": "vinxi dev",
"build": "vinxi build",
"start": "vinxi start"
},
"dependencies": {
"@solidjs/start": "^1.1.0",
"solid-js": "^1.9.5",
"vinxi": "^0.5.3"
},
"engines": {
"node": ">=22"
}
}
Binary file added examples/with-strict-csp/public/favicon.ico
Binary file not shown.
61 changes: 61 additions & 0 deletions examples/with-strict-csp/src/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
body {
font-family: Gordita, Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}

a {
margin-right: 1rem;
}

main {
text-align: center;
padding: 1em;
margin: 0 auto;
}

h1 {
color: #335d92;
text-transform: uppercase;
font-size: 4rem;
font-weight: 100;
line-height: 1.1;
margin: 4rem auto;
max-width: 14rem;
}

p {
max-width: 14rem;
margin: 2rem auto;
line-height: 1.35;
}

@media (min-width: 480px) {
h1 {
max-width: none;
}

p {
max-width: none;
}
}

.increment {
font-family: inherit;
font-size: inherit;
padding: 1em 2em;
color: #335d92;
background-color: rgba(68, 107, 158, 0.1);
border-radius: 2em;
border: 2px solid rgba(68, 107, 158, 0);
outline: none;
width: 200px;
font-variant-numeric: tabular-nums;
cursor: pointer;
}

.increment:focus {
border: 2px solid #335d92;
}

.increment:active {
background-color: rgba(68, 107, 158, 0.2);
}
22 changes: 22 additions & 0 deletions examples/with-strict-csp/src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createSignal } from "solid-js";
import "./app.css";

export default function App() {
const [count, setCount] = createSignal(0);

return (
<main>
<h1>Hello world!</h1>
<button class="increment" onClick={() => setCount(count() + 1)} type="button">
Clicks: {count()}
</button>
<p>
Visit{" "}
<a href="https://start.solidjs.com" target="_blank">
start.solidjs.com
</a>{" "}
to learn how to build SolidStart apps.
</p>
</main>
);
}
4 changes: 4 additions & 0 deletions examples/with-strict-csp/src/entry-client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @refresh reload
import { mount, StartClient } from "@solidjs/start/client";

mount(() => <StartClient />, document.getElementById("app")!);
24 changes: 24 additions & 0 deletions examples/with-strict-csp/src/entry-server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @refresh reload
import { createHandler, StartServer } from "@solidjs/start/server";

export default createHandler(
() => (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
),
event => ({ nonce: event.locals.nonce })
);
1 change: 1 addition & 0 deletions examples/with-strict-csp/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@solidjs/start/env" />
40 changes: 40 additions & 0 deletions examples/with-strict-csp/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createMiddleware } from "@solidjs/start/middleware";
import { randomBytes } from "crypto";

const isProd = import.meta.env.PROD;

export default createMiddleware({
onRequest: event => {
const nonce = randomBytes(16).toString("base64");

if (isProd) {
event.locals.nonce = nonce;
}

// Notes:
// 1. SolidStart uses `eval` for data serialization, which may require you to include the 'unsafe-eval' directive in your CSP.
// For more information, see: https://github.com/solidjs/solid-start/issues/1825
// 2. In development, Vite inlines small CSS files to improve performance, so you'll need to include the 'unsafe-inline' directive in development.
// 3. During the build process, Vite inlines small assets as data URLs.
// Therefore, it's necessary to add `data:` to the relevant directives (e.g., img-src, font-src, etc.).
// For more details, see: https://vite.dev/config/build-options.html#build-assetsinlinelimit
const csp = `
default-src 'self';
script-src ${
isProd
? // Note: The `https:` and `'unsafe-inline'` directives do not reduce the effectiveness of the CSP.
// They are only fallbacks for older browsers that don't support `'strict-dynamic'`.
`'nonce-${nonce}' 'strict-dynamic' 'unsafe-eval' https: 'unsafe-inline'`
: "'self' 'unsafe-inline' 'unsafe-eval' https: http:"
};
style-src ${isProd ? `'nonce-${nonce}'` : "'self' 'unsafe-inline'"};
img-src 'self' data:;
object-src 'none';
base-uri 'none';
frame-ancestors 'none';
form-action 'self';
`.replace(/\s+/g, " ");

event.response.headers.set("Content-Security-Policy", csp);
}
});
19 changes: 19 additions & 0 deletions examples/with-strict-csp/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"allowJs": true,
"strict": true,
"noEmit": true,
"types": ["vinxi/types/client"],
"isolatedModules": true,
"paths": {
"~/*": ["./src/*"]
}
}
}