nextjs-secure 0.1.1 → 0.2.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/README.md +128 -0
- package/dist/csrf.cjs +187 -10
- package/dist/csrf.cjs.map +1 -1
- package/dist/csrf.d.cts +71 -22
- package/dist/csrf.d.ts +71 -22
- package/dist/csrf.js +182 -8
- package/dist/csrf.js.map +1 -1
- package/dist/index.cjs +190 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +185 -2
- package/dist/index.js.map +1 -1
- package/dist/{memory-g9zyQPy5.d.cts → memory-Dauy-IH3.d.cts} +1 -1
- package/dist/{memory-g9zyQPy5.d.ts → memory-Dauy-IH3.d.ts} +1 -1
- package/dist/rate-limit.d.cts +2 -2
- package/dist/rate-limit.d.ts +2 -2
- package/package.json +1 -1
package/dist/csrf.js
CHANGED
|
@@ -1,14 +1,188 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { webcrypto } from 'crypto';
|
|
2
|
+
|
|
3
|
+
// src/middleware/csrf/token.ts
|
|
4
|
+
var encoder = new TextEncoder();
|
|
5
|
+
function randomBytes(length) {
|
|
6
|
+
const bytes = new Uint8Array(length);
|
|
7
|
+
webcrypto.getRandomValues(bytes);
|
|
8
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
9
|
+
}
|
|
10
|
+
async function createSignature(data, secret) {
|
|
11
|
+
const key = await webcrypto.subtle.importKey(
|
|
12
|
+
"raw",
|
|
13
|
+
encoder.encode(secret),
|
|
14
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
15
|
+
false,
|
|
16
|
+
["sign"]
|
|
17
|
+
);
|
|
18
|
+
const sig = await webcrypto.subtle.sign("HMAC", key, encoder.encode(data));
|
|
19
|
+
return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
20
|
+
}
|
|
21
|
+
function safeCompare(a, b) {
|
|
22
|
+
if (a.length !== b.length) return false;
|
|
23
|
+
let result = 0;
|
|
24
|
+
for (let i = 0; i < a.length; i++) {
|
|
25
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
26
|
+
}
|
|
27
|
+
return result === 0;
|
|
28
|
+
}
|
|
29
|
+
async function createToken(secret, length = 32) {
|
|
30
|
+
const data = randomBytes(length);
|
|
31
|
+
const sig = await createSignature(data, secret);
|
|
32
|
+
return `${data}.${sig}`;
|
|
33
|
+
}
|
|
34
|
+
async function verifyToken(token, secret) {
|
|
35
|
+
if (!token || typeof token !== "string") return false;
|
|
36
|
+
const parts = token.split(".");
|
|
37
|
+
if (parts.length !== 2) return false;
|
|
38
|
+
const [data, sig] = parts;
|
|
39
|
+
if (!data || !sig) return false;
|
|
40
|
+
try {
|
|
41
|
+
const expected = await createSignature(data, secret);
|
|
42
|
+
return safeCompare(sig, expected);
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function tokensMatch(a, b) {
|
|
48
|
+
if (!a || !b) return false;
|
|
49
|
+
return safeCompare(a, b);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/middleware/csrf/middleware.ts
|
|
53
|
+
var DEFAULT_COOKIE = {
|
|
54
|
+
name: "__csrf",
|
|
55
|
+
path: "/",
|
|
56
|
+
httpOnly: true,
|
|
57
|
+
secure: process.env.NODE_ENV === "production",
|
|
58
|
+
sameSite: "strict",
|
|
59
|
+
maxAge: 86400
|
|
60
|
+
// 24h
|
|
61
|
+
};
|
|
62
|
+
var DEFAULT_CONFIG = {
|
|
63
|
+
headerName: "x-csrf-token",
|
|
64
|
+
fieldName: "_csrf",
|
|
65
|
+
tokenLength: 32,
|
|
66
|
+
protectedMethods: ["POST", "PUT", "PATCH", "DELETE"]
|
|
67
|
+
};
|
|
68
|
+
function getSecret(config) {
|
|
69
|
+
const secret = config.secret || process.env.CSRF_SECRET;
|
|
70
|
+
if (!secret) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
"CSRF secret is required. Set config.secret or CSRF_SECRET env variable."
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return secret;
|
|
76
|
+
}
|
|
77
|
+
function buildCookieString(name, value, opts) {
|
|
78
|
+
let cookie = `${name}=${value}`;
|
|
79
|
+
if (opts.path) cookie += `; Path=${opts.path}`;
|
|
80
|
+
if (opts.domain) cookie += `; Domain=${opts.domain}`;
|
|
81
|
+
if (opts.maxAge) cookie += `; Max-Age=${opts.maxAge}`;
|
|
82
|
+
if (opts.httpOnly) cookie += "; HttpOnly";
|
|
83
|
+
if (opts.secure) cookie += "; Secure";
|
|
84
|
+
if (opts.sameSite) cookie += `; SameSite=${opts.sameSite}`;
|
|
85
|
+
return cookie;
|
|
86
|
+
}
|
|
87
|
+
async function extractToken(req, headerName, fieldName) {
|
|
88
|
+
const headerToken = req.headers.get(headerName);
|
|
89
|
+
if (headerToken) return headerToken;
|
|
90
|
+
const contentType = req.headers.get("content-type") || "";
|
|
91
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
92
|
+
try {
|
|
93
|
+
const cloned = req.clone();
|
|
94
|
+
const formData = await cloned.formData();
|
|
95
|
+
const token = formData.get(fieldName);
|
|
96
|
+
if (typeof token === "string") return token;
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (contentType.includes("application/json")) {
|
|
101
|
+
try {
|
|
102
|
+
const cloned = req.clone();
|
|
103
|
+
const body = await cloned.json();
|
|
104
|
+
if (body && typeof body[fieldName] === "string") {
|
|
105
|
+
return body[fieldName];
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
function defaultErrorResponse(_req, reason) {
|
|
113
|
+
return new Response(JSON.stringify({ error: "CSRF validation failed", reason }), {
|
|
114
|
+
status: 403,
|
|
115
|
+
headers: { "Content-Type": "application/json" }
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function withCSRF(handler, config = {}) {
|
|
119
|
+
const secret = getSecret(config);
|
|
120
|
+
const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie };
|
|
121
|
+
const headerName = config.headerName || DEFAULT_CONFIG.headerName;
|
|
122
|
+
const fieldName = config.fieldName || DEFAULT_CONFIG.fieldName;
|
|
123
|
+
const protectedMethods = config.protectedMethods || DEFAULT_CONFIG.protectedMethods;
|
|
124
|
+
const onError = config.onError || defaultErrorResponse;
|
|
125
|
+
return async (req) => {
|
|
126
|
+
const method = req.method.toUpperCase();
|
|
127
|
+
if (!protectedMethods.includes(method)) {
|
|
128
|
+
return handler(req);
|
|
129
|
+
}
|
|
130
|
+
if (config.skip) {
|
|
131
|
+
const shouldSkip = await config.skip(req);
|
|
132
|
+
if (shouldSkip) return handler(req);
|
|
133
|
+
}
|
|
134
|
+
const cookieName = cookieOpts.name || "__csrf";
|
|
135
|
+
const cookieToken = req.cookies.get(cookieName)?.value;
|
|
136
|
+
if (!cookieToken) {
|
|
137
|
+
return onError(req, "missing_cookie");
|
|
138
|
+
}
|
|
139
|
+
const cookieValid = await verifyToken(cookieToken, secret);
|
|
140
|
+
if (!cookieValid) {
|
|
141
|
+
return onError(req, "invalid_cookie");
|
|
142
|
+
}
|
|
143
|
+
const requestToken = await extractToken(req, headerName, fieldName);
|
|
144
|
+
if (!requestToken) {
|
|
145
|
+
return onError(req, "missing_token");
|
|
146
|
+
}
|
|
147
|
+
if (!tokensMatch(cookieToken, requestToken)) {
|
|
148
|
+
return onError(req, "token_mismatch");
|
|
149
|
+
}
|
|
150
|
+
return handler(req);
|
|
151
|
+
};
|
|
4
152
|
}
|
|
5
|
-
function
|
|
6
|
-
|
|
153
|
+
async function generateCSRF(config = {}) {
|
|
154
|
+
const secret = getSecret(config);
|
|
155
|
+
const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie };
|
|
156
|
+
const tokenLength = config.tokenLength || DEFAULT_CONFIG.tokenLength;
|
|
157
|
+
const cookieName = cookieOpts.name || "__csrf";
|
|
158
|
+
const token = await createToken(secret, tokenLength);
|
|
159
|
+
const cookieHeader = buildCookieString(cookieName, token, cookieOpts);
|
|
160
|
+
return { token, cookieHeader };
|
|
7
161
|
}
|
|
8
|
-
function
|
|
9
|
-
|
|
162
|
+
async function validateCSRF(req, config = {}) {
|
|
163
|
+
const secret = getSecret(config);
|
|
164
|
+
const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie };
|
|
165
|
+
const headerName = config.headerName || DEFAULT_CONFIG.headerName;
|
|
166
|
+
const fieldName = config.fieldName || DEFAULT_CONFIG.fieldName;
|
|
167
|
+
const cookieName = cookieOpts.name || "__csrf";
|
|
168
|
+
const cookieToken = req.cookies.get(cookieName)?.value;
|
|
169
|
+
if (!cookieToken) {
|
|
170
|
+
return { valid: false, reason: "missing_cookie" };
|
|
171
|
+
}
|
|
172
|
+
const cookieValid = await verifyToken(cookieToken, secret);
|
|
173
|
+
if (!cookieValid) {
|
|
174
|
+
return { valid: false, reason: "invalid_cookie" };
|
|
175
|
+
}
|
|
176
|
+
const requestToken = await extractToken(req, headerName, fieldName);
|
|
177
|
+
if (!requestToken) {
|
|
178
|
+
return { valid: false, reason: "missing_token" };
|
|
179
|
+
}
|
|
180
|
+
if (!tokensMatch(cookieToken, requestToken)) {
|
|
181
|
+
return { valid: false, reason: "token_mismatch" };
|
|
182
|
+
}
|
|
183
|
+
return { valid: true };
|
|
10
184
|
}
|
|
11
185
|
|
|
12
|
-
export {
|
|
186
|
+
export { createToken, generateCSRF, tokensMatch, validateCSRF, verifyToken, withCSRF };
|
|
13
187
|
//# sourceMappingURL=csrf.js.map
|
|
14
188
|
//# sourceMappingURL=csrf.js.map
|
package/dist/csrf.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware/csrf/index.ts"],"names":[],"mappings":";AAuBO,SAAS,QAAA,GAAW;AACzB,EAAA,MAAM,IAAI,MAAM,uCAAuC,CAAA;AACzD;AAEO,SAAS,iBAAA,GAAoB;AAClC,EAAA,MAAM,IAAI,MAAM,uCAAuC,CAAA;AACzD;AAEO,SAAS,iBAAA,GAAoB;AAClC,EAAA,MAAM,IAAI,MAAM,uCAAuC,CAAA;AACzD","file":"csrf.js","sourcesContent":["/**\n * CSRF Protection Middleware (Coming Soon)\n *\n * @example\n * ```typescript\n * import { withCsrf, generateCsrfToken } from 'next-secure/csrf'\n *\n * // Generate token\n * export async function GET() {\n * const token = await generateCsrfToken()\n * return Response.json({ csrfToken: token })\n * }\n *\n * // Validate token\n * export const POST = withCsrf(async (req) => {\n * return Response.json({ ok: true })\n * })\n * ```\n *\n * @packageDocumentation\n */\n\n// Placeholder for CSRF middleware\nexport function withCsrf() {\n throw new Error('CSRF middleware coming soon in v0.2.0')\n}\n\nexport function generateCsrfToken() {\n throw new Error('CSRF middleware coming soon in v0.2.0')\n}\n\nexport function validateCsrfToken() {\n throw new Error('CSRF middleware coming soon in v0.2.0')\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/middleware/csrf/token.ts","../src/middleware/csrf/middleware.ts"],"names":[],"mappings":"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","file":"csrf.js","sourcesContent":["import { webcrypto } from 'node:crypto'\n\nconst encoder = new TextEncoder()\n\n/**\n * Generate random bytes as hex string\n */\nexport function randomBytes(length: number): string {\n const bytes = new Uint8Array(length)\n webcrypto.getRandomValues(bytes)\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Create HMAC signature\n */\nasync function createSignature(data: string, secret: string): Promise<string> {\n const key = await webcrypto.subtle.importKey(\n 'raw',\n encoder.encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign']\n )\n\n const sig = await webcrypto.subtle.sign('HMAC', key, encoder.encode(data))\n return Array.from(new Uint8Array(sig))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n/**\n * Constant-time string comparison to prevent timing attacks\n */\nfunction safeCompare(a: string, b: string): boolean {\n if (a.length !== b.length) return false\n\n let result = 0\n for (let i = 0; i < a.length; i++) {\n result |= a.charCodeAt(i) ^ b.charCodeAt(i)\n }\n return result === 0\n}\n\n/**\n * Create a signed CSRF token\n */\nexport async function createToken(\n secret: string,\n length: number = 32\n): Promise<string> {\n const data = randomBytes(length)\n const sig = await createSignature(data, secret)\n return `${data}.${sig}`\n}\n\n/**\n * Verify a signed CSRF token\n */\nexport async function verifyToken(\n token: string,\n secret: string\n): Promise<boolean> {\n if (!token || typeof token !== 'string') return false\n\n const parts = token.split('.')\n if (parts.length !== 2) return false\n\n const [data, sig] = parts\n if (!data || !sig) return false\n\n try {\n const expected = await createSignature(data, secret)\n return safeCompare(sig, expected)\n } catch {\n return false\n }\n}\n\n/**\n * Compare two tokens (constant-time)\n */\nexport function tokensMatch(a: string, b: string): boolean {\n if (!a || !b) return false\n return safeCompare(a, b)\n}\n","import type { NextRequest } from 'next/server'\nimport type { CSRFConfig, CSRFCookieOptions } from './types'\nimport { createToken, verifyToken, tokensMatch } from './token'\n\ntype RouteHandler = (req: NextRequest) => Response | Promise<Response>\n\nconst DEFAULT_COOKIE: CSRFCookieOptions = {\n name: '__csrf',\n path: '/',\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: 86400, // 24h\n}\n\nconst DEFAULT_CONFIG: Required<Omit<CSRFConfig, 'skip' | 'onError'>> = {\n cookie: DEFAULT_COOKIE,\n headerName: 'x-csrf-token',\n fieldName: '_csrf',\n secret: '',\n tokenLength: 32,\n protectedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'],\n}\n\nfunction getSecret(config: CSRFConfig): string {\n const secret = config.secret || process.env.CSRF_SECRET\n if (!secret) {\n throw new Error(\n 'CSRF secret is required. Set config.secret or CSRF_SECRET env variable.'\n )\n }\n return secret\n}\n\nfunction buildCookieString(name: string, value: string, opts: CSRFCookieOptions): string {\n let cookie = `${name}=${value}`\n\n if (opts.path) cookie += `; Path=${opts.path}`\n if (opts.domain) cookie += `; Domain=${opts.domain}`\n if (opts.maxAge) cookie += `; Max-Age=${opts.maxAge}`\n if (opts.httpOnly) cookie += '; HttpOnly'\n if (opts.secure) cookie += '; Secure'\n if (opts.sameSite) cookie += `; SameSite=${opts.sameSite}`\n\n return cookie\n}\n\n/**\n * Extract token from request (header or body)\n */\nasync function extractToken(\n req: NextRequest,\n headerName: string,\n fieldName: string\n): Promise<string | null> {\n // check header first\n const headerToken = req.headers.get(headerName)\n if (headerToken) return headerToken\n\n // try to get from form data\n const contentType = req.headers.get('content-type') || ''\n\n if (contentType.includes('application/x-www-form-urlencoded')) {\n try {\n const cloned = req.clone()\n const formData = await cloned.formData()\n const token = formData.get(fieldName)\n if (typeof token === 'string') return token\n } catch {\n // ignore parse errors\n }\n }\n\n if (contentType.includes('application/json')) {\n try {\n const cloned = req.clone()\n const body = await cloned.json()\n if (body && typeof body[fieldName] === 'string') {\n return body[fieldName]\n }\n } catch {\n // ignore parse errors\n }\n }\n\n return null\n}\n\nfunction defaultErrorResponse(_req: NextRequest, reason: string): Response {\n return new Response(JSON.stringify({ error: 'CSRF validation failed', reason }), {\n status: 403,\n headers: { 'Content-Type': 'application/json' },\n })\n}\n\n/**\n * CSRF protection middleware\n *\n * Uses double submit cookie pattern:\n * 1. Server sets a signed token in a cookie\n * 2. Client sends the same token in header/body\n * 3. Server compares both values\n */\nexport function withCSRF(handler: RouteHandler, config: CSRFConfig = {}): RouteHandler {\n const secret = getSecret(config)\n const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie }\n const headerName = config.headerName || DEFAULT_CONFIG.headerName\n const fieldName = config.fieldName || DEFAULT_CONFIG.fieldName\n const protectedMethods = config.protectedMethods || DEFAULT_CONFIG.protectedMethods\n const onError = config.onError || defaultErrorResponse\n\n return async (req: NextRequest): Promise<Response> => {\n const method = req.method.toUpperCase()\n\n // skip unprotected methods\n if (!protectedMethods.includes(method)) {\n return handler(req)\n }\n\n // custom skip logic\n if (config.skip) {\n const shouldSkip = await config.skip(req)\n if (shouldSkip) return handler(req)\n }\n\n const cookieName = cookieOpts.name || '__csrf'\n const cookieToken = req.cookies.get(cookieName)?.value\n\n // no cookie = first request, reject\n if (!cookieToken) {\n return onError(req, 'missing_cookie')\n }\n\n // verify cookie token is valid (signed by us)\n const cookieValid = await verifyToken(cookieToken, secret)\n if (!cookieValid) {\n return onError(req, 'invalid_cookie')\n }\n\n // get token from request\n const requestToken = await extractToken(req, headerName, fieldName)\n if (!requestToken) {\n return onError(req, 'missing_token')\n }\n\n // compare tokens\n if (!tokensMatch(cookieToken, requestToken)) {\n return onError(req, 'token_mismatch')\n }\n\n return handler(req)\n }\n}\n\n/**\n * Generate a new CSRF token and cookie header\n * Use this in GET routes to set the initial token\n */\nexport async function generateCSRF(config: CSRFConfig = {}): Promise<{\n token: string\n cookieHeader: string\n}> {\n const secret = getSecret(config)\n const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie }\n const tokenLength = config.tokenLength || DEFAULT_CONFIG.tokenLength\n const cookieName = cookieOpts.name || '__csrf'\n\n const token = await createToken(secret, tokenLength)\n const cookieHeader = buildCookieString(cookieName, token, cookieOpts)\n\n return { token, cookieHeader }\n}\n\n/**\n * Validate a CSRF token without middleware\n * Useful for custom validation flows\n */\nexport async function validateCSRF(\n req: NextRequest,\n config: CSRFConfig = {}\n): Promise<{ valid: boolean; reason?: string }> {\n const secret = getSecret(config)\n const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie }\n const headerName = config.headerName || DEFAULT_CONFIG.headerName\n const fieldName = config.fieldName || DEFAULT_CONFIG.fieldName\n const cookieName = cookieOpts.name || '__csrf'\n\n const cookieToken = req.cookies.get(cookieName)?.value\n if (!cookieToken) {\n return { valid: false, reason: 'missing_cookie' }\n }\n\n const cookieValid = await verifyToken(cookieToken, secret)\n if (!cookieValid) {\n return { valid: false, reason: 'invalid_cookie' }\n }\n\n const requestToken = await extractToken(req, headerName, fieldName)\n if (!requestToken) {\n return { valid: false, reason: 'missing_token' }\n }\n\n if (!tokensMatch(cookieToken, requestToken)) {\n return { valid: false, reason: 'token_mismatch' }\n }\n\n return { valid: true }\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var crypto$1 = require('crypto');
|
|
4
|
+
|
|
3
5
|
// src/core/errors.ts
|
|
4
6
|
var SecureError = class extends Error {
|
|
5
7
|
/**
|
|
@@ -994,9 +996,190 @@ function clearAllRateLimits() {
|
|
|
994
996
|
defaultStore.clear();
|
|
995
997
|
}
|
|
996
998
|
}
|
|
999
|
+
var encoder = new TextEncoder();
|
|
1000
|
+
function randomBytes(length) {
|
|
1001
|
+
const bytes = new Uint8Array(length);
|
|
1002
|
+
crypto$1.webcrypto.getRandomValues(bytes);
|
|
1003
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1004
|
+
}
|
|
1005
|
+
async function createSignature(data, secret) {
|
|
1006
|
+
const key = await crypto$1.webcrypto.subtle.importKey(
|
|
1007
|
+
"raw",
|
|
1008
|
+
encoder.encode(secret),
|
|
1009
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
1010
|
+
false,
|
|
1011
|
+
["sign"]
|
|
1012
|
+
);
|
|
1013
|
+
const sig = await crypto$1.webcrypto.subtle.sign("HMAC", key, encoder.encode(data));
|
|
1014
|
+
return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1015
|
+
}
|
|
1016
|
+
function safeCompare(a, b) {
|
|
1017
|
+
if (a.length !== b.length) return false;
|
|
1018
|
+
let result = 0;
|
|
1019
|
+
for (let i = 0; i < a.length; i++) {
|
|
1020
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
1021
|
+
}
|
|
1022
|
+
return result === 0;
|
|
1023
|
+
}
|
|
1024
|
+
async function createToken(secret, length = 32) {
|
|
1025
|
+
const data = randomBytes(length);
|
|
1026
|
+
const sig = await createSignature(data, secret);
|
|
1027
|
+
return `${data}.${sig}`;
|
|
1028
|
+
}
|
|
1029
|
+
async function verifyToken(token, secret) {
|
|
1030
|
+
if (!token || typeof token !== "string") return false;
|
|
1031
|
+
const parts = token.split(".");
|
|
1032
|
+
if (parts.length !== 2) return false;
|
|
1033
|
+
const [data, sig] = parts;
|
|
1034
|
+
if (!data || !sig) return false;
|
|
1035
|
+
try {
|
|
1036
|
+
const expected = await createSignature(data, secret);
|
|
1037
|
+
return safeCompare(sig, expected);
|
|
1038
|
+
} catch {
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function tokensMatch(a, b) {
|
|
1043
|
+
if (!a || !b) return false;
|
|
1044
|
+
return safeCompare(a, b);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// src/middleware/csrf/middleware.ts
|
|
1048
|
+
var DEFAULT_COOKIE = {
|
|
1049
|
+
name: "__csrf",
|
|
1050
|
+
path: "/",
|
|
1051
|
+
httpOnly: true,
|
|
1052
|
+
secure: process.env.NODE_ENV === "production",
|
|
1053
|
+
sameSite: "strict",
|
|
1054
|
+
maxAge: 86400
|
|
1055
|
+
// 24h
|
|
1056
|
+
};
|
|
1057
|
+
var DEFAULT_CONFIG2 = {
|
|
1058
|
+
headerName: "x-csrf-token",
|
|
1059
|
+
fieldName: "_csrf",
|
|
1060
|
+
tokenLength: 32,
|
|
1061
|
+
protectedMethods: ["POST", "PUT", "PATCH", "DELETE"]
|
|
1062
|
+
};
|
|
1063
|
+
function getSecret(config) {
|
|
1064
|
+
const secret = config.secret || process.env.CSRF_SECRET;
|
|
1065
|
+
if (!secret) {
|
|
1066
|
+
throw new Error(
|
|
1067
|
+
"CSRF secret is required. Set config.secret or CSRF_SECRET env variable."
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
return secret;
|
|
1071
|
+
}
|
|
1072
|
+
function buildCookieString(name, value, opts) {
|
|
1073
|
+
let cookie = `${name}=${value}`;
|
|
1074
|
+
if (opts.path) cookie += `; Path=${opts.path}`;
|
|
1075
|
+
if (opts.domain) cookie += `; Domain=${opts.domain}`;
|
|
1076
|
+
if (opts.maxAge) cookie += `; Max-Age=${opts.maxAge}`;
|
|
1077
|
+
if (opts.httpOnly) cookie += "; HttpOnly";
|
|
1078
|
+
if (opts.secure) cookie += "; Secure";
|
|
1079
|
+
if (opts.sameSite) cookie += `; SameSite=${opts.sameSite}`;
|
|
1080
|
+
return cookie;
|
|
1081
|
+
}
|
|
1082
|
+
async function extractToken(req, headerName, fieldName) {
|
|
1083
|
+
const headerToken = req.headers.get(headerName);
|
|
1084
|
+
if (headerToken) return headerToken;
|
|
1085
|
+
const contentType = req.headers.get("content-type") || "";
|
|
1086
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
1087
|
+
try {
|
|
1088
|
+
const cloned = req.clone();
|
|
1089
|
+
const formData = await cloned.formData();
|
|
1090
|
+
const token = formData.get(fieldName);
|
|
1091
|
+
if (typeof token === "string") return token;
|
|
1092
|
+
} catch {
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
if (contentType.includes("application/json")) {
|
|
1096
|
+
try {
|
|
1097
|
+
const cloned = req.clone();
|
|
1098
|
+
const body = await cloned.json();
|
|
1099
|
+
if (body && typeof body[fieldName] === "string") {
|
|
1100
|
+
return body[fieldName];
|
|
1101
|
+
}
|
|
1102
|
+
} catch {
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
function defaultErrorResponse(_req, reason) {
|
|
1108
|
+
return new Response(JSON.stringify({ error: "CSRF validation failed", reason }), {
|
|
1109
|
+
status: 403,
|
|
1110
|
+
headers: { "Content-Type": "application/json" }
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
function withCSRF(handler, config = {}) {
|
|
1114
|
+
const secret = getSecret(config);
|
|
1115
|
+
const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie };
|
|
1116
|
+
const headerName = config.headerName || DEFAULT_CONFIG2.headerName;
|
|
1117
|
+
const fieldName = config.fieldName || DEFAULT_CONFIG2.fieldName;
|
|
1118
|
+
const protectedMethods = config.protectedMethods || DEFAULT_CONFIG2.protectedMethods;
|
|
1119
|
+
const onError = config.onError || defaultErrorResponse;
|
|
1120
|
+
return async (req) => {
|
|
1121
|
+
const method = req.method.toUpperCase();
|
|
1122
|
+
if (!protectedMethods.includes(method)) {
|
|
1123
|
+
return handler(req);
|
|
1124
|
+
}
|
|
1125
|
+
if (config.skip) {
|
|
1126
|
+
const shouldSkip = await config.skip(req);
|
|
1127
|
+
if (shouldSkip) return handler(req);
|
|
1128
|
+
}
|
|
1129
|
+
const cookieName = cookieOpts.name || "__csrf";
|
|
1130
|
+
const cookieToken = req.cookies.get(cookieName)?.value;
|
|
1131
|
+
if (!cookieToken) {
|
|
1132
|
+
return onError(req, "missing_cookie");
|
|
1133
|
+
}
|
|
1134
|
+
const cookieValid = await verifyToken(cookieToken, secret);
|
|
1135
|
+
if (!cookieValid) {
|
|
1136
|
+
return onError(req, "invalid_cookie");
|
|
1137
|
+
}
|
|
1138
|
+
const requestToken = await extractToken(req, headerName, fieldName);
|
|
1139
|
+
if (!requestToken) {
|
|
1140
|
+
return onError(req, "missing_token");
|
|
1141
|
+
}
|
|
1142
|
+
if (!tokensMatch(cookieToken, requestToken)) {
|
|
1143
|
+
return onError(req, "token_mismatch");
|
|
1144
|
+
}
|
|
1145
|
+
return handler(req);
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
async function generateCSRF(config = {}) {
|
|
1149
|
+
const secret = getSecret(config);
|
|
1150
|
+
const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie };
|
|
1151
|
+
const tokenLength = config.tokenLength || DEFAULT_CONFIG2.tokenLength;
|
|
1152
|
+
const cookieName = cookieOpts.name || "__csrf";
|
|
1153
|
+
const token = await createToken(secret, tokenLength);
|
|
1154
|
+
const cookieHeader = buildCookieString(cookieName, token, cookieOpts);
|
|
1155
|
+
return { token, cookieHeader };
|
|
1156
|
+
}
|
|
1157
|
+
async function validateCSRF(req, config = {}) {
|
|
1158
|
+
const secret = getSecret(config);
|
|
1159
|
+
const cookieOpts = { ...DEFAULT_COOKIE, ...config.cookie };
|
|
1160
|
+
const headerName = config.headerName || DEFAULT_CONFIG2.headerName;
|
|
1161
|
+
const fieldName = config.fieldName || DEFAULT_CONFIG2.fieldName;
|
|
1162
|
+
const cookieName = cookieOpts.name || "__csrf";
|
|
1163
|
+
const cookieToken = req.cookies.get(cookieName)?.value;
|
|
1164
|
+
if (!cookieToken) {
|
|
1165
|
+
return { valid: false, reason: "missing_cookie" };
|
|
1166
|
+
}
|
|
1167
|
+
const cookieValid = await verifyToken(cookieToken, secret);
|
|
1168
|
+
if (!cookieValid) {
|
|
1169
|
+
return { valid: false, reason: "invalid_cookie" };
|
|
1170
|
+
}
|
|
1171
|
+
const requestToken = await extractToken(req, headerName, fieldName);
|
|
1172
|
+
if (!requestToken) {
|
|
1173
|
+
return { valid: false, reason: "missing_token" };
|
|
1174
|
+
}
|
|
1175
|
+
if (!tokensMatch(cookieToken, requestToken)) {
|
|
1176
|
+
return { valid: false, reason: "token_mismatch" };
|
|
1177
|
+
}
|
|
1178
|
+
return { valid: true };
|
|
1179
|
+
}
|
|
997
1180
|
|
|
998
1181
|
// src/index.ts
|
|
999
|
-
var VERSION = "0.
|
|
1182
|
+
var VERSION = "0.2.0";
|
|
1000
1183
|
|
|
1001
1184
|
exports.AuthenticationError = AuthenticationError;
|
|
1002
1185
|
exports.AuthorizationError = AuthorizationError;
|
|
@@ -1010,9 +1193,11 @@ exports.ValidationError = ValidationError;
|
|
|
1010
1193
|
exports.anonymizeIp = anonymizeIp;
|
|
1011
1194
|
exports.checkRateLimit = checkRateLimit;
|
|
1012
1195
|
exports.clearAllRateLimits = clearAllRateLimits;
|
|
1196
|
+
exports.createCSRFToken = createToken;
|
|
1013
1197
|
exports.createMemoryStore = createMemoryStore;
|
|
1014
1198
|
exports.createRateLimiter = createRateLimiter;
|
|
1015
1199
|
exports.formatDuration = formatDuration;
|
|
1200
|
+
exports.generateCSRF = generateCSRF;
|
|
1016
1201
|
exports.getClientIp = getClientIp;
|
|
1017
1202
|
exports.getGeoInfo = getGeoInfo;
|
|
1018
1203
|
exports.getGlobalMemoryStore = getGlobalMemoryStore;
|
|
@@ -1028,6 +1213,10 @@ exports.parseDuration = parseDuration;
|
|
|
1028
1213
|
exports.resetRateLimit = resetRateLimit;
|
|
1029
1214
|
exports.sleep = sleep;
|
|
1030
1215
|
exports.toSecureError = toSecureError;
|
|
1216
|
+
exports.tokensMatch = tokensMatch;
|
|
1217
|
+
exports.validateCSRF = validateCSRF;
|
|
1218
|
+
exports.verifyCSRFToken = verifyToken;
|
|
1219
|
+
exports.withCSRF = withCSRF;
|
|
1031
1220
|
exports.withRateLimit = withRateLimit;
|
|
1032
1221
|
//# sourceMappingURL=index.cjs.map
|
|
1033
1222
|
//# sourceMappingURL=index.cjs.map
|