├── .eslintrc.cjs
├── .github
└── workflows
│ ├── ci.yml
│ └── publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── SECURITY.md
├── examples
├── express
│ ├── README.md
│ ├── app.js
│ └── package.json
├── next13-approuter-html-submission
│ ├── .eslintrc.json
│ ├── README.md
│ ├── app
│ │ ├── form-handler
│ │ │ └── route.ts
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ └── globals.css
│ └── tsconfig.json
├── next13-approuter-js-submission-dynamic
│ ├── .eslintrc.json
│ ├── README.md
│ ├── app
│ │ ├── form-handler
│ │ │ └── route.ts
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ └── globals.css
│ └── tsconfig.json
├── next13-approuter-js-submission-static
│ ├── .eslintrc.json
│ ├── README.md
│ ├── app
│ │ ├── form-handler
│ │ │ └── route.ts
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ └── globals.css
│ └── tsconfig.json
├── next13-pagesrouter-html-submission
│ ├── .eslintrc.json
│ ├── README.md
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── api
│ │ │ └── form-handler.ts
│ │ └── index.tsx
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ ├── Home.module.css
│ │ └── globals.css
│ └── tsconfig.json
├── next14-approuter-html-submission
│ ├── .eslintrc.json
│ ├── README.md
│ ├── app
│ │ ├── form-handler
│ │ │ └── route.ts
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ └── globals.css
│ └── tsconfig.json
├── next14-approuter-js-submission-dynamic
│ ├── .eslintrc.json
│ ├── README.md
│ ├── app
│ │ ├── form-handler
│ │ │ └── route.ts
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ └── globals.css
│ └── tsconfig.json
├── next14-approuter-js-submission-static
│ ├── .eslintrc.json
│ ├── README.md
│ ├── app
│ │ ├── form-handler
│ │ │ └── route.ts
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ └── globals.css
│ └── tsconfig.json
├── next14-approuter-sentry
│ ├── .eslintrc.json
│ ├── .sentryclirc
│ ├── README.md
│ ├── app
│ │ ├── api
│ │ │ └── sentry-example-api
│ │ │ │ └── route.js
│ │ ├── favicon.ico
│ │ ├── global-error.jsx
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── sentry-example-page
│ │ │ └── page.jsx
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ │ ├── next.svg
│ │ └── vercel.svg
│ ├── sentry.client.config.ts
│ ├── sentry.edge.config.ts
│ ├── sentry.server.config.ts
│ ├── tailwind.config.ts
│ └── tsconfig.json
├── next14-approuter-server-action-form-submission
│ ├── .eslintrc.json
│ ├── README.md
│ ├── app
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ └── globals.css
│ └── tsconfig.json
├── next14-approuter-server-action-non-form-submission
│ ├── .eslintrc.json
│ ├── README.md
│ ├── app
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── lib
│ │ └── actions.ts
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ └── globals.css
│ └── tsconfig.json
├── next14-pagesrouter-html-submission
│ ├── .eslintrc.json
│ ├── README.md
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── api
│ │ │ └── form-handler.ts
│ │ └── index.tsx
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ ├── Home.module.css
│ │ └── globals.css
│ └── tsconfig.json
├── next15-approuter-html-submission
│ ├── .eslintrc.json
│ ├── README.md
│ ├── app
│ │ ├── form-handler
│ │ │ └── route.ts
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ └── globals.css
│ └── tsconfig.json
├── next15-approuter-js-submission-dynamic
│ ├── .eslintrc.json
│ ├── README.md
│ ├── app
│ │ ├── form-handler
│ │ │ └── route.ts
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ └── globals.css
│ └── tsconfig.json
├── next15-approuter-js-submission-static
│ ├── .eslintrc.json
│ ├── README.md
│ ├── app
│ │ ├── form-handler
│ │ │ └── route.ts
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ └── globals.css
│ └── tsconfig.json
├── next15-approuter-sentry
│ ├── .eslintrc.json
│ ├── .sentryclirc
│ ├── README.md
│ ├── app
│ │ ├── api
│ │ │ └── sentry-example-api
│ │ │ │ └── route.js
│ │ ├── favicon.ico
│ │ ├── global-error.jsx
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── sentry-example-page
│ │ │ └── page.jsx
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ │ ├── next.svg
│ │ └── vercel.svg
│ ├── sentry.client.config.ts
│ ├── sentry.edge.config.ts
│ ├── sentry.server.config.ts
│ ├── tailwind.config.ts
│ └── tsconfig.json
├── next15-approuter-server-action-form-submission
│ ├── .eslintrc.json
│ ├── README.md
│ ├── app
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ └── globals.css
│ └── tsconfig.json
├── next15-approuter-server-action-non-form-submission
│ ├── .eslintrc.json
│ ├── README.md
│ ├── app
│ │ ├── layout.tsx
│ │ └── page.tsx
│ ├── lib
│ │ └── actions.ts
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ └── globals.css
│ └── tsconfig.json
├── next15-pagesrouter-html-submission
│ ├── .eslintrc.json
│ ├── README.md
│ ├── middleware.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.tsx
│ │ ├── api
│ │ │ └── form-handler.ts
│ │ └── index.tsx
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── styles
│ │ ├── Home.module.css
│ │ └── globals.css
│ └── tsconfig.json
├── node-http
│ ├── README.md
│ ├── package.json
│ └── server.js
├── sveltekit-cloudflare
│ ├── .gitignore
│ ├── .npmrc
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── app.d.ts
│ │ ├── app.html
│ │ ├── hooks.server.ts
│ │ ├── lib
│ │ │ └── index.ts
│ │ └── routes
│ │ │ ├── +page.server.ts
│ │ │ └── +page.svelte
│ ├── static
│ │ ├── favicon.ico
│ │ └── favicon.png
│ ├── svelte.config.js
│ ├── tsconfig.json
│ ├── vite.config.ts
│ └── wrangler.toml
└── sveltekit-vercel
│ ├── .gitignore
│ ├── .npmrc
│ ├── README.md
│ ├── eslint.config.js
│ ├── package.json
│ ├── src
│ ├── app.d.ts
│ ├── app.html
│ ├── hooks.server.ts
│ ├── lib
│ │ └── index.ts
│ └── routes
│ │ ├── +page.server.ts
│ │ └── +page.svelte
│ ├── static
│ ├── favicon.ico
│ └── favicon.png
│ ├── svelte.config.js
│ ├── tsconfig.json
│ └── vite.config.ts
├── package.json
├── packages
├── core
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── express
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── index.test.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── nextjs
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── index.test.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── node-http
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── index.test.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vite.config.ts
└── sveltekit
│ ├── README.md
│ ├── package.json
│ ├── src
│ ├── index.test.ts
│ └── index.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── set-version.sh
├── shared
├── package.json
├── src
│ ├── config.test.ts
│ ├── config.ts
│ ├── protect.test.ts
│ ├── protect.ts
│ ├── util.test.ts
│ ├── util.ts
│ └── vite-plugin-dts.ts
├── tsconfig.json
└── vite.config.ts
└── tsconfig.json
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "root": true,
3 | "plugins": [
4 | "@typescript-eslint"
5 | ],
6 | "extends": [
7 | "airbnb-base",
8 | "airbnb-typescript/base",
9 | "plugin:@typescript-eslint/recommended"
10 | ],
11 | "parser": "@typescript-eslint/parser",
12 | "parserOptions": {
13 | "project": "./tsconfig.json"
14 | },
15 | "ignorePatterns": [],
16 | "rules": {
17 | "@typescript-eslint/naming-convention": "off",
18 | "@typescript-eslint/no-explicit-any": "off",
19 | "import/extensions": "off",
20 | "import/no-extraneous-dependencies": ["error", {
21 | "packageDir": [".", __dirname] // This tells ESLint to check both the local and root package.json files
22 | }],
23 | "import/prefer-default-export": "off",
24 | "max-classes-per-file": "off",
25 | "max-len": "off",
26 | "no-await-in-loop": "off",
27 | "no-restricted-syntax": "off",
28 | "no-underscore-dangle": "off",
29 | "object-curly-newline": "off"
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - '**'
7 |
8 | jobs:
9 | lint:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | - uses: actions/setup-node@v4
14 | with:
15 | node-version: 20
16 | - uses: pnpm/action-setup@v3
17 | with:
18 | version: 9
19 | - run: pnpm install
20 | - run: pnpm -r lint
21 |
22 | test:
23 | runs-on: ubuntu-latest
24 | steps:
25 | - uses: actions/checkout@v4
26 | - uses: actions/setup-node@v4
27 | with:
28 | node-version: 20
29 | - uses: pnpm/action-setup@v3
30 | with:
31 | version: 9
32 | - name: Install dependencies
33 | run: pnpm install
34 | - name: Run tests
35 | run: |
36 | pnpm -r build # only necessary for miniflare
37 | pnpm -r test run -- --environment node
38 | pnpm -r test run -- --environment edge-runtime
39 | pnpm -r test run -- --environment miniflare
40 |
41 | test-nextjs:
42 | runs-on: ubuntu-latest
43 | strategy:
44 | matrix:
45 | version: [13, 14, 15]
46 | steps:
47 | - uses: actions/checkout@v4
48 | - uses: actions/setup-node@v4
49 | with:
50 | node-version: 20
51 | - uses: pnpm/action-setup@v3
52 | with:
53 | version: 9
54 | - name: Install dependencies
55 | run: pnpm install
56 | - name: Install Next.js version
57 | working-directory: packages/nextjs
58 | run: pnpm install next@${{ matrix.version }}
59 | - name: Run tests
60 | working-directory: packages/nextjs
61 | run: |
62 | pnpm build # only necessary for miniflare
63 | pnpm test run -- --environment node
64 | pnpm test run -- --environment edge-runtime
65 | pnpm test run -- --environment miniflare
66 |
67 | build:
68 | runs-on: ubuntu-latest
69 | steps:
70 | - uses: actions/checkout@v4
71 | - uses: actions/setup-node@v4
72 | with:
73 | node-version: 20
74 | - uses: pnpm/action-setup@v3
75 | with:
76 | version: 9
77 | - run: pnpm install
78 | - run: pnpm -r build
79 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - '**'
7 |
8 | jobs:
9 | publish:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | packages: write
13 | contents: read
14 | steps:
15 | - name: Get tag name
16 | uses: olegtarasov/get-tag@2.1.3
17 | id: tagName
18 | - uses: actions/checkout@v4
19 | - uses: actions/setup-node@v4
20 | with:
21 | node-version: 20
22 | registry-url: https://registry.npmjs.org
23 | - uses: pnpm/action-setup@v3
24 | with:
25 | version: 8
26 | - run: pnpm install
27 | - run: pnpm -r build
28 | - run: /bin/bash set-version.sh ${{ steps.tagName.outputs.tag }}
29 | - run: pnpm -r publish --no-git-checks --access public
30 | env:
31 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # git-ls-files --others --exclude-from=.git/info/exclude
2 | # Lines that start with '#' are comments.
3 | # For a project mostly in C, the following would be a good set of
4 | # exclude patterns (uncomment them if you want to use them):
5 | # *.[oa]
6 | .DS_Store
7 | *~
8 | \#*
9 | node_modules
10 | .next
11 | .env
12 | .env*.local
13 | .env.*
14 | !.env.example
15 | dist
16 | build
17 | .vscode
18 |
19 | # vite
20 | vite.config.js.timestamp-*
21 | vite.config.ts.timestamp-*
22 |
23 | # sveltkit
24 | .svelte-kit
25 |
26 | # vercel
27 | .vercel
28 |
29 | # wrangler files
30 | .wrangler
31 | .dev.vars
32 |
33 | # lerna
34 | .nx
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022 Andres Morey
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Use this section to tell people about which versions of your project are
6 | currently being supported with security updates.
7 |
8 | | Version | Supported |
9 | | ------- | ------------------ |
10 | | ALL | :white_check_mark: |
11 |
12 | ## Reporting a Vulnerability
13 |
14 | If you find a security vulnerability please send us an email
15 | (hello@kubetail.com) and we will respond to you ASAP.
16 |
--------------------------------------------------------------------------------
/examples/express/README.md:
--------------------------------------------------------------------------------
1 | This is an [Express](https://expressjs.com) example app.
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the server:
16 |
17 | ```bash
18 | node app.js
19 | ```
20 |
21 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
22 |
--------------------------------------------------------------------------------
/examples/express/app.js:
--------------------------------------------------------------------------------
1 | import { createCsrfMiddleware } from '@edge-csrf/express';
2 | import express from 'express';
3 |
4 | // initalize csrf protection middleware
5 | const csrfMiddleware = createCsrfMiddleware({
6 | cookie: {
7 | secure: process.env.NODE_ENV === 'production',
8 | },
9 | });
10 |
11 | // init app
12 | const app = express();
13 | const port = 3000;
14 |
15 | // add csrf middleware
16 | app.use(csrfMiddleware);
17 |
18 | // define handlers
19 | app.get('/', (req, res) => {
20 | const csrfToken = res.getHeader('X-CSRF-Token') || 'missing';
21 | res.send(`
22 |
23 |
24 |
25 | CSRF token value: ${csrfToken}
26 | HTML Form Submission Example:
27 |
32 |
33 |
39 |
40 |
46 |
47 |
48 | `);
49 | });
50 |
51 | app.post('/form-handler', (req, res) => {
52 | res.send('success');
53 | });
54 |
55 | // start server
56 | app.listen(port, () => {
57 | console.log(`Example app listening on port ${port}`)
58 | });
59 |
--------------------------------------------------------------------------------
/examples/express/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "type": "module",
6 | "dependencies": {
7 | "@edge-csrf/express": "^2.2.0",
8 | "express": "^4.19.2"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/examples/next13-approuter-html-submission/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next13-approuter-html-submission/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
27 |
--------------------------------------------------------------------------------
/examples/next13-approuter-html-submission/app/form-handler/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 |
3 | export async function POST() {
4 | return NextResponse.json({ status: 'success' });
5 | }
6 |
--------------------------------------------------------------------------------
/examples/next13-approuter-html-submission/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 |
3 | export const metadata: Metadata = {
4 | title: 'edge-csrf html form submission example',
5 | };
6 |
7 | export default function Layout({
8 | children,
9 | }: {
10 | children: React.ReactNode;
11 | }) {
12 | return (
13 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/next13-approuter-html-submission/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { headers } from 'next/headers';
2 |
3 | import '../styles/globals.css';
4 |
5 | export default function Page() {
6 | const csrfToken = headers().get('X-CSRF-Token') || 'missing';
7 |
8 | return (
9 | <>
10 |
11 | CSRF token value:
12 | {csrfToken}
13 |
14 | HTML Form Submission Example:
15 |
20 |
21 |
27 |
28 |
34 | HTML File Upload Example:
35 |
40 |
41 |
47 |
48 |
54 | >
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/examples/next13-approuter-html-submission/middleware.ts:
--------------------------------------------------------------------------------
1 | import { createCsrfMiddleware } from '@edge-csrf/nextjs';
2 |
3 | // initalize csrf protection middleware
4 | const csrfMiddleware = createCsrfMiddleware({
5 | cookie: {
6 | secure: process.env.NODE_ENV === 'production',
7 | },
8 | });
9 |
10 | export const middleware = csrfMiddleware;
11 |
--------------------------------------------------------------------------------
/examples/next13-approuter-html-submission/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next13-approuter-html-submission/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | }
6 |
7 | module.exports = nextConfig;
8 |
--------------------------------------------------------------------------------
/examples/next13-approuter-html-submission/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^13.5.6",
18 | "next": "13.5.6",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next13-approuter-html-submission/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next13-approuter-html-submission/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next13-approuter-html-submission/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next13-approuter-html-submission/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: blue;
11 | text-decoration: underline;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next13-approuter-html-submission/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | ".next/types/**/*.ts"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-dynamic/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-dynamic/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
27 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-dynamic/app/form-handler/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 |
3 | export async function POST() {
4 | return NextResponse.json({ status: 'success' });
5 | }
6 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-dynamic/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 | import { headers } from 'next/headers';
3 |
4 | export async function generateMetadata(): Promise {
5 | const csrfToken = headers().get('X-CSRF-Token') || 'missing';
6 |
7 | return {
8 | title: 'edge-csrf example',
9 | other: {
10 | 'x-csrf-token': csrfToken,
11 | },
12 | };
13 | }
14 |
15 | export default function Layout({
16 | children,
17 | }: {
18 | children: React.ReactNode;
19 | }) {
20 | return (
21 |
22 |
23 | {children}
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-dynamic/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useState, useEffect } from 'react';
4 |
5 | import '../styles/globals.css';
6 |
7 | export default function Page() {
8 | const [csrfToken, setCsrfToken] = useState('loading...');
9 |
10 | useEffect(() => {
11 | const el = document.querySelector('meta[name="x-csrf-token"]') as HTMLMetaElement | null;
12 | if (el) setCsrfToken(el.content);
13 | else setCsrfToken('missing');
14 | }, []);
15 |
16 | // method to generate form handlers
17 | const onSubmit = (tokenVal: string | null): React.FormEventHandler => (
18 | async (event: React.FormEvent) => {
19 | event.preventDefault();
20 |
21 | // get form values
22 | const data = new FormData(event.currentTarget);
23 |
24 | // build fetch args
25 | const fetchArgs = { method: 'POST', headers: {}, body: JSON.stringify(data) };
26 | if (tokenVal != null) fetchArgs.headers = { 'X-CSRF-Token': tokenVal };
27 |
28 | // send to backend
29 | const response = await fetch('/form-handler', fetchArgs);
30 |
31 | // show response
32 | // eslint-disable-next-line no-alert
33 | alert(response.statusText);
34 | }
35 | );
36 |
37 | return (
38 | <>
39 | JavaScript Form Submission Example:
40 |
41 | CSRF token value:
42 | {csrfToken}
43 |
44 |
49 |
50 |
55 |
56 |
61 | >
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-dynamic/middleware.ts:
--------------------------------------------------------------------------------
1 | import { createCsrfMiddleware } from '@edge-csrf/nextjs';
2 |
3 | // initalize csrf protection middleware
4 | const csrfMiddleware = createCsrfMiddleware({
5 | cookie: {
6 | secure: process.env.NODE_ENV === 'production',
7 | },
8 | });
9 |
10 | export const middleware = csrfMiddleware;
11 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-dynamic/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-dynamic/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | }
6 |
7 | module.exports = nextConfig;
8 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-dynamic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^13.5.6",
18 | "next": "^13.5.6",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-dynamic/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next13-approuter-js-submission-dynamic/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-dynamic/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-dynamic/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: blue;
11 | text-decoration: underline;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-dynamic/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | ".next/types/**/*.ts"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-static/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-static/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
27 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-static/app/form-handler/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 |
3 | export async function POST() {
4 | return NextResponse.json({ status: 'success' });
5 | }
6 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-static/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 |
3 | export const metadata: Metadata = {
4 | title: 'edge-csrf examples',
5 | };
6 |
7 | export default function Layout({
8 | children,
9 | }: {
10 | children: React.ReactNode;
11 | }) {
12 | return (
13 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-static/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import '../styles/globals.css';
4 |
5 | export default function Page() {
6 | const handleSubmit = async (ev: React.FormEvent) => {
7 | // prevent default form submission
8 | ev.preventDefault();
9 |
10 | // get form values
11 | const data = new FormData(ev.currentTarget);
12 |
13 | // get token (see middleware.ts)
14 | const csrfResp = await fetch('/csrf-token');
15 | const { csrfToken } = await csrfResp.json();
16 |
17 | // build fetch args
18 | const fetchArgs = { method: 'POST', headers: {}, body: JSON.stringify(data) };
19 | if (csrfToken) fetchArgs.headers = { 'X-CSRF-Token': csrfToken };
20 |
21 | // send to backend
22 | const response = await fetch('/form-handler', fetchArgs);
23 |
24 | // show response
25 | // eslint-disable-next-line no-alert
26 | alert(response.statusText);
27 | };
28 |
29 | return (
30 | <>
31 | JavaScript Form Submission Example (Static Optimized):
32 |
37 | >
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-static/middleware.ts:
--------------------------------------------------------------------------------
1 | import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs';
2 | import { NextResponse } from 'next/server';
3 | import type { NextRequest } from 'next/server';
4 |
5 | const csrfProtect = createCsrfProtect({
6 | cookie: {
7 | secure: process.env.NODE_ENV === 'production',
8 | },
9 | });
10 |
11 | export async function middleware(request: NextRequest) {
12 | const response = NextResponse.next();
13 |
14 | // csrf protection
15 | try {
16 | await csrfProtect(request, response);
17 | } catch (err) {
18 | if (err instanceof CsrfError) return new NextResponse('invalid csrf token', { status: 403 });
19 | throw err;
20 | }
21 |
22 | // return token (for use in static-optimized-example)
23 | if (request.nextUrl.pathname === '/csrf-token') {
24 | return NextResponse.json({ csrfToken: response.headers.get('X-CSRF-Token') || 'missing' });
25 | }
26 |
27 | return response;
28 | }
29 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-static/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-static/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | }
6 |
7 | module.exports = nextConfig;
8 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-static/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^13.5.6",
18 | "next": "^13.5.6",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-static/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next13-approuter-js-submission-static/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-static/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-static/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: blue;
11 | text-decoration: underline;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next13-approuter-js-submission-static/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | ".next/types/**/*.ts"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/examples/next13-pagesrouter-html-submission/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals",
3 | "rules": {
4 | "react/function-component-definition": "off",
5 | "react/jsx-props-no-spreading": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/next13-pagesrouter-html-submission/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
--------------------------------------------------------------------------------
/examples/next13-pagesrouter-html-submission/middleware.ts:
--------------------------------------------------------------------------------
1 | import { createCsrfMiddleware } from '@edge-csrf/nextjs';
2 |
3 | // initalize csrf protection middleware
4 | const csrfMiddleware = createCsrfMiddleware({
5 | cookie: {
6 | secure: process.env.NODE_ENV === 'production',
7 | },
8 | });
9 |
10 | export const middleware = csrfMiddleware;
11 |
--------------------------------------------------------------------------------
/examples/next13-pagesrouter-html-submission/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next13-pagesrouter-html-submission/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true
5 | }
6 |
7 | module.exports = nextConfig;
8 |
--------------------------------------------------------------------------------
/examples/next13-pagesrouter-html-submission/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^13.5.6",
18 | "next": "^13.5.6",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next13-pagesrouter-html-submission/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import '../styles/globals.css';
2 | import type { AppProps } from 'next/app';
3 |
4 | export default function App({ Component, pageProps }: AppProps) {
5 | return ;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/next13-pagesrouter-html-submission/pages/api/form-handler.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | type Data = {
4 | status: string;
5 | };
6 |
7 | export default function handler(req: NextApiRequest, res: NextApiResponse) {
8 | res.status(200).json({ status: 'success' });
9 | }
10 |
--------------------------------------------------------------------------------
/examples/next13-pagesrouter-html-submission/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage, GetServerSideProps } from 'next';
2 |
3 | type Props = {
4 | csrfToken: string;
5 | };
6 |
7 | export const getServerSideProps: GetServerSideProps = async ({ res }) => {
8 | const csrfToken = res.getHeader('X-CSRF-Token') || 'missing';
9 | return { props: { csrfToken } };
10 | };
11 |
12 | const Home: NextPage = ({ csrfToken }) => (
13 | <>
14 |
15 | CSRF token value:
16 | {csrfToken}
17 |
18 | HTML Form Submission Example
19 |
24 |
25 |
31 |
32 |
38 | >
39 | );
40 |
41 | export default Home;
42 |
--------------------------------------------------------------------------------
/examples/next13-pagesrouter-html-submission/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next13-pagesrouter-html-submission/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next13-pagesrouter-html-submission/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next13-pagesrouter-html-submission/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next13-pagesrouter-html-submission/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "bundler",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/examples/next14-approuter-html-submission/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next14-approuter-html-submission/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
27 |
--------------------------------------------------------------------------------
/examples/next14-approuter-html-submission/app/form-handler/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 |
3 | export async function POST() {
4 | return NextResponse.json({ status: 'success' });
5 | }
6 |
--------------------------------------------------------------------------------
/examples/next14-approuter-html-submission/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 |
3 | export const metadata: Metadata = {
4 | title: 'edge-csrf html form submission example',
5 | };
6 |
7 | export default function Layout({
8 | children,
9 | }: {
10 | children: React.ReactNode;
11 | }) {
12 | return (
13 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/next14-approuter-html-submission/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { headers } from 'next/headers';
2 |
3 | import '../styles/globals.css';
4 |
5 | export default function Page() {
6 | const csrfToken = headers().get('X-CSRF-Token') || 'missing';
7 |
8 | return (
9 | <>
10 |
11 | CSRF token value:
12 | {csrfToken}
13 |
14 | HTML Form Submission Example:
15 |
20 |
21 |
27 |
28 |
34 | HTML File Upload Example:
35 |
40 |
41 |
47 |
48 |
54 | >
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/examples/next14-approuter-html-submission/middleware.ts:
--------------------------------------------------------------------------------
1 | import { createCsrfMiddleware } from '@edge-csrf/nextjs';
2 |
3 | // initalize csrf protection middleware
4 | const csrfMiddleware = createCsrfMiddleware({
5 | cookie: {
6 | secure: process.env.NODE_ENV === 'production',
7 | },
8 | });
9 |
10 | export const middleware = csrfMiddleware;
11 |
--------------------------------------------------------------------------------
/examples/next14-approuter-html-submission/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next14-approuter-html-submission/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | }
6 |
7 | module.exports = nextConfig;
8 |
--------------------------------------------------------------------------------
/examples/next14-approuter-html-submission/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^14.0.1",
18 | "next": "^14.0.1",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next14-approuter-html-submission/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next14-approuter-html-submission/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next14-approuter-html-submission/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next14-approuter-html-submission/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: blue;
11 | text-decoration: underline;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next14-approuter-html-submission/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "dom",
5 | "dom.iterable",
6 | "esnext"
7 | ],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noEmit": true,
13 | "esModuleInterop": true,
14 | "module": "esnext",
15 | "moduleResolution": "bundler",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "jsx": "preserve",
19 | "incremental": true,
20 | "plugins": [
21 | {
22 | "name": "next"
23 | }
24 | ]
25 | },
26 | "include": [
27 | "next-env.d.ts",
28 | "**/*.ts",
29 | "**/*.tsx",
30 | ".next/types/**/*.ts"
31 | ],
32 | "exclude": [
33 | "node_modules"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-dynamic/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-dynamic/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
27 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-dynamic/app/form-handler/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 |
3 | export async function POST() {
4 | return NextResponse.json({ status: 'success' });
5 | }
6 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-dynamic/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 | import { headers } from 'next/headers';
3 |
4 | export async function generateMetadata(): Promise {
5 | const csrfToken = headers().get('X-CSRF-Token') || 'missing';
6 |
7 | return {
8 | title: 'edge-csrf example',
9 | other: {
10 | 'x-csrf-token': csrfToken,
11 | },
12 | };
13 | }
14 |
15 | export default function Layout({
16 | children,
17 | }: {
18 | children: React.ReactNode;
19 | }) {
20 | return (
21 |
22 |
23 | {children}
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-dynamic/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useState, useEffect } from 'react';
4 |
5 | import '../styles/globals.css';
6 |
7 | export default function Page() {
8 | const [csrfToken, setCsrfToken] = useState('loading...');
9 |
10 | useEffect(() => {
11 | const el = document.querySelector('meta[name="x-csrf-token"]') as HTMLMetaElement | null;
12 | if (el) setCsrfToken(el.content);
13 | else setCsrfToken('missing');
14 | }, []);
15 |
16 | // method to generate form handlers
17 | const onSubmit = (tokenVal: string | null): React.FormEventHandler => (
18 | async (event: React.FormEvent) => {
19 | event.preventDefault();
20 |
21 | // get form values
22 | const data = new FormData(event.currentTarget);
23 |
24 | // build fetch args
25 | const fetchArgs = { method: 'POST', headers: {}, body: JSON.stringify(data) };
26 | if (tokenVal != null) fetchArgs.headers = { 'X-CSRF-Token': tokenVal };
27 |
28 | // send to backend
29 | const response = await fetch('/form-handler', fetchArgs);
30 |
31 | // show response
32 | // eslint-disable-next-line no-alert
33 | alert(response.statusText);
34 | }
35 | );
36 |
37 | return (
38 | <>
39 | JavaScript Form Submission Example:
40 |
41 | CSRF token value:
42 | {csrfToken}
43 |
44 |
49 |
50 |
55 |
56 |
61 | >
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-dynamic/middleware.ts:
--------------------------------------------------------------------------------
1 | import { createCsrfMiddleware } from '@edge-csrf/nextjs';
2 |
3 | // initalize csrf protection middleware
4 | const csrfMiddleware = createCsrfMiddleware({
5 | cookie: {
6 | secure: process.env.NODE_ENV === 'production',
7 | },
8 | });
9 |
10 | export const middleware = csrfMiddleware;
11 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-dynamic/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-dynamic/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | }
6 |
7 | module.exports = nextConfig;
8 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-dynamic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^14.0.1",
18 | "next": "^14.0.1",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-dynamic/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next14-approuter-js-submission-dynamic/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-dynamic/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-dynamic/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: blue;
11 | text-decoration: underline;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-dynamic/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | ".next/types/**/*.ts"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-static/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-static/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
27 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-static/app/form-handler/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 |
3 | export async function POST() {
4 | return NextResponse.json({ status: 'success' });
5 | }
6 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-static/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 |
3 | export const metadata: Metadata = {
4 | title: 'edge-csrf examples',
5 | };
6 |
7 | export default function Layout({
8 | children,
9 | }: {
10 | children: React.ReactNode;
11 | }) {
12 | return (
13 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-static/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import '../styles/globals.css';
4 |
5 | export default function Page() {
6 | const handleSubmit = async (ev: React.FormEvent) => {
7 | // prevent default form submission
8 | ev.preventDefault();
9 |
10 | // get form values
11 | const data = new FormData(ev.currentTarget);
12 |
13 | // get token (see middleware.ts)
14 | const csrfResp = await fetch('/csrf-token');
15 | const { csrfToken } = await csrfResp.json();
16 |
17 | // build fetch args
18 | const fetchArgs = { method: 'POST', headers: {}, body: JSON.stringify(data) };
19 | if (csrfToken) fetchArgs.headers = { 'X-CSRF-Token': csrfToken };
20 |
21 | // send to backend
22 | const response = await fetch('/form-handler', fetchArgs);
23 |
24 | // show response
25 | // eslint-disable-next-line no-alert
26 | alert(response.statusText);
27 | };
28 |
29 | return (
30 | <>
31 | JavaScript Form Submission Example (Static Optimized):
32 |
37 | >
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-static/middleware.ts:
--------------------------------------------------------------------------------
1 | import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs';
2 | import { NextResponse } from 'next/server';
3 | import type { NextRequest } from 'next/server';
4 |
5 | const csrfProtect = createCsrfProtect({
6 | cookie: {
7 | secure: process.env.NODE_ENV === 'production',
8 | },
9 | });
10 |
11 | export async function middleware(request: NextRequest) {
12 | const response = NextResponse.next();
13 |
14 | // csrf protection
15 | try {
16 | await csrfProtect(request, response);
17 | } catch (err) {
18 | if (err instanceof CsrfError) return new NextResponse('invalid csrf token', { status: 403 });
19 | throw err;
20 | }
21 |
22 | // return token (for use in static-optimized-example)
23 | if (request.nextUrl.pathname === '/csrf-token') {
24 | return NextResponse.json({ csrfToken: response.headers.get('X-CSRF-Token') || 'missing' });
25 | }
26 |
27 | return response;
28 | }
29 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-static/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-static/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | }
6 |
7 | module.exports = nextConfig;
8 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-static/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^14.0.1",
18 | "next": "^14.0.1",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-static/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next14-approuter-js-submission-static/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-static/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-static/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: blue;
11 | text-decoration: underline;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next14-approuter-js-submission-static/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | ".next/types/**/*.ts"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/.sentryclirc:
--------------------------------------------------------------------------------
1 |
2 | [auth]
3 | token=REPLACEME
4 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/app/api/sentry-example-api/route.js:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 |
3 | export const dynamic = "force-dynamic";
4 |
5 | // A faulty API route to test Sentry's error monitoring
6 | export function GET() {
7 | throw new Error("Sentry Example API Route Error");
8 | return NextResponse.json({ data: "Testing Sentry Error..." });
9 | }
10 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next14-approuter-sentry/app/favicon.ico
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/app/global-error.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as Sentry from "@sentry/nextjs";
4 | import Error from "next/error";
5 | import { useEffect } from "react";
6 |
7 | export default function GlobalError({ error }) {
8 | useEffect(() => {
9 | Sentry.captureException(error);
10 | }, [error]);
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | color: rgb(var(--foreground-rgb));
21 | background: linear-gradient(
22 | to bottom,
23 | transparent,
24 | rgb(var(--background-end-rgb))
25 | )
26 | rgb(var(--background-start-rgb));
27 | }
28 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 | import { Inter } from 'next/font/google'
3 | import './globals.css'
4 |
5 | const inter = Inter({ subsets: ['latin'] })
6 |
7 | export const metadata: Metadata = {
8 | title: 'Create Next App',
9 | description: 'Generated by create next app',
10 | }
11 |
12 | export default function RootLayout({
13 | children,
14 | }: {
15 | children: React.ReactNode
16 | }) {
17 | return (
18 |
19 | {children}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/middleware.ts:
--------------------------------------------------------------------------------
1 | import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs';
2 | import { NextResponse } from 'next/server';
3 | import type { NextRequest } from 'next/server';
4 |
5 | const csrfProtect = createCsrfProtect({
6 | cookie: {
7 | secure: process.env.NODE_ENV === 'production',
8 | },
9 | });
10 |
11 | export async function middleware(request: NextRequest) {
12 | const response = NextResponse.next();
13 |
14 | // csrf protection
15 | try {
16 | await csrfProtect(request, response);
17 | } catch (err) {
18 | if (err instanceof CsrfError) return new NextResponse('invalid csrf token', { status: 403 });
19 | throw err;
20 | }
21 |
22 | // return token (for use in static-optimized-example)
23 | if (request.nextUrl.pathname === '/csrf-token') {
24 | return NextResponse.json({ csrfToken: response.headers.get('X-CSRF-Token') || 'missing' });
25 | }
26 |
27 | return response;
28 | }
29 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | module.exports = nextConfig
5 |
6 |
7 | // Injected content via Sentry wizard below
8 |
9 | const { withSentryConfig } = require("@sentry/nextjs");
10 |
11 | module.exports = withSentryConfig(
12 | module.exports,
13 | {
14 | // For all available options, see:
15 | // https://github.com/getsentry/sentry-webpack-plugin#options
16 |
17 | // Suppresses source map uploading logs during build
18 | silent: true,
19 | org: "REPLACEME",
20 | project: "REPLACEME",
21 | },
22 | {
23 | // For all available options, see:
24 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
25 |
26 | // Upload a larger set of source maps for prettier stack traces (increases build time)
27 | widenClientFileUpload: true,
28 |
29 | // Transpiles SDK to be compatible with IE11 (increases bundle size)
30 | transpileClientSDK: true,
31 |
32 | // Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
33 | tunnelRoute: "/monitoring",
34 |
35 | // Hides source maps from generated client bundles
36 | hideSourceMaps: true,
37 |
38 | // Automatically tree-shake Sentry logger statements to reduce bundle size
39 | disableLogger: true,
40 |
41 | // Enables automatic instrumentation of Vercel Cron Monitors.
42 | // See the following for more information:
43 | // https://docs.sentry.io/product/crons/
44 | // https://vercel.com/docs/cron-jobs
45 | automaticVercelMonitors: true,
46 | }
47 | );
48 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@sentry/nextjs": "^7.93.0",
14 | "next": "14.2.1",
15 | "react": "^18",
16 | "react-dom": "^18"
17 | },
18 | "devDependencies": {
19 | "@types/node": "^20",
20 | "@types/react": "^18",
21 | "@types/react-dom": "^18",
22 | "autoprefixer": "^10.0.1",
23 | "eslint": "^8",
24 | "eslint-config-next": "14.0.4",
25 | "postcss": "^8",
26 | "tailwindcss": "^3.3.0",
27 | "typescript": "^5"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/sentry.client.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry on the client.
2 | // The config you add here will be used whenever a users loads a page in their browser.
3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
4 |
5 | import * as Sentry from "@sentry/nextjs";
6 | import type { BaseTransportOptions } from '@sentry/types';
7 |
8 | async function fetchWithCSRFHeader(input: RequestInfo | URL, init: RequestInit = {}): Promise {
9 | // get csrf token (see middleware.ts)
10 | const csrfResp = await fetch('/csrf-token');
11 | const { csrfToken } = await csrfResp.json();
12 |
13 | // add token to headers
14 | const headers = new Headers(init.headers);
15 | headers.append('X-CSRF-Token', csrfToken);
16 |
17 | // construct init object with the updated headers
18 | const modifiedInit = { ...init, headers };
19 |
20 | // call native fetch function with the original input and the modified init object
21 | return fetch(input, modifiedInit);
22 | }
23 |
24 | Sentry.init({
25 | dsn: "https://REPLACEME.ingest.sentry.io/REPLACEME",
26 |
27 | // Adjust this value in production, or use tracesSampler for greater control
28 | tracesSampleRate: 1,
29 |
30 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
31 | debug: false,
32 |
33 | replaysOnErrorSampleRate: 1.0,
34 |
35 | // This sets the sample rate to be 10%. You may want this to be 100% while
36 | // in development and sample at a lower rate in production
37 | replaysSessionSampleRate: 0.1,
38 |
39 | // You can remove this option if you're not planning to use the Sentry Session Replay feature:
40 | integrations: [
41 | new Sentry.Replay({
42 | // Additional Replay configuration goes in here, for example:
43 | maskAllText: true,
44 | blockAllMedia: true,
45 | }),
46 | ],
47 |
48 | transport: (options: BaseTransportOptions) => {
49 | return Sentry.makeFetchTransport(options, fetchWithCSRFHeader);
50 | }
51 | });
52 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/sentry.edge.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
2 | // The config you add here will be used whenever one of the edge features is loaded.
3 | // Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
4 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
5 |
6 | import * as Sentry from "@sentry/nextjs";
7 |
8 | Sentry.init({
9 | dsn: "https://REPLACEME.ingest.sentry.io/REPLACEME",
10 |
11 | // Adjust this value in production, or use tracesSampler for greater control
12 | tracesSampleRate: 1,
13 |
14 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
15 | debug: false,
16 | });
17 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/sentry.server.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry on the server.
2 | // The config you add here will be used whenever the server handles a request.
3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
4 |
5 | import * as Sentry from "@sentry/nextjs";
6 |
7 | Sentry.init({
8 | dsn: "https://REPLACEME.ingest.sentry.io/REPLACEME",
9 |
10 | // Adjust this value in production, or use tracesSampler for greater control
11 | tracesSampleRate: 1,
12 |
13 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
14 | debug: false,
15 | });
16 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 |
3 | const config: Config = {
4 | content: [
5 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13 | 'gradient-conic':
14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
15 | },
16 | },
17 | },
18 | plugins: [],
19 | }
20 | export default config
21 |
--------------------------------------------------------------------------------
/examples/next14-approuter-sentry/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-form-submission/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-form-submission/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
27 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-form-submission/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 |
3 | export const metadata: Metadata = {
4 | title: 'edge-csrf examples',
5 | };
6 |
7 | export default function Layout({
8 | children,
9 | }: {
10 | children: React.ReactNode;
11 | }) {
12 | return (
13 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-form-submission/middleware.ts:
--------------------------------------------------------------------------------
1 | import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs';
2 | import { NextResponse } from 'next/server';
3 | import type { NextRequest } from 'next/server';
4 |
5 | const csrfProtect = createCsrfProtect({
6 | cookie: {
7 | secure: process.env.NODE_ENV === 'production',
8 | },
9 | });
10 |
11 | export async function middleware(request: NextRequest) {
12 | const response = NextResponse.next();
13 |
14 | // csrf protection
15 | try {
16 | await csrfProtect(request, response);
17 | } catch (err) {
18 | if (err instanceof CsrfError) return new NextResponse('invalid csrf token', { status: 403 });
19 | throw err;
20 | }
21 |
22 | // return token (for use in static-optimized-example)
23 | if (request.nextUrl.pathname === '/csrf-token') {
24 | return NextResponse.json({ csrfToken: response.headers.get('X-CSRF-Token') || 'missing' });
25 | }
26 |
27 | return response;
28 | }
29 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-form-submission/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-form-submission/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | }
6 |
7 | module.exports = nextConfig;
8 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-form-submission/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^14.0.1",
18 | "next": "^14.2.1",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-form-submission/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next14-approuter-server-action-form-submission/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-form-submission/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-form-submission/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: blue;
11 | text-decoration: underline;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-form-submission/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | ".next/types/**/*.ts"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-non-form-submission/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-non-form-submission/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
27 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-non-form-submission/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 |
3 | export const metadata: Metadata = {
4 | title: 'edge-csrf examples',
5 | };
6 |
7 | export default function Layout({
8 | children,
9 | }: {
10 | children: React.ReactNode;
11 | }) {
12 | return (
13 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-non-form-submission/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { example1, example2 } from '../lib/actions';
4 |
5 | export default function Page() {
6 | const handleClick1 = async () => {
7 | const csrfResp = await fetch('/csrf-token');
8 | const { csrfToken } = await csrfResp.json();
9 |
10 | const data = {
11 | key1: 'val1',
12 | key2: 'val2',
13 | };
14 |
15 | // use token as first argument to server action
16 | await example1(csrfToken, data);
17 | };
18 |
19 | const handleClick2 = async () => {
20 | const csrfResp = await fetch('/csrf-token');
21 | const { csrfToken } = await csrfResp.json();
22 |
23 | // add token to FormData instance
24 | const data = new FormData();
25 | data.set('csrf_token', csrfToken);
26 | data.set('key1', 'val1');
27 | data.set('key2', 'val2');
28 |
29 | await example2(data);
30 | };
31 |
32 | return (
33 | <>
34 | Server Action Non-Form Submission Examples:
35 | NOTE: Look at browser network logs and server console for submission feedback
36 | Example with object argument:
37 |
38 | Example with FormData argument:
39 |
40 | >
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-non-form-submission/lib/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | export async function example1(csrfToken: string, data: { key1: string; key2: string; }) {
4 | // eslint-disable-next-line no-console
5 | console.log(data);
6 | }
7 |
8 | export async function example2(data: FormData) {
9 | // eslint-disable-next-line no-console
10 | console.log(data);
11 | }
12 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-non-form-submission/middleware.ts:
--------------------------------------------------------------------------------
1 | import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs';
2 | import { NextResponse } from 'next/server';
3 | import type { NextRequest } from 'next/server';
4 |
5 | const csrfProtect = createCsrfProtect({
6 | cookie: {
7 | secure: process.env.NODE_ENV === 'production',
8 | },
9 | });
10 |
11 | export async function middleware(request: NextRequest) {
12 | const response = NextResponse.next();
13 |
14 | // csrf protection
15 | try {
16 | await csrfProtect(request, response);
17 | } catch (err) {
18 | if (err instanceof CsrfError) return new NextResponse('invalid csrf token', { status: 403 });
19 | throw err;
20 | }
21 |
22 | // return token (for use in static-optimized-example)
23 | if (request.nextUrl.pathname === '/csrf-token') {
24 | return NextResponse.json({ csrfToken: response.headers.get('X-CSRF-Token') || 'missing' });
25 | }
26 |
27 | return response;
28 | }
29 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-non-form-submission/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-non-form-submission/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true,
5 | }
6 |
7 | module.exports = nextConfig;
8 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-non-form-submission/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^14.0.1",
18 | "next": "^14.0.1",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-non-form-submission/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next14-approuter-server-action-non-form-submission/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-non-form-submission/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-non-form-submission/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: blue;
11 | text-decoration: underline;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next14-approuter-server-action-non-form-submission/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | ".next/types/**/*.ts"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/examples/next14-pagesrouter-html-submission/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals",
3 | "rules": {
4 | "react/function-component-definition": "off",
5 | "react/jsx-props-no-spreading": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/next14-pagesrouter-html-submission/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
--------------------------------------------------------------------------------
/examples/next14-pagesrouter-html-submission/middleware.ts:
--------------------------------------------------------------------------------
1 | import { createCsrfMiddleware } from '@edge-csrf/nextjs';
2 |
3 | // initalize csrf protection middleware
4 | const csrfMiddleware = createCsrfMiddleware({
5 | cookie: {
6 | secure: process.env.NODE_ENV === 'production',
7 | },
8 | });
9 |
10 | export const middleware = csrfMiddleware;
11 |
--------------------------------------------------------------------------------
/examples/next14-pagesrouter-html-submission/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next14-pagesrouter-html-submission/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | swcMinify: true
5 | }
6 |
7 | module.exports = nextConfig;
8 |
--------------------------------------------------------------------------------
/examples/next14-pagesrouter-html-submission/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^14.0.1",
18 | "next": "^14.2.1",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next14-pagesrouter-html-submission/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import '../styles/globals.css';
2 | import type { AppProps } from 'next/app';
3 |
4 | export default function App({ Component, pageProps }: AppProps) {
5 | return ;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/next14-pagesrouter-html-submission/pages/api/form-handler.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | type Data = {
4 | status: string;
5 | };
6 |
7 | export default function handler(req: NextApiRequest, res: NextApiResponse) {
8 | res.status(200).json({ status: 'success' });
9 | }
10 |
--------------------------------------------------------------------------------
/examples/next14-pagesrouter-html-submission/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage, GetServerSideProps } from 'next';
2 |
3 | type Props = {
4 | csrfToken: string;
5 | };
6 |
7 | export const getServerSideProps: GetServerSideProps = async ({ res }) => {
8 | const csrfToken = res.getHeader('X-CSRF-Token') || 'missing';
9 | return { props: { csrfToken } };
10 | };
11 |
12 | const Home: NextPage = ({ csrfToken }) => (
13 | <>
14 |
15 | CSRF token value:
16 | {csrfToken}
17 |
18 | HTML Form Submission Example
19 |
24 |
25 |
31 |
32 |
38 | >
39 | );
40 |
41 | export default Home;
42 |
--------------------------------------------------------------------------------
/examples/next14-pagesrouter-html-submission/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next14-pagesrouter-html-submission/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next14-pagesrouter-html-submission/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next14-pagesrouter-html-submission/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next14-pagesrouter-html-submission/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "bundler",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/examples/next15-approuter-html-submission/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next15-approuter-html-submission/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
27 |
--------------------------------------------------------------------------------
/examples/next15-approuter-html-submission/app/form-handler/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 |
3 | export async function POST() {
4 | return NextResponse.json({ status: 'success' });
5 | }
6 |
--------------------------------------------------------------------------------
/examples/next15-approuter-html-submission/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 |
3 | export const metadata: Metadata = {
4 | title: 'edge-csrf html form submission example',
5 | };
6 |
7 | export default function Layout({
8 | children,
9 | }: {
10 | children: React.ReactNode;
11 | }) {
12 | return (
13 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/next15-approuter-html-submission/middleware.ts:
--------------------------------------------------------------------------------
1 | import { createCsrfMiddleware } from '@edge-csrf/nextjs';
2 |
3 | // initalize csrf protection middleware
4 | const csrfMiddleware = createCsrfMiddleware({
5 | cookie: {
6 | secure: process.env.NODE_ENV === 'production',
7 | },
8 | });
9 |
10 | export const middleware = csrfMiddleware;
11 |
--------------------------------------------------------------------------------
/examples/next15-approuter-html-submission/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next15-approuter-html-submission/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig;
7 |
--------------------------------------------------------------------------------
/examples/next15-approuter-html-submission/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^15.0.0",
18 | "next": "^15.0.0",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next15-approuter-html-submission/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next15-approuter-html-submission/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next15-approuter-html-submission/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next15-approuter-html-submission/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: blue;
11 | text-decoration: underline;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next15-approuter-html-submission/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "dom",
5 | "dom.iterable",
6 | "esnext"
7 | ],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noEmit": true,
13 | "esModuleInterop": true,
14 | "module": "esnext",
15 | "moduleResolution": "bundler",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "jsx": "preserve",
19 | "incremental": true,
20 | "target": "ES2017",
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | ".next/types/**/*.ts"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-dynamic/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-dynamic/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
27 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-dynamic/app/form-handler/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 |
3 | export async function POST() {
4 | return NextResponse.json({ status: 'success' });
5 | }
6 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-dynamic/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 | import { headers } from 'next/headers';
3 |
4 | export async function generateMetadata(): Promise {
5 | const headersList = await headers();
6 | const csrfToken = headersList.get('X-CSRF-Token') || 'missing';
7 |
8 | return {
9 | title: 'edge-csrf example',
10 | other: {
11 | 'x-csrf-token': csrfToken,
12 | },
13 | };
14 | }
15 |
16 | export default function Layout({
17 | children,
18 | }: {
19 | children: React.ReactNode;
20 | }) {
21 | return (
22 |
23 |
24 | {children}
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-dynamic/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useState, useEffect } from 'react';
4 |
5 | import '../styles/globals.css';
6 |
7 | export default function Page() {
8 | const [csrfToken, setCsrfToken] = useState('loading...');
9 |
10 | useEffect(() => {
11 | const el = document.querySelector('meta[name="x-csrf-token"]') as HTMLMetaElement | null;
12 | if (el) setCsrfToken(el.content);
13 | else setCsrfToken('missing');
14 | }, []);
15 |
16 | // method to generate form handlers
17 | const onSubmit = (tokenVal: string | null): React.FormEventHandler => (
18 | async (event: React.FormEvent) => {
19 | event.preventDefault();
20 |
21 | // get form values
22 | const data = new FormData(event.currentTarget);
23 |
24 | // build fetch args
25 | const fetchArgs = { method: 'POST', headers: {}, body: JSON.stringify(data) };
26 | if (tokenVal != null) fetchArgs.headers = { 'X-CSRF-Token': tokenVal };
27 |
28 | // send to backend
29 | const response = await fetch('/form-handler', fetchArgs);
30 |
31 | // show response
32 | // eslint-disable-next-line no-alert
33 | alert(response.statusText);
34 | }
35 | );
36 |
37 | return (
38 | <>
39 | JavaScript Form Submission Example:
40 |
41 | CSRF token value:
42 | {csrfToken}
43 |
44 |
49 |
50 |
55 |
56 |
61 | >
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-dynamic/middleware.ts:
--------------------------------------------------------------------------------
1 | import { createCsrfMiddleware } from '@edge-csrf/nextjs';
2 |
3 | // initalize csrf protection middleware
4 | const csrfMiddleware = createCsrfMiddleware({
5 | cookie: {
6 | secure: process.env.NODE_ENV === 'production',
7 | },
8 | });
9 |
10 | export const middleware = csrfMiddleware;
11 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-dynamic/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-dynamic/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig;
7 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-dynamic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^15.0.0",
18 | "next": "^15.0.0",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-dynamic/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next15-approuter-js-submission-dynamic/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-dynamic/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-dynamic/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: blue;
11 | text-decoration: underline;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-dynamic/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | ".next/types/**/*.ts"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-static/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-static/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
27 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-static/app/form-handler/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server';
2 |
3 | export async function POST() {
4 | return NextResponse.json({ status: 'success' });
5 | }
6 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-static/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 |
3 | export const metadata: Metadata = {
4 | title: 'edge-csrf examples',
5 | };
6 |
7 | export default function Layout({
8 | children,
9 | }: {
10 | children: React.ReactNode;
11 | }) {
12 | return (
13 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-static/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import '../styles/globals.css';
4 |
5 | export default function Page() {
6 | const handleSubmit = async (ev: React.FormEvent) => {
7 | // prevent default form submission
8 | ev.preventDefault();
9 |
10 | // get form values
11 | const data = new FormData(ev.currentTarget);
12 |
13 | // get token (see middleware.ts)
14 | const csrfResp = await fetch('/csrf-token');
15 | const { csrfToken } = await csrfResp.json();
16 |
17 | // build fetch args
18 | const fetchArgs = { method: 'POST', headers: {}, body: JSON.stringify(data) };
19 | if (csrfToken) fetchArgs.headers = { 'X-CSRF-Token': csrfToken };
20 |
21 | // send to backend
22 | const response = await fetch('/form-handler', fetchArgs);
23 |
24 | // show response
25 | // eslint-disable-next-line no-alert
26 | alert(response.statusText);
27 | };
28 |
29 | return (
30 | <>
31 | JavaScript Form Submission Example (Static Optimized):
32 |
37 | >
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-static/middleware.ts:
--------------------------------------------------------------------------------
1 | import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs';
2 | import { NextResponse } from 'next/server';
3 | import type { NextRequest } from 'next/server';
4 |
5 | const csrfProtect = createCsrfProtect({
6 | cookie: {
7 | secure: process.env.NODE_ENV === 'production',
8 | },
9 | });
10 |
11 | export async function middleware(request: NextRequest) {
12 | const response = NextResponse.next();
13 |
14 | // csrf protection
15 | try {
16 | await csrfProtect(request, response);
17 | } catch (err) {
18 | if (err instanceof CsrfError) return new NextResponse('invalid csrf token', { status: 403 });
19 | throw err;
20 | }
21 |
22 | // return token (for use in static-optimized-example)
23 | if (request.nextUrl.pathname === '/csrf-token') {
24 | return NextResponse.json({ csrfToken: response.headers.get('X-CSRF-Token') || 'missing' });
25 | }
26 |
27 | return response;
28 | }
29 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-static/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-static/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig;
7 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-static/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^15.0.0",
18 | "next": "^15.0.0",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-static/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next15-approuter-js-submission-static/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-static/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-static/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: blue;
11 | text-decoration: underline;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next15-approuter-js-submission-static/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | ".next/types/**/*.ts"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/.sentryclirc:
--------------------------------------------------------------------------------
1 |
2 | [auth]
3 | token=REPLACEME
4 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/app/api/sentry-example-api/route.js:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 |
3 | export const dynamic = "force-dynamic";
4 |
5 | // A faulty API route to test Sentry's error monitoring
6 | export function GET() {
7 | throw new Error("Sentry Example API Route Error");
8 | return NextResponse.json({ data: "Testing Sentry Error..." });
9 | }
10 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next15-approuter-sentry/app/favicon.ico
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/app/global-error.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as Sentry from "@sentry/nextjs";
4 | import Error from "next/error";
5 | import { useEffect } from "react";
6 |
7 | export default function GlobalError({ error }) {
8 | useEffect(() => {
9 | Sentry.captureException(error);
10 | }, [error]);
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | color: rgb(var(--foreground-rgb));
21 | background: linear-gradient(
22 | to bottom,
23 | transparent,
24 | rgb(var(--background-end-rgb))
25 | )
26 | rgb(var(--background-start-rgb));
27 | }
28 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 | import { Inter } from 'next/font/google'
3 | import './globals.css'
4 |
5 | const inter = Inter({ subsets: ['latin'] })
6 |
7 | export const metadata: Metadata = {
8 | title: 'Create Next App',
9 | description: 'Generated by create next app',
10 | }
11 |
12 | export default function RootLayout({
13 | children,
14 | }: {
15 | children: React.ReactNode
16 | }) {
17 | return (
18 |
19 | {children}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/middleware.ts:
--------------------------------------------------------------------------------
1 | import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs';
2 | import { NextResponse } from 'next/server';
3 | import type { NextRequest } from 'next/server';
4 |
5 | const csrfProtect = createCsrfProtect({
6 | cookie: {
7 | secure: process.env.NODE_ENV === 'production',
8 | },
9 | });
10 |
11 | export async function middleware(request: NextRequest) {
12 | const response = NextResponse.next();
13 |
14 | // csrf protection
15 | try {
16 | await csrfProtect(request, response);
17 | } catch (err) {
18 | if (err instanceof CsrfError) return new NextResponse('invalid csrf token', { status: 403 });
19 | throw err;
20 | }
21 |
22 | // return token (for use in static-optimized-example)
23 | if (request.nextUrl.pathname === '/csrf-token') {
24 | return NextResponse.json({ csrfToken: response.headers.get('X-CSRF-Token') || 'missing' });
25 | }
26 |
27 | return response;
28 | }
29 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | module.exports = nextConfig
5 |
6 |
7 | // Injected content via Sentry wizard below
8 |
9 | const { withSentryConfig } = require("@sentry/nextjs");
10 |
11 | module.exports = withSentryConfig(
12 | module.exports,
13 | {
14 | // For all available options, see:
15 | // https://github.com/getsentry/sentry-webpack-plugin#options
16 |
17 | // Suppresses source map uploading logs during build
18 | silent: true,
19 | org: "REPLACEME",
20 | project: "REPLACEME",
21 | },
22 | {
23 | // For all available options, see:
24 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
25 |
26 | // Upload a larger set of source maps for prettier stack traces (increases build time)
27 | widenClientFileUpload: true,
28 |
29 | // Transpiles SDK to be compatible with IE11 (increases bundle size)
30 | transpileClientSDK: true,
31 |
32 | // Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
33 | tunnelRoute: "/monitoring",
34 |
35 | // Hides source maps from generated client bundles
36 | hideSourceMaps: true,
37 |
38 | // Automatically tree-shake Sentry logger statements to reduce bundle size
39 | disableLogger: true,
40 |
41 | // Enables automatic instrumentation of Vercel Cron Monitors.
42 | // See the following for more information:
43 | // https://docs.sentry.io/product/crons/
44 | // https://vercel.com/docs/cron-jobs
45 | automaticVercelMonitors: true,
46 | }
47 | );
48 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@sentry/nextjs": "^7.93.0",
14 | "next": "^15.0.0",
15 | "react": "^18",
16 | "react-dom": "^18"
17 | },
18 | "devDependencies": {
19 | "@types/node": "^20",
20 | "@types/react": "^18",
21 | "@types/react-dom": "^18",
22 | "autoprefixer": "^10.0.1",
23 | "eslint": "^8",
24 | "eslint-config-next": "^15.0.0",
25 | "postcss": "^8",
26 | "tailwindcss": "^3.3.0",
27 | "typescript": "^5"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/sentry.client.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry on the client.
2 | // The config you add here will be used whenever a users loads a page in their browser.
3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
4 |
5 | import * as Sentry from "@sentry/nextjs";
6 | import type { BaseTransportOptions } from '@sentry/types';
7 |
8 | async function fetchWithCSRFHeader(input: RequestInfo | URL, init: RequestInit = {}): Promise {
9 | // get csrf token (see middleware.ts)
10 | const csrfResp = await fetch('/csrf-token');
11 | const { csrfToken } = await csrfResp.json();
12 |
13 | // add token to headers
14 | const headers = new Headers(init.headers);
15 | headers.append('X-CSRF-Token', csrfToken);
16 |
17 | // construct init object with the updated headers
18 | const modifiedInit = { ...init, headers };
19 |
20 | // call native fetch function with the original input and the modified init object
21 | return fetch(input, modifiedInit);
22 | }
23 |
24 | Sentry.init({
25 | dsn: "https://REPLACEME.ingest.sentry.io/REPLACEME",
26 |
27 | // Adjust this value in production, or use tracesSampler for greater control
28 | tracesSampleRate: 1,
29 |
30 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
31 | debug: false,
32 |
33 | replaysOnErrorSampleRate: 1.0,
34 |
35 | // This sets the sample rate to be 10%. You may want this to be 100% while
36 | // in development and sample at a lower rate in production
37 | replaysSessionSampleRate: 0.1,
38 |
39 | // You can remove this option if you're not planning to use the Sentry Session Replay feature:
40 | integrations: [
41 | new Sentry.Replay({
42 | // Additional Replay configuration goes in here, for example:
43 | maskAllText: true,
44 | blockAllMedia: true,
45 | }),
46 | ],
47 |
48 | transport: (options: BaseTransportOptions) => {
49 | return Sentry.makeFetchTransport(options, fetchWithCSRFHeader);
50 | }
51 | });
52 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/sentry.edge.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
2 | // The config you add here will be used whenever one of the edge features is loaded.
3 | // Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
4 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
5 |
6 | import * as Sentry from "@sentry/nextjs";
7 |
8 | Sentry.init({
9 | dsn: "https://REPLACEME.ingest.sentry.io/REPLACEME",
10 |
11 | // Adjust this value in production, or use tracesSampler for greater control
12 | tracesSampleRate: 1,
13 |
14 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
15 | debug: false,
16 | });
17 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/sentry.server.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry on the server.
2 | // The config you add here will be used whenever the server handles a request.
3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
4 |
5 | import * as Sentry from "@sentry/nextjs";
6 |
7 | Sentry.init({
8 | dsn: "https://REPLACEME.ingest.sentry.io/REPLACEME",
9 |
10 | // Adjust this value in production, or use tracesSampler for greater control
11 | tracesSampleRate: 1,
12 |
13 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
14 | debug: false,
15 | });
16 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 |
3 | const config: Config = {
4 | content: [
5 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13 | 'gradient-conic':
14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
15 | },
16 | },
17 | },
18 | plugins: [],
19 | }
20 | export default config
21 |
--------------------------------------------------------------------------------
/examples/next15-approuter-sentry/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-form-submission/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-form-submission/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
27 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-form-submission/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 |
3 | export const metadata: Metadata = {
4 | title: 'edge-csrf examples',
5 | };
6 |
7 | export default function Layout({
8 | children,
9 | }: {
10 | children: React.ReactNode;
11 | }) {
12 | return (
13 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-form-submission/middleware.ts:
--------------------------------------------------------------------------------
1 | import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs';
2 | import { NextResponse } from 'next/server';
3 | import type { NextRequest } from 'next/server';
4 |
5 | const csrfProtect = createCsrfProtect({
6 | cookie: {
7 | secure: process.env.NODE_ENV === 'production',
8 | },
9 | });
10 |
11 | export async function middleware(request: NextRequest) {
12 | const response = NextResponse.next();
13 |
14 | // csrf protection
15 | try {
16 | await csrfProtect(request, response);
17 | } catch (err) {
18 | if (err instanceof CsrfError) return new NextResponse('invalid csrf token', { status: 403 });
19 | throw err;
20 | }
21 |
22 | // return token (for use in static-optimized-example)
23 | if (request.nextUrl.pathname === '/csrf-token') {
24 | return NextResponse.json({ csrfToken: response.headers.get('X-CSRF-Token') || 'missing' });
25 | }
26 |
27 | return response;
28 | }
29 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-form-submission/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-form-submission/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig;
7 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-form-submission/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^15.0.0",
18 | "next": "^15.0.0",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-form-submission/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next15-approuter-server-action-form-submission/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-form-submission/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-form-submission/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: blue;
11 | text-decoration: underline;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-form-submission/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | ".next/types/**/*.ts"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-non-form-submission/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-non-form-submission/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
27 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-non-form-submission/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next';
2 |
3 | export const metadata: Metadata = {
4 | title: 'edge-csrf examples',
5 | };
6 |
7 | export default function Layout({
8 | children,
9 | }: {
10 | children: React.ReactNode;
11 | }) {
12 | return (
13 |
14 |
15 | {children}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-non-form-submission/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { example1, example2 } from '../lib/actions';
4 |
5 | export default function Page() {
6 | const handleClick1 = async () => {
7 | const csrfResp = await fetch('/csrf-token');
8 | const { csrfToken } = await csrfResp.json();
9 |
10 | const data = {
11 | key1: 'val1',
12 | key2: 'val2',
13 | };
14 |
15 | // use token as first argument to server action
16 | await example1(csrfToken, data);
17 | };
18 |
19 | const handleClick2 = async () => {
20 | const csrfResp = await fetch('/csrf-token');
21 | const { csrfToken } = await csrfResp.json();
22 |
23 | // add token to FormData instance
24 | const data = new FormData();
25 | data.set('csrf_token', csrfToken);
26 | data.set('key1', 'val1');
27 | data.set('key2', 'val2');
28 |
29 | await example2(data);
30 | };
31 |
32 | return (
33 | <>
34 | Server Action Non-Form Submission Examples:
35 | NOTE: Look at browser network logs and server console for submission feedback
36 | Example with object argument:
37 |
38 | Example with FormData argument:
39 |
40 | >
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-non-form-submission/lib/actions.ts:
--------------------------------------------------------------------------------
1 | 'use server';
2 |
3 | export async function example1(csrfToken: string, data: { key1: string; key2: string; }) {
4 | // eslint-disable-next-line no-console
5 | console.log(data);
6 | }
7 |
8 | export async function example2(data: FormData) {
9 | // eslint-disable-next-line no-console
10 | console.log(data);
11 | }
12 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-non-form-submission/middleware.ts:
--------------------------------------------------------------------------------
1 | import { CsrfError, createCsrfProtect } from '@edge-csrf/nextjs';
2 | import { NextResponse } from 'next/server';
3 | import type { NextRequest } from 'next/server';
4 |
5 | const csrfProtect = createCsrfProtect({
6 | cookie: {
7 | secure: process.env.NODE_ENV === 'production',
8 | },
9 | });
10 |
11 | export async function middleware(request: NextRequest) {
12 | const response = NextResponse.next();
13 |
14 | // csrf protection
15 | try {
16 | await csrfProtect(request, response);
17 | } catch (err) {
18 | if (err instanceof CsrfError) return new NextResponse('invalid csrf token', { status: 403 });
19 | throw err;
20 | }
21 |
22 | // return token (for use in static-optimized-example)
23 | if (request.nextUrl.pathname === '/csrf-token') {
24 | return NextResponse.json({ csrfToken: response.headers.get('X-CSRF-Token') || 'missing' });
25 | }
26 |
27 | return response;
28 | }
29 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-non-form-submission/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-non-form-submission/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig;
7 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-non-form-submission/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^15.0.0",
18 | "next": "^15.0.0",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-non-form-submission/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next15-approuter-server-action-non-form-submission/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-non-form-submission/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-non-form-submission/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: blue;
11 | text-decoration: underline;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next15-approuter-server-action-non-form-submission/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "incremental": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ]
26 | },
27 | "include": [
28 | "next-env.d.ts",
29 | "**/*.ts",
30 | "**/*.tsx",
31 | ".next/types/**/*.ts"
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/examples/next15-pagesrouter-html-submission/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals",
3 | "rules": {
4 | "react/function-component-definition": "off",
5 | "react/jsx-props-no-spreading": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/next15-pagesrouter-html-submission/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the development server:
16 |
17 | ```bash
18 | npm run dev
19 | # or
20 | pnpm dev
21 | # or
22 | yarn dev
23 | ```
24 |
25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
26 |
--------------------------------------------------------------------------------
/examples/next15-pagesrouter-html-submission/middleware.ts:
--------------------------------------------------------------------------------
1 | import { createCsrfMiddleware } from '@edge-csrf/nextjs';
2 |
3 | // initalize csrf protection middleware
4 | const csrfMiddleware = createCsrfMiddleware({
5 | cookie: {
6 | secure: process.env.NODE_ENV === 'production',
7 | },
8 | });
9 |
10 | export const middleware = csrfMiddleware;
11 |
--------------------------------------------------------------------------------
/examples/next15-pagesrouter-html-submission/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
6 |
--------------------------------------------------------------------------------
/examples/next15-pagesrouter-html-submission/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig;
7 |
--------------------------------------------------------------------------------
/examples/next15-pagesrouter-html-submission/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@edge-csrf/nextjs": "^2.0.0",
13 | "@types/node": "^20.8.9",
14 | "@types/react": "^18.2.33",
15 | "@types/react-dom": "^18.2.14",
16 | "eslint": "^8.52.0",
17 | "eslint-config-next": "^15.0.0",
18 | "next": "^15.0.0",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "typescript": "^5.2.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next15-pagesrouter-html-submission/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import '../styles/globals.css';
2 | import type { AppProps } from 'next/app';
3 |
4 | export default function App({ Component, pageProps }: AppProps) {
5 | return ;
6 | }
7 |
--------------------------------------------------------------------------------
/examples/next15-pagesrouter-html-submission/pages/api/form-handler.ts:
--------------------------------------------------------------------------------
1 | import type { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | type Data = {
4 | status: string;
5 | };
6 |
7 | export default function handler(req: NextApiRequest, res: NextApiResponse) {
8 | res.status(200).json({ status: 'success' });
9 | }
10 |
--------------------------------------------------------------------------------
/examples/next15-pagesrouter-html-submission/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import type { NextPage, GetServerSideProps } from 'next';
2 |
3 | type Props = {
4 | csrfToken: string;
5 | };
6 |
7 | export const getServerSideProps: GetServerSideProps = async ({ res }) => {
8 | const csrfToken = res.getHeader('X-CSRF-Token') || 'missing';
9 | return { props: { csrfToken } };
10 | };
11 |
12 | const Home: NextPage = ({ csrfToken }) => (
13 | <>
14 |
15 | CSRF token value:
16 | {csrfToken}
17 |
18 | HTML Form Submission Example
19 |
24 |
25 |
31 |
32 |
38 | >
39 | );
40 |
41 | export default Home;
42 |
--------------------------------------------------------------------------------
/examples/next15-pagesrouter-html-submission/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/next15-pagesrouter-html-submission/public/favicon.ico
--------------------------------------------------------------------------------
/examples/next15-pagesrouter-html-submission/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/next15-pagesrouter-html-submission/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | @media (prefers-color-scheme: dark) {
19 | html {
20 | color-scheme: dark;
21 | }
22 | body {
23 | color: white;
24 | background: black;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/next15-pagesrouter-html-submission/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "bundler",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/examples/node-http/README.md:
--------------------------------------------------------------------------------
1 | This is an [Express](https://expressjs.com) example app.
2 |
3 | ## Getting Started
4 |
5 | First, install dependencies:
6 |
7 | ```bash
8 | npm install
9 | # or
10 | pnpm install
11 | # or
12 | yarn install
13 | ```
14 |
15 | Next, run the server:
16 |
17 | ```bash
18 | node server.js
19 | ```
20 |
21 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
22 |
--------------------------------------------------------------------------------
/examples/node-http/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edge-csrf-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "type": "module",
6 | "dependencies": {
7 | "@edge-csrf/node-http": "^2.2.0"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | vite.config.js.timestamp-*
10 | vite.config.ts.timestamp-*
11 |
12 | # wrangler files
13 | .wrangler
14 | .dev.vars
15 |
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/README.md:
--------------------------------------------------------------------------------
1 | # create-svelte
2 |
3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
4 |
5 | ## Creating a project
6 |
7 | If you're seeing this, you've probably already done this step. Congrats!
8 |
9 | ```bash
10 | # create a new project in the current directory
11 | npm create svelte@latest
12 |
13 | # create a new project in my-app
14 | npm create svelte@latest my-app
15 | ```
16 |
17 | ## Developing
18 |
19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
20 |
21 | ```bash
22 | npm run dev
23 |
24 | # or start the server and open the app in a new browser tab
25 | npm run dev -- --open
26 | ```
27 |
28 | ## Building
29 |
30 | To create a production version of your app:
31 |
32 | ```bash
33 | npm run build
34 | ```
35 |
36 | You can preview the production build with `npm run preview`.
37 |
38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
39 |
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sveltekit-cloudflare",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite dev",
7 | "build": "vite build",
8 | "preview": "pnpm run build && wrangler pages dev .svelte-kit/cloudflare",
9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
11 | "deploy": "pnpm run build && wrangler pages deploy .svelte-kit/cloudflare",
12 | "build-cf-types": "wrangler types && mv worker-configuration.d.ts src/"
13 | },
14 | "devDependencies": {
15 | "@cloudflare/workers-types": "^4.20240405.0",
16 | "@sveltejs/adapter-auto": "^3.0.0",
17 | "@sveltejs/adapter-cloudflare": "^4.3.0",
18 | "@sveltejs/kit": "^2.0.0",
19 | "@sveltejs/vite-plugin-svelte": "^3.0.0",
20 | "svelte": "^4.2.7",
21 | "svelte-check": "^3.6.0",
22 | "tslib": "^2.4.1",
23 | "typescript": "^5.0.0",
24 | "vite": "^5.0.3",
25 | "wrangler": "^3.50.0"
26 | },
27 | "type": "module",
28 | "dependencies": {
29 | "@edge-csrf/sveltekit": "^2.0.0"
30 | }
31 | }
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/src/app.d.ts:
--------------------------------------------------------------------------------
1 | import type { CsrfLocals } from '@edge-csrf/sveltekit';
2 |
3 | // See https://kit.svelte.dev/docs/types#app
4 | // for information about these interfaces
5 | declare global {
6 | namespace App {
7 | interface Locals extends CsrfLocals {}
8 |
9 | interface Platform {
10 | env: Env
11 | cf: CfProperties
12 | ctx: ExecutionContext
13 | }
14 | }
15 | }
16 |
17 | export {};
18 |
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/src/hooks.server.ts:
--------------------------------------------------------------------------------
1 | import { createCsrfHandle } from '@edge-csrf/sveltekit';
2 |
3 | // initalize csrf protection handle
4 | const csrfHandle = createCsrfHandle({
5 | cookie: {
6 | secure: process.env.NODE_ENV === 'production',
7 | },
8 | });
9 |
10 | export const handle = csrfHandle;
11 |
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | // place files you want to import through the `$lib` alias in this folder.
2 |
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/src/routes/+page.server.ts:
--------------------------------------------------------------------------------
1 | export async function load({ locals }) {
2 | return {
3 | csrfToken: locals.csrfToken,
4 | };
5 | }
6 |
7 | export const actions = {
8 | default: async () => {
9 | return { success: true };
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 | {#if form?.success}
8 | success
9 | {:else}
10 | CSRF token value: {data.csrfToken}
11 | HTML Form Submission Example:
12 |
17 |
18 |
24 |
25 |
31 | {/if}
32 |
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/sveltekit-cloudflare/static/favicon.ico
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/sveltekit-cloudflare/static/favicon.png
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from "@sveltejs/adapter-cloudflare";
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors
7 | // for more information about preprocessors
8 | preprocess: vitePreprocess(),
9 |
10 | kit: {
11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
12 | // If your environment is not supported or you settled on a specific environment, switch out the adapter.
13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters.
14 | adapter: adapter()
15 | }
16 | };
17 |
18 | export default config;
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "moduleResolution": "bundler",
13 | "types": [
14 | "@cloudflare/workers-types/2023-07-01"
15 | ]
16 | }
17 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
18 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
19 | //
20 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
21 | // from the referenced tsconfig.json - TypeScript does not merge them in
22 | }
23 |
--------------------------------------------------------------------------------
/examples/sveltekit-cloudflare/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { defineConfig } from 'vite';
3 |
4 | export default defineConfig({
5 | plugins: [sveltekit()]
6 | });
7 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | vite.config.js.timestamp-*
10 | vite.config.ts.timestamp-*
11 | .vercel
12 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/README.md:
--------------------------------------------------------------------------------
1 | # create-svelte
2 |
3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
4 |
5 | ## Creating a project
6 |
7 | If you're seeing this, you've probably already done this step. Congrats!
8 |
9 | ```bash
10 | # create a new project in the current directory
11 | npm create svelte@latest
12 |
13 | # create a new project in my-app
14 | npm create svelte@latest my-app
15 | ```
16 |
17 | ## Developing
18 |
19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
20 |
21 | ```bash
22 | npm run dev
23 |
24 | # or start the server and open the app in a new browser tab
25 | npm run dev -- --open
26 | ```
27 |
28 | ## Building
29 |
30 | To create a production version of your app:
31 |
32 | ```bash
33 | npm run build
34 | ```
35 |
36 | You can preview the production build with `npm run preview`.
37 |
38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
39 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import ts from 'typescript-eslint';
3 | import svelte from 'eslint-plugin-svelte';
4 | import globals from 'globals';
5 |
6 | /** @type {import('eslint').Linter.FlatConfig[]} */
7 | export default [
8 | js.configs.recommended,
9 | ...ts.configs.recommended,
10 | ...svelte.configs['flat/recommended'],
11 | {
12 | languageOptions: {
13 | globals: {
14 | ...globals.browser,
15 | ...globals.node
16 | }
17 | }
18 | },
19 | {
20 | files: ['**/*.svelte'],
21 | languageOptions: {
22 | parserOptions: {
23 | parser: ts.parser
24 | }
25 | }
26 | },
27 | {
28 | ignores: ['build/', '.svelte-kit/', 'package/']
29 | }
30 | ];
31 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sveltekit-vercel",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite dev",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
11 | "lint": "eslint ."
12 | },
13 | "devDependencies": {
14 | "@sveltejs/adapter-auto": "^3.0.0",
15 | "@sveltejs/adapter-vercel": "^5.2.0",
16 | "@sveltejs/kit": "^2.0.0",
17 | "@sveltejs/vite-plugin-svelte": "^3.0.0",
18 | "@types/eslint": "^8.56.7",
19 | "@types/node": "^20.12.7",
20 | "eslint": "^8.57.0",
21 | "eslint-plugin-svelte": "^2.36.0",
22 | "globals": "^15.0.0",
23 | "svelte": "^4.2.7",
24 | "svelte-check": "^3.6.0",
25 | "tslib": "^2.4.1",
26 | "typescript": "^5.0.0",
27 | "typescript-eslint": "^7.5.0",
28 | "vercel": "^34.0.0",
29 | "vite": "^5.0.3"
30 | },
31 | "type": "module",
32 | "dependencies": {
33 | "@edge-csrf/sveltekit": "^2.0.0"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/src/app.d.ts:
--------------------------------------------------------------------------------
1 | import type { CsrfLocals } from '@edge-csrf/sveltekit';
2 |
3 | // See https://kit.svelte.dev/docs/types#app
4 | // for information about these interfaces
5 | declare global {
6 | namespace App {
7 | // interface Error {}
8 | interface Locals extends CsrfLocals {}
9 | // interface PageData {}
10 | // interface PageState {}
11 | // interface Platform {}
12 | }
13 | }
14 |
15 | export {};
16 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/src/hooks.server.ts:
--------------------------------------------------------------------------------
1 | import { createCsrfHandle } from '@edge-csrf/sveltekit';
2 |
3 | // initalize csrf protection handle
4 | const csrfHandle = createCsrfHandle({
5 | cookie: {
6 | secure: process.env.NODE_ENV === 'production',
7 | },
8 | });
9 |
10 | export const handle = csrfHandle;
11 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | // place files you want to import through the `$lib` alias in this folder.
2 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/src/routes/+page.server.ts:
--------------------------------------------------------------------------------
1 | export async function load({ locals }) {
2 | return {
3 | csrfToken: locals.csrfToken,
4 | };
5 | }
6 |
7 | export const actions = {
8 | default: async () => {
9 | return { success: true };
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 | {#if form?.success}
8 | success
9 | {:else}
10 | CSRF token value: {data.csrfToken}
11 | HTML Form Submission Example:
12 |
17 |
18 |
24 |
25 |
31 | {/if}
32 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/sveltekit-vercel/static/favicon.ico
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amorey/edge-csrf/4f265c6e05fad2d5108cdd7fc1dc79468a5f0352/examples/sveltekit-vercel/static/favicon.png
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-vercel';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors
7 | // for more information about preprocessors
8 | preprocess: vitePreprocess(),
9 |
10 | kit: {
11 | adapter: adapter({
12 | runtime: 'edge',
13 | })
14 | }
15 | };
16 |
17 | export default config;
18 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "moduleResolution": "bundler"
13 | }
14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
16 | //
17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
18 | // from the referenced tsconfig.json - TypeScript does not merge them in
19 | }
20 |
--------------------------------------------------------------------------------
/examples/sveltekit-vercel/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { defineConfig } from 'vite';
3 |
4 | export default defineConfig({
5 | plugins: [sveltekit()]
6 | });
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "pnpm -r build",
5 | "clean": "pnpm -r exec rm -rf node_modules",
6 | "lint": "pnpm -r lint",
7 | "test": "pnpm -r test run",
8 | "test-all": "pnpm -r test-all"
9 | },
10 | "devDependencies": {
11 | "@edge-runtime/vm": "^3.2.0",
12 | "@types/jest": "^29.5.14",
13 | "@types/node": "^20.17.1",
14 | "@typescript-eslint/eslint-plugin": "^7.18.0",
15 | "@typescript-eslint/parser": "^7.18.0",
16 | "eslint": "^8.57.1",
17 | "eslint-config-airbnb-base": "^15.0.0",
18 | "eslint-config-airbnb-typescript": "^18.0.0",
19 | "tsup": "^8.3.4",
20 | "typescript": "^5.6.3",
21 | "vite": "^5.4.10",
22 | "vitest": "^1.6.0",
23 | "vitest-environment-miniflare": "^2.14.4"
24 | },
25 | "packageManager": "pnpm@9.12.2+sha512.22721b3a11f81661ae1ec68ce1a7b879425a1ca5b991c975b074ac220b187ce56c708fe5db69f4c962c989452eee76c82877f4ee80f474cebd61ee13461b6228"
26 | }
27 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # Core API
2 |
3 | This is the documentation for Edge-CSRF's low-level API.
4 |
5 | ## Install
6 |
7 | ```console
8 | npm install @edge-csrf/core
9 | # or
10 | pnpm add @edge-csrf/core
11 | # or
12 | yarn add @edge-csrf/core
13 | ```
14 |
15 | ## Documentation
16 |
17 | The following methods are named exports in the the `@edge-csrf/core` module:
18 |
19 | ```
20 | createSecret(length) - Create new secret (cryptographically secure)
21 |
22 | * @param {int} length - Byte length of secret
23 | * @returns {Uint8Array} - The secret
24 |
25 | createToken(secret, saltByteLength) - Create new CSRF token (cryptographically insecure
26 | salt hashed with secret)
27 |
28 | * @param {Uint8Array} secret - The secret
29 | * @param {int} saltByteLength - Salt length in number of bytes
30 | * @returns {Promise} - A promise returning the token in Uint8Array format
31 |
32 | getTokenString(request) - Get the CSRF token from the request
33 |
34 | * @param {Request} request - The request object
35 | * @returns {Promise} - A promise returning the token in string format
36 |
37 | verifyToken(token, secret) - Verify the CSRF token and secret obtained from the request
38 |
39 | * @param {Uint8Array} token - The CSRF token
40 | * @param {Uint8Array} secret - The CSRF secret
41 | * @returns {Promise} - A promise returning result of verification
42 |
43 | utoa(input) - Encode Uint8Array as base64 string
44 |
45 | * @param {Uint8Array} input - The data to be converted from Uint8Array to base64
46 | * @returns {string} The base64 encoded string
47 |
48 | atou(input) - Decode base64 string into Uint8Array
49 |
50 | * @param {string} input - The data to be converted from base64 to Uint8Array
51 | * @returns {Uint8Array} - The Uint8Array representing the input string
52 | ```
53 |
54 | __Note__: If you're using these methods you're probably working on a custom framework integration. If so, please consider contributing it back to this project!
55 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@edge-csrf/core",
3 | "version": "0.0.0",
4 | "description": "Edge-CSRF core API",
5 | "author": "Andres Morey",
6 | "license": "MIT",
7 | "repository": "kubetail-org/edge-csrf",
8 | "type": "module",
9 | "sideEffects": false,
10 | "main": "dist/index.cjs",
11 | "module": "dist/index.js",
12 | "types": "dist/index.d.ts",
13 | "files": [
14 | "/dist"
15 | ],
16 | "scripts": {
17 | "build": "tsc && vite build",
18 | "lint": "eslint \"./src/**/*.ts{,x}\""
19 | },
20 | "keywords": [
21 | "csrf",
22 | "tokens",
23 | "edge"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | // util exports
2 | export {
3 | createSecret,
4 | createToken,
5 | getTokenString,
6 | verifyToken,
7 | utoa,
8 | atou,
9 | } from '@shared/util';
10 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": [
4 | "./src/**/*.ts"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/packages/core/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { resolve } from 'path';
3 | import { defineConfig } from 'vite';
4 |
5 | import dts from '../../shared/src/vite-plugin-dts';
6 |
7 | export default defineConfig({
8 | resolve: {
9 | alias: {
10 | '@shared': resolve(__dirname, '../../shared/src'),
11 | },
12 | },
13 | plugins: [dts()],
14 | build: {
15 | lib: {
16 | entry: [
17 | resolve(__dirname, 'src/index.ts'),
18 | ],
19 | name: '@edge-csrf/core',
20 | formats: ['es', 'cjs'],
21 | },
22 | rollupOptions: {
23 | external: [],
24 | },
25 | },
26 | test: {
27 | environment: 'edge-runtime',
28 | globals: true,
29 | },
30 | });
31 |
--------------------------------------------------------------------------------
/packages/express/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@edge-csrf/express",
3 | "version": "0.0.0",
4 | "description": "Edge-CSRF Express integration library",
5 | "author": "Andres Morey",
6 | "license": "MIT",
7 | "repository": "kubetail-org/edge-csrf",
8 | "type": "module",
9 | "sideEffects": false,
10 | "main": "dist/index.cjs",
11 | "module": "dist/index.js",
12 | "types": "dist/index.d.ts",
13 | "files": [
14 | "/dist"
15 | ],
16 | "scripts": {
17 | "build": "tsc && vite build",
18 | "lint": "eslint \"./src/**/*.ts{,x}\"",
19 | "test": "vitest",
20 | "test-all": "vitest run --environment node && vitest run --environment edge-runtime && vitest run --environment miniflare"
21 | },
22 | "keywords": [
23 | "csrf",
24 | "tokens",
25 | "edge",
26 | "express"
27 | ],
28 | "dependencies": {
29 | "cookie": "^0.7.0"
30 | },
31 | "devDependencies": {
32 | "@types/cookie": "^0.6.0",
33 | "@types/express": "^4.17.21",
34 | "@types/supertest": "^6.0.2",
35 | "express": "^4.19.2",
36 | "supertest": "^7.0.0"
37 | },
38 | "peerDependencies": {
39 | "express": "^4.0.0"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/express/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": [
4 | "./src/**/*.ts"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/packages/express/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { resolve } from 'path';
3 | import { defineConfig } from 'vite';
4 |
5 | import dts from '../../shared/src/vite-plugin-dts';
6 |
7 | export default defineConfig({
8 | resolve: {
9 | alias: {
10 | '@shared': resolve(__dirname, '../../shared/src'),
11 | },
12 | },
13 | plugins: [dts()],
14 | build: {
15 | lib: {
16 | entry: [
17 | resolve(__dirname, 'src/index.ts'),
18 | ],
19 | name: '@edge-csrf/express',
20 | formats: ['es', 'cjs'],
21 | },
22 | rollupOptions: {
23 | external: ['express'],
24 | },
25 | },
26 | test: {
27 | environment: 'edge-runtime',
28 | globals: true,
29 | },
30 | });
31 |
--------------------------------------------------------------------------------
/packages/nextjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@edge-csrf/nextjs",
3 | "version": "0.0.0",
4 | "description": "Edge-CSRF Next.js integration library",
5 | "author": "Andres Morey",
6 | "license": "MIT",
7 | "repository": "kubetail-org/edge-csrf",
8 | "type": "module",
9 | "sideEffects": false,
10 | "main": "dist/index.cjs",
11 | "module": "dist/index.js",
12 | "types": "dist/index.d.ts",
13 | "files": [
14 | "/dist"
15 | ],
16 | "scripts": {
17 | "build": "tsc && vite build",
18 | "lint": "eslint \"./src/**/*.ts{,x}\"",
19 | "test": "vitest",
20 | "test-all": "vitest run --environment node && vitest run --environment edge-runtime && vitest run --environment miniflare"
21 | },
22 | "devDependencies": {
23 | "next": "^15.0.0"
24 | },
25 | "peerDependencies": {
26 | "next": "^13.0.0 || ^14.0.0 || ^15.0.0"
27 | },
28 | "keywords": [
29 | "csrf",
30 | "tokens",
31 | "edge",
32 | "nextjs",
33 | "next"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/packages/nextjs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": [
4 | "./src/**/*.ts"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/packages/nextjs/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { resolve } from 'path';
3 | import { defineConfig } from 'vite';
4 |
5 | import dts from '../../shared/src/vite-plugin-dts';
6 |
7 | export default defineConfig({
8 | resolve: {
9 | alias: {
10 | '@shared': resolve(__dirname, '../../shared/src'),
11 | },
12 | },
13 | plugins: [dts()],
14 | build: {
15 | lib: {
16 | entry: [
17 | resolve(__dirname, 'src/index.ts'),
18 | ],
19 | name: '@edge-csrf/nextjs',
20 | formats: ['es', 'cjs'],
21 | },
22 | rollupOptions: {
23 | external: ['next/server'],
24 | },
25 | },
26 | test: {
27 | environment: 'edge-runtime',
28 | globals: true,
29 | },
30 | });
31 |
--------------------------------------------------------------------------------
/packages/node-http/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@edge-csrf/node-http",
3 | "version": "0.0.0",
4 | "description": "Edge-CSRF integration library for node's http module",
5 | "author": "Andres Morey",
6 | "license": "MIT",
7 | "repository": "kubetail-org/edge-csrf",
8 | "type": "module",
9 | "sideEffects": false,
10 | "main": "dist/index.cjs",
11 | "module": "dist/index.js",
12 | "types": "dist/index.d.ts",
13 | "files": [
14 | "/dist"
15 | ],
16 | "scripts": {
17 | "build": "tsc && vite build",
18 | "lint": "eslint \"./src/**/*.ts{,x}\"",
19 | "test": "vitest",
20 | "test-all": "vitest run --environment node && vitest run --environment edge-runtime && vitest run --environment miniflare"
21 | },
22 | "keywords": [
23 | "csrf",
24 | "tokens",
25 | "edge",
26 | "node",
27 | "createServer"
28 | ],
29 | "dependencies": {
30 | "cookie": "^0.7.0"
31 | },
32 | "devDependencies": {
33 | "@types/cookie": "^0.6.0",
34 | "@types/supertest": "^6.0.2",
35 | "supertest": "^7.0.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/node-http/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": [
4 | "./src/**/*.ts"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/packages/node-http/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { resolve } from 'path';
3 | import { defineConfig } from 'vite';
4 |
5 | import dts from '../../shared/src/vite-plugin-dts';
6 |
7 | export default defineConfig({
8 | resolve: {
9 | alias: {
10 | '@shared': resolve(__dirname, '../../shared/src'),
11 | },
12 | },
13 | plugins: [dts()],
14 | build: {
15 | lib: {
16 | entry: [
17 | resolve(__dirname, 'src/index.ts'),
18 | ],
19 | name: '@edge-csrf/node-http',
20 | formats: ['es', 'cjs'],
21 | }
22 | },
23 | test: {
24 | environment: 'edge-runtime',
25 | globals: true,
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/packages/sveltekit/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@edge-csrf/sveltekit",
3 | "version": "0.0.0",
4 | "description": "Edge-CSRF SvelteKit integration library",
5 | "author": "Andres Morey",
6 | "license": "MIT",
7 | "repository": "kubetail-org/edge-csrf",
8 | "type": "module",
9 | "sideEffects": false,
10 | "main": "dist/index.cjs",
11 | "module": "dist/index.js",
12 | "types": "dist/index.d.ts",
13 | "files": [
14 | "/dist"
15 | ],
16 | "scripts": {
17 | "build": "tsc && vite build",
18 | "lint": "eslint \"./src/**/*.ts{,x}\"",
19 | "test": "vitest",
20 | "test-all": "vitest run --environment node && vitest run --environment edge-runtime && vitest run --environment miniflare"
21 | },
22 | "devDependencies": {
23 | "@sveltejs/kit": "^2.5.6"
24 | },
25 | "keywords": [
26 | "csrf",
27 | "tokens",
28 | "edge",
29 | "sveltekit",
30 | "svelte"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/packages/sveltekit/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "include": [
4 | "./src/**/*.ts"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/packages/sveltekit/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { resolve } from 'path';
3 | import { defineConfig } from 'vite';
4 |
5 | import dts from '../../shared/src/vite-plugin-dts';
6 |
7 | export default defineConfig({
8 | resolve: {
9 | alias: {
10 | '@shared': resolve(__dirname, '../../shared/src'),
11 | },
12 | },
13 | plugins: [dts()],
14 | build: {
15 | lib: {
16 | entry: [
17 | resolve(__dirname, 'src/index.ts'),
18 | ],
19 | name: '@edge-csrf/sveltekit',
20 | formats: ['es', 'cjs'],
21 | },
22 | rollupOptions: {
23 | external: [],
24 | },
25 | },
26 | test: {
27 | environment: 'edge-runtime',
28 | globals: true,
29 | },
30 | });
31 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "packages/**"
3 | - "shared"
4 | - "!examples/**"
5 |
--------------------------------------------------------------------------------
/set-version.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Check if a version number was provided as an argument
4 | if [ "$#" -ne 1 ]; then
5 | echo "Usage: $0 "
6 | exit 1
7 | fi
8 |
9 | # Assign the new version number from command line arguments
10 | NEW_VERSION=$1
11 |
12 | # Function to update the version in a package.json file
13 | update_version() {
14 | local file=$1
15 | echo "Updating version in $file to $NEW_VERSION"
16 | # Portable handling of in-place editing with sed
17 | sed -i.bak -E "s/\"version\": \".*\"/\"version\": \"$NEW_VERSION\"/" "$file" && rm "$file.bak"
18 | }
19 |
20 | export -f update_version
21 | export NEW_VERSION
22 |
23 | # Find all package.json files in the packages directory, excluding node_modules directories
24 | find ./packages -name "package.json" -not -path "*/node_modules/*" -exec bash -c 'update_version "$0"' {} \;
25 |
26 | echo "Version update completed."
27 |
--------------------------------------------------------------------------------
/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@edge-csrf/shared",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "lint": "eslint \"./src/**/*.ts{,x}\"",
7 | "test": "vitest",
8 | "test-all": "vitest run --environment node && vitest run --environment edge-runtime && vitest run --environment miniflare"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/shared/src/vite-plugin-dts.ts:
--------------------------------------------------------------------------------
1 | import { build } from 'tsup';
2 | import type { Plugin } from 'vite';
3 |
4 | export default (): Plugin => ({
5 | name: 'dts',
6 | apply: 'build',
7 | closeBundle: async () => {
8 | await build({
9 | entry: ['src/index.ts'],
10 | outDir: 'dist',
11 | format: 'esm',
12 | dts: {
13 | only: true,
14 | },
15 | });
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/shared/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "include": [
4 | "./src/**/*.ts"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/shared/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { resolve } from 'path';
3 | import { defineConfig } from 'vite';
4 |
5 | export default defineConfig({
6 | resolve: {
7 | alias: {
8 | '@shared': resolve(__dirname, '../../shared/src'),
9 | },
10 | },
11 | test: {
12 | environment: 'edge-runtime',
13 | globals: true,
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "ESNext",
4 | "target": "ES2020",
5 | "lib": [
6 | "DOM",
7 | "DOM.Iterable",
8 | "ESNext"
9 | ],
10 | "declaration": true,
11 | "esModuleInterop": true,
12 | "skipLibCheck": true,
13 |
14 | /* bundler mode */
15 | "moduleResolution": "node",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "noEmit": true,
19 |
20 | /* linting */
21 | "strict": true,
22 | "forceConsistentCasingInFileNames": true,
23 | "noFallthroughCasesInSwitch": true,
24 | "noImplicitAny": true,
25 | "noUnusedLocals": true,
26 | "noUnusedParameters": true,
27 |
28 | /* import paths */
29 | "baseUrl": "./",
30 | "paths": {
31 | "@shared/*": [
32 | "shared/src/*"
33 | ]
34 | },
35 |
36 | /* misc */
37 | "types": [
38 | "vitest/globals"
39 | ]
40 | },
41 | "exclude": [
42 | "node_modules",
43 | "dist"
44 | ]
45 | }
--------------------------------------------------------------------------------