Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Node-HTTP implementation library #46

Merged
merged 6 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 93 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ We hope you enjoy using this software. Contributions and suggestions are welcome
## Features

- Runs on both node and edge runtimes
- Includes integrations for [Next.js](packages/nextjs), [Sveltekit](packages/sveltekit) and [Express](packages/express)
- Includes integrations for [Next.js](packages/nextjs), [Sveltekit](packages/sveltekit), [Express](packages/express) and [Node-HTTP](packages/node-http)
- Includes a low-level API for custom integrations ([see here](packages/core))
- Handles form-urlencoded, multipart/form-data or json-encoded HTTP request bodies
- Gets token from HTTP request header or from request body
Expand All @@ -21,6 +21,7 @@ We hope you enjoy using this software. Contributions and suggestions are welcome
* [Next.js](packages/nextjs)
* [SvelteKit](packages/sveltekit)
* [Express](packages/express)
* [Node-HTTP](packages/node-http)
* [Core API](packages/core)

## Quickstart (Next.js)
Expand Down Expand Up @@ -198,38 +199,18 @@ const csrfMiddleware = createCsrfMiddleware({
const app = express();
const port = 3000;

// add body parsing middleware
app.use(express.urlencoded({ extended: false }));

// add csrf middleware
app.use(csrfMiddleware);

// define handlers
app.get('/', (_, res) => {
res.status(200).json({ success: true });
});

// start server
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
});
```

Now, all HTTP submission requests (e.g. POST, PUT, DELETE, PATCH) will be rejected if they do not include a valid CSRF token. To add the CSRF token to your forms, you can fetch it from the `X-CSRF-Token` HTTP response header server-side or client-side. For example:

```javascript
// app.js
...

// define handlers
app.get('/my-form', (req, res) => {
app.get('/', (req, res) => {
const csrfToken = res.getHeader('X-CSRF-Token') || 'missing';
res.send(`
<!doctype html>
<html>
<body>
<p>CSRF token value: ${csrfToken}</p>
<form action="/my-form" method="post">
<form action="/" method="post">
<legend>Form with CSRF (should succeed):</legend>
<input type="hidden" name="csrf_token" value="${csrfToken}" />
<input type="text" name="input1" />
Expand All @@ -240,13 +221,100 @@ app.get('/my-form', (req, res) => {
`);
});

app.post('/my-form', (req, res) => {
app.post('/', (req, res) => {
res.send('success');
});

...
// start server
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
});
```

With the middleware installed, all HTTP submission requests (e.g. POST, PUT, DELETE, PATCH) will be rejected if they do not include a valid CSRF token.

## Quickstart (Node-HTTP)

First, install Edge-CSRF's Node-HTTP integration library:

```console
npm install @edge-csrf/node-http
# or
pnpm add @edge-csrf/node-http
# or
yarn add @edge-csrf/node-http
```

Next, add the Edge-CSRF CSRF protection function to your request handlers:

```javascript
// server.js

import { createServer } from 'http';

import { createCsrfProtect } from '@edge-csrf/node-http';

// initalize csrf protection middleware
const csrfProtect = createCsrfProtect({
cookie: {
secure: process.env.NODE_ENV === 'production',
},
});

// init server
const server = createServer(async (req, res) => {
// apply csrf protection
try {
await csrfProtect(req, res);
} catch (err) {
if (err instanceof CsrfError) {
res.writeHead(403);
res.end('invalid csrf token');
return;
}
throw err;
}

// add handler
if (req.url === '/') {
if (req.method === 'GET') {
const csrfToken = res.getHeader('X-CSRF-Token') || 'missing';
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<!doctype html>
<html>
<body>
<form action="/" method="post">
<legend>Form with CSRF (should succeed):</legend>
<input type="hidden" name="csrf_token" value="${csrfToken}" />
<input type="text" name="input1" />
<button type="submit">Submit</button>
</form>
</body>
</html>
`);
return;
}

if (req.method === 'POST') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('success');
return;
}
}

res.writeHead(404);
res.end('not found');
});

// start server
server.listen(3000, () => {
console.log('Server is listening on port 3000');
});
```

With the CSRF protection method, all HTTP submission requests (e.g. POST, PUT, DELETE, PATCH) will be rejected if they do not include a valid CSRF token.

## Development

### Get the code
Expand Down
3 changes: 0 additions & 3 deletions examples/express/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ const csrfMiddleware = createCsrfMiddleware({
const app = express();
const port = 3000;

// add body parsing middleware
app.use(express.urlencoded({ extended: false }));

// add csrf middleware
app.use(csrfMiddleware);

Expand Down
2 changes: 1 addition & 1 deletion examples/express/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"type": "module",
"dependencies": {
"@edge-csrf/express": "^2.1.0",
"@edge-csrf/express": "^2.2.0",
"express": "^4.19.2"
}
}
21 changes: 21 additions & 0 deletions examples/node-http/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
This is an [Express](https://expressjs.com) example app.

## Getting Started

First, install dependencies:

```bash
npm install
# or
pnpm install
# or
yarn install
```

Next, run the server:

```bash
node app.js
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
9 changes: 9 additions & 0 deletions examples/node-http/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "edge-csrf-example",
"version": "0.1.0",
"private": true,
"type": "module",
"dependencies": {
"@edge-csrf/node-http": "^2.2.0"
}
}
76 changes: 76 additions & 0 deletions examples/node-http/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { createServer } from 'http';

import { CsrfError, createCsrfProtect } from '@edge-csrf/node-http';

// initalize csrf protection method
const csrfProtect = createCsrfProtect({
cookie: {
secure: process.env.NODE_ENV === 'production',
},
});

// init server
const server = createServer(async (req, res) => {
// apply csrf protection
try {
await csrfProtect(req, res);
} catch (err) {
if (err instanceof CsrfError) {
res.writeHead(403);
res.end('invalid csrf token');
return;
}
throw err;
}

// add handler
if (req.url === '/') {
if (req.method === 'GET') {
const csrfToken = res.getHeader('X-CSRF-Token') || 'missing';
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<!doctype html>
<html>
<body>
<p>CSRF token value: ${csrfToken}</p>
<h2>HTML Form Submission Example:</h2>
<form action="/" method="post">
<legend>Form without CSRF (should fail):</legend>
<input type="text" name="input1" />
<button type="submit">Submit</button>
</form>
<br />
<form action="/" method="post">
<legend>Form with incorrect CSRF (should fail):</legend>
<input type="hidden" name="csrf_token" value="notvalid" />
<input type="text" name="input1" />
<button type="submit">Submit</button>
</form>
<br />
<form action="/" method="post">
<legend>Form with CSRF (should succeed):</legend>
<input type="hidden" name="csrf_token" value="${csrfToken}" />
<input type="text" name="input1" />
<button type="submit">Submit</button>
</form>
</body>
</html>
`);
return;
}

if (req.method === 'POST') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('success');
return;
}
}

res.writeHead(404);
res.end('not found');
});

// start server
server.listen(3000, () => {
console.log('Server is listening on port 3000');
});
6 changes: 0 additions & 6 deletions packages/express/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ const csrfMiddleware = createCsrfMiddleware({
const app = express();
const port = 3000;

// add body parsing middleware
app.use(express.urlencoded({ extended: false }));

// add csrf middleware
app.use(csrfMiddleware);

Expand Down Expand Up @@ -107,9 +104,6 @@ const csrfProtect = createCsrfProtect({
const app = express();
const port = 3000;

// add body parsing middleware
app.use(express.urlencoded({ extended: false }));

// add csrf middleware
app.use(async (req, res, next) => {
try {
Expand Down
Loading