# Sofi CSRF

A fast, fully-typed, and framework-agnostic CSRF (Cross-Site Request Forgery) protection library tailored for Node.js. It features a standalone Core logic engine with an out-of-the-box adapter specifically designed for Express.js.

## Features
- **Framework-Agnostic Core**: Secure payload generator and timing-safe verification extracted from the routing logic. Fits any HTTP node server.
- **TypeScript First**: End-to-end typed classes and functions properly documented with `TSDoc`.
- **Express Adapter**: Pre-packaged middleware wrapping `req`, `res` and cookies appropriately to effortlessly handle injection and error throwing.
- **Token Rotation**: Avoid token reuse attacks with the out-of-the-box regeneration middlewares.

## Supported Frameworks & Contributing
Currently, `sofi-csrf` provides official "out-of-the-box" adapters for:
- **Express.js**
- **Hono**

Thanks to its framework-agnostic core engine, we welcome community PRs! Feel free to contribute by creating new adapters (Fastify, Koa, NestJS, etc.) to expand the ecosystem. Likewise, I will be adding more official adapters progressively as time permits.

## Installation

```bash
npm install sofi-csrf
```

*(Note for Express adapter: You should also use `cookie-parser` within your Express application)*.

## Usage Guide (Express)

```typescript
import express from 'express';
// Note: [cookie-parser](https://www.npmjs.com/package/cookie-parser) is required to extract cookies smoothly before this middleware
import cookieParser from 'cookie-parser';
import { expressCsrf } from 'sofi-csrf';

const app = express();
app.use(express.json());
app.use(cookieParser());

// 1. Initialize the middleware adapter (Accepts partial options)
const { csrfMiddleware, verifyCsrfToken, regenerateCsrfToken } = expressCsrf({
  cookieName: 'xsrf-token', // Defaults to 'csrfToken'
  cookieOptions: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: 3600000
  }
});

// 2. Inject tokens globally (assigns token to local cookies, req and res.locals)
app.use(csrfMiddleware);

// 3. Retrieve token for frontend binding or views
app.get('/form', (req, res) => {
  // Can be easily retrieved from req.csrfToken, or res.locals.csrfToken inside EJS/Pug templates!
  res.json({ csrfToken: req.csrfToken });
});

// 4. Secure your data mutations routes 
// Client must send the token either in req.body._csrf OR headers['x-csrf-token']
app.post('/submit', verifyCsrfToken, (req, res) => {
  res.json({ message: "Successfully executed operation with valid token" });
});

// 5. Rotate the token 
// Often necessary when performing high privilege or session-upgrade tasks
app.post('/sensitive-update', regenerateCsrfToken, (req, res) => {
   res.json({ message: "Task completed securely. A new token has rolled.", newToken: req.csrfToken });
});
```

## Usage Guide (Hono)

```typescript
import { Hono } from 'hono';
import { honoCsrf } from 'sofi-csrf';

const app = new Hono();

// 1. Initialize the adapter
const { csrfMiddleware, verifyCsrfToken, regenerateCsrfToken } = honoCsrf({
  cookieName: 'xsrf-token',
});

// 2. Inject token in cookies and set it in context variables (c.get('csrfToken'))
app.use('*', csrfMiddleware);

// 3. Simple retrieval
app.get('/form', (c) => {
  return c.json({ token: c.get('csrfToken') });
});

// 4. Secure data mutations, accepts 'x-csrf-token' header or '_csrf' in body
app.post('/submit', verifyCsrfToken, (c) => {
  return c.text('Processed securely');
});

// 5. Native Token Rotation
app.post('/rotate', regenerateCsrfToken, (c) => {
  return c.json({ message: "Rotated successfully", newToken: c.get('csrfToken') });
});
```

## API Documentation
Explore the code using IDE intelligence to enjoy detailed `TSDocs` snippets and rich context.

### Built-In Interfaces
- `CsrfCore`: Handpicks the algorithm validations.
- `CsrfOptions` and `defaultOptions`: Handle customizable rules.
- `CsrfForbiddenError`: Default thrown exception containing the `403` status.

### Options Configuration
When initializing your adapter (e.g., `expressCsrf()` or `honoCsrf()`), you can pass an optional configuration object to override the `defaultOptions`. 

Below is the exhaustive list of options you can configure:

| Option | Type | Default Value | Description |
| :--- | :--- | :--- | :--- |
| `cookieName` | `string` | `'csrfToken'` | The name of the cookie where the generated token is stored. |
| `tokenLength` | `number` | `24` | Cryptographic byte length for generation (produces a 48 hexadecimal character string). |
| `cookieOptions.httpOnly` | `boolean` | `true` | Prevents client-side scripts from accessing the cookie via `document.cookie`. |
| `cookieOptions.secure` | `boolean` | `false` | Whether to ensure the cookie is only sent over HTTPS (Highly recommended to set to `true` in production). |
| `cookieOptions.maxAge` | `number` | `3600000` (1 hour) | Expiration time for the cookie in milliseconds. |

---

## Support
If you found this library helpful, consider supporting my work!

[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/sofidev)
