sofi-csrf 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/dist/adapters/express.d.ts +34 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/express.js +78 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/hono.d.ts +34 -0
- package/dist/adapters/hono.d.ts.map +1 -0
- package/dist/adapters/hono.js +104 -0
- package/dist/adapters/hono.js.map +1 -0
- package/dist/core/csrf.d.ts +50 -0
- package/dist/core/csrf.d.ts.map +1 -0
- package/dist/core/csrf.js +88 -0
- package/dist/core/csrf.js.map +1 -0
- package/dist/core/errors.d.ts +10 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +18 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/options.d.ts +23 -0
- package/dist/core/options.d.ts.map +1 -0
- package/dist/core/options.js +16 -0
- package/dist/core/options.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sofidev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Sofi CSRF
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- **Framework-Agnostic Core**: Secure payload generator and timing-safe verification extracted from the routing logic. Fits any HTTP node server.
|
|
7
|
+
- **TypeScript First**: End-to-end typed classes and functions properly documented with `TSDoc`.
|
|
8
|
+
- **Express Adapter**: Pre-packaged middleware wrapping `req`, `res` and cookies appropriately to effortlessly handle injection and error throwing.
|
|
9
|
+
- **Token Rotation**: Avoid token reuse attacks with the out-of-the-box regeneration middlewares.
|
|
10
|
+
|
|
11
|
+
## Supported Frameworks & Contributing
|
|
12
|
+
Currently, `sofi-csrf` provides official "out-of-the-box" adapters for:
|
|
13
|
+
- **Express.js**
|
|
14
|
+
- **Hono**
|
|
15
|
+
|
|
16
|
+
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.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install sofi-csrf
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
*(Note for Express adapter: You should also use `cookie-parser` within your Express application)*.
|
|
25
|
+
|
|
26
|
+
## Usage Guide (Express)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import express from 'express';
|
|
30
|
+
// Note: [cookie-parser](https://www.npmjs.com/package/cookie-parser) is required to extract cookies smoothly before this middleware
|
|
31
|
+
import cookieParser from 'cookie-parser';
|
|
32
|
+
import { expressCsrf } from 'sofi-csrf';
|
|
33
|
+
|
|
34
|
+
const app = express();
|
|
35
|
+
app.use(express.json());
|
|
36
|
+
app.use(cookieParser());
|
|
37
|
+
|
|
38
|
+
// 1. Initialize the middleware adapter (Accepts partial options)
|
|
39
|
+
const { csrfMiddleware, verifyCsrfToken, regenerateCsrfToken } = expressCsrf({
|
|
40
|
+
cookieName: 'xsrf-token', // Defaults to 'csrfToken'
|
|
41
|
+
cookieOptions: {
|
|
42
|
+
secure: process.env.NODE_ENV === 'production',
|
|
43
|
+
httpOnly: true,
|
|
44
|
+
maxAge: 3600000
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 2. Inject tokens globally (assigns token to local cookies, req and res.locals)
|
|
49
|
+
app.use(csrfMiddleware);
|
|
50
|
+
|
|
51
|
+
// 3. Retrieve token for frontend binding or views
|
|
52
|
+
app.get('/form', (req, res) => {
|
|
53
|
+
// Can be easily retrieved from req.csrfToken, or res.locals.csrfToken inside EJS/Pug templates!
|
|
54
|
+
res.json({ csrfToken: req.csrfToken });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 4. Secure your data mutations routes
|
|
58
|
+
// Client must send the token either in req.body._csrf OR headers['x-csrf-token']
|
|
59
|
+
app.post('/submit', verifyCsrfToken, (req, res) => {
|
|
60
|
+
res.json({ message: "Successfully executed operation with valid token" });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// 5. Rotate the token
|
|
64
|
+
// Often necessary when performing high privilege or session-upgrade tasks
|
|
65
|
+
app.post('/sensitive-update', regenerateCsrfToken, (req, res) => {
|
|
66
|
+
res.json({ message: "Task completed securely. A new token has rolled.", newToken: req.csrfToken });
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage Guide (Hono)
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { Hono } from 'hono';
|
|
74
|
+
import { honoCsrf } from 'sofi-csrf';
|
|
75
|
+
|
|
76
|
+
const app = new Hono();
|
|
77
|
+
|
|
78
|
+
// 1. Initialize the adapter
|
|
79
|
+
const { csrfMiddleware, verifyCsrfToken, regenerateCsrfToken } = honoCsrf({
|
|
80
|
+
cookieName: 'xsrf-token',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// 2. Inject token in cookies and set it in context variables (c.get('csrfToken'))
|
|
84
|
+
app.use('*', csrfMiddleware);
|
|
85
|
+
|
|
86
|
+
// 3. Simple retrieval
|
|
87
|
+
app.get('/form', (c) => {
|
|
88
|
+
return c.json({ token: c.get('csrfToken') });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// 4. Secure data mutations, accepts 'x-csrf-token' header or '_csrf' in body
|
|
92
|
+
app.post('/submit', verifyCsrfToken, (c) => {
|
|
93
|
+
return c.text('Processed securely');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// 5. Native Token Rotation
|
|
97
|
+
app.post('/rotate', regenerateCsrfToken, (c) => {
|
|
98
|
+
return c.json({ message: "Rotated successfully", newToken: c.get('csrfToken') });
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## API Documentation
|
|
103
|
+
Explore the code using IDE intelligence to enjoy detailed `TSDocs` snippets and rich context.
|
|
104
|
+
|
|
105
|
+
### Built-In Interfaces
|
|
106
|
+
- `CsrfCore`: Handpicks the algorithm validations.
|
|
107
|
+
- `CsrfOptions` and `defaultOptions`: Handle customizable rules.
|
|
108
|
+
- `CsrfForbiddenError`: Default thrown exception containing the `403` status.
|
|
109
|
+
|
|
110
|
+
### Options Configuration
|
|
111
|
+
When initializing your adapter (e.g., `expressCsrf()` or `honoCsrf()`), you can pass an optional configuration object to override the `defaultOptions`.
|
|
112
|
+
|
|
113
|
+
Below is the exhaustive list of options you can configure:
|
|
114
|
+
|
|
115
|
+
| Option | Type | Default Value | Description |
|
|
116
|
+
| :--- | :--- | :--- | :--- |
|
|
117
|
+
| `cookieName` | `string` | `'csrfToken'` | The name of the cookie where the generated token is stored. |
|
|
118
|
+
| `tokenLength` | `number` | `24` | Cryptographic byte length for generation (produces a 48 hexadecimal character string). |
|
|
119
|
+
| `cookieOptions.httpOnly` | `boolean` | `true` | Prevents client-side scripts from accessing the cookie via `document.cookie`. |
|
|
120
|
+
| `cookieOptions.secure` | `boolean` | `process.env.NODE_ENV === 'production'` | Whether to ensure the cookie is only sent over HTTPS. |
|
|
121
|
+
| `cookieOptions.maxAge` | `number` | `3600000` (1 hour) | Expiration time for the cookie in milliseconds. |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Support
|
|
126
|
+
If you found this library helpful, consider supporting my work!
|
|
127
|
+
|
|
128
|
+
[](https://ko-fi.com/sofidev)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { RequestHandler } from 'express';
|
|
2
|
+
import { CsrfOptions } from '../core/options';
|
|
3
|
+
declare global {
|
|
4
|
+
namespace Express {
|
|
5
|
+
interface Request {
|
|
6
|
+
csrfToken?: string;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Creates an Express compatibility adapter for the CSRF core.
|
|
12
|
+
* Provides middlewares to inject tokens, verify them, and rotate them on demand.
|
|
13
|
+
*
|
|
14
|
+
* @param options - Optional configuration extending defaults.
|
|
15
|
+
* @returns Three RequestHandlers: csrfMiddleware, verifyCsrfToken, and regenerateCsrfToken.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import express from 'express';
|
|
20
|
+
* import { expressCsrf } from 'sofi-csrf';
|
|
21
|
+
*
|
|
22
|
+
* const app = express();
|
|
23
|
+
* const { csrfMiddleware, verifyCsrfToken } = expressCsrf();
|
|
24
|
+
*
|
|
25
|
+
* app.use(csrfMiddleware);
|
|
26
|
+
* app.post('/data', verifyCsrfToken, (req, res) => res.send('OK'));
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function expressCsrf(options?: Partial<CsrfOptions>): {
|
|
30
|
+
csrfMiddleware: RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
31
|
+
verifyCsrfToken: RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
32
|
+
regenerateCsrfToken: RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAE1E,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAiB,OAAO;YACtB,SAAS,CAAC,EAAE,MAAM,CAAC;SACpB;KACF;CACF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;;;;EAgEzD"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.expressCsrf = expressCsrf;
|
|
4
|
+
const csrf_1 = require("../core/csrf");
|
|
5
|
+
/**
|
|
6
|
+
* Creates an Express compatibility adapter for the CSRF core.
|
|
7
|
+
* Provides middlewares to inject tokens, verify them, and rotate them on demand.
|
|
8
|
+
*
|
|
9
|
+
* @param options - Optional configuration extending defaults.
|
|
10
|
+
* @returns Three RequestHandlers: csrfMiddleware, verifyCsrfToken, and regenerateCsrfToken.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import express from 'express';
|
|
15
|
+
* import { expressCsrf } from 'sofi-csrf';
|
|
16
|
+
*
|
|
17
|
+
* const app = express();
|
|
18
|
+
* const { csrfMiddleware, verifyCsrfToken } = expressCsrf();
|
|
19
|
+
*
|
|
20
|
+
* app.use(csrfMiddleware);
|
|
21
|
+
* app.post('/data', verifyCsrfToken, (req, res) => res.send('OK'));
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
function expressCsrf(options) {
|
|
25
|
+
const core = new csrf_1.CsrfCore(options);
|
|
26
|
+
/**
|
|
27
|
+
* Middleware that injects a valid token into cookies, request, and locals.
|
|
28
|
+
*/
|
|
29
|
+
const csrfMiddleware = (req, res, next) => {
|
|
30
|
+
const { cookieName, cookieOptions } = core.getOptions();
|
|
31
|
+
if (!req.cookies || !req.cookies[cookieName]) {
|
|
32
|
+
const newToken = core.generateToken();
|
|
33
|
+
res.cookie(cookieName, newToken, cookieOptions);
|
|
34
|
+
req.csrfToken = newToken;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
req.csrfToken = req.cookies[cookieName];
|
|
38
|
+
}
|
|
39
|
+
res.locals.csrfToken = req.csrfToken;
|
|
40
|
+
next();
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Middleware that prevents unauthorized modifications.
|
|
44
|
+
* Only validates data-modifying HTTP methods (POST, PUT, DELETE, PATCH).
|
|
45
|
+
*/
|
|
46
|
+
const verifyCsrfToken = (req, res, next) => {
|
|
47
|
+
if (!["POST", "PUT", "DELETE", "PATCH"].includes(req.method)) {
|
|
48
|
+
return next();
|
|
49
|
+
}
|
|
50
|
+
const { cookieName } = core.getOptions();
|
|
51
|
+
const cookieToken = req.cookies ? req.cookies[cookieName] : undefined;
|
|
52
|
+
const bodyToken = req.body?._csrf || req.headers["x-csrf-token"];
|
|
53
|
+
if (!core.verifyToken(cookieToken, bodyToken)) {
|
|
54
|
+
return next(core.createError());
|
|
55
|
+
}
|
|
56
|
+
next();
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Middleware that rotates the token immediately after sensitive operations.
|
|
60
|
+
*/
|
|
61
|
+
const regenerateCsrfToken = (req, res, next) => {
|
|
62
|
+
if (!["POST", "PUT", "DELETE", "PATCH"].includes(req.method)) {
|
|
63
|
+
return next();
|
|
64
|
+
}
|
|
65
|
+
const { cookieName, cookieOptions } = core.getOptions();
|
|
66
|
+
const newToken = core.generateToken();
|
|
67
|
+
res.cookie(cookieName, newToken, cookieOptions);
|
|
68
|
+
req.csrfToken = newToken;
|
|
69
|
+
res.locals.csrfToken = newToken;
|
|
70
|
+
next();
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
csrfMiddleware,
|
|
74
|
+
verifyCsrfToken,
|
|
75
|
+
regenerateCsrfToken
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":";;AA+BA,kCAgEC;AA9FD,uCAAwC;AAWxC;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,WAAW,CAAC,OAA8B;IACxD,MAAM,IAAI,GAAG,IAAI,eAAQ,CAAC,OAAO,CAAC,CAAC;IAEnC;;OAEG;IACH,MAAM,cAAc,GAAmB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACzF,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAExD,IAAI,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YACtC,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;YAChD,GAAG,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC1C,CAAC;QAED,GAAG,CAAC,MAAM,CAAC,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC;QACrC,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;IAEF;;;OAGG;IACH,MAAM,eAAe,GAAmB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC1F,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7D,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QAED,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QACzC,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACtE,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAEjE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,CAAC;YAC9C,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAClC,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;IAEF;;OAEG;IACH,MAAM,mBAAmB,GAAmB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC9F,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7D,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;QAED,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QAEtC,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;QAChD,GAAG,CAAC,SAAS,GAAG,QAAQ,CAAC;QACzB,GAAG,CAAC,MAAM,CAAC,SAAS,GAAG,QAAQ,CAAC;QAEhC,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;IAEF,OAAO;QACL,cAAc;QACd,eAAe;QACf,mBAAmB;KACpB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Context, Next } from 'hono';
|
|
2
|
+
import { CsrfOptions } from '../core/options';
|
|
3
|
+
declare module 'hono' {
|
|
4
|
+
interface ContextVariableMap {
|
|
5
|
+
csrfToken: string;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Creates a Hono compatibility adapter for the CSRF core.
|
|
10
|
+
* Provides middlewares to inject tokens, verify them, and rotate them on demand.
|
|
11
|
+
*
|
|
12
|
+
* @param options - Optional configuration extending defaults.
|
|
13
|
+
* @returns Three RequestHandlers: csrfMiddleware, verifyCsrfToken, and regenerateCsrfToken.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { Hono } from 'hono';
|
|
18
|
+
* import { honoCsrf } from 'sofi-csrf';
|
|
19
|
+
*
|
|
20
|
+
* const app = new Hono();
|
|
21
|
+
* const { csrfMiddleware, verifyCsrfToken } = honoCsrf();
|
|
22
|
+
*
|
|
23
|
+
* app.use('*', csrfMiddleware);
|
|
24
|
+
* app.post('/data', verifyCsrfToken, (c) => c.text('OK'));
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function honoCsrf(options?: Partial<CsrfOptions>): {
|
|
28
|
+
csrfMiddleware: (c: Context, next: Next) => Promise<void>;
|
|
29
|
+
verifyCsrfToken: (c: Context, next: Next) => Promise<void | (Response & import("hono").TypedResponse<{
|
|
30
|
+
error: string;
|
|
31
|
+
}, any, "json">)>;
|
|
32
|
+
regenerateCsrfToken: (c: Context, next: Next) => Promise<void>;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=hono.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG1C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,OAAO,QAAQ,MAAM,CAAC;IACpB,UAAU,kBAAkB;QAC1B,SAAS,EAAE,MAAM,CAAC;KACnB;CACF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;wBAOpB,OAAO,QAAQ,IAAI;yBAuBlB,OAAO,QAAQ,IAAI;;;6BA0Cf,OAAO,QAAQ,IAAI;EAoB1D"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.honoCsrf = honoCsrf;
|
|
4
|
+
const cookie_1 = require("hono/cookie");
|
|
5
|
+
const csrf_1 = require("../core/csrf");
|
|
6
|
+
/**
|
|
7
|
+
* Creates a Hono compatibility adapter for the CSRF core.
|
|
8
|
+
* Provides middlewares to inject tokens, verify them, and rotate them on demand.
|
|
9
|
+
*
|
|
10
|
+
* @param options - Optional configuration extending defaults.
|
|
11
|
+
* @returns Three RequestHandlers: csrfMiddleware, verifyCsrfToken, and regenerateCsrfToken.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { Hono } from 'hono';
|
|
16
|
+
* import { honoCsrf } from 'sofi-csrf';
|
|
17
|
+
*
|
|
18
|
+
* const app = new Hono();
|
|
19
|
+
* const { csrfMiddleware, verifyCsrfToken } = honoCsrf();
|
|
20
|
+
*
|
|
21
|
+
* app.use('*', csrfMiddleware);
|
|
22
|
+
* app.post('/data', verifyCsrfToken, (c) => c.text('OK'));
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function honoCsrf(options) {
|
|
26
|
+
const core = new csrf_1.CsrfCore(options);
|
|
27
|
+
/**
|
|
28
|
+
* Middleware that injects a valid token into cookies and Hono Context variables.
|
|
29
|
+
* Exposes token in `c.get('csrfToken')`.
|
|
30
|
+
*/
|
|
31
|
+
const csrfMiddleware = async (c, next) => {
|
|
32
|
+
const { cookieName, cookieOptions } = core.getOptions();
|
|
33
|
+
let token = (0, cookie_1.getCookie)(c, cookieName);
|
|
34
|
+
if (!token) {
|
|
35
|
+
token = core.generateToken();
|
|
36
|
+
// En hono, maxAge es en segundos si no me equivoco, pero respetaremos la interfaz original (milisegundos) o ajustaremos.
|
|
37
|
+
// Hono setCookie takes maxAge strictly as number (in seconds generally as per standard).
|
|
38
|
+
// Express maxAge is MS. Let's make it compatible.
|
|
39
|
+
const honoCookieOpts = { ...cookieOptions, maxAge: Math.floor(cookieOptions.maxAge / 1000) };
|
|
40
|
+
(0, cookie_1.setCookie)(c, cookieName, token, honoCookieOpts);
|
|
41
|
+
}
|
|
42
|
+
// Almacenar variable en el request context
|
|
43
|
+
c.set('csrfToken', token);
|
|
44
|
+
await next();
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Middleware that prevents unauthorized modifications.
|
|
48
|
+
* Only validates data-modifying HTTP methods (POST, PUT, DELETE, PATCH).
|
|
49
|
+
*/
|
|
50
|
+
const verifyCsrfToken = async (c, next) => {
|
|
51
|
+
if (!["POST", "PUT", "DELETE", "PATCH"].includes(c.req.method)) {
|
|
52
|
+
return await next();
|
|
53
|
+
}
|
|
54
|
+
const { cookieName } = core.getOptions();
|
|
55
|
+
const cookieToken = (0, cookie_1.getCookie)(c, cookieName);
|
|
56
|
+
let bodyToken = undefined;
|
|
57
|
+
// 1. Header lookup (Safest)
|
|
58
|
+
bodyToken = c.req.header('x-csrf-token');
|
|
59
|
+
// 2. Body lookup via cloning to prevent destructive reading
|
|
60
|
+
if (!bodyToken) {
|
|
61
|
+
const contentType = c.req.header('content-type') || '';
|
|
62
|
+
try {
|
|
63
|
+
if (contentType.includes('application/json')) {
|
|
64
|
+
const cloned = c.req.raw.clone();
|
|
65
|
+
const data = await cloned.json();
|
|
66
|
+
bodyToken = data._csrf;
|
|
67
|
+
}
|
|
68
|
+
else if (contentType.includes('application/x-www-form-urlencoded') || contentType.includes('multipart/form-data')) {
|
|
69
|
+
const cloned = c.req.raw.clone();
|
|
70
|
+
const formData = await cloned.formData();
|
|
71
|
+
bodyToken = formData.get('_csrf')?.toString();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Ignore parse errors, bodyToken will remain undefined
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!core.verifyToken(cookieToken, bodyToken)) {
|
|
79
|
+
const error = core.createError();
|
|
80
|
+
return c.json({ error: error.message }, error.status);
|
|
81
|
+
}
|
|
82
|
+
await next();
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Middleware that rotates the token immediately after sensitive operations.
|
|
86
|
+
*/
|
|
87
|
+
const regenerateCsrfToken = async (c, next) => {
|
|
88
|
+
if (!["POST", "PUT", "DELETE", "PATCH"].includes(c.req.method)) {
|
|
89
|
+
return await next();
|
|
90
|
+
}
|
|
91
|
+
const { cookieName, cookieOptions } = core.getOptions();
|
|
92
|
+
const honoCookieOpts = { ...cookieOptions, maxAge: Math.floor(cookieOptions.maxAge / 1000) };
|
|
93
|
+
const newToken = core.generateToken();
|
|
94
|
+
(0, cookie_1.setCookie)(c, cookieName, newToken, honoCookieOpts);
|
|
95
|
+
c.set('csrfToken', newToken);
|
|
96
|
+
await next();
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
csrfMiddleware,
|
|
100
|
+
verifyCsrfToken,
|
|
101
|
+
regenerateCsrfToken
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=hono.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.js","sourceRoot":"","sources":["../../src/adapters/hono.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { CsrfOptions } from './options';
|
|
2
|
+
import { CsrfForbiddenError } from './errors';
|
|
3
|
+
/**
|
|
4
|
+
* Core CSRF Token generator and validator.
|
|
5
|
+
* Framework-agnostic implementation handling secure cryptographic operations.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const core = new CsrfCore({ tokenLength: 32 });
|
|
10
|
+
* const token = core.generateToken();
|
|
11
|
+
* const isValid = core.verifyToken(token, 'user-provided-token');
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare class CsrfCore {
|
|
15
|
+
private options;
|
|
16
|
+
/**
|
|
17
|
+
* Initializes the CSRF Core engine.
|
|
18
|
+
*
|
|
19
|
+
* @param options - Optional configuration overriding default properties.
|
|
20
|
+
*/
|
|
21
|
+
constructor(options?: Partial<CsrfOptions>);
|
|
22
|
+
/**
|
|
23
|
+
* Retrieves the current configuration options.
|
|
24
|
+
*
|
|
25
|
+
* @returns Current CSRF options.
|
|
26
|
+
*/
|
|
27
|
+
getOptions(): CsrfOptions;
|
|
28
|
+
/**
|
|
29
|
+
* Generates a new secure random token.
|
|
30
|
+
*
|
|
31
|
+
* @returns A secure hexadecimal string token.
|
|
32
|
+
*/
|
|
33
|
+
generateToken(): string;
|
|
34
|
+
/**
|
|
35
|
+
* Validates if the token stored in the cookie matches the token sent in the request.
|
|
36
|
+
* Uses timing-safe equality to prevent timing attacks.
|
|
37
|
+
*
|
|
38
|
+
* @param cookieToken - The token retrieved from the trusted HTTP cookie.
|
|
39
|
+
* @param bodyOrHeaderToken - The token sent by the client via body or headers.
|
|
40
|
+
* @returns `true` if both tokens match and exist, `false` otherwise.
|
|
41
|
+
*/
|
|
42
|
+
verifyToken(cookieToken?: string, bodyOrHeaderToken?: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Creates an instance of the configured CSRF validation error.
|
|
45
|
+
*
|
|
46
|
+
* @returns A CsrfForbiddenError indicating a forbidden action.
|
|
47
|
+
*/
|
|
48
|
+
createError(): CsrfForbiddenError;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=csrf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../../src/core/csrf.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAkB,MAAM,WAAW,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;;;;;;GAUG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,OAAO,CAAc;IAE7B;;;;OAIG;gBACS,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC;IAW1C;;;;OAIG;IACI,UAAU,IAAI,WAAW;IAIhC;;;;OAIG;IACI,aAAa,IAAI,MAAM;IAI9B;;;;;;;OAOG;IACI,WAAW,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,OAAO;IAkB7E;;;;OAIG;IACI,WAAW,IAAI,kBAAkB;CAGzC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CsrfCore = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const options_1 = require("./options");
|
|
9
|
+
const errors_1 = require("./errors");
|
|
10
|
+
/**
|
|
11
|
+
* Core CSRF Token generator and validator.
|
|
12
|
+
* Framework-agnostic implementation handling secure cryptographic operations.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const core = new CsrfCore({ tokenLength: 32 });
|
|
17
|
+
* const token = core.generateToken();
|
|
18
|
+
* const isValid = core.verifyToken(token, 'user-provided-token');
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
class CsrfCore {
|
|
22
|
+
options;
|
|
23
|
+
/**
|
|
24
|
+
* Initializes the CSRF Core engine.
|
|
25
|
+
*
|
|
26
|
+
* @param options - Optional configuration overriding default properties.
|
|
27
|
+
*/
|
|
28
|
+
constructor(options) {
|
|
29
|
+
this.options = {
|
|
30
|
+
...options_1.defaultOptions,
|
|
31
|
+
...options,
|
|
32
|
+
cookieOptions: {
|
|
33
|
+
...options_1.defaultOptions.cookieOptions,
|
|
34
|
+
...(options?.cookieOptions || {}),
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Retrieves the current configuration options.
|
|
40
|
+
*
|
|
41
|
+
* @returns Current CSRF options.
|
|
42
|
+
*/
|
|
43
|
+
getOptions() {
|
|
44
|
+
return this.options;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Generates a new secure random token.
|
|
48
|
+
*
|
|
49
|
+
* @returns A secure hexadecimal string token.
|
|
50
|
+
*/
|
|
51
|
+
generateToken() {
|
|
52
|
+
return crypto_1.default.randomBytes(this.options.tokenLength).toString('hex');
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validates if the token stored in the cookie matches the token sent in the request.
|
|
56
|
+
* Uses timing-safe equality to prevent timing attacks.
|
|
57
|
+
*
|
|
58
|
+
* @param cookieToken - The token retrieved from the trusted HTTP cookie.
|
|
59
|
+
* @param bodyOrHeaderToken - The token sent by the client via body or headers.
|
|
60
|
+
* @returns `true` if both tokens match and exist, `false` otherwise.
|
|
61
|
+
*/
|
|
62
|
+
verifyToken(cookieToken, bodyOrHeaderToken) {
|
|
63
|
+
if (!cookieToken || !bodyOrHeaderToken) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const buffer1 = Buffer.from(cookieToken, 'hex');
|
|
68
|
+
const buffer2 = Buffer.from(bodyOrHeaderToken, 'hex');
|
|
69
|
+
if (buffer1.length !== buffer2.length) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return crypto_1.default.timingSafeEqual(buffer1, buffer2);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return cookieToken === bodyOrHeaderToken;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Creates an instance of the configured CSRF validation error.
|
|
80
|
+
*
|
|
81
|
+
* @returns A CsrfForbiddenError indicating a forbidden action.
|
|
82
|
+
*/
|
|
83
|
+
createError() {
|
|
84
|
+
return new errors_1.CsrfForbiddenError();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.CsrfCore = CsrfCore;
|
|
88
|
+
//# sourceMappingURL=csrf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csrf.js","sourceRoot":"","sources":["../../src/core/csrf.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AAC5B,uCAAwD;AACxD,qCAA8C;AAE9C;;;;;;;;;;GAUG;AACH,MAAa,QAAQ;IACX,OAAO,CAAc;IAE7B;;;;OAIG;IACH,YAAY,OAA8B;QACxC,IAAI,CAAC,OAAO,GAAG;YACb,GAAG,wBAAc;YACjB,GAAG,OAAO;YACV,aAAa,EAAE;gBACb,GAAG,wBAAc,CAAC,aAAa;gBAC/B,GAAG,CAAC,OAAO,EAAE,aAAa,IAAI,EAAE,CAAC;aAClC;SACF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACI,UAAU;QACf,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;;;OAIG;IACI,aAAa;QAClB,OAAO,gBAAM,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtE,CAAC;IAED;;;;;;;OAOG;IACI,WAAW,CAAC,WAAoB,EAAE,iBAA0B;QACjE,IAAI,CAAC,WAAW,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;YAChD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;YAEtD,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;gBACrC,OAAO,KAAK,CAAC;YAChB,CAAC;YACD,OAAO,gBAAM,CAAC,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACN,OAAO,WAAW,KAAK,iBAAiB,CAAC;QAC5C,CAAC;IACH,CAAC;IAED;;;;OAIG;IACI,WAAW;QAChB,OAAO,IAAI,2BAAkB,EAAE,CAAC;IAClC,CAAC;CACF;AAvED,4BAuEC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard forbidden error thrown when a CSRF attack is detected
|
|
3
|
+
* or tokens mismatch.
|
|
4
|
+
*/
|
|
5
|
+
export declare class CsrfForbiddenError extends Error {
|
|
6
|
+
/** The HTTP status code indicating the error. Always 403. */
|
|
7
|
+
status: number;
|
|
8
|
+
constructor(message?: string);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,6DAA6D;IACtD,MAAM,EAAE,MAAM,CAAC;gBAEV,OAAO,SAAuB;CAK3C"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CsrfForbiddenError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Standard forbidden error thrown when a CSRF attack is detected
|
|
6
|
+
* or tokens mismatch.
|
|
7
|
+
*/
|
|
8
|
+
class CsrfForbiddenError extends Error {
|
|
9
|
+
/** The HTTP status code indicating the error. Always 403. */
|
|
10
|
+
status;
|
|
11
|
+
constructor(message = "Invalid CSRF token") {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "CsrfForbiddenError";
|
|
14
|
+
this.status = 403;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.CsrfForbiddenError = CsrfForbiddenError;
|
|
18
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACH,MAAa,kBAAmB,SAAQ,KAAK;IAC3C,6DAA6D;IACtD,MAAM,CAAS;IAEtB,YAAY,OAAO,GAAG,oBAAoB;QACxC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC;IACpB,CAAC;CACF;AATD,gDASC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global default options used for the initial setup.
|
|
3
|
+
*/
|
|
4
|
+
export declare const defaultOptions: CsrfOptions;
|
|
5
|
+
/**
|
|
6
|
+
* Configuration options for the CSRF protections.
|
|
7
|
+
*/
|
|
8
|
+
export interface CsrfOptions {
|
|
9
|
+
/** The name of the cookie to store the token. Defaults to 'csrfToken' */
|
|
10
|
+
cookieName: string;
|
|
11
|
+
/** Security and lifetime options for the cookie */
|
|
12
|
+
cookieOptions: {
|
|
13
|
+
/** Prevent client-side JS from accessing the cookie */
|
|
14
|
+
httpOnly: boolean;
|
|
15
|
+
/** Require HTTPS (usually set to true in production automatically) */
|
|
16
|
+
secure: boolean;
|
|
17
|
+
/** Cookie lifetime in milliseconds. Defaults to 1 hour */
|
|
18
|
+
maxAge: number;
|
|
19
|
+
};
|
|
20
|
+
/** Length of the random bytes generator. Output will be hex (x2 string length). */
|
|
21
|
+
tokenLength: number;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/core/options.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,WAQ5B,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,aAAa,EAAE;QACb,uDAAuD;QACvD,QAAQ,EAAE,OAAO,CAAC;QAClB,sEAAsE;QACtE,MAAM,EAAE,OAAO,CAAC;QAChB,0DAA0D;QAC1D,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,mFAAmF;IACnF,WAAW,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultOptions = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Global default options used for the initial setup.
|
|
6
|
+
*/
|
|
7
|
+
exports.defaultOptions = {
|
|
8
|
+
cookieName: 'csrfToken',
|
|
9
|
+
cookieOptions: {
|
|
10
|
+
httpOnly: true,
|
|
11
|
+
secure: process.env.NODE_ENV === 'production',
|
|
12
|
+
maxAge: 3600000,
|
|
13
|
+
},
|
|
14
|
+
tokenLength: 24,
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/core/options.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACU,QAAA,cAAc,GAAgB;IACzC,UAAU,EAAE,WAAW;IACvB,aAAa,EAAE;QACb,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;QAC7C,MAAM,EAAE,OAAO;KAChB;IACD,WAAW,EAAE,EAAE;CAChB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { CsrfCore } from './core/csrf';
|
|
2
|
+
export { CsrfOptions, defaultOptions } from './core/options';
|
|
3
|
+
export { CsrfForbiddenError } from './core/errors';
|
|
4
|
+
export { expressCsrf } from './adapters/express';
|
|
5
|
+
export { honoCsrf } from './adapters/hono';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.honoCsrf = exports.expressCsrf = exports.CsrfForbiddenError = exports.defaultOptions = exports.CsrfCore = void 0;
|
|
4
|
+
var csrf_1 = require("./core/csrf");
|
|
5
|
+
Object.defineProperty(exports, "CsrfCore", { enumerable: true, get: function () { return csrf_1.CsrfCore; } });
|
|
6
|
+
var options_1 = require("./core/options");
|
|
7
|
+
Object.defineProperty(exports, "defaultOptions", { enumerable: true, get: function () { return options_1.defaultOptions; } });
|
|
8
|
+
var errors_1 = require("./core/errors");
|
|
9
|
+
Object.defineProperty(exports, "CsrfForbiddenError", { enumerable: true, get: function () { return errors_1.CsrfForbiddenError; } });
|
|
10
|
+
var express_1 = require("./adapters/express");
|
|
11
|
+
Object.defineProperty(exports, "expressCsrf", { enumerable: true, get: function () { return express_1.expressCsrf; } });
|
|
12
|
+
var hono_1 = require("./adapters/hono");
|
|
13
|
+
Object.defineProperty(exports, "honoCsrf", { enumerable: true, get: function () { return hono_1.honoCsrf; } });
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,oCAAuC;AAA9B,gGAAA,QAAQ,OAAA;AACjB,0CAA6D;AAAvC,yGAAA,cAAc,OAAA;AACpC,wCAAmD;AAA1C,4GAAA,kBAAkB,OAAA;AAC3B,8CAAiD;AAAxC,sGAAA,WAAW,OAAA;AACpB,wCAA2C;AAAlC,gGAAA,QAAQ,OAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sofi-csrf",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Fast, fully-typed, and framework-agnostic CSRF protection library for Node.js. Works with Express and Hono.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"README.md",
|
|
9
|
+
"LICENSE"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build && npm run test"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"csrf",
|
|
18
|
+
"security",
|
|
19
|
+
"express",
|
|
20
|
+
"hono",
|
|
21
|
+
"middleware",
|
|
22
|
+
"framework-agnostic"
|
|
23
|
+
],
|
|
24
|
+
"author": "sofidev",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/cookie-parser": "^1.4.10",
|
|
28
|
+
"@types/express": "^5.0.6",
|
|
29
|
+
"@types/jest": "^30.0.0",
|
|
30
|
+
"@types/node": "^25.5.2",
|
|
31
|
+
"@types/supertest": "^7.2.0",
|
|
32
|
+
"cookie-parser": "^1.4.7",
|
|
33
|
+
"express": "^5.2.1",
|
|
34
|
+
"hono": "^4.12.10",
|
|
35
|
+
"jest": "^30.3.0",
|
|
36
|
+
"supertest": "^7.2.2",
|
|
37
|
+
"ts-jest": "^29.4.9",
|
|
38
|
+
"typescript": "^6.0.2"
|
|
39
|
+
},
|
|
40
|
+
"types": "dist/index.d.ts"
|
|
41
|
+
}
|