@shware/http 1.2.2 → 1.2.3
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/dist/hono/__tests__/csrf.test.cjs +166 -0
- package/dist/hono/__tests__/csrf.test.cjs.map +1 -1
- package/dist/hono/__tests__/csrf.test.mjs +166 -0
- package/dist/hono/__tests__/csrf.test.mjs.map +1 -1
- package/dist/hono/csrf.cjs +14 -12
- package/dist/hono/csrf.cjs.map +1 -1
- package/dist/hono/csrf.d.cts +10 -0
- package/dist/hono/csrf.d.ts +10 -0
- package/dist/hono/csrf.mjs +14 -12
- package/dist/hono/csrf.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -158,5 +158,171 @@ describe("CSRF Protection", () => {
|
|
|
158
158
|
res = await app.request("/public/data", { method: "POST" });
|
|
159
159
|
expect(res.status).toBe(200);
|
|
160
160
|
});
|
|
161
|
+
describe("Origin bypass", () => {
|
|
162
|
+
it("should allow requests from configured origins", async () => {
|
|
163
|
+
app.use((0, import_csrf.csrf)({ origin: ["https://example.com", "https://trusted.com"] }));
|
|
164
|
+
app.post("/test", (c) => c.text("OK"));
|
|
165
|
+
const res = await app.request("/test", {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: { Origin: "https://example.com" }
|
|
168
|
+
});
|
|
169
|
+
expect(res.status).toBe(200);
|
|
170
|
+
expect(await res.text()).toBe("OK");
|
|
171
|
+
});
|
|
172
|
+
it("should allow requests from any configured origin", async () => {
|
|
173
|
+
app.use((0, import_csrf.csrf)({ origin: ["https://example.com", "https://trusted.com"] }));
|
|
174
|
+
app.post("/test", (c) => c.text("OK"));
|
|
175
|
+
const res = await app.request("/test", {
|
|
176
|
+
method: "POST",
|
|
177
|
+
headers: { Origin: "https://trusted.com" }
|
|
178
|
+
});
|
|
179
|
+
expect(res.status).toBe(200);
|
|
180
|
+
});
|
|
181
|
+
it("should reject requests from non-configured origins", async () => {
|
|
182
|
+
app.use((0, import_csrf.csrf)({ origin: ["https://example.com"] }));
|
|
183
|
+
app.post("/test", (c) => c.text("OK"));
|
|
184
|
+
const res = await app.request("/test", {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: { Origin: "https://malicious.com" }
|
|
187
|
+
});
|
|
188
|
+
expect(res.status).toBe(403);
|
|
189
|
+
});
|
|
190
|
+
it("should reject requests without origin header", async () => {
|
|
191
|
+
app.use((0, import_csrf.csrf)({ origin: ["https://example.com"] }));
|
|
192
|
+
app.post("/test", (c) => c.text("OK"));
|
|
193
|
+
const res = await app.request("/test", { method: "POST" });
|
|
194
|
+
expect(res.status).toBe(403);
|
|
195
|
+
});
|
|
196
|
+
it("should handle empty origin list", async () => {
|
|
197
|
+
app.use((0, import_csrf.csrf)({ origin: [] }));
|
|
198
|
+
app.post("/test", (c) => c.text("OK"));
|
|
199
|
+
const res = await app.request("/test", {
|
|
200
|
+
method: "POST",
|
|
201
|
+
headers: { Origin: "https://example.com" }
|
|
202
|
+
});
|
|
203
|
+
expect(res.status).toBe(403);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe("Sec-Fetch-Site bypass", () => {
|
|
207
|
+
it("should allow requests with configured sec-fetch-site values", async () => {
|
|
208
|
+
app.use((0, import_csrf.csrf)({ secFetchSite: ["same-origin", "same-site"] }));
|
|
209
|
+
app.post("/test", (c) => c.text("OK"));
|
|
210
|
+
const res = await app.request("/test", {
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: { "Sec-Fetch-Site": "same-origin" }
|
|
213
|
+
});
|
|
214
|
+
expect(res.status).toBe(200);
|
|
215
|
+
expect(await res.text()).toBe("OK");
|
|
216
|
+
});
|
|
217
|
+
it("should allow requests with any configured sec-fetch-site value", async () => {
|
|
218
|
+
app.use((0, import_csrf.csrf)({ secFetchSite: ["same-origin", "same-site", "cross-origin"] }));
|
|
219
|
+
app.post("/test", (c) => c.text("OK"));
|
|
220
|
+
const res = await app.request("/test", {
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers: { "Sec-Fetch-Site": "cross-origin" }
|
|
223
|
+
});
|
|
224
|
+
expect(res.status).toBe(200);
|
|
225
|
+
});
|
|
226
|
+
it("should reject requests with non-configured sec-fetch-site values", async () => {
|
|
227
|
+
app.use((0, import_csrf.csrf)({ secFetchSite: ["same-origin"] }));
|
|
228
|
+
app.post("/test", (c) => c.text("OK"));
|
|
229
|
+
const res = await app.request("/test", {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: { "Sec-Fetch-Site": "same-site" }
|
|
232
|
+
});
|
|
233
|
+
expect(res.status).toBe(403);
|
|
234
|
+
});
|
|
235
|
+
it("should reject requests without sec-fetch-site header", async () => {
|
|
236
|
+
app.use((0, import_csrf.csrf)({ secFetchSite: ["same-origin"] }));
|
|
237
|
+
app.post("/test", (c) => c.text("OK"));
|
|
238
|
+
const res = await app.request("/test", { method: "POST" });
|
|
239
|
+
expect(res.status).toBe(403);
|
|
240
|
+
});
|
|
241
|
+
it("should handle all valid sec-fetch-site values", async () => {
|
|
242
|
+
app.use((0, import_csrf.csrf)({ secFetchSite: ["same-origin", "same-site", "none", "cross-origin"] }));
|
|
243
|
+
app.post("/test", (c) => c.text("OK"));
|
|
244
|
+
const validValues = ["same-origin", "same-site", "none", "cross-origin"];
|
|
245
|
+
for (const value of validValues) {
|
|
246
|
+
const res = await app.request("/test", {
|
|
247
|
+
method: "POST",
|
|
248
|
+
headers: { "Sec-Fetch-Site": value }
|
|
249
|
+
});
|
|
250
|
+
expect(res.status).toBe(200);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
it("should handle empty sec-fetch-site list", async () => {
|
|
254
|
+
app.use((0, import_csrf.csrf)({ secFetchSite: [] }));
|
|
255
|
+
app.post("/test", (c) => c.text("OK"));
|
|
256
|
+
const res = await app.request("/test", {
|
|
257
|
+
method: "POST",
|
|
258
|
+
headers: { "Sec-Fetch-Site": "same-origin" }
|
|
259
|
+
});
|
|
260
|
+
expect(res.status).toBe(403);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
describe("Combined origin and sec-fetch-site bypass", () => {
|
|
264
|
+
it("should allow requests matching either origin or sec-fetch-site", async () => {
|
|
265
|
+
app.use(
|
|
266
|
+
(0, import_csrf.csrf)({
|
|
267
|
+
origin: ["https://example.com"],
|
|
268
|
+
secFetchSite: ["same-site"]
|
|
269
|
+
})
|
|
270
|
+
);
|
|
271
|
+
app.post("/test", (c) => c.text("OK"));
|
|
272
|
+
let res = await app.request("/test", {
|
|
273
|
+
method: "POST",
|
|
274
|
+
headers: { Origin: "https://example.com" }
|
|
275
|
+
});
|
|
276
|
+
expect(res.status).toBe(200);
|
|
277
|
+
res = await app.request("/test", {
|
|
278
|
+
method: "POST",
|
|
279
|
+
headers: { "Sec-Fetch-Site": "same-site" }
|
|
280
|
+
});
|
|
281
|
+
expect(res.status).toBe(200);
|
|
282
|
+
});
|
|
283
|
+
it("should reject requests matching neither origin nor sec-fetch-site", async () => {
|
|
284
|
+
app.use(
|
|
285
|
+
(0, import_csrf.csrf)({
|
|
286
|
+
origin: ["https://example.com"],
|
|
287
|
+
secFetchSite: ["same-origin"]
|
|
288
|
+
})
|
|
289
|
+
);
|
|
290
|
+
app.post("/test", (c) => c.text("OK"));
|
|
291
|
+
const res = await app.request("/test", {
|
|
292
|
+
method: "POST",
|
|
293
|
+
headers: {
|
|
294
|
+
Origin: "https://malicious.com",
|
|
295
|
+
"Sec-Fetch-Site": "cross-origin"
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
expect(res.status).toBe(403);
|
|
299
|
+
});
|
|
300
|
+
it("should work with origin, sec-fetch-site and ignore rules combined", async () => {
|
|
301
|
+
app.use(
|
|
302
|
+
(0, import_csrf.csrf)({
|
|
303
|
+
origin: ["https://example.com"],
|
|
304
|
+
secFetchSite: ["same-site"],
|
|
305
|
+
ignores: [{ path: "/webhook/*", methods: ["POST"] }]
|
|
306
|
+
})
|
|
307
|
+
);
|
|
308
|
+
app.post("/test", (c) => c.text("OK"));
|
|
309
|
+
app.post("/webhook/stripe", (c) => c.text("OK"));
|
|
310
|
+
app.post("/api/data", (c) => c.text("OK"));
|
|
311
|
+
let res = await app.request("/test", {
|
|
312
|
+
method: "POST",
|
|
313
|
+
headers: { Origin: "https://example.com" }
|
|
314
|
+
});
|
|
315
|
+
expect(res.status).toBe(200);
|
|
316
|
+
res = await app.request("/test", {
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: { "Sec-Fetch-Site": "same-site" }
|
|
319
|
+
});
|
|
320
|
+
expect(res.status).toBe(200);
|
|
321
|
+
res = await app.request("/webhook/stripe", { method: "POST" });
|
|
322
|
+
expect(res.status).toBe(200);
|
|
323
|
+
res = await app.request("/api/data", { method: "POST" });
|
|
324
|
+
expect(res.status).toBe(403);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
161
327
|
});
|
|
162
328
|
//# sourceMappingURL=csrf.test.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/hono/__tests__/csrf.test.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport { csrf } from '../csrf';\nimport { type Env, errorHandler } from '../handler';\n\ndescribe('CSRF Protection', () => {\n let app: Hono<Env>;\n\n beforeEach(() => {\n app = new Hono();\n app.onError(errorHandler);\n });\n\n it('should allow GET requests without CSRF token', async () => {\n app.use(csrf());\n app.get('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test');\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should allow HEAD requests without CSRF token', async () => {\n app.use(csrf());\n app.all('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'HEAD' });\n expect(res.status).toBe(200);\n });\n\n it('should allow OPTIONS requests without CSRF token', async () => {\n app.use(csrf());\n app.options('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'OPTIONS' });\n expect(res.status).toBe(200);\n });\n\n it('should reject POST requests without CSRF token', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n\n it('should reject POST requests with mismatched tokens', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': 'header-token', Cookie: 'XSRF-TOKEN=cookie-token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should allow POST requests with matching tokens', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n 'X-XSRF-TOKEN': 'matching-token',\n Cookie: 'XSRF-TOKEN=matching-token',\n },\n });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should use custom cookie and header names', async () => {\n app.use(csrf({ cookieName: 'csrf-token', headerName: 'X-CSRF-Token' }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n 'X-CSRF-Token': 'test-token',\n Cookie: 'csrf-token=test-token',\n },\n });\n expect(res.status).toBe(200);\n });\n\n it('should ignore specified paths', async () => {\n app.use(csrf({ ignores: [{ path: '/webhook/*', methods: ['POST'] }] }));\n app.post('/webhook/stripe', (c) => c.text('OK'));\n app.post('/api/data', (c) => c.text('OK'));\n\n // Ignored path should work without CSRF token\n const webhookRes = await app.request('/webhook/stripe', { method: 'POST' });\n expect(webhookRes.status).toBe(200);\n\n // Non-ignored path should require CSRF token\n const apiRes = await app.request('/api/data', { method: 'POST' });\n expect(apiRes.status).toBe(403);\n });\n\n it('should ignore all methods for a path when methods not specified', async () => {\n app.use(csrf({ ignores: [{ path: '/auth/apple/callback' }] }));\n app.post('/auth/apple/callback', (c) => c.text('OK'));\n app.put('/auth/apple/callback', (c) => c.text('OK'));\n\n const postRes = await app.request('/auth/apple/callback', { method: 'POST' });\n expect(postRes.status).toBe(200);\n\n const putRes = await app.request('/auth/apple/callback', { method: 'PUT' });\n expect(putRes.status).toBe(200);\n });\n\n it('should handle empty tokens safely', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': '', Cookie: 'XSRF-TOKEN=' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should handle missing cookie', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': 'token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should handle missing header', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Cookie: 'XSRF-TOKEN=token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should use custom error message', async () => {\n app.use(csrf({ errorMessage: 'Custom CSRF error' }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n const body = await res.json();\n expect(body.error.message).toBe('Custom CSRF error');\n });\n\n it('should work with custom safe methods', async () => {\n app.use(csrf({ safeMethods: ['GET'] }));\n app.all('/test', (c) => c.text('OK'));\n\n // HEAD is no longer safe, should require CSRF token\n const res = await app.request('/test', { method: 'HEAD' });\n expect(res.status).toBe(403);\n });\n\n it('should handle complex ignore patterns', async () => {\n app.use(\n csrf({\n ignores: [\n { path: '/api/v1/*', methods: ['GET', 'POST'] },\n { path: '/api/v2/*', methods: ['POST'] },\n { path: '/public/*' }, // All methods\n ],\n })\n );\n\n app.get('/api/v1/users', (c) => c.text('OK'));\n app.post('/api/v1/users', (c) => c.text('OK'));\n app.put('/api/v1/users', (c) => c.text('OK'));\n app.post('/api/v2/users', (c) => c.text('OK'));\n app.get('/api/v2/users', (c) => c.text('OK'));\n app.post('/public/data', (c) => c.text('OK'));\n\n // Ignored GET and POST for /api/v1/*\n let res = await app.request('/api/v1/users', { method: 'GET' });\n expect(res.status).toBe(200);\n\n res = await app.request('/api/v1/users', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // PUT not ignored for /api/v1/*\n res = await app.request('/api/v1/users', { method: 'PUT' });\n expect(res.status).toBe(403);\n\n // Only POST ignored for /api/v2/*\n res = await app.request('/api/v2/users', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // GET not ignored for /api/v2/* (but GET is safe by default)\n res = await app.request('/api/v2/users', { method: 'GET' });\n expect(res.status).toBe(200);\n\n // All methods ignored for /public/*\n res = await app.request('/public/data', { method: 'POST' });\n expect(res.status).toBe(200);\n });\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/hono/__tests__/csrf.test.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport { csrf } from '../csrf';\nimport { type Env, errorHandler } from '../handler';\n\ndescribe('CSRF Protection', () => {\n let app: Hono<Env>;\n\n beforeEach(() => {\n app = new Hono();\n app.onError(errorHandler);\n });\n\n it('should allow GET requests without CSRF token', async () => {\n app.use(csrf());\n app.get('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test');\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should allow HEAD requests without CSRF token', async () => {\n app.use(csrf());\n app.all('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'HEAD' });\n expect(res.status).toBe(200);\n });\n\n it('should allow OPTIONS requests without CSRF token', async () => {\n app.use(csrf());\n app.options('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'OPTIONS' });\n expect(res.status).toBe(200);\n });\n\n it('should reject POST requests without CSRF token', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n\n it('should reject POST requests with mismatched tokens', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': 'header-token', Cookie: 'XSRF-TOKEN=cookie-token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should allow POST requests with matching tokens', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n 'X-XSRF-TOKEN': 'matching-token',\n Cookie: 'XSRF-TOKEN=matching-token',\n },\n });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should use custom cookie and header names', async () => {\n app.use(csrf({ cookieName: 'csrf-token', headerName: 'X-CSRF-Token' }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n 'X-CSRF-Token': 'test-token',\n Cookie: 'csrf-token=test-token',\n },\n });\n expect(res.status).toBe(200);\n });\n\n it('should ignore specified paths', async () => {\n app.use(csrf({ ignores: [{ path: '/webhook/*', methods: ['POST'] }] }));\n app.post('/webhook/stripe', (c) => c.text('OK'));\n app.post('/api/data', (c) => c.text('OK'));\n\n // Ignored path should work without CSRF token\n const webhookRes = await app.request('/webhook/stripe', { method: 'POST' });\n expect(webhookRes.status).toBe(200);\n\n // Non-ignored path should require CSRF token\n const apiRes = await app.request('/api/data', { method: 'POST' });\n expect(apiRes.status).toBe(403);\n });\n\n it('should ignore all methods for a path when methods not specified', async () => {\n app.use(csrf({ ignores: [{ path: '/auth/apple/callback' }] }));\n app.post('/auth/apple/callback', (c) => c.text('OK'));\n app.put('/auth/apple/callback', (c) => c.text('OK'));\n\n const postRes = await app.request('/auth/apple/callback', { method: 'POST' });\n expect(postRes.status).toBe(200);\n\n const putRes = await app.request('/auth/apple/callback', { method: 'PUT' });\n expect(putRes.status).toBe(200);\n });\n\n it('should handle empty tokens safely', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': '', Cookie: 'XSRF-TOKEN=' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should handle missing cookie', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': 'token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should handle missing header', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Cookie: 'XSRF-TOKEN=token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should use custom error message', async () => {\n app.use(csrf({ errorMessage: 'Custom CSRF error' }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n const body = await res.json();\n expect(body.error.message).toBe('Custom CSRF error');\n });\n\n it('should work with custom safe methods', async () => {\n app.use(csrf({ safeMethods: ['GET'] }));\n app.all('/test', (c) => c.text('OK'));\n\n // HEAD is no longer safe, should require CSRF token\n const res = await app.request('/test', { method: 'HEAD' });\n expect(res.status).toBe(403);\n });\n\n it('should handle complex ignore patterns', async () => {\n app.use(\n csrf({\n ignores: [\n { path: '/api/v1/*', methods: ['GET', 'POST'] },\n { path: '/api/v2/*', methods: ['POST'] },\n { path: '/public/*' }, // All methods\n ],\n })\n );\n\n app.get('/api/v1/users', (c) => c.text('OK'));\n app.post('/api/v1/users', (c) => c.text('OK'));\n app.put('/api/v1/users', (c) => c.text('OK'));\n app.post('/api/v2/users', (c) => c.text('OK'));\n app.get('/api/v2/users', (c) => c.text('OK'));\n app.post('/public/data', (c) => c.text('OK'));\n\n // Ignored GET and POST for /api/v1/*\n let res = await app.request('/api/v1/users', { method: 'GET' });\n expect(res.status).toBe(200);\n\n res = await app.request('/api/v1/users', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // PUT not ignored for /api/v1/*\n res = await app.request('/api/v1/users', { method: 'PUT' });\n expect(res.status).toBe(403);\n\n // Only POST ignored for /api/v2/*\n res = await app.request('/api/v2/users', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // GET not ignored for /api/v2/* (but GET is safe by default)\n res = await app.request('/api/v2/users', { method: 'GET' });\n expect(res.status).toBe(200);\n\n // All methods ignored for /public/*\n res = await app.request('/public/data', { method: 'POST' });\n expect(res.status).toBe(200);\n });\n\n describe('Origin bypass', () => {\n it('should allow requests from configured origins', async () => {\n app.use(csrf({ origin: ['https://example.com', 'https://trusted.com'] }));\n app.post('/test', (c) => c.text('OK'));\n\n // Request from allowed origin should work without CSRF token\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://example.com' },\n });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should allow requests from any configured origin', async () => {\n app.use(csrf({ origin: ['https://example.com', 'https://trusted.com'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://trusted.com' },\n });\n expect(res.status).toBe(200);\n });\n\n it('should reject requests from non-configured origins', async () => {\n app.use(csrf({ origin: ['https://example.com'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://malicious.com' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should reject requests without origin header', async () => {\n app.use(csrf({ origin: ['https://example.com'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n\n it('should handle empty origin list', async () => {\n app.use(csrf({ origin: [] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://example.com' },\n });\n expect(res.status).toBe(403);\n });\n });\n\n describe('Sec-Fetch-Site bypass', () => {\n it('should allow requests with configured sec-fetch-site values', async () => {\n app.use(csrf({ secFetchSite: ['same-origin', 'same-site'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-origin' },\n });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should allow requests with any configured sec-fetch-site value', async () => {\n app.use(csrf({ secFetchSite: ['same-origin', 'same-site', 'cross-origin'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'cross-origin' },\n });\n expect(res.status).toBe(200);\n });\n\n it('should reject requests with non-configured sec-fetch-site values', async () => {\n app.use(csrf({ secFetchSite: ['same-origin'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-site' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should reject requests without sec-fetch-site header', async () => {\n app.use(csrf({ secFetchSite: ['same-origin'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n\n it('should handle all valid sec-fetch-site values', async () => {\n app.use(csrf({ secFetchSite: ['same-origin', 'same-site', 'none', 'cross-origin'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const validValues = ['same-origin', 'same-site', 'none', 'cross-origin'];\n\n for (const value of validValues) {\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': value },\n });\n expect(res.status).toBe(200);\n }\n });\n\n it('should handle empty sec-fetch-site list', async () => {\n app.use(csrf({ secFetchSite: [] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-origin' },\n });\n expect(res.status).toBe(403);\n });\n });\n\n describe('Combined origin and sec-fetch-site bypass', () => {\n it('should allow requests matching either origin or sec-fetch-site', async () => {\n app.use(\n csrf({\n origin: ['https://example.com'],\n secFetchSite: ['same-site'],\n })\n );\n app.post('/test', (c) => c.text('OK'));\n\n // Should work with matching origin\n let res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://example.com' },\n });\n expect(res.status).toBe(200);\n\n // Should work with matching sec-fetch-site\n res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-site' },\n });\n expect(res.status).toBe(200);\n });\n\n it('should reject requests matching neither origin nor sec-fetch-site', async () => {\n app.use(\n csrf({\n origin: ['https://example.com'],\n secFetchSite: ['same-origin'],\n })\n );\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n Origin: 'https://malicious.com',\n 'Sec-Fetch-Site': 'cross-origin',\n },\n });\n expect(res.status).toBe(403);\n });\n\n it('should work with origin, sec-fetch-site and ignore rules combined', async () => {\n app.use(\n csrf({\n origin: ['https://example.com'],\n secFetchSite: ['same-site'],\n ignores: [{ path: '/webhook/*', methods: ['POST'] }],\n })\n );\n\n app.post('/test', (c) => c.text('OK'));\n app.post('/webhook/stripe', (c) => c.text('OK'));\n app.post('/api/data', (c) => c.text('OK'));\n\n // Should work with origin bypass\n let res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://example.com' },\n });\n expect(res.status).toBe(200);\n\n // Should work with sec-fetch-site bypass\n res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-site' },\n });\n expect(res.status).toBe(200);\n\n // Should work with ignore rule\n res = await app.request('/webhook/stripe', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // Should fail without any bypass\n res = await app.request('/api/data', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n });\n});\n"],"mappings":"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","names":[]}
|
|
@@ -156,5 +156,171 @@ describe("CSRF Protection", () => {
|
|
|
156
156
|
res = await app.request("/public/data", { method: "POST" });
|
|
157
157
|
expect(res.status).toBe(200);
|
|
158
158
|
});
|
|
159
|
+
describe("Origin bypass", () => {
|
|
160
|
+
it("should allow requests from configured origins", async () => {
|
|
161
|
+
app.use(csrf({ origin: ["https://example.com", "https://trusted.com"] }));
|
|
162
|
+
app.post("/test", (c) => c.text("OK"));
|
|
163
|
+
const res = await app.request("/test", {
|
|
164
|
+
method: "POST",
|
|
165
|
+
headers: { Origin: "https://example.com" }
|
|
166
|
+
});
|
|
167
|
+
expect(res.status).toBe(200);
|
|
168
|
+
expect(await res.text()).toBe("OK");
|
|
169
|
+
});
|
|
170
|
+
it("should allow requests from any configured origin", async () => {
|
|
171
|
+
app.use(csrf({ origin: ["https://example.com", "https://trusted.com"] }));
|
|
172
|
+
app.post("/test", (c) => c.text("OK"));
|
|
173
|
+
const res = await app.request("/test", {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: { Origin: "https://trusted.com" }
|
|
176
|
+
});
|
|
177
|
+
expect(res.status).toBe(200);
|
|
178
|
+
});
|
|
179
|
+
it("should reject requests from non-configured origins", async () => {
|
|
180
|
+
app.use(csrf({ origin: ["https://example.com"] }));
|
|
181
|
+
app.post("/test", (c) => c.text("OK"));
|
|
182
|
+
const res = await app.request("/test", {
|
|
183
|
+
method: "POST",
|
|
184
|
+
headers: { Origin: "https://malicious.com" }
|
|
185
|
+
});
|
|
186
|
+
expect(res.status).toBe(403);
|
|
187
|
+
});
|
|
188
|
+
it("should reject requests without origin header", async () => {
|
|
189
|
+
app.use(csrf({ origin: ["https://example.com"] }));
|
|
190
|
+
app.post("/test", (c) => c.text("OK"));
|
|
191
|
+
const res = await app.request("/test", { method: "POST" });
|
|
192
|
+
expect(res.status).toBe(403);
|
|
193
|
+
});
|
|
194
|
+
it("should handle empty origin list", async () => {
|
|
195
|
+
app.use(csrf({ origin: [] }));
|
|
196
|
+
app.post("/test", (c) => c.text("OK"));
|
|
197
|
+
const res = await app.request("/test", {
|
|
198
|
+
method: "POST",
|
|
199
|
+
headers: { Origin: "https://example.com" }
|
|
200
|
+
});
|
|
201
|
+
expect(res.status).toBe(403);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
describe("Sec-Fetch-Site bypass", () => {
|
|
205
|
+
it("should allow requests with configured sec-fetch-site values", async () => {
|
|
206
|
+
app.use(csrf({ secFetchSite: ["same-origin", "same-site"] }));
|
|
207
|
+
app.post("/test", (c) => c.text("OK"));
|
|
208
|
+
const res = await app.request("/test", {
|
|
209
|
+
method: "POST",
|
|
210
|
+
headers: { "Sec-Fetch-Site": "same-origin" }
|
|
211
|
+
});
|
|
212
|
+
expect(res.status).toBe(200);
|
|
213
|
+
expect(await res.text()).toBe("OK");
|
|
214
|
+
});
|
|
215
|
+
it("should allow requests with any configured sec-fetch-site value", async () => {
|
|
216
|
+
app.use(csrf({ secFetchSite: ["same-origin", "same-site", "cross-origin"] }));
|
|
217
|
+
app.post("/test", (c) => c.text("OK"));
|
|
218
|
+
const res = await app.request("/test", {
|
|
219
|
+
method: "POST",
|
|
220
|
+
headers: { "Sec-Fetch-Site": "cross-origin" }
|
|
221
|
+
});
|
|
222
|
+
expect(res.status).toBe(200);
|
|
223
|
+
});
|
|
224
|
+
it("should reject requests with non-configured sec-fetch-site values", async () => {
|
|
225
|
+
app.use(csrf({ secFetchSite: ["same-origin"] }));
|
|
226
|
+
app.post("/test", (c) => c.text("OK"));
|
|
227
|
+
const res = await app.request("/test", {
|
|
228
|
+
method: "POST",
|
|
229
|
+
headers: { "Sec-Fetch-Site": "same-site" }
|
|
230
|
+
});
|
|
231
|
+
expect(res.status).toBe(403);
|
|
232
|
+
});
|
|
233
|
+
it("should reject requests without sec-fetch-site header", async () => {
|
|
234
|
+
app.use(csrf({ secFetchSite: ["same-origin"] }));
|
|
235
|
+
app.post("/test", (c) => c.text("OK"));
|
|
236
|
+
const res = await app.request("/test", { method: "POST" });
|
|
237
|
+
expect(res.status).toBe(403);
|
|
238
|
+
});
|
|
239
|
+
it("should handle all valid sec-fetch-site values", async () => {
|
|
240
|
+
app.use(csrf({ secFetchSite: ["same-origin", "same-site", "none", "cross-origin"] }));
|
|
241
|
+
app.post("/test", (c) => c.text("OK"));
|
|
242
|
+
const validValues = ["same-origin", "same-site", "none", "cross-origin"];
|
|
243
|
+
for (const value of validValues) {
|
|
244
|
+
const res = await app.request("/test", {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: { "Sec-Fetch-Site": value }
|
|
247
|
+
});
|
|
248
|
+
expect(res.status).toBe(200);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
it("should handle empty sec-fetch-site list", async () => {
|
|
252
|
+
app.use(csrf({ secFetchSite: [] }));
|
|
253
|
+
app.post("/test", (c) => c.text("OK"));
|
|
254
|
+
const res = await app.request("/test", {
|
|
255
|
+
method: "POST",
|
|
256
|
+
headers: { "Sec-Fetch-Site": "same-origin" }
|
|
257
|
+
});
|
|
258
|
+
expect(res.status).toBe(403);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
describe("Combined origin and sec-fetch-site bypass", () => {
|
|
262
|
+
it("should allow requests matching either origin or sec-fetch-site", async () => {
|
|
263
|
+
app.use(
|
|
264
|
+
csrf({
|
|
265
|
+
origin: ["https://example.com"],
|
|
266
|
+
secFetchSite: ["same-site"]
|
|
267
|
+
})
|
|
268
|
+
);
|
|
269
|
+
app.post("/test", (c) => c.text("OK"));
|
|
270
|
+
let res = await app.request("/test", {
|
|
271
|
+
method: "POST",
|
|
272
|
+
headers: { Origin: "https://example.com" }
|
|
273
|
+
});
|
|
274
|
+
expect(res.status).toBe(200);
|
|
275
|
+
res = await app.request("/test", {
|
|
276
|
+
method: "POST",
|
|
277
|
+
headers: { "Sec-Fetch-Site": "same-site" }
|
|
278
|
+
});
|
|
279
|
+
expect(res.status).toBe(200);
|
|
280
|
+
});
|
|
281
|
+
it("should reject requests matching neither origin nor sec-fetch-site", async () => {
|
|
282
|
+
app.use(
|
|
283
|
+
csrf({
|
|
284
|
+
origin: ["https://example.com"],
|
|
285
|
+
secFetchSite: ["same-origin"]
|
|
286
|
+
})
|
|
287
|
+
);
|
|
288
|
+
app.post("/test", (c) => c.text("OK"));
|
|
289
|
+
const res = await app.request("/test", {
|
|
290
|
+
method: "POST",
|
|
291
|
+
headers: {
|
|
292
|
+
Origin: "https://malicious.com",
|
|
293
|
+
"Sec-Fetch-Site": "cross-origin"
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
expect(res.status).toBe(403);
|
|
297
|
+
});
|
|
298
|
+
it("should work with origin, sec-fetch-site and ignore rules combined", async () => {
|
|
299
|
+
app.use(
|
|
300
|
+
csrf({
|
|
301
|
+
origin: ["https://example.com"],
|
|
302
|
+
secFetchSite: ["same-site"],
|
|
303
|
+
ignores: [{ path: "/webhook/*", methods: ["POST"] }]
|
|
304
|
+
})
|
|
305
|
+
);
|
|
306
|
+
app.post("/test", (c) => c.text("OK"));
|
|
307
|
+
app.post("/webhook/stripe", (c) => c.text("OK"));
|
|
308
|
+
app.post("/api/data", (c) => c.text("OK"));
|
|
309
|
+
let res = await app.request("/test", {
|
|
310
|
+
method: "POST",
|
|
311
|
+
headers: { Origin: "https://example.com" }
|
|
312
|
+
});
|
|
313
|
+
expect(res.status).toBe(200);
|
|
314
|
+
res = await app.request("/test", {
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers: { "Sec-Fetch-Site": "same-site" }
|
|
317
|
+
});
|
|
318
|
+
expect(res.status).toBe(200);
|
|
319
|
+
res = await app.request("/webhook/stripe", { method: "POST" });
|
|
320
|
+
expect(res.status).toBe(200);
|
|
321
|
+
res = await app.request("/api/data", { method: "POST" });
|
|
322
|
+
expect(res.status).toBe(403);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
159
325
|
});
|
|
160
326
|
//# sourceMappingURL=csrf.test.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/hono/__tests__/csrf.test.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport { csrf } from '../csrf';\nimport { type Env, errorHandler } from '../handler';\n\ndescribe('CSRF Protection', () => {\n let app: Hono<Env>;\n\n beforeEach(() => {\n app = new Hono();\n app.onError(errorHandler);\n });\n\n it('should allow GET requests without CSRF token', async () => {\n app.use(csrf());\n app.get('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test');\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should allow HEAD requests without CSRF token', async () => {\n app.use(csrf());\n app.all('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'HEAD' });\n expect(res.status).toBe(200);\n });\n\n it('should allow OPTIONS requests without CSRF token', async () => {\n app.use(csrf());\n app.options('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'OPTIONS' });\n expect(res.status).toBe(200);\n });\n\n it('should reject POST requests without CSRF token', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n\n it('should reject POST requests with mismatched tokens', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': 'header-token', Cookie: 'XSRF-TOKEN=cookie-token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should allow POST requests with matching tokens', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n 'X-XSRF-TOKEN': 'matching-token',\n Cookie: 'XSRF-TOKEN=matching-token',\n },\n });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should use custom cookie and header names', async () => {\n app.use(csrf({ cookieName: 'csrf-token', headerName: 'X-CSRF-Token' }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n 'X-CSRF-Token': 'test-token',\n Cookie: 'csrf-token=test-token',\n },\n });\n expect(res.status).toBe(200);\n });\n\n it('should ignore specified paths', async () => {\n app.use(csrf({ ignores: [{ path: '/webhook/*', methods: ['POST'] }] }));\n app.post('/webhook/stripe', (c) => c.text('OK'));\n app.post('/api/data', (c) => c.text('OK'));\n\n // Ignored path should work without CSRF token\n const webhookRes = await app.request('/webhook/stripe', { method: 'POST' });\n expect(webhookRes.status).toBe(200);\n\n // Non-ignored path should require CSRF token\n const apiRes = await app.request('/api/data', { method: 'POST' });\n expect(apiRes.status).toBe(403);\n });\n\n it('should ignore all methods for a path when methods not specified', async () => {\n app.use(csrf({ ignores: [{ path: '/auth/apple/callback' }] }));\n app.post('/auth/apple/callback', (c) => c.text('OK'));\n app.put('/auth/apple/callback', (c) => c.text('OK'));\n\n const postRes = await app.request('/auth/apple/callback', { method: 'POST' });\n expect(postRes.status).toBe(200);\n\n const putRes = await app.request('/auth/apple/callback', { method: 'PUT' });\n expect(putRes.status).toBe(200);\n });\n\n it('should handle empty tokens safely', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': '', Cookie: 'XSRF-TOKEN=' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should handle missing cookie', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': 'token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should handle missing header', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Cookie: 'XSRF-TOKEN=token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should use custom error message', async () => {\n app.use(csrf({ errorMessage: 'Custom CSRF error' }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n const body = await res.json();\n expect(body.error.message).toBe('Custom CSRF error');\n });\n\n it('should work with custom safe methods', async () => {\n app.use(csrf({ safeMethods: ['GET'] }));\n app.all('/test', (c) => c.text('OK'));\n\n // HEAD is no longer safe, should require CSRF token\n const res = await app.request('/test', { method: 'HEAD' });\n expect(res.status).toBe(403);\n });\n\n it('should handle complex ignore patterns', async () => {\n app.use(\n csrf({\n ignores: [\n { path: '/api/v1/*', methods: ['GET', 'POST'] },\n { path: '/api/v2/*', methods: ['POST'] },\n { path: '/public/*' }, // All methods\n ],\n })\n );\n\n app.get('/api/v1/users', (c) => c.text('OK'));\n app.post('/api/v1/users', (c) => c.text('OK'));\n app.put('/api/v1/users', (c) => c.text('OK'));\n app.post('/api/v2/users', (c) => c.text('OK'));\n app.get('/api/v2/users', (c) => c.text('OK'));\n app.post('/public/data', (c) => c.text('OK'));\n\n // Ignored GET and POST for /api/v1/*\n let res = await app.request('/api/v1/users', { method: 'GET' });\n expect(res.status).toBe(200);\n\n res = await app.request('/api/v1/users', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // PUT not ignored for /api/v1/*\n res = await app.request('/api/v1/users', { method: 'PUT' });\n expect(res.status).toBe(403);\n\n // Only POST ignored for /api/v2/*\n res = await app.request('/api/v2/users', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // GET not ignored for /api/v2/* (but GET is safe by default)\n res = await app.request('/api/v2/users', { method: 'GET' });\n expect(res.status).toBe(200);\n\n // All methods ignored for /public/*\n res = await app.request('/public/data', { method: 'POST' });\n expect(res.status).toBe(200);\n });\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/hono/__tests__/csrf.test.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport { csrf } from '../csrf';\nimport { type Env, errorHandler } from '../handler';\n\ndescribe('CSRF Protection', () => {\n let app: Hono<Env>;\n\n beforeEach(() => {\n app = new Hono();\n app.onError(errorHandler);\n });\n\n it('should allow GET requests without CSRF token', async () => {\n app.use(csrf());\n app.get('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test');\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should allow HEAD requests without CSRF token', async () => {\n app.use(csrf());\n app.all('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'HEAD' });\n expect(res.status).toBe(200);\n });\n\n it('should allow OPTIONS requests without CSRF token', async () => {\n app.use(csrf());\n app.options('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'OPTIONS' });\n expect(res.status).toBe(200);\n });\n\n it('should reject POST requests without CSRF token', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n\n it('should reject POST requests with mismatched tokens', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': 'header-token', Cookie: 'XSRF-TOKEN=cookie-token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should allow POST requests with matching tokens', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n 'X-XSRF-TOKEN': 'matching-token',\n Cookie: 'XSRF-TOKEN=matching-token',\n },\n });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should use custom cookie and header names', async () => {\n app.use(csrf({ cookieName: 'csrf-token', headerName: 'X-CSRF-Token' }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n 'X-CSRF-Token': 'test-token',\n Cookie: 'csrf-token=test-token',\n },\n });\n expect(res.status).toBe(200);\n });\n\n it('should ignore specified paths', async () => {\n app.use(csrf({ ignores: [{ path: '/webhook/*', methods: ['POST'] }] }));\n app.post('/webhook/stripe', (c) => c.text('OK'));\n app.post('/api/data', (c) => c.text('OK'));\n\n // Ignored path should work without CSRF token\n const webhookRes = await app.request('/webhook/stripe', { method: 'POST' });\n expect(webhookRes.status).toBe(200);\n\n // Non-ignored path should require CSRF token\n const apiRes = await app.request('/api/data', { method: 'POST' });\n expect(apiRes.status).toBe(403);\n });\n\n it('should ignore all methods for a path when methods not specified', async () => {\n app.use(csrf({ ignores: [{ path: '/auth/apple/callback' }] }));\n app.post('/auth/apple/callback', (c) => c.text('OK'));\n app.put('/auth/apple/callback', (c) => c.text('OK'));\n\n const postRes = await app.request('/auth/apple/callback', { method: 'POST' });\n expect(postRes.status).toBe(200);\n\n const putRes = await app.request('/auth/apple/callback', { method: 'PUT' });\n expect(putRes.status).toBe(200);\n });\n\n it('should handle empty tokens safely', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': '', Cookie: 'XSRF-TOKEN=' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should handle missing cookie', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'X-XSRF-TOKEN': 'token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should handle missing header', async () => {\n app.use(csrf());\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Cookie: 'XSRF-TOKEN=token' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should use custom error message', async () => {\n app.use(csrf({ errorMessage: 'Custom CSRF error' }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n const body = await res.json();\n expect(body.error.message).toBe('Custom CSRF error');\n });\n\n it('should work with custom safe methods', async () => {\n app.use(csrf({ safeMethods: ['GET'] }));\n app.all('/test', (c) => c.text('OK'));\n\n // HEAD is no longer safe, should require CSRF token\n const res = await app.request('/test', { method: 'HEAD' });\n expect(res.status).toBe(403);\n });\n\n it('should handle complex ignore patterns', async () => {\n app.use(\n csrf({\n ignores: [\n { path: '/api/v1/*', methods: ['GET', 'POST'] },\n { path: '/api/v2/*', methods: ['POST'] },\n { path: '/public/*' }, // All methods\n ],\n })\n );\n\n app.get('/api/v1/users', (c) => c.text('OK'));\n app.post('/api/v1/users', (c) => c.text('OK'));\n app.put('/api/v1/users', (c) => c.text('OK'));\n app.post('/api/v2/users', (c) => c.text('OK'));\n app.get('/api/v2/users', (c) => c.text('OK'));\n app.post('/public/data', (c) => c.text('OK'));\n\n // Ignored GET and POST for /api/v1/*\n let res = await app.request('/api/v1/users', { method: 'GET' });\n expect(res.status).toBe(200);\n\n res = await app.request('/api/v1/users', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // PUT not ignored for /api/v1/*\n res = await app.request('/api/v1/users', { method: 'PUT' });\n expect(res.status).toBe(403);\n\n // Only POST ignored for /api/v2/*\n res = await app.request('/api/v2/users', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // GET not ignored for /api/v2/* (but GET is safe by default)\n res = await app.request('/api/v2/users', { method: 'GET' });\n expect(res.status).toBe(200);\n\n // All methods ignored for /public/*\n res = await app.request('/public/data', { method: 'POST' });\n expect(res.status).toBe(200);\n });\n\n describe('Origin bypass', () => {\n it('should allow requests from configured origins', async () => {\n app.use(csrf({ origin: ['https://example.com', 'https://trusted.com'] }));\n app.post('/test', (c) => c.text('OK'));\n\n // Request from allowed origin should work without CSRF token\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://example.com' },\n });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should allow requests from any configured origin', async () => {\n app.use(csrf({ origin: ['https://example.com', 'https://trusted.com'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://trusted.com' },\n });\n expect(res.status).toBe(200);\n });\n\n it('should reject requests from non-configured origins', async () => {\n app.use(csrf({ origin: ['https://example.com'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://malicious.com' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should reject requests without origin header', async () => {\n app.use(csrf({ origin: ['https://example.com'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n\n it('should handle empty origin list', async () => {\n app.use(csrf({ origin: [] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://example.com' },\n });\n expect(res.status).toBe(403);\n });\n });\n\n describe('Sec-Fetch-Site bypass', () => {\n it('should allow requests with configured sec-fetch-site values', async () => {\n app.use(csrf({ secFetchSite: ['same-origin', 'same-site'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-origin' },\n });\n expect(res.status).toBe(200);\n expect(await res.text()).toBe('OK');\n });\n\n it('should allow requests with any configured sec-fetch-site value', async () => {\n app.use(csrf({ secFetchSite: ['same-origin', 'same-site', 'cross-origin'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'cross-origin' },\n });\n expect(res.status).toBe(200);\n });\n\n it('should reject requests with non-configured sec-fetch-site values', async () => {\n app.use(csrf({ secFetchSite: ['same-origin'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-site' },\n });\n expect(res.status).toBe(403);\n });\n\n it('should reject requests without sec-fetch-site header', async () => {\n app.use(csrf({ secFetchSite: ['same-origin'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n\n it('should handle all valid sec-fetch-site values', async () => {\n app.use(csrf({ secFetchSite: ['same-origin', 'same-site', 'none', 'cross-origin'] }));\n app.post('/test', (c) => c.text('OK'));\n\n const validValues = ['same-origin', 'same-site', 'none', 'cross-origin'];\n\n for (const value of validValues) {\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': value },\n });\n expect(res.status).toBe(200);\n }\n });\n\n it('should handle empty sec-fetch-site list', async () => {\n app.use(csrf({ secFetchSite: [] }));\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-origin' },\n });\n expect(res.status).toBe(403);\n });\n });\n\n describe('Combined origin and sec-fetch-site bypass', () => {\n it('should allow requests matching either origin or sec-fetch-site', async () => {\n app.use(\n csrf({\n origin: ['https://example.com'],\n secFetchSite: ['same-site'],\n })\n );\n app.post('/test', (c) => c.text('OK'));\n\n // Should work with matching origin\n let res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://example.com' },\n });\n expect(res.status).toBe(200);\n\n // Should work with matching sec-fetch-site\n res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-site' },\n });\n expect(res.status).toBe(200);\n });\n\n it('should reject requests matching neither origin nor sec-fetch-site', async () => {\n app.use(\n csrf({\n origin: ['https://example.com'],\n secFetchSite: ['same-origin'],\n })\n );\n app.post('/test', (c) => c.text('OK'));\n\n const res = await app.request('/test', {\n method: 'POST',\n headers: {\n Origin: 'https://malicious.com',\n 'Sec-Fetch-Site': 'cross-origin',\n },\n });\n expect(res.status).toBe(403);\n });\n\n it('should work with origin, sec-fetch-site and ignore rules combined', async () => {\n app.use(\n csrf({\n origin: ['https://example.com'],\n secFetchSite: ['same-site'],\n ignores: [{ path: '/webhook/*', methods: ['POST'] }],\n })\n );\n\n app.post('/test', (c) => c.text('OK'));\n app.post('/webhook/stripe', (c) => c.text('OK'));\n app.post('/api/data', (c) => c.text('OK'));\n\n // Should work with origin bypass\n let res = await app.request('/test', {\n method: 'POST',\n headers: { Origin: 'https://example.com' },\n });\n expect(res.status).toBe(200);\n\n // Should work with sec-fetch-site bypass\n res = await app.request('/test', {\n method: 'POST',\n headers: { 'Sec-Fetch-Site': 'same-site' },\n });\n expect(res.status).toBe(200);\n\n // Should work with ignore rule\n res = await app.request('/webhook/stripe', { method: 'POST' });\n expect(res.status).toBe(200);\n\n // Should fail without any bypass\n res = await app.request('/api/data', { method: 'POST' });\n expect(res.status).toBe(403);\n });\n });\n});\n"],"mappings":"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","names":[]}
|
package/dist/hono/csrf.cjs
CHANGED
|
@@ -59,21 +59,23 @@ function csrf(config = {}) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
function shouldIgnore(method, path) {
|
|
63
|
-
if (safeMethods.has(method)) {
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
const [matchedAll] = router.match(import_router.METHOD_NAME_ALL, path);
|
|
67
|
-
if (matchedAll.length > 0) {
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
const [matchedMethod] = router.match(method, path);
|
|
71
|
-
return matchedMethod.length > 0;
|
|
72
|
-
}
|
|
73
62
|
return async (c, next) => {
|
|
74
63
|
const method = c.req.method;
|
|
75
64
|
const path = c.req.path;
|
|
76
|
-
if (
|
|
65
|
+
if (safeMethods.has(method)) {
|
|
66
|
+
await next();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (config.origin && config.origin.includes(c.req.header("origin") ?? "")) {
|
|
70
|
+
await next();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (config.secFetchSite && config.secFetchSite.includes(c.req.header("sec-fetch-site") ?? "")) {
|
|
74
|
+
await next();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const [matched] = router.match(method, path);
|
|
78
|
+
if (matched.length > 0) {
|
|
77
79
|
await next();
|
|
78
80
|
return;
|
|
79
81
|
}
|
package/dist/hono/csrf.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hono/csrf.ts"],"sourcesContent":["import { timingSafeEqual } from 'crypto';\nimport { getCookie } from 'hono/cookie';\nimport { METHOD_NAME_ALL } from 'hono/router';\nimport { RegExpRouter } from 'hono/router/reg-exp-router';\nimport { SmartRouter } from 'hono/router/smart-router';\nimport { TrieRouter } from 'hono/router/trie-router';\nimport { Status } from '../error/status';\nimport type { MiddlewareHandler } from 'hono';\n\ntype HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';\n\nexport type CSRFIgnoreRule = string | { path: string; methods?: [HTTPMethod, ...HTTPMethod[]] };\n\nexport interface CSRFConfig {\n /**\n * Cookie name for CSRF token\n * @default 'XSRF-TOKEN'\n */\n cookieName?: string;\n\n /**\n * Header name for CSRF token\n * @default 'X-XSRF-TOKEN'\n */\n headerName?: string;\n\n /**\n * Ignore rules for specific paths and methods\n * @example\n * [\n * { path: '/api/webhook/*', methods: ['POST'] },\n * { path: '/auth/apple/callback' }, // ignores all methods\n * ]\n */\n ignores?: CSRFIgnoreRule[];\n\n /**\n * Skip CSRF check for these methods\n * @default ['GET', 'HEAD', 'OPTIONS']\n */\n safeMethods?: HTTPMethod[];\n\n /**\n * Custom error message\n * @default 'CSRF token validation failed'\n */\n errorMessage?: string;\n}\n\n/** use timing safe compare to prevent timing attack */\nfunction safeCompare(a: string, b: string): boolean {\n if (typeof a !== 'string' || typeof b !== 'string') return false;\n if (a.length === 0 || b.length === 0) return false;\n\n const bufferA = Buffer.from(a, 'utf-8');\n const bufferB = Buffer.from(b, 'utf-8');\n if (bufferA.length !== bufferB.length) return false;\n return timingSafeEqual(bufferA, bufferB);\n}\n\n/**\n * Create CSRF protection middleware\n *\n * @example\n * ```ts\n * import { Hono } from 'hono';\n * import { csrf } from '@shware/http/hono';\n *\n * const app = new Hono();\n *\n * // basic usage\n * app.use(csrf());\n *\n * // with configuration\n * app.use(csrf({\n * cookieName: 'csrf-token',\n * headerName: 'X-CSRF-Token',\n * ignores: [\n * { path: '/api/webhook/*', methods: ['POST'] },\n * { path: '/auth/apple/callback' },\n * ]\n * }));\n * ```\n */\nexport function csrf(config: CSRFConfig = {}): MiddlewareHandler {\n const cookieName = config.cookieName ?? 'XSRF-TOKEN';\n const headerName = config.headerName ?? 'X-XSRF-TOKEN';\n const safeMethods = new Set(config.safeMethods ?? ['GET', 'HEAD', 'OPTIONS']);\n const errorMessage = config.errorMessage ?? 'CSRF token validation failed';\n\n // initialize router for matching ignore rules\n const router = new SmartRouter<boolean>({\n routers: [new RegExpRouter(), new TrieRouter()],\n });\n\n // register ignore rules\n if (config.ignores) {\n for (const rule of config.ignores) {\n if (typeof rule === 'string') {\n router.add(METHOD_NAME_ALL, rule, true);\n } else if (rule.methods && rule.methods.length > 0) {\n for (const method of rule.methods) {\n router.add(method, rule.path, true);\n }\n } else {\n // if no methods are specified, ignore all methods\n router.add(METHOD_NAME_ALL, rule.path, true);\n }\n }\n }\n\n // check if the request should be ignored\n
|
|
1
|
+
{"version":3,"sources":["../../src/hono/csrf.ts"],"sourcesContent":["import { timingSafeEqual } from 'crypto';\nimport { getCookie } from 'hono/cookie';\nimport { METHOD_NAME_ALL } from 'hono/router';\nimport { RegExpRouter } from 'hono/router/reg-exp-router';\nimport { SmartRouter } from 'hono/router/smart-router';\nimport { TrieRouter } from 'hono/router/trie-router';\nimport { Status } from '../error/status';\nimport type { MiddlewareHandler } from 'hono';\n\ntype HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';\n\nexport type CSRFIgnoreRule = string | { path: string; methods?: [HTTPMethod, ...HTTPMethod[]] };\n\nexport interface CSRFConfig {\n /**\n * Cookie name for CSRF token\n * @default 'XSRF-TOKEN'\n */\n cookieName?: string;\n\n /**\n * Header name for CSRF token\n * @default 'X-XSRF-TOKEN'\n */\n headerName?: string;\n\n /**\n * Ignore rules for specific paths and methods\n * @example\n * [\n * { path: '/api/webhook/*', methods: ['POST'] },\n * { path: '/auth/apple/callback' }, // ignores all methods\n * ]\n */\n ignores?: CSRFIgnoreRule[];\n\n /**\n * Skip CSRF check for these methods\n * @default ['GET', 'HEAD', 'OPTIONS']\n */\n safeMethods?: HTTPMethod[];\n\n /**\n * Origin allowed to bypass CSRF check\n * @default undefined\n */\n origin?: string[];\n\n /**\n * Sec-Fetch-Site allowed to bypass CSRF check\n * @default undefined\n */\n secFetchSite?: Array<'same-origin' | 'same-site' | 'none' | 'cross-origin'>;\n\n /**\n * Custom error message\n * @default 'CSRF token validation failed'\n */\n errorMessage?: string;\n}\n\n/** use timing safe compare to prevent timing attack */\nfunction safeCompare(a: string, b: string): boolean {\n if (typeof a !== 'string' || typeof b !== 'string') return false;\n if (a.length === 0 || b.length === 0) return false;\n\n const bufferA = Buffer.from(a, 'utf-8');\n const bufferB = Buffer.from(b, 'utf-8');\n if (bufferA.length !== bufferB.length) return false;\n return timingSafeEqual(bufferA, bufferB);\n}\n\n/**\n * Create CSRF protection middleware\n *\n * @example\n * ```ts\n * import { Hono } from 'hono';\n * import { csrf } from '@shware/http/hono';\n *\n * const app = new Hono();\n *\n * // basic usage\n * app.use(csrf());\n *\n * // with configuration\n * app.use(csrf({\n * cookieName: 'csrf-token',\n * headerName: 'X-CSRF-Token',\n * ignores: [\n * { path: '/api/webhook/*', methods: ['POST'] },\n * { path: '/auth/apple/callback' },\n * ]\n * }));\n * ```\n */\nexport function csrf(config: CSRFConfig = {}): MiddlewareHandler {\n const cookieName = config.cookieName ?? 'XSRF-TOKEN';\n const headerName = config.headerName ?? 'X-XSRF-TOKEN';\n const safeMethods = new Set(config.safeMethods ?? ['GET', 'HEAD', 'OPTIONS']);\n const errorMessage = config.errorMessage ?? 'CSRF token validation failed';\n\n // initialize router for matching ignore rules\n const router = new SmartRouter<boolean>({\n routers: [new RegExpRouter(), new TrieRouter()],\n });\n\n // register ignore rules\n if (config.ignores) {\n for (const rule of config.ignores) {\n if (typeof rule === 'string') {\n router.add(METHOD_NAME_ALL, rule, true);\n } else if (rule.methods && rule.methods.length > 0) {\n for (const method of rule.methods) {\n router.add(method, rule.path, true);\n }\n } else {\n // if no methods are specified, ignore all methods\n router.add(METHOD_NAME_ALL, rule.path, true);\n }\n }\n }\n\n // return middleware\n return async (c, next) => {\n const method = c.req.method;\n const path = c.req.path;\n\n // check if the request should be ignored\n // 1. ignore safe methods\n if (safeMethods.has(method)) {\n await next();\n return;\n }\n\n // 2. ignore configured origin\n if (config.origin && config.origin.includes(c.req.header('origin') ?? '')) {\n await next();\n return;\n }\n\n // 3. ignore configured secFetchSite\n if (\n config.secFetchSite &&\n config.secFetchSite.includes((c.req.header('sec-fetch-site') ?? '') as never)\n ) {\n await next();\n return;\n }\n\n // 4. ignore configured ignore rules\n const [matched] = router.match(method, path);\n if (matched.length > 0) {\n await next();\n return;\n }\n\n const cookieToken = getCookie(c, cookieName);\n const headerToken = c.req.header(headerName);\n\n if (!cookieToken || !headerToken) {\n throw Status.permissionDenied(errorMessage).error();\n }\n\n if (!safeCompare(cookieToken, headerToken)) {\n throw Status.permissionDenied(errorMessage).error();\n }\n\n await next();\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAgC;AAChC,oBAA0B;AAC1B,oBAAgC;AAChC,4BAA6B;AAC7B,0BAA4B;AAC5B,yBAA2B;AAC3B,oBAAuB;AAwDvB,SAAS,YAAY,GAAW,GAAoB;AAClD,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SAAU,QAAO;AAC3D,MAAI,EAAE,WAAW,KAAK,EAAE,WAAW,EAAG,QAAO;AAE7C,QAAM,UAAU,OAAO,KAAK,GAAG,OAAO;AACtC,QAAM,UAAU,OAAO,KAAK,GAAG,OAAO;AACtC,MAAI,QAAQ,WAAW,QAAQ,OAAQ,QAAO;AAC9C,aAAO,+BAAgB,SAAS,OAAO;AACzC;AA0BO,SAAS,KAAK,SAAqB,CAAC,GAAsB;AAC/D,QAAM,aAAa,OAAO,cAAc;AACxC,QAAM,aAAa,OAAO,cAAc;AACxC,QAAM,cAAc,IAAI,IAAI,OAAO,eAAe,CAAC,OAAO,QAAQ,SAAS,CAAC;AAC5E,QAAM,eAAe,OAAO,gBAAgB;AAG5C,QAAM,SAAS,IAAI,gCAAqB;AAAA,IACtC,SAAS,CAAC,IAAI,mCAAa,GAAG,IAAI,8BAAW,CAAC;AAAA,EAChD,CAAC;AAGD,MAAI,OAAO,SAAS;AAClB,eAAW,QAAQ,OAAO,SAAS;AACjC,UAAI,OAAO,SAAS,UAAU;AAC5B,eAAO,IAAI,+BAAiB,MAAM,IAAI;AAAA,MACxC,WAAW,KAAK,WAAW,KAAK,QAAQ,SAAS,GAAG;AAClD,mBAAW,UAAU,KAAK,SAAS;AACjC,iBAAO,IAAI,QAAQ,KAAK,MAAM,IAAI;AAAA,QACpC;AAAA,MACF,OAAO;AAEL,eAAO,IAAI,+BAAiB,KAAK,MAAM,IAAI;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AAGA,SAAO,OAAO,GAAG,SAAS;AACxB,UAAM,SAAS,EAAE,IAAI;AACrB,UAAM,OAAO,EAAE,IAAI;AAInB,QAAI,YAAY,IAAI,MAAM,GAAG;AAC3B,YAAM,KAAK;AACX;AAAA,IACF;AAGA,QAAI,OAAO,UAAU,OAAO,OAAO,SAAS,EAAE,IAAI,OAAO,QAAQ,KAAK,EAAE,GAAG;AACzE,YAAM,KAAK;AACX;AAAA,IACF;AAGA,QACE,OAAO,gBACP,OAAO,aAAa,SAAU,EAAE,IAAI,OAAO,gBAAgB,KAAK,EAAY,GAC5E;AACA,YAAM,KAAK;AACX;AAAA,IACF;AAGA,UAAM,CAAC,OAAO,IAAI,OAAO,MAAM,QAAQ,IAAI;AAC3C,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,KAAK;AACX;AAAA,IACF;AAEA,UAAM,kBAAc,yBAAU,GAAG,UAAU;AAC3C,UAAM,cAAc,EAAE,IAAI,OAAO,UAAU;AAE3C,QAAI,CAAC,eAAe,CAAC,aAAa;AAChC,YAAM,qBAAO,iBAAiB,YAAY,EAAE,MAAM;AAAA,IACpD;AAEA,QAAI,CAAC,YAAY,aAAa,WAAW,GAAG;AAC1C,YAAM,qBAAO,iBAAiB,YAAY,EAAE,MAAM;AAAA,IACpD;AAEA,UAAM,KAAK;AAAA,EACb;AACF;","names":[]}
|
package/dist/hono/csrf.d.cts
CHANGED
|
@@ -30,6 +30,16 @@ interface CSRFConfig {
|
|
|
30
30
|
* @default ['GET', 'HEAD', 'OPTIONS']
|
|
31
31
|
*/
|
|
32
32
|
safeMethods?: HTTPMethod[];
|
|
33
|
+
/**
|
|
34
|
+
* Origin allowed to bypass CSRF check
|
|
35
|
+
* @default undefined
|
|
36
|
+
*/
|
|
37
|
+
origin?: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Sec-Fetch-Site allowed to bypass CSRF check
|
|
40
|
+
* @default undefined
|
|
41
|
+
*/
|
|
42
|
+
secFetchSite?: Array<'same-origin' | 'same-site' | 'none' | 'cross-origin'>;
|
|
33
43
|
/**
|
|
34
44
|
* Custom error message
|
|
35
45
|
* @default 'CSRF token validation failed'
|
package/dist/hono/csrf.d.ts
CHANGED
|
@@ -30,6 +30,16 @@ interface CSRFConfig {
|
|
|
30
30
|
* @default ['GET', 'HEAD', 'OPTIONS']
|
|
31
31
|
*/
|
|
32
32
|
safeMethods?: HTTPMethod[];
|
|
33
|
+
/**
|
|
34
|
+
* Origin allowed to bypass CSRF check
|
|
35
|
+
* @default undefined
|
|
36
|
+
*/
|
|
37
|
+
origin?: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Sec-Fetch-Site allowed to bypass CSRF check
|
|
40
|
+
* @default undefined
|
|
41
|
+
*/
|
|
42
|
+
secFetchSite?: Array<'same-origin' | 'same-site' | 'none' | 'cross-origin'>;
|
|
33
43
|
/**
|
|
34
44
|
* Custom error message
|
|
35
45
|
* @default 'CSRF token validation failed'
|
package/dist/hono/csrf.mjs
CHANGED
|
@@ -35,21 +35,23 @@ function csrf(config = {}) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
function shouldIgnore(method, path) {
|
|
39
|
-
if (safeMethods.has(method)) {
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
const [matchedAll] = router.match(METHOD_NAME_ALL, path);
|
|
43
|
-
if (matchedAll.length > 0) {
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
const [matchedMethod] = router.match(method, path);
|
|
47
|
-
return matchedMethod.length > 0;
|
|
48
|
-
}
|
|
49
38
|
return async (c, next) => {
|
|
50
39
|
const method = c.req.method;
|
|
51
40
|
const path = c.req.path;
|
|
52
|
-
if (
|
|
41
|
+
if (safeMethods.has(method)) {
|
|
42
|
+
await next();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (config.origin && config.origin.includes(c.req.header("origin") ?? "")) {
|
|
46
|
+
await next();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (config.secFetchSite && config.secFetchSite.includes(c.req.header("sec-fetch-site") ?? "")) {
|
|
50
|
+
await next();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const [matched] = router.match(method, path);
|
|
54
|
+
if (matched.length > 0) {
|
|
53
55
|
await next();
|
|
54
56
|
return;
|
|
55
57
|
}
|
package/dist/hono/csrf.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hono/csrf.ts"],"sourcesContent":["import { timingSafeEqual } from 'crypto';\nimport { getCookie } from 'hono/cookie';\nimport { METHOD_NAME_ALL } from 'hono/router';\nimport { RegExpRouter } from 'hono/router/reg-exp-router';\nimport { SmartRouter } from 'hono/router/smart-router';\nimport { TrieRouter } from 'hono/router/trie-router';\nimport { Status } from '../error/status';\nimport type { MiddlewareHandler } from 'hono';\n\ntype HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';\n\nexport type CSRFIgnoreRule = string | { path: string; methods?: [HTTPMethod, ...HTTPMethod[]] };\n\nexport interface CSRFConfig {\n /**\n * Cookie name for CSRF token\n * @default 'XSRF-TOKEN'\n */\n cookieName?: string;\n\n /**\n * Header name for CSRF token\n * @default 'X-XSRF-TOKEN'\n */\n headerName?: string;\n\n /**\n * Ignore rules for specific paths and methods\n * @example\n * [\n * { path: '/api/webhook/*', methods: ['POST'] },\n * { path: '/auth/apple/callback' }, // ignores all methods\n * ]\n */\n ignores?: CSRFIgnoreRule[];\n\n /**\n * Skip CSRF check for these methods\n * @default ['GET', 'HEAD', 'OPTIONS']\n */\n safeMethods?: HTTPMethod[];\n\n /**\n * Custom error message\n * @default 'CSRF token validation failed'\n */\n errorMessage?: string;\n}\n\n/** use timing safe compare to prevent timing attack */\nfunction safeCompare(a: string, b: string): boolean {\n if (typeof a !== 'string' || typeof b !== 'string') return false;\n if (a.length === 0 || b.length === 0) return false;\n\n const bufferA = Buffer.from(a, 'utf-8');\n const bufferB = Buffer.from(b, 'utf-8');\n if (bufferA.length !== bufferB.length) return false;\n return timingSafeEqual(bufferA, bufferB);\n}\n\n/**\n * Create CSRF protection middleware\n *\n * @example\n * ```ts\n * import { Hono } from 'hono';\n * import { csrf } from '@shware/http/hono';\n *\n * const app = new Hono();\n *\n * // basic usage\n * app.use(csrf());\n *\n * // with configuration\n * app.use(csrf({\n * cookieName: 'csrf-token',\n * headerName: 'X-CSRF-Token',\n * ignores: [\n * { path: '/api/webhook/*', methods: ['POST'] },\n * { path: '/auth/apple/callback' },\n * ]\n * }));\n * ```\n */\nexport function csrf(config: CSRFConfig = {}): MiddlewareHandler {\n const cookieName = config.cookieName ?? 'XSRF-TOKEN';\n const headerName = config.headerName ?? 'X-XSRF-TOKEN';\n const safeMethods = new Set(config.safeMethods ?? ['GET', 'HEAD', 'OPTIONS']);\n const errorMessage = config.errorMessage ?? 'CSRF token validation failed';\n\n // initialize router for matching ignore rules\n const router = new SmartRouter<boolean>({\n routers: [new RegExpRouter(), new TrieRouter()],\n });\n\n // register ignore rules\n if (config.ignores) {\n for (const rule of config.ignores) {\n if (typeof rule === 'string') {\n router.add(METHOD_NAME_ALL, rule, true);\n } else if (rule.methods && rule.methods.length > 0) {\n for (const method of rule.methods) {\n router.add(method, rule.path, true);\n }\n } else {\n // if no methods are specified, ignore all methods\n router.add(METHOD_NAME_ALL, rule.path, true);\n }\n }\n }\n\n // check if the request should be ignored\n
|
|
1
|
+
{"version":3,"sources":["../../src/hono/csrf.ts"],"sourcesContent":["import { timingSafeEqual } from 'crypto';\nimport { getCookie } from 'hono/cookie';\nimport { METHOD_NAME_ALL } from 'hono/router';\nimport { RegExpRouter } from 'hono/router/reg-exp-router';\nimport { SmartRouter } from 'hono/router/smart-router';\nimport { TrieRouter } from 'hono/router/trie-router';\nimport { Status } from '../error/status';\nimport type { MiddlewareHandler } from 'hono';\n\ntype HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';\n\nexport type CSRFIgnoreRule = string | { path: string; methods?: [HTTPMethod, ...HTTPMethod[]] };\n\nexport interface CSRFConfig {\n /**\n * Cookie name for CSRF token\n * @default 'XSRF-TOKEN'\n */\n cookieName?: string;\n\n /**\n * Header name for CSRF token\n * @default 'X-XSRF-TOKEN'\n */\n headerName?: string;\n\n /**\n * Ignore rules for specific paths and methods\n * @example\n * [\n * { path: '/api/webhook/*', methods: ['POST'] },\n * { path: '/auth/apple/callback' }, // ignores all methods\n * ]\n */\n ignores?: CSRFIgnoreRule[];\n\n /**\n * Skip CSRF check for these methods\n * @default ['GET', 'HEAD', 'OPTIONS']\n */\n safeMethods?: HTTPMethod[];\n\n /**\n * Origin allowed to bypass CSRF check\n * @default undefined\n */\n origin?: string[];\n\n /**\n * Sec-Fetch-Site allowed to bypass CSRF check\n * @default undefined\n */\n secFetchSite?: Array<'same-origin' | 'same-site' | 'none' | 'cross-origin'>;\n\n /**\n * Custom error message\n * @default 'CSRF token validation failed'\n */\n errorMessage?: string;\n}\n\n/** use timing safe compare to prevent timing attack */\nfunction safeCompare(a: string, b: string): boolean {\n if (typeof a !== 'string' || typeof b !== 'string') return false;\n if (a.length === 0 || b.length === 0) return false;\n\n const bufferA = Buffer.from(a, 'utf-8');\n const bufferB = Buffer.from(b, 'utf-8');\n if (bufferA.length !== bufferB.length) return false;\n return timingSafeEqual(bufferA, bufferB);\n}\n\n/**\n * Create CSRF protection middleware\n *\n * @example\n * ```ts\n * import { Hono } from 'hono';\n * import { csrf } from '@shware/http/hono';\n *\n * const app = new Hono();\n *\n * // basic usage\n * app.use(csrf());\n *\n * // with configuration\n * app.use(csrf({\n * cookieName: 'csrf-token',\n * headerName: 'X-CSRF-Token',\n * ignores: [\n * { path: '/api/webhook/*', methods: ['POST'] },\n * { path: '/auth/apple/callback' },\n * ]\n * }));\n * ```\n */\nexport function csrf(config: CSRFConfig = {}): MiddlewareHandler {\n const cookieName = config.cookieName ?? 'XSRF-TOKEN';\n const headerName = config.headerName ?? 'X-XSRF-TOKEN';\n const safeMethods = new Set(config.safeMethods ?? ['GET', 'HEAD', 'OPTIONS']);\n const errorMessage = config.errorMessage ?? 'CSRF token validation failed';\n\n // initialize router for matching ignore rules\n const router = new SmartRouter<boolean>({\n routers: [new RegExpRouter(), new TrieRouter()],\n });\n\n // register ignore rules\n if (config.ignores) {\n for (const rule of config.ignores) {\n if (typeof rule === 'string') {\n router.add(METHOD_NAME_ALL, rule, true);\n } else if (rule.methods && rule.methods.length > 0) {\n for (const method of rule.methods) {\n router.add(method, rule.path, true);\n }\n } else {\n // if no methods are specified, ignore all methods\n router.add(METHOD_NAME_ALL, rule.path, true);\n }\n }\n }\n\n // return middleware\n return async (c, next) => {\n const method = c.req.method;\n const path = c.req.path;\n\n // check if the request should be ignored\n // 1. ignore safe methods\n if (safeMethods.has(method)) {\n await next();\n return;\n }\n\n // 2. ignore configured origin\n if (config.origin && config.origin.includes(c.req.header('origin') ?? '')) {\n await next();\n return;\n }\n\n // 3. ignore configured secFetchSite\n if (\n config.secFetchSite &&\n config.secFetchSite.includes((c.req.header('sec-fetch-site') ?? '') as never)\n ) {\n await next();\n return;\n }\n\n // 4. ignore configured ignore rules\n const [matched] = router.match(method, path);\n if (matched.length > 0) {\n await next();\n return;\n }\n\n const cookieToken = getCookie(c, cookieName);\n const headerToken = c.req.header(headerName);\n\n if (!cookieToken || !headerToken) {\n throw Status.permissionDenied(errorMessage).error();\n }\n\n if (!safeCompare(cookieToken, headerToken)) {\n throw Status.permissionDenied(errorMessage).error();\n }\n\n await next();\n };\n}\n"],"mappings":";AAAA,SAAS,uBAAuB;AAChC,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB;AAChC,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AAwDvB,SAAS,YAAY,GAAW,GAAoB;AAClD,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SAAU,QAAO;AAC3D,MAAI,EAAE,WAAW,KAAK,EAAE,WAAW,EAAG,QAAO;AAE7C,QAAM,UAAU,OAAO,KAAK,GAAG,OAAO;AACtC,QAAM,UAAU,OAAO,KAAK,GAAG,OAAO;AACtC,MAAI,QAAQ,WAAW,QAAQ,OAAQ,QAAO;AAC9C,SAAO,gBAAgB,SAAS,OAAO;AACzC;AA0BO,SAAS,KAAK,SAAqB,CAAC,GAAsB;AAC/D,QAAM,aAAa,OAAO,cAAc;AACxC,QAAM,aAAa,OAAO,cAAc;AACxC,QAAM,cAAc,IAAI,IAAI,OAAO,eAAe,CAAC,OAAO,QAAQ,SAAS,CAAC;AAC5E,QAAM,eAAe,OAAO,gBAAgB;AAG5C,QAAM,SAAS,IAAI,YAAqB;AAAA,IACtC,SAAS,CAAC,IAAI,aAAa,GAAG,IAAI,WAAW,CAAC;AAAA,EAChD,CAAC;AAGD,MAAI,OAAO,SAAS;AAClB,eAAW,QAAQ,OAAO,SAAS;AACjC,UAAI,OAAO,SAAS,UAAU;AAC5B,eAAO,IAAI,iBAAiB,MAAM,IAAI;AAAA,MACxC,WAAW,KAAK,WAAW,KAAK,QAAQ,SAAS,GAAG;AAClD,mBAAW,UAAU,KAAK,SAAS;AACjC,iBAAO,IAAI,QAAQ,KAAK,MAAM,IAAI;AAAA,QACpC;AAAA,MACF,OAAO;AAEL,eAAO,IAAI,iBAAiB,KAAK,MAAM,IAAI;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AAGA,SAAO,OAAO,GAAG,SAAS;AACxB,UAAM,SAAS,EAAE,IAAI;AACrB,UAAM,OAAO,EAAE,IAAI;AAInB,QAAI,YAAY,IAAI,MAAM,GAAG;AAC3B,YAAM,KAAK;AACX;AAAA,IACF;AAGA,QAAI,OAAO,UAAU,OAAO,OAAO,SAAS,EAAE,IAAI,OAAO,QAAQ,KAAK,EAAE,GAAG;AACzE,YAAM,KAAK;AACX;AAAA,IACF;AAGA,QACE,OAAO,gBACP,OAAO,aAAa,SAAU,EAAE,IAAI,OAAO,gBAAgB,KAAK,EAAY,GAC5E;AACA,YAAM,KAAK;AACX;AAAA,IACF;AAGA,UAAM,CAAC,OAAO,IAAI,OAAO,MAAM,QAAQ,IAAI;AAC3C,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,KAAK;AACX;AAAA,IACF;AAEA,UAAM,cAAc,UAAU,GAAG,UAAU;AAC3C,UAAM,cAAc,EAAE,IAAI,OAAO,UAAU;AAE3C,QAAI,CAAC,eAAe,CAAC,aAAa;AAChC,YAAM,OAAO,iBAAiB,YAAY,EAAE,MAAM;AAAA,IACpD;AAEA,QAAI,CAAC,YAAY,aAAa,WAAW,GAAG;AAC1C,YAAM,OAAO,iBAAiB,YAAY,EAAE,MAAM;AAAA,IACpD;AAEA,UAAM,KAAK;AAAA,EACb;AACF;","names":[]}
|