@shware/http 1.1.9 → 1.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/hono/__tests__/csrf.test.cjs +164 -0
- package/dist/hono/__tests__/csrf.test.cjs.map +1 -0
- package/dist/hono/__tests__/csrf.test.mjs +162 -0
- package/dist/hono/__tests__/csrf.test.mjs.map +1 -0
- package/dist/hono/authorizer.cjs +30 -2
- package/dist/hono/authorizer.cjs.map +1 -1
- package/dist/hono/authorizer.d.cts +10 -1
- package/dist/hono/authorizer.d.ts +10 -1
- package/dist/hono/authorizer.mjs +28 -1
- package/dist/hono/authorizer.mjs.map +1 -1
- package/dist/hono/csrf.cjs +93 -0
- package/dist/hono/csrf.cjs.map +1 -0
- package/dist/hono/csrf.d.cts +65 -0
- package/dist/hono/csrf.d.ts +65 -0
- package/dist/hono/csrf.mjs +68 -0
- package/dist/hono/csrf.mjs.map +1 -0
- package/dist/hono/handler.cjs.map +1 -1
- package/dist/hono/handler.d.cts +1 -1
- package/dist/hono/handler.d.ts +1 -1
- package/dist/hono/handler.mjs.map +1 -1
- package/dist/hono/index.cjs +5 -0
- package/dist/hono/index.cjs.map +1 -1
- package/dist/hono/index.d.cts +2 -1
- package/dist/hono/index.d.ts +2 -1
- package/dist/hono/index.mjs +4 -1
- package/dist/hono/index.mjs.map +1 -1
- package/dist/{__tests__ → utils/__tests__}/ip.test.cjs +2 -2
- package/dist/utils/__tests__/ip.test.cjs.map +1 -0
- package/dist/utils/__tests__/ip.test.d.cts +2 -0
- package/dist/utils/__tests__/ip.test.d.ts +2 -0
- package/dist/{__tests__ → utils/__tests__}/ip.test.mjs +2 -2
- package/dist/utils/__tests__/ip.test.mjs.map +1 -0
- package/package.json +12 -12
- package/dist/__tests__/ip.test.cjs.map +0 -1
- package/dist/__tests__/ip.test.mjs.map +0 -1
- /package/dist/{__tests__/ip.test.d.cts → hono/__tests__/csrf.test.d.cts} +0 -0
- /package/dist/{__tests__/ip.test.d.ts → hono/__tests__/csrf.test.d.ts} +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Shware Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// src/hono/__tests__/csrf.test.ts
|
|
4
|
+
var import_hono = require("hono");
|
|
5
|
+
var import_csrf = require("../csrf.cjs");
|
|
6
|
+
var import_handler = require("../handler.cjs");
|
|
7
|
+
describe("CSRF Protection", () => {
|
|
8
|
+
describe("csrfProtection middleware", () => {
|
|
9
|
+
let app;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
app = new import_hono.Hono();
|
|
12
|
+
app.onError(import_handler.errorHandler);
|
|
13
|
+
});
|
|
14
|
+
it("should allow GET requests without CSRF token", async () => {
|
|
15
|
+
app.use((0, import_csrf.csrf)());
|
|
16
|
+
app.get("/test", (c) => c.text("OK"));
|
|
17
|
+
const res = await app.request("/test");
|
|
18
|
+
expect(res.status).toBe(200);
|
|
19
|
+
expect(await res.text()).toBe("OK");
|
|
20
|
+
});
|
|
21
|
+
it("should allow HEAD requests without CSRF token", async () => {
|
|
22
|
+
app.use((0, import_csrf.csrf)());
|
|
23
|
+
app.all("/test", (c) => c.text("OK"));
|
|
24
|
+
const res = await app.request("/test", { method: "HEAD" });
|
|
25
|
+
expect(res.status).toBe(200);
|
|
26
|
+
});
|
|
27
|
+
it("should allow OPTIONS requests without CSRF token", async () => {
|
|
28
|
+
app.use((0, import_csrf.csrf)());
|
|
29
|
+
app.options("/test", (c) => c.text("OK"));
|
|
30
|
+
const res = await app.request("/test", { method: "OPTIONS" });
|
|
31
|
+
expect(res.status).toBe(200);
|
|
32
|
+
});
|
|
33
|
+
it("should reject POST requests without CSRF token", async () => {
|
|
34
|
+
app.use((0, import_csrf.csrf)());
|
|
35
|
+
app.post("/test", (c) => c.text("OK"));
|
|
36
|
+
const res = await app.request("/test", { method: "POST" });
|
|
37
|
+
expect(res.status).toBe(403);
|
|
38
|
+
});
|
|
39
|
+
it("should reject POST requests with mismatched tokens", async () => {
|
|
40
|
+
app.use((0, import_csrf.csrf)());
|
|
41
|
+
app.post("/test", (c) => c.text("OK"));
|
|
42
|
+
const res = await app.request("/test", {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: { "X-XSRF-TOKEN": "header-token", Cookie: "XSRF-TOKEN=cookie-token" }
|
|
45
|
+
});
|
|
46
|
+
expect(res.status).toBe(403);
|
|
47
|
+
});
|
|
48
|
+
it("should allow POST requests with matching tokens", async () => {
|
|
49
|
+
app.use((0, import_csrf.csrf)());
|
|
50
|
+
app.post("/test", (c) => c.text("OK"));
|
|
51
|
+
const res = await app.request("/test", {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers: {
|
|
54
|
+
"X-XSRF-TOKEN": "matching-token",
|
|
55
|
+
Cookie: "XSRF-TOKEN=matching-token"
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
expect(res.status).toBe(200);
|
|
59
|
+
expect(await res.text()).toBe("OK");
|
|
60
|
+
});
|
|
61
|
+
it("should use custom cookie and header names", async () => {
|
|
62
|
+
app.use((0, import_csrf.csrf)({ cookieName: "csrf-token", headerName: "X-CSRF-Token" }));
|
|
63
|
+
app.post("/test", (c) => c.text("OK"));
|
|
64
|
+
const res = await app.request("/test", {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
"X-CSRF-Token": "test-token",
|
|
68
|
+
Cookie: "csrf-token=test-token"
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
expect(res.status).toBe(200);
|
|
72
|
+
});
|
|
73
|
+
it("should ignore specified paths", async () => {
|
|
74
|
+
app.use((0, import_csrf.csrf)({ ignores: [{ path: "/webhook/*", methods: ["POST"] }] }));
|
|
75
|
+
app.post("/webhook/stripe", (c) => c.text("OK"));
|
|
76
|
+
app.post("/api/data", (c) => c.text("OK"));
|
|
77
|
+
const webhookRes = await app.request("/webhook/stripe", { method: "POST" });
|
|
78
|
+
expect(webhookRes.status).toBe(200);
|
|
79
|
+
const apiRes = await app.request("/api/data", { method: "POST" });
|
|
80
|
+
expect(apiRes.status).toBe(403);
|
|
81
|
+
});
|
|
82
|
+
it("should ignore all methods for a path when methods not specified", async () => {
|
|
83
|
+
app.use((0, import_csrf.csrf)({ ignores: [{ path: "/auth/apple/callback" }] }));
|
|
84
|
+
app.post("/auth/apple/callback", (c) => c.text("OK"));
|
|
85
|
+
app.put("/auth/apple/callback", (c) => c.text("OK"));
|
|
86
|
+
const postRes = await app.request("/auth/apple/callback", { method: "POST" });
|
|
87
|
+
expect(postRes.status).toBe(200);
|
|
88
|
+
const putRes = await app.request("/auth/apple/callback", { method: "PUT" });
|
|
89
|
+
expect(putRes.status).toBe(200);
|
|
90
|
+
});
|
|
91
|
+
it("should handle empty tokens safely", async () => {
|
|
92
|
+
app.use((0, import_csrf.csrf)());
|
|
93
|
+
app.post("/test", (c) => c.text("OK"));
|
|
94
|
+
const res = await app.request("/test", {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: { "X-XSRF-TOKEN": "", Cookie: "XSRF-TOKEN=" }
|
|
97
|
+
});
|
|
98
|
+
expect(res.status).toBe(403);
|
|
99
|
+
});
|
|
100
|
+
it("should handle missing cookie", async () => {
|
|
101
|
+
app.use((0, import_csrf.csrf)());
|
|
102
|
+
app.post("/test", (c) => c.text("OK"));
|
|
103
|
+
const res = await app.request("/test", {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: { "X-XSRF-TOKEN": "token" }
|
|
106
|
+
});
|
|
107
|
+
expect(res.status).toBe(403);
|
|
108
|
+
});
|
|
109
|
+
it("should handle missing header", async () => {
|
|
110
|
+
app.use((0, import_csrf.csrf)());
|
|
111
|
+
app.post("/test", (c) => c.text("OK"));
|
|
112
|
+
const res = await app.request("/test", {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: { Cookie: "XSRF-TOKEN=token" }
|
|
115
|
+
});
|
|
116
|
+
expect(res.status).toBe(403);
|
|
117
|
+
});
|
|
118
|
+
it("should use custom error message", async () => {
|
|
119
|
+
app.use((0, import_csrf.csrf)({ errorMessage: "Custom CSRF error" }));
|
|
120
|
+
app.post("/test", (c) => c.text("OK"));
|
|
121
|
+
const res = await app.request("/test", { method: "POST" });
|
|
122
|
+
expect(res.status).toBe(403);
|
|
123
|
+
const body = await res.json();
|
|
124
|
+
expect(body.error.message).toBe("Custom CSRF error");
|
|
125
|
+
});
|
|
126
|
+
it("should work with custom safe methods", async () => {
|
|
127
|
+
app.use((0, import_csrf.csrf)({ safeMethods: ["GET"] }));
|
|
128
|
+
app.all("/test", (c) => c.text("OK"));
|
|
129
|
+
const res = await app.request("/test", { method: "HEAD" });
|
|
130
|
+
expect(res.status).toBe(403);
|
|
131
|
+
});
|
|
132
|
+
it("should handle complex ignore patterns", async () => {
|
|
133
|
+
app.use(
|
|
134
|
+
(0, import_csrf.csrf)({
|
|
135
|
+
ignores: [
|
|
136
|
+
{ path: "/api/v1/*", methods: ["GET", "POST"] },
|
|
137
|
+
{ path: "/api/v2/*", methods: ["POST"] },
|
|
138
|
+
{ path: "/public/*" }
|
|
139
|
+
// All methods
|
|
140
|
+
]
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
app.get("/api/v1/users", (c) => c.text("OK"));
|
|
144
|
+
app.post("/api/v1/users", (c) => c.text("OK"));
|
|
145
|
+
app.put("/api/v1/users", (c) => c.text("OK"));
|
|
146
|
+
app.post("/api/v2/users", (c) => c.text("OK"));
|
|
147
|
+
app.get("/api/v2/users", (c) => c.text("OK"));
|
|
148
|
+
app.post("/public/data", (c) => c.text("OK"));
|
|
149
|
+
let res = await app.request("/api/v1/users", { method: "GET" });
|
|
150
|
+
expect(res.status).toBe(200);
|
|
151
|
+
res = await app.request("/api/v1/users", { method: "POST" });
|
|
152
|
+
expect(res.status).toBe(200);
|
|
153
|
+
res = await app.request("/api/v1/users", { method: "PUT" });
|
|
154
|
+
expect(res.status).toBe(403);
|
|
155
|
+
res = await app.request("/api/v2/users", { method: "POST" });
|
|
156
|
+
expect(res.status).toBe(200);
|
|
157
|
+
res = await app.request("/api/v2/users", { method: "GET" });
|
|
158
|
+
expect(res.status).toBe(200);
|
|
159
|
+
res = await app.request("/public/data", { method: "POST" });
|
|
160
|
+
expect(res.status).toBe(200);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
//# sourceMappingURL=csrf.test.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hono/__tests__/csrf.test.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport { csrf } from '../csrf';\nimport { errorHandler, type Env } from '../handler';\n\ndescribe('CSRF Protection', () => {\n describe('csrfProtection middleware', () => {\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});\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// src/hono/__tests__/csrf.test.ts
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { csrf } from "../csrf.mjs";
|
|
4
|
+
import { errorHandler } from "../handler.mjs";
|
|
5
|
+
describe("CSRF Protection", () => {
|
|
6
|
+
describe("csrfProtection middleware", () => {
|
|
7
|
+
let app;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
app = new Hono();
|
|
10
|
+
app.onError(errorHandler);
|
|
11
|
+
});
|
|
12
|
+
it("should allow GET requests without CSRF token", async () => {
|
|
13
|
+
app.use(csrf());
|
|
14
|
+
app.get("/test", (c) => c.text("OK"));
|
|
15
|
+
const res = await app.request("/test");
|
|
16
|
+
expect(res.status).toBe(200);
|
|
17
|
+
expect(await res.text()).toBe("OK");
|
|
18
|
+
});
|
|
19
|
+
it("should allow HEAD requests without CSRF token", async () => {
|
|
20
|
+
app.use(csrf());
|
|
21
|
+
app.all("/test", (c) => c.text("OK"));
|
|
22
|
+
const res = await app.request("/test", { method: "HEAD" });
|
|
23
|
+
expect(res.status).toBe(200);
|
|
24
|
+
});
|
|
25
|
+
it("should allow OPTIONS requests without CSRF token", async () => {
|
|
26
|
+
app.use(csrf());
|
|
27
|
+
app.options("/test", (c) => c.text("OK"));
|
|
28
|
+
const res = await app.request("/test", { method: "OPTIONS" });
|
|
29
|
+
expect(res.status).toBe(200);
|
|
30
|
+
});
|
|
31
|
+
it("should reject POST requests without CSRF token", async () => {
|
|
32
|
+
app.use(csrf());
|
|
33
|
+
app.post("/test", (c) => c.text("OK"));
|
|
34
|
+
const res = await app.request("/test", { method: "POST" });
|
|
35
|
+
expect(res.status).toBe(403);
|
|
36
|
+
});
|
|
37
|
+
it("should reject POST requests with mismatched tokens", async () => {
|
|
38
|
+
app.use(csrf());
|
|
39
|
+
app.post("/test", (c) => c.text("OK"));
|
|
40
|
+
const res = await app.request("/test", {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: { "X-XSRF-TOKEN": "header-token", Cookie: "XSRF-TOKEN=cookie-token" }
|
|
43
|
+
});
|
|
44
|
+
expect(res.status).toBe(403);
|
|
45
|
+
});
|
|
46
|
+
it("should allow POST requests with matching tokens", async () => {
|
|
47
|
+
app.use(csrf());
|
|
48
|
+
app.post("/test", (c) => c.text("OK"));
|
|
49
|
+
const res = await app.request("/test", {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: {
|
|
52
|
+
"X-XSRF-TOKEN": "matching-token",
|
|
53
|
+
Cookie: "XSRF-TOKEN=matching-token"
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
expect(res.status).toBe(200);
|
|
57
|
+
expect(await res.text()).toBe("OK");
|
|
58
|
+
});
|
|
59
|
+
it("should use custom cookie and header names", async () => {
|
|
60
|
+
app.use(csrf({ cookieName: "csrf-token", headerName: "X-CSRF-Token" }));
|
|
61
|
+
app.post("/test", (c) => c.text("OK"));
|
|
62
|
+
const res = await app.request("/test", {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: {
|
|
65
|
+
"X-CSRF-Token": "test-token",
|
|
66
|
+
Cookie: "csrf-token=test-token"
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
expect(res.status).toBe(200);
|
|
70
|
+
});
|
|
71
|
+
it("should ignore specified paths", async () => {
|
|
72
|
+
app.use(csrf({ ignores: [{ path: "/webhook/*", methods: ["POST"] }] }));
|
|
73
|
+
app.post("/webhook/stripe", (c) => c.text("OK"));
|
|
74
|
+
app.post("/api/data", (c) => c.text("OK"));
|
|
75
|
+
const webhookRes = await app.request("/webhook/stripe", { method: "POST" });
|
|
76
|
+
expect(webhookRes.status).toBe(200);
|
|
77
|
+
const apiRes = await app.request("/api/data", { method: "POST" });
|
|
78
|
+
expect(apiRes.status).toBe(403);
|
|
79
|
+
});
|
|
80
|
+
it("should ignore all methods for a path when methods not specified", async () => {
|
|
81
|
+
app.use(csrf({ ignores: [{ path: "/auth/apple/callback" }] }));
|
|
82
|
+
app.post("/auth/apple/callback", (c) => c.text("OK"));
|
|
83
|
+
app.put("/auth/apple/callback", (c) => c.text("OK"));
|
|
84
|
+
const postRes = await app.request("/auth/apple/callback", { method: "POST" });
|
|
85
|
+
expect(postRes.status).toBe(200);
|
|
86
|
+
const putRes = await app.request("/auth/apple/callback", { method: "PUT" });
|
|
87
|
+
expect(putRes.status).toBe(200);
|
|
88
|
+
});
|
|
89
|
+
it("should handle empty tokens safely", async () => {
|
|
90
|
+
app.use(csrf());
|
|
91
|
+
app.post("/test", (c) => c.text("OK"));
|
|
92
|
+
const res = await app.request("/test", {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: { "X-XSRF-TOKEN": "", Cookie: "XSRF-TOKEN=" }
|
|
95
|
+
});
|
|
96
|
+
expect(res.status).toBe(403);
|
|
97
|
+
});
|
|
98
|
+
it("should handle missing cookie", async () => {
|
|
99
|
+
app.use(csrf());
|
|
100
|
+
app.post("/test", (c) => c.text("OK"));
|
|
101
|
+
const res = await app.request("/test", {
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: { "X-XSRF-TOKEN": "token" }
|
|
104
|
+
});
|
|
105
|
+
expect(res.status).toBe(403);
|
|
106
|
+
});
|
|
107
|
+
it("should handle missing header", async () => {
|
|
108
|
+
app.use(csrf());
|
|
109
|
+
app.post("/test", (c) => c.text("OK"));
|
|
110
|
+
const res = await app.request("/test", {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: { Cookie: "XSRF-TOKEN=token" }
|
|
113
|
+
});
|
|
114
|
+
expect(res.status).toBe(403);
|
|
115
|
+
});
|
|
116
|
+
it("should use custom error message", async () => {
|
|
117
|
+
app.use(csrf({ errorMessage: "Custom CSRF error" }));
|
|
118
|
+
app.post("/test", (c) => c.text("OK"));
|
|
119
|
+
const res = await app.request("/test", { method: "POST" });
|
|
120
|
+
expect(res.status).toBe(403);
|
|
121
|
+
const body = await res.json();
|
|
122
|
+
expect(body.error.message).toBe("Custom CSRF error");
|
|
123
|
+
});
|
|
124
|
+
it("should work with custom safe methods", async () => {
|
|
125
|
+
app.use(csrf({ safeMethods: ["GET"] }));
|
|
126
|
+
app.all("/test", (c) => c.text("OK"));
|
|
127
|
+
const res = await app.request("/test", { method: "HEAD" });
|
|
128
|
+
expect(res.status).toBe(403);
|
|
129
|
+
});
|
|
130
|
+
it("should handle complex ignore patterns", async () => {
|
|
131
|
+
app.use(
|
|
132
|
+
csrf({
|
|
133
|
+
ignores: [
|
|
134
|
+
{ path: "/api/v1/*", methods: ["GET", "POST"] },
|
|
135
|
+
{ path: "/api/v2/*", methods: ["POST"] },
|
|
136
|
+
{ path: "/public/*" }
|
|
137
|
+
// All methods
|
|
138
|
+
]
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
app.get("/api/v1/users", (c) => c.text("OK"));
|
|
142
|
+
app.post("/api/v1/users", (c) => c.text("OK"));
|
|
143
|
+
app.put("/api/v1/users", (c) => c.text("OK"));
|
|
144
|
+
app.post("/api/v2/users", (c) => c.text("OK"));
|
|
145
|
+
app.get("/api/v2/users", (c) => c.text("OK"));
|
|
146
|
+
app.post("/public/data", (c) => c.text("OK"));
|
|
147
|
+
let res = await app.request("/api/v1/users", { method: "GET" });
|
|
148
|
+
expect(res.status).toBe(200);
|
|
149
|
+
res = await app.request("/api/v1/users", { method: "POST" });
|
|
150
|
+
expect(res.status).toBe(200);
|
|
151
|
+
res = await app.request("/api/v1/users", { method: "PUT" });
|
|
152
|
+
expect(res.status).toBe(403);
|
|
153
|
+
res = await app.request("/api/v2/users", { method: "POST" });
|
|
154
|
+
expect(res.status).toBe(200);
|
|
155
|
+
res = await app.request("/api/v2/users", { method: "GET" });
|
|
156
|
+
expect(res.status).toBe(200);
|
|
157
|
+
res = await app.request("/public/data", { method: "POST" });
|
|
158
|
+
expect(res.status).toBe(200);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
//# sourceMappingURL=csrf.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hono/__tests__/csrf.test.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport { csrf } from '../csrf';\nimport { errorHandler, type Env } from '../handler';\n\ndescribe('CSRF Protection', () => {\n describe('csrfProtection middleware', () => {\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});\n"],"mappings":"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","names":[]}
|
package/dist/hono/authorizer.cjs
CHANGED
|
@@ -20,7 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/hono/authorizer.ts
|
|
21
21
|
var authorizer_exports = {};
|
|
22
22
|
__export(authorizer_exports, {
|
|
23
|
-
Authorizer: () => Authorizer
|
|
23
|
+
Authorizer: () => Authorizer,
|
|
24
|
+
authorizer: () => authorizer
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(authorizer_exports);
|
|
26
27
|
var import_router = require("hono/router");
|
|
@@ -64,8 +65,35 @@ var Authorizer = class _Authorizer {
|
|
|
64
65
|
};
|
|
65
66
|
};
|
|
66
67
|
};
|
|
68
|
+
function authorizer({
|
|
69
|
+
auth,
|
|
70
|
+
errorMessage = "Unauthorized, please login to continue.",
|
|
71
|
+
rules = []
|
|
72
|
+
}) {
|
|
73
|
+
const router = new import_smart_router.SmartRouter({ routers: [new import_reg_exp_router.RegExpRouter(), new import_trie_router.TrieRouter()] });
|
|
74
|
+
for (const { path, methods = [import_router.METHOD_NAME_ALL] } of rules) {
|
|
75
|
+
for (const method of methods) {
|
|
76
|
+
router.add(method, path, null);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return async (c, next) => {
|
|
80
|
+
if (c.req.method === "OPTIONS") {
|
|
81
|
+
await next();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const [matched] = router.match(c.req.method, c.req.path);
|
|
85
|
+
if (matched.length === 0) {
|
|
86
|
+
await next();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const authenticated = await auth.isAuthenticated(c.req.raw);
|
|
90
|
+
if (!authenticated) throw import_status.Status.unauthorized(errorMessage).error();
|
|
91
|
+
await next();
|
|
92
|
+
};
|
|
93
|
+
}
|
|
67
94
|
// Annotate the CommonJS export names for ESM import in node:
|
|
68
95
|
0 && (module.exports = {
|
|
69
|
-
Authorizer
|
|
96
|
+
Authorizer,
|
|
97
|
+
authorizer
|
|
70
98
|
});
|
|
71
99
|
//# sourceMappingURL=authorizer.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hono/authorizer.ts"],"sourcesContent":["import { 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 Methods = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n\ntype Auth = {
|
|
1
|
+
{"version":3,"sources":["../../src/hono/authorizer.ts"],"sourcesContent":["import { 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 Methods = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n\ntype Auth = { isAuthenticated: (request: Request) => Promise<boolean> };\n\nexport class Authorizer {\n private readonly router = new SmartRouter<null>({\n routers: [new RegExpRouter(), new TrieRouter()],\n });\n\n private readonly auth: Auth;\n\n private constructor(auth: Auth) {\n this.auth = auth;\n }\n\n static create = (auth: Auth) => new Authorizer(auth);\n\n match(path: string, methods?: [Methods, ...Methods[]]) {\n if (methods) {\n for (const method of methods) {\n this.router.add(method, path, null);\n }\n } else {\n this.router.add(METHOD_NAME_ALL, path, null);\n }\n return this;\n }\n\n build = (): MiddlewareHandler => {\n return async (c, next) => {\n if (c.req.method === 'OPTIONS') {\n await next();\n return;\n }\n\n const [matched] = this.router.match(c.req.method, c.req.path);\n if (matched.length === 0) {\n await next();\n return;\n }\n\n const authenticated = await this.auth.isAuthenticated(c.req.raw);\n if (!authenticated) throw Status.unauthorized().error();\n await next();\n };\n };\n}\n\nexport interface AuthorizerConfig {\n auth: Auth;\n errorMessage?: string;\n rules?: { path: string; methods?: [Methods, ...Methods[]] }[];\n}\n\nexport function authorizer({\n auth,\n errorMessage = 'Unauthorized, please login to continue.',\n rules = [],\n}: AuthorizerConfig): MiddlewareHandler {\n const router = new SmartRouter<null>({ routers: [new RegExpRouter(), new TrieRouter()] });\n\n for (const { path, methods = [METHOD_NAME_ALL] } of rules) {\n for (const method of methods) {\n router.add(method, path, null);\n }\n }\n\n return async (c, next) => {\n if (c.req.method === 'OPTIONS') {\n await next();\n return;\n }\n\n const [matched] = router.match(c.req.method, c.req.path);\n if (matched.length === 0) {\n await next();\n return;\n }\n\n const authenticated = await auth.isAuthenticated(c.req.raw);\n if (!authenticated) throw Status.unauthorized(errorMessage).error();\n await next();\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAgC;AAChC,4BAA6B;AAC7B,0BAA4B;AAC5B,yBAA2B;AAC3B,oBAAuB;AAOhB,IAAM,aAAN,MAAM,YAAW;AAAA,EACL,SAAS,IAAI,gCAAkB;AAAA,IAC9C,SAAS,CAAC,IAAI,mCAAa,GAAG,IAAI,8BAAW,CAAC;AAAA,EAChD,CAAC;AAAA,EAEgB;AAAA,EAET,YAAY,MAAY;AAC9B,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,OAAO,SAAS,CAAC,SAAe,IAAI,YAAW,IAAI;AAAA,EAEnD,MAAM,MAAc,SAAmC;AACrD,QAAI,SAAS;AACX,iBAAW,UAAU,SAAS;AAC5B,aAAK,OAAO,IAAI,QAAQ,MAAM,IAAI;AAAA,MACpC;AAAA,IACF,OAAO;AACL,WAAK,OAAO,IAAI,+BAAiB,MAAM,IAAI;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ,MAAyB;AAC/B,WAAO,OAAO,GAAG,SAAS;AACxB,UAAI,EAAE,IAAI,WAAW,WAAW;AAC9B,cAAM,KAAK;AACX;AAAA,MACF;AAEA,YAAM,CAAC,OAAO,IAAI,KAAK,OAAO,MAAM,EAAE,IAAI,QAAQ,EAAE,IAAI,IAAI;AAC5D,UAAI,QAAQ,WAAW,GAAG;AACxB,cAAM,KAAK;AACX;AAAA,MACF;AAEA,YAAM,gBAAgB,MAAM,KAAK,KAAK,gBAAgB,EAAE,IAAI,GAAG;AAC/D,UAAI,CAAC,cAAe,OAAM,qBAAO,aAAa,EAAE,MAAM;AACtD,YAAM,KAAK;AAAA,IACb;AAAA,EACF;AACF;AAQO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,eAAe;AAAA,EACf,QAAQ,CAAC;AACX,GAAwC;AACtC,QAAM,SAAS,IAAI,gCAAkB,EAAE,SAAS,CAAC,IAAI,mCAAa,GAAG,IAAI,8BAAW,CAAC,EAAE,CAAC;AAExF,aAAW,EAAE,MAAM,UAAU,CAAC,6BAAe,EAAE,KAAK,OAAO;AACzD,eAAW,UAAU,SAAS;AAC5B,aAAO,IAAI,QAAQ,MAAM,IAAI;AAAA,IAC/B;AAAA,EACF;AAEA,SAAO,OAAO,GAAG,SAAS;AACxB,QAAI,EAAE,IAAI,WAAW,WAAW;AAC9B,YAAM,KAAK;AACX;AAAA,IACF;AAEA,UAAM,CAAC,OAAO,IAAI,OAAO,MAAM,EAAE,IAAI,QAAQ,EAAE,IAAI,IAAI;AACvD,QAAI,QAAQ,WAAW,GAAG;AACxB,YAAM,KAAK;AACX;AAAA,IACF;AAEA,UAAM,gBAAgB,MAAM,KAAK,gBAAgB,EAAE,IAAI,GAAG;AAC1D,QAAI,CAAC,cAAe,OAAM,qBAAO,aAAa,YAAY,EAAE,MAAM;AAClE,UAAM,KAAK;AAAA,EACb;AACF;","names":[]}
|
|
@@ -12,5 +12,14 @@ declare class Authorizer {
|
|
|
12
12
|
match(path: string, methods?: [Methods, ...Methods[]]): this;
|
|
13
13
|
build: () => MiddlewareHandler;
|
|
14
14
|
}
|
|
15
|
+
interface AuthorizerConfig {
|
|
16
|
+
auth: Auth;
|
|
17
|
+
errorMessage?: string;
|
|
18
|
+
rules?: {
|
|
19
|
+
path: string;
|
|
20
|
+
methods?: [Methods, ...Methods[]];
|
|
21
|
+
}[];
|
|
22
|
+
}
|
|
23
|
+
declare function authorizer({ auth, errorMessage, rules, }: AuthorizerConfig): MiddlewareHandler;
|
|
15
24
|
|
|
16
|
-
export { Authorizer };
|
|
25
|
+
export { Authorizer, type AuthorizerConfig, authorizer };
|
|
@@ -12,5 +12,14 @@ declare class Authorizer {
|
|
|
12
12
|
match(path: string, methods?: [Methods, ...Methods[]]): this;
|
|
13
13
|
build: () => MiddlewareHandler;
|
|
14
14
|
}
|
|
15
|
+
interface AuthorizerConfig {
|
|
16
|
+
auth: Auth;
|
|
17
|
+
errorMessage?: string;
|
|
18
|
+
rules?: {
|
|
19
|
+
path: string;
|
|
20
|
+
methods?: [Methods, ...Methods[]];
|
|
21
|
+
}[];
|
|
22
|
+
}
|
|
23
|
+
declare function authorizer({ auth, errorMessage, rules, }: AuthorizerConfig): MiddlewareHandler;
|
|
15
24
|
|
|
16
|
-
export { Authorizer };
|
|
25
|
+
export { Authorizer, type AuthorizerConfig, authorizer };
|
package/dist/hono/authorizer.mjs
CHANGED
|
@@ -40,7 +40,34 @@ var Authorizer = class _Authorizer {
|
|
|
40
40
|
};
|
|
41
41
|
};
|
|
42
42
|
};
|
|
43
|
+
function authorizer({
|
|
44
|
+
auth,
|
|
45
|
+
errorMessage = "Unauthorized, please login to continue.",
|
|
46
|
+
rules = []
|
|
47
|
+
}) {
|
|
48
|
+
const router = new SmartRouter({ routers: [new RegExpRouter(), new TrieRouter()] });
|
|
49
|
+
for (const { path, methods = [METHOD_NAME_ALL] } of rules) {
|
|
50
|
+
for (const method of methods) {
|
|
51
|
+
router.add(method, path, null);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return async (c, next) => {
|
|
55
|
+
if (c.req.method === "OPTIONS") {
|
|
56
|
+
await next();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const [matched] = router.match(c.req.method, c.req.path);
|
|
60
|
+
if (matched.length === 0) {
|
|
61
|
+
await next();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const authenticated = await auth.isAuthenticated(c.req.raw);
|
|
65
|
+
if (!authenticated) throw Status.unauthorized(errorMessage).error();
|
|
66
|
+
await next();
|
|
67
|
+
};
|
|
68
|
+
}
|
|
43
69
|
export {
|
|
44
|
-
Authorizer
|
|
70
|
+
Authorizer,
|
|
71
|
+
authorizer
|
|
45
72
|
};
|
|
46
73
|
//# sourceMappingURL=authorizer.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hono/authorizer.ts"],"sourcesContent":["import { 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 Methods = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n\ntype Auth = {
|
|
1
|
+
{"version":3,"sources":["../../src/hono/authorizer.ts"],"sourcesContent":["import { 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 Methods = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';\n\ntype Auth = { isAuthenticated: (request: Request) => Promise<boolean> };\n\nexport class Authorizer {\n private readonly router = new SmartRouter<null>({\n routers: [new RegExpRouter(), new TrieRouter()],\n });\n\n private readonly auth: Auth;\n\n private constructor(auth: Auth) {\n this.auth = auth;\n }\n\n static create = (auth: Auth) => new Authorizer(auth);\n\n match(path: string, methods?: [Methods, ...Methods[]]) {\n if (methods) {\n for (const method of methods) {\n this.router.add(method, path, null);\n }\n } else {\n this.router.add(METHOD_NAME_ALL, path, null);\n }\n return this;\n }\n\n build = (): MiddlewareHandler => {\n return async (c, next) => {\n if (c.req.method === 'OPTIONS') {\n await next();\n return;\n }\n\n const [matched] = this.router.match(c.req.method, c.req.path);\n if (matched.length === 0) {\n await next();\n return;\n }\n\n const authenticated = await this.auth.isAuthenticated(c.req.raw);\n if (!authenticated) throw Status.unauthorized().error();\n await next();\n };\n };\n}\n\nexport interface AuthorizerConfig {\n auth: Auth;\n errorMessage?: string;\n rules?: { path: string; methods?: [Methods, ...Methods[]] }[];\n}\n\nexport function authorizer({\n auth,\n errorMessage = 'Unauthorized, please login to continue.',\n rules = [],\n}: AuthorizerConfig): MiddlewareHandler {\n const router = new SmartRouter<null>({ routers: [new RegExpRouter(), new TrieRouter()] });\n\n for (const { path, methods = [METHOD_NAME_ALL] } of rules) {\n for (const method of methods) {\n router.add(method, path, null);\n }\n }\n\n return async (c, next) => {\n if (c.req.method === 'OPTIONS') {\n await next();\n return;\n }\n\n const [matched] = router.match(c.req.method, c.req.path);\n if (matched.length === 0) {\n await next();\n return;\n }\n\n const authenticated = await auth.isAuthenticated(c.req.raw);\n if (!authenticated) throw Status.unauthorized(errorMessage).error();\n await next();\n };\n}\n"],"mappings":";AAAA,SAAS,uBAAuB;AAChC,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AAOhB,IAAM,aAAN,MAAM,YAAW;AAAA,EACL,SAAS,IAAI,YAAkB;AAAA,IAC9C,SAAS,CAAC,IAAI,aAAa,GAAG,IAAI,WAAW,CAAC;AAAA,EAChD,CAAC;AAAA,EAEgB;AAAA,EAET,YAAY,MAAY;AAC9B,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,OAAO,SAAS,CAAC,SAAe,IAAI,YAAW,IAAI;AAAA,EAEnD,MAAM,MAAc,SAAmC;AACrD,QAAI,SAAS;AACX,iBAAW,UAAU,SAAS;AAC5B,aAAK,OAAO,IAAI,QAAQ,MAAM,IAAI;AAAA,MACpC;AAAA,IACF,OAAO;AACL,WAAK,OAAO,IAAI,iBAAiB,MAAM,IAAI;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ,MAAyB;AAC/B,WAAO,OAAO,GAAG,SAAS;AACxB,UAAI,EAAE,IAAI,WAAW,WAAW;AAC9B,cAAM,KAAK;AACX;AAAA,MACF;AAEA,YAAM,CAAC,OAAO,IAAI,KAAK,OAAO,MAAM,EAAE,IAAI,QAAQ,EAAE,IAAI,IAAI;AAC5D,UAAI,QAAQ,WAAW,GAAG;AACxB,cAAM,KAAK;AACX;AAAA,MACF;AAEA,YAAM,gBAAgB,MAAM,KAAK,KAAK,gBAAgB,EAAE,IAAI,GAAG;AAC/D,UAAI,CAAC,cAAe,OAAM,OAAO,aAAa,EAAE,MAAM;AACtD,YAAM,KAAK;AAAA,IACb;AAAA,EACF;AACF;AAQO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,eAAe;AAAA,EACf,QAAQ,CAAC;AACX,GAAwC;AACtC,QAAM,SAAS,IAAI,YAAkB,EAAE,SAAS,CAAC,IAAI,aAAa,GAAG,IAAI,WAAW,CAAC,EAAE,CAAC;AAExF,aAAW,EAAE,MAAM,UAAU,CAAC,eAAe,EAAE,KAAK,OAAO;AACzD,eAAW,UAAU,SAAS;AAC5B,aAAO,IAAI,QAAQ,MAAM,IAAI;AAAA,IAC/B;AAAA,EACF;AAEA,SAAO,OAAO,GAAG,SAAS;AACxB,QAAI,EAAE,IAAI,WAAW,WAAW;AAC9B,YAAM,KAAK;AACX;AAAA,IACF;AAEA,UAAM,CAAC,OAAO,IAAI,OAAO,MAAM,EAAE,IAAI,QAAQ,EAAE,IAAI,IAAI;AACvD,QAAI,QAAQ,WAAW,GAAG;AACxB,YAAM,KAAK;AACX;AAAA,IACF;AAEA,UAAM,gBAAgB,MAAM,KAAK,gBAAgB,EAAE,IAAI,GAAG;AAC1D,QAAI,CAAC,cAAe,OAAM,OAAO,aAAa,YAAY,EAAE,MAAM;AAClE,UAAM,KAAK;AAAA,EACb;AACF;","names":[]}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/hono/csrf.ts
|
|
21
|
+
var csrf_exports = {};
|
|
22
|
+
__export(csrf_exports, {
|
|
23
|
+
csrf: () => csrf
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(csrf_exports);
|
|
26
|
+
var import_crypto = require("crypto");
|
|
27
|
+
var import_cookie = require("hono/cookie");
|
|
28
|
+
var import_router = require("hono/router");
|
|
29
|
+
var import_reg_exp_router = require("hono/router/reg-exp-router");
|
|
30
|
+
var import_smart_router = require("hono/router/smart-router");
|
|
31
|
+
var import_trie_router = require("hono/router/trie-router");
|
|
32
|
+
var import_status = require("../error/status.cjs");
|
|
33
|
+
function safeCompare(a, b) {
|
|
34
|
+
if (typeof a !== "string" || typeof b !== "string") return false;
|
|
35
|
+
if (a.length === 0 || b.length === 0) return false;
|
|
36
|
+
const bufferA = Buffer.from(a, "utf-8");
|
|
37
|
+
const bufferB = Buffer.from(b, "utf-8");
|
|
38
|
+
if (bufferA.length !== bufferB.length) return false;
|
|
39
|
+
return (0, import_crypto.timingSafeEqual)(bufferA, bufferB);
|
|
40
|
+
}
|
|
41
|
+
function csrf(config = {}) {
|
|
42
|
+
const cookieName = config.cookieName ?? "XSRF-TOKEN";
|
|
43
|
+
const headerName = config.headerName ?? "X-XSRF-TOKEN";
|
|
44
|
+
const safeMethods = new Set(config.safeMethods ?? ["GET", "HEAD", "OPTIONS"]);
|
|
45
|
+
const errorMessage = config.errorMessage ?? "CSRF token validation failed";
|
|
46
|
+
const router = new import_smart_router.SmartRouter({
|
|
47
|
+
routers: [new import_reg_exp_router.RegExpRouter(), new import_trie_router.TrieRouter()]
|
|
48
|
+
});
|
|
49
|
+
if (config.ignores) {
|
|
50
|
+
for (const rule of config.ignores) {
|
|
51
|
+
if (rule.methods && rule.methods.length > 0) {
|
|
52
|
+
for (const method of rule.methods) {
|
|
53
|
+
router.add(method, rule.path, true);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
router.add(import_router.METHOD_NAME_ALL, rule.path, true);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function shouldIgnore(method, path) {
|
|
61
|
+
if (safeMethods.has(method)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
const [matchedAll] = router.match(import_router.METHOD_NAME_ALL, path);
|
|
65
|
+
if (matchedAll.length > 0) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
const [matchedMethod] = router.match(method, path);
|
|
69
|
+
return matchedMethod.length > 0;
|
|
70
|
+
}
|
|
71
|
+
return async (c, next) => {
|
|
72
|
+
const method = c.req.method;
|
|
73
|
+
const path = c.req.path;
|
|
74
|
+
if (shouldIgnore(method, path)) {
|
|
75
|
+
await next();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const cookieToken = (0, import_cookie.getCookie)(c, cookieName);
|
|
79
|
+
const headerToken = c.req.header(headerName);
|
|
80
|
+
if (!cookieToken || !headerToken) {
|
|
81
|
+
throw import_status.Status.permissionDenied(errorMessage).error();
|
|
82
|
+
}
|
|
83
|
+
if (!safeCompare(cookieToken, headerToken)) {
|
|
84
|
+
throw import_status.Status.permissionDenied(errorMessage).error();
|
|
85
|
+
}
|
|
86
|
+
await next();
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
90
|
+
0 && (module.exports = {
|
|
91
|
+
csrf
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=csrf.cjs.map
|
|
@@ -0,0 +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 interface CSRFIgnoreRule {\n path: string;\n methods?: HTTPMethod[];\n}\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 (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 function shouldIgnore(method: string, path: string): boolean {\n if (safeMethods.has(method)) {\n return true;\n }\n const [matchedAll] = router.match(METHOD_NAME_ALL, path);\n if (matchedAll.length > 0) {\n return true;\n }\n const [matchedMethod] = router.match(method, path);\n return matchedMethod.length > 0;\n }\n\n // return middleware\n return async (c, next) => {\n const method = c.req.method;\n const path = c.req.path;\n\n if (shouldIgnore(method, path)) {\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;AA+CvB,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,KAAK,WAAW,KAAK,QAAQ,SAAS,GAAG;AAC3C,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,WAAS,aAAa,QAAgB,MAAuB;AAC3D,QAAI,YAAY,IAAI,MAAM,GAAG;AAC3B,aAAO;AAAA,IACT;AACA,UAAM,CAAC,UAAU,IAAI,OAAO,MAAM,+BAAiB,IAAI;AACvD,QAAI,WAAW,SAAS,GAAG;AACzB,aAAO;AAAA,IACT;AACA,UAAM,CAAC,aAAa,IAAI,OAAO,MAAM,QAAQ,IAAI;AACjD,WAAO,cAAc,SAAS;AAAA,EAChC;AAGA,SAAO,OAAO,GAAG,SAAS;AACxB,UAAM,SAAS,EAAE,IAAI;AACrB,UAAM,OAAO,EAAE,IAAI;AAEnB,QAAI,aAAa,QAAQ,IAAI,GAAG;AAC9B,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":[]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { MiddlewareHandler } from 'hono';
|
|
2
|
+
|
|
3
|
+
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
|
4
|
+
interface CSRFIgnoreRule {
|
|
5
|
+
path: string;
|
|
6
|
+
methods?: HTTPMethod[];
|
|
7
|
+
}
|
|
8
|
+
interface CSRFConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Cookie name for CSRF token
|
|
11
|
+
* @default 'XSRF-TOKEN'
|
|
12
|
+
*/
|
|
13
|
+
cookieName?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Header name for CSRF token
|
|
16
|
+
* @default 'X-XSRF-TOKEN'
|
|
17
|
+
*/
|
|
18
|
+
headerName?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Ignore rules for specific paths and methods
|
|
21
|
+
* @example
|
|
22
|
+
* [
|
|
23
|
+
* { path: '/api/webhook/*', methods: ['POST'] },
|
|
24
|
+
* { path: '/auth/apple/callback' }, // ignores all methods
|
|
25
|
+
* ]
|
|
26
|
+
*/
|
|
27
|
+
ignores?: CSRFIgnoreRule[];
|
|
28
|
+
/**
|
|
29
|
+
* Skip CSRF check for these methods
|
|
30
|
+
* @default ['GET', 'HEAD', 'OPTIONS']
|
|
31
|
+
*/
|
|
32
|
+
safeMethods?: HTTPMethod[];
|
|
33
|
+
/**
|
|
34
|
+
* Custom error message
|
|
35
|
+
* @default 'CSRF token validation failed'
|
|
36
|
+
*/
|
|
37
|
+
errorMessage?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create CSRF protection middleware
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import { Hono } from 'hono';
|
|
45
|
+
* import { csrf } from '@shware/http/hono';
|
|
46
|
+
*
|
|
47
|
+
* const app = new Hono();
|
|
48
|
+
*
|
|
49
|
+
* // basic usage
|
|
50
|
+
* app.use(csrf());
|
|
51
|
+
*
|
|
52
|
+
* // with configuration
|
|
53
|
+
* app.use(csrf({
|
|
54
|
+
* cookieName: 'csrf-token',
|
|
55
|
+
* headerName: 'X-CSRF-Token',
|
|
56
|
+
* ignores: [
|
|
57
|
+
* { path: '/api/webhook/*', methods: ['POST'] },
|
|
58
|
+
* { path: '/auth/apple/callback' },
|
|
59
|
+
* ]
|
|
60
|
+
* }));
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function csrf(config?: CSRFConfig): MiddlewareHandler;
|
|
64
|
+
|
|
65
|
+
export { type CSRFConfig, type CSRFIgnoreRule, csrf };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { MiddlewareHandler } from 'hono';
|
|
2
|
+
|
|
3
|
+
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
|
4
|
+
interface CSRFIgnoreRule {
|
|
5
|
+
path: string;
|
|
6
|
+
methods?: HTTPMethod[];
|
|
7
|
+
}
|
|
8
|
+
interface CSRFConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Cookie name for CSRF token
|
|
11
|
+
* @default 'XSRF-TOKEN'
|
|
12
|
+
*/
|
|
13
|
+
cookieName?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Header name for CSRF token
|
|
16
|
+
* @default 'X-XSRF-TOKEN'
|
|
17
|
+
*/
|
|
18
|
+
headerName?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Ignore rules for specific paths and methods
|
|
21
|
+
* @example
|
|
22
|
+
* [
|
|
23
|
+
* { path: '/api/webhook/*', methods: ['POST'] },
|
|
24
|
+
* { path: '/auth/apple/callback' }, // ignores all methods
|
|
25
|
+
* ]
|
|
26
|
+
*/
|
|
27
|
+
ignores?: CSRFIgnoreRule[];
|
|
28
|
+
/**
|
|
29
|
+
* Skip CSRF check for these methods
|
|
30
|
+
* @default ['GET', 'HEAD', 'OPTIONS']
|
|
31
|
+
*/
|
|
32
|
+
safeMethods?: HTTPMethod[];
|
|
33
|
+
/**
|
|
34
|
+
* Custom error message
|
|
35
|
+
* @default 'CSRF token validation failed'
|
|
36
|
+
*/
|
|
37
|
+
errorMessage?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create CSRF protection middleware
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import { Hono } from 'hono';
|
|
45
|
+
* import { csrf } from '@shware/http/hono';
|
|
46
|
+
*
|
|
47
|
+
* const app = new Hono();
|
|
48
|
+
*
|
|
49
|
+
* // basic usage
|
|
50
|
+
* app.use(csrf());
|
|
51
|
+
*
|
|
52
|
+
* // with configuration
|
|
53
|
+
* app.use(csrf({
|
|
54
|
+
* cookieName: 'csrf-token',
|
|
55
|
+
* headerName: 'X-CSRF-Token',
|
|
56
|
+
* ignores: [
|
|
57
|
+
* { path: '/api/webhook/*', methods: ['POST'] },
|
|
58
|
+
* { path: '/auth/apple/callback' },
|
|
59
|
+
* ]
|
|
60
|
+
* }));
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function csrf(config?: CSRFConfig): MiddlewareHandler;
|
|
64
|
+
|
|
65
|
+
export { type CSRFConfig, type CSRFIgnoreRule, csrf };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// src/hono/csrf.ts
|
|
2
|
+
import { timingSafeEqual } from "crypto";
|
|
3
|
+
import { getCookie } from "hono/cookie";
|
|
4
|
+
import { METHOD_NAME_ALL } from "hono/router";
|
|
5
|
+
import { RegExpRouter } from "hono/router/reg-exp-router";
|
|
6
|
+
import { SmartRouter } from "hono/router/smart-router";
|
|
7
|
+
import { TrieRouter } from "hono/router/trie-router";
|
|
8
|
+
import { Status } from "../error/status.mjs";
|
|
9
|
+
function safeCompare(a, b) {
|
|
10
|
+
if (typeof a !== "string" || typeof b !== "string") return false;
|
|
11
|
+
if (a.length === 0 || b.length === 0) return false;
|
|
12
|
+
const bufferA = Buffer.from(a, "utf-8");
|
|
13
|
+
const bufferB = Buffer.from(b, "utf-8");
|
|
14
|
+
if (bufferA.length !== bufferB.length) return false;
|
|
15
|
+
return timingSafeEqual(bufferA, bufferB);
|
|
16
|
+
}
|
|
17
|
+
function csrf(config = {}) {
|
|
18
|
+
const cookieName = config.cookieName ?? "XSRF-TOKEN";
|
|
19
|
+
const headerName = config.headerName ?? "X-XSRF-TOKEN";
|
|
20
|
+
const safeMethods = new Set(config.safeMethods ?? ["GET", "HEAD", "OPTIONS"]);
|
|
21
|
+
const errorMessage = config.errorMessage ?? "CSRF token validation failed";
|
|
22
|
+
const router = new SmartRouter({
|
|
23
|
+
routers: [new RegExpRouter(), new TrieRouter()]
|
|
24
|
+
});
|
|
25
|
+
if (config.ignores) {
|
|
26
|
+
for (const rule of config.ignores) {
|
|
27
|
+
if (rule.methods && rule.methods.length > 0) {
|
|
28
|
+
for (const method of rule.methods) {
|
|
29
|
+
router.add(method, rule.path, true);
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
router.add(METHOD_NAME_ALL, rule.path, true);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function shouldIgnore(method, path) {
|
|
37
|
+
if (safeMethods.has(method)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
const [matchedAll] = router.match(METHOD_NAME_ALL, path);
|
|
41
|
+
if (matchedAll.length > 0) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
const [matchedMethod] = router.match(method, path);
|
|
45
|
+
return matchedMethod.length > 0;
|
|
46
|
+
}
|
|
47
|
+
return async (c, next) => {
|
|
48
|
+
const method = c.req.method;
|
|
49
|
+
const path = c.req.path;
|
|
50
|
+
if (shouldIgnore(method, path)) {
|
|
51
|
+
await next();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const cookieToken = getCookie(c, cookieName);
|
|
55
|
+
const headerToken = c.req.header(headerName);
|
|
56
|
+
if (!cookieToken || !headerToken) {
|
|
57
|
+
throw Status.permissionDenied(errorMessage).error();
|
|
58
|
+
}
|
|
59
|
+
if (!safeCompare(cookieToken, headerToken)) {
|
|
60
|
+
throw Status.permissionDenied(errorMessage).error();
|
|
61
|
+
}
|
|
62
|
+
await next();
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
csrf
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=csrf.mjs.map
|
|
@@ -0,0 +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 interface CSRFIgnoreRule {\n path: string;\n methods?: HTTPMethod[];\n}\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 (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 function shouldIgnore(method: string, path: string): boolean {\n if (safeMethods.has(method)) {\n return true;\n }\n const [matchedAll] = router.match(METHOD_NAME_ALL, path);\n if (matchedAll.length > 0) {\n return true;\n }\n const [matchedMethod] = router.match(method, path);\n return matchedMethod.length > 0;\n }\n\n // return middleware\n return async (c, next) => {\n const method = c.req.method;\n const path = c.req.path;\n\n if (shouldIgnore(method, path)) {\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;AA+CvB,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,KAAK,WAAW,KAAK,QAAQ,SAAS,GAAG;AAC3C,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,WAAS,aAAa,QAAgB,MAAuB;AAC3D,QAAI,YAAY,IAAI,MAAM,GAAG;AAC3B,aAAO;AAAA,IACT;AACA,UAAM,CAAC,UAAU,IAAI,OAAO,MAAM,iBAAiB,IAAI;AACvD,QAAI,WAAW,SAAS,GAAG;AACzB,aAAO;AAAA,IACT;AACA,UAAM,CAAC,aAAa,IAAI,OAAO,MAAM,QAAQ,IAAI;AACjD,WAAO,cAAc,SAAS;AAAA,EAChC;AAGA,SAAO,OAAO,GAAG,SAAS;AACxB,UAAM,SAAS,EAAE,IAAI;AACrB,UAAM,OAAO,EAAE,IAAI;AAEnB,QAAI,aAAa,QAAQ,IAAI,GAAG;AAC9B,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":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hono/handler.ts"],"sourcesContent":["import { Details, DetailType } from '../error/detail';\nimport { Status, StatusError } from '../error/status';\nimport type { AxiosError } from 'axios';\nimport type { Context } from 'hono';\nimport type { RequestIdVariables } from 'hono/request-id';\nimport type { HTTPResponseError, Bindings } from 'hono/types';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\n\
|
|
1
|
+
{"version":3,"sources":["../../src/hono/handler.ts"],"sourcesContent":["import { Details, DetailType } from '../error/detail';\nimport { Status, StatusError } from '../error/status';\nimport type { AxiosError } from 'axios';\nimport type { Context } from 'hono';\nimport type { RequestIdVariables } from 'hono/request-id';\nimport type { HTTPResponseError, Bindings } from 'hono/types';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\n\nexport type Env = {\n Variables: RequestIdVariables;\n Bindings?: Bindings;\n};\n\nexport function isAxiosError(payload: unknown): payload is AxiosError {\n return (\n payload !== null &&\n typeof payload === 'object' &&\n 'isAxiosError' in payload &&\n payload.isAxiosError === true\n );\n}\n\nexport function errorHandler<E extends Env = never>(\n error: Error | HTTPResponseError,\n c: Context<E>\n): Response | Promise<Response> {\n const requestId = c.get('requestId');\n const servingData = `${c.req.method}: ${c.req.path}`;\n const details = Details.new().requestInfo({ requestId, servingData });\n\n if (error instanceof StatusError) {\n error.body?.error?.details?.push(...details.list);\n const badRequest = error.body?.error?.details.find((d) => d.type === DetailType.BAD_REQUEST);\n if (badRequest) console.warn(servingData, badRequest);\n return c.json(error.body, error.status as ContentfulStatusCode);\n }\n\n if (error instanceof SyntaxError) {\n if (/^Cannot convert .* to a BigInt$/.test(error.message)) {\n return Status.invalidArgument(`Invalid number. ${error.message}`).response(details);\n }\n }\n\n if (isAxiosError(error)) {\n console.error({\n status: error.status,\n message: error.message,\n request: {\n method: error.config?.method,\n url: error.config?.url,\n data: error.config?.data,\n },\n response: { data: error.response?.data },\n });\n return Status.internal('Axios error').response(details);\n }\n\n console.error(`Unknown error: ${servingData}`, error);\n return Status.internal('Unknown error').response(details);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAoC;AACpC,oBAAoC;AAY7B,SAAS,aAAa,SAAyC;AACpE,SACE,YAAY,QACZ,OAAO,YAAY,YACnB,kBAAkB,WAClB,QAAQ,iBAAiB;AAE7B;AAEO,SAAS,aACd,OACA,GAC8B;AAC9B,QAAM,YAAY,EAAE,IAAI,WAAW;AACnC,QAAM,cAAc,GAAG,EAAE,IAAI,MAAM,KAAK,EAAE,IAAI,IAAI;AAClD,QAAM,UAAU,sBAAQ,IAAI,EAAE,YAAY,EAAE,WAAW,YAAY,CAAC;AAEpE,MAAI,iBAAiB,2BAAa;AAChC,UAAM,MAAM,OAAO,SAAS,KAAK,GAAG,QAAQ,IAAI;AAChD,UAAM,aAAa,MAAM,MAAM,OAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,yBAAW,WAAW;AAC3F,QAAI,WAAY,SAAQ,KAAK,aAAa,UAAU;AACpD,WAAO,EAAE,KAAK,MAAM,MAAM,MAAM,MAA8B;AAAA,EAChE;AAEA,MAAI,iBAAiB,aAAa;AAChC,QAAI,kCAAkC,KAAK,MAAM,OAAO,GAAG;AACzD,aAAO,qBAAO,gBAAgB,mBAAmB,MAAM,OAAO,EAAE,EAAE,SAAS,OAAO;AAAA,IACpF;AAAA,EACF;AAEA,MAAI,aAAa,KAAK,GAAG;AACvB,YAAQ,MAAM;AAAA,MACZ,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,SAAS;AAAA,QACP,QAAQ,MAAM,QAAQ;AAAA,QACtB,KAAK,MAAM,QAAQ;AAAA,QACnB,MAAM,MAAM,QAAQ;AAAA,MACtB;AAAA,MACA,UAAU,EAAE,MAAM,MAAM,UAAU,KAAK;AAAA,IACzC,CAAC;AACD,WAAO,qBAAO,SAAS,aAAa,EAAE,SAAS,OAAO;AAAA,EACxD;AAEA,UAAQ,MAAM,kBAAkB,WAAW,IAAI,KAAK;AACpD,SAAO,qBAAO,SAAS,eAAe,EAAE,SAAS,OAAO;AAC1D;","names":[]}
|
package/dist/hono/handler.d.cts
CHANGED
|
@@ -10,4 +10,4 @@ type Env = {
|
|
|
10
10
|
declare function isAxiosError(payload: unknown): payload is AxiosError;
|
|
11
11
|
declare function errorHandler<E extends Env = never>(error: Error | HTTPResponseError, c: Context<E>): Response | Promise<Response>;
|
|
12
12
|
|
|
13
|
-
export { errorHandler, isAxiosError };
|
|
13
|
+
export { type Env, errorHandler, isAxiosError };
|
package/dist/hono/handler.d.ts
CHANGED
|
@@ -10,4 +10,4 @@ type Env = {
|
|
|
10
10
|
declare function isAxiosError(payload: unknown): payload is AxiosError;
|
|
11
11
|
declare function errorHandler<E extends Env = never>(error: Error | HTTPResponseError, c: Context<E>): Response | Promise<Response>;
|
|
12
12
|
|
|
13
|
-
export { errorHandler, isAxiosError };
|
|
13
|
+
export { type Env, errorHandler, isAxiosError };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hono/handler.ts"],"sourcesContent":["import { Details, DetailType } from '../error/detail';\nimport { Status, StatusError } from '../error/status';\nimport type { AxiosError } from 'axios';\nimport type { Context } from 'hono';\nimport type { RequestIdVariables } from 'hono/request-id';\nimport type { HTTPResponseError, Bindings } from 'hono/types';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\n\
|
|
1
|
+
{"version":3,"sources":["../../src/hono/handler.ts"],"sourcesContent":["import { Details, DetailType } from '../error/detail';\nimport { Status, StatusError } from '../error/status';\nimport type { AxiosError } from 'axios';\nimport type { Context } from 'hono';\nimport type { RequestIdVariables } from 'hono/request-id';\nimport type { HTTPResponseError, Bindings } from 'hono/types';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\n\nexport type Env = {\n Variables: RequestIdVariables;\n Bindings?: Bindings;\n};\n\nexport function isAxiosError(payload: unknown): payload is AxiosError {\n return (\n payload !== null &&\n typeof payload === 'object' &&\n 'isAxiosError' in payload &&\n payload.isAxiosError === true\n );\n}\n\nexport function errorHandler<E extends Env = never>(\n error: Error | HTTPResponseError,\n c: Context<E>\n): Response | Promise<Response> {\n const requestId = c.get('requestId');\n const servingData = `${c.req.method}: ${c.req.path}`;\n const details = Details.new().requestInfo({ requestId, servingData });\n\n if (error instanceof StatusError) {\n error.body?.error?.details?.push(...details.list);\n const badRequest = error.body?.error?.details.find((d) => d.type === DetailType.BAD_REQUEST);\n if (badRequest) console.warn(servingData, badRequest);\n return c.json(error.body, error.status as ContentfulStatusCode);\n }\n\n if (error instanceof SyntaxError) {\n if (/^Cannot convert .* to a BigInt$/.test(error.message)) {\n return Status.invalidArgument(`Invalid number. ${error.message}`).response(details);\n }\n }\n\n if (isAxiosError(error)) {\n console.error({\n status: error.status,\n message: error.message,\n request: {\n method: error.config?.method,\n url: error.config?.url,\n data: error.config?.data,\n },\n response: { data: error.response?.data },\n });\n return Status.internal('Axios error').response(details);\n }\n\n console.error(`Unknown error: ${servingData}`, error);\n return Status.internal('Unknown error').response(details);\n}\n"],"mappings":";AAAA,SAAS,SAAS,kBAAkB;AACpC,SAAS,QAAQ,mBAAmB;AAY7B,SAAS,aAAa,SAAyC;AACpE,SACE,YAAY,QACZ,OAAO,YAAY,YACnB,kBAAkB,WAClB,QAAQ,iBAAiB;AAE7B;AAEO,SAAS,aACd,OACA,GAC8B;AAC9B,QAAM,YAAY,EAAE,IAAI,WAAW;AACnC,QAAM,cAAc,GAAG,EAAE,IAAI,MAAM,KAAK,EAAE,IAAI,IAAI;AAClD,QAAM,UAAU,QAAQ,IAAI,EAAE,YAAY,EAAE,WAAW,YAAY,CAAC;AAEpE,MAAI,iBAAiB,aAAa;AAChC,UAAM,MAAM,OAAO,SAAS,KAAK,GAAG,QAAQ,IAAI;AAChD,UAAM,aAAa,MAAM,MAAM,OAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,WAAW,WAAW;AAC3F,QAAI,WAAY,SAAQ,KAAK,aAAa,UAAU;AACpD,WAAO,EAAE,KAAK,MAAM,MAAM,MAAM,MAA8B;AAAA,EAChE;AAEA,MAAI,iBAAiB,aAAa;AAChC,QAAI,kCAAkC,KAAK,MAAM,OAAO,GAAG;AACzD,aAAO,OAAO,gBAAgB,mBAAmB,MAAM,OAAO,EAAE,EAAE,SAAS,OAAO;AAAA,IACpF;AAAA,EACF;AAEA,MAAI,aAAa,KAAK,GAAG;AACvB,YAAQ,MAAM;AAAA,MACZ,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,SAAS;AAAA,QACP,QAAQ,MAAM,QAAQ;AAAA,QACtB,KAAK,MAAM,QAAQ;AAAA,QACnB,MAAM,MAAM,QAAQ;AAAA,MACtB;AAAA,MACA,UAAU,EAAE,MAAM,MAAM,UAAU,KAAK;AAAA,IACzC,CAAC;AACD,WAAO,OAAO,SAAS,aAAa,EAAE,SAAS,OAAO;AAAA,EACxD;AAEA,UAAQ,MAAM,kBAAkB,WAAW,IAAI,KAAK;AACpD,SAAO,OAAO,SAAS,eAAe,EAAE,SAAS,OAAO;AAC1D;","names":[]}
|
package/dist/hono/index.cjs
CHANGED
|
@@ -21,7 +21,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var hono_exports = {};
|
|
22
22
|
__export(hono_exports, {
|
|
23
23
|
Authorizer: () => import_authorizer.Authorizer,
|
|
24
|
+
authorizer: () => import_authorizer.authorizer,
|
|
24
25
|
bigintId: () => import_validator.bigintId,
|
|
26
|
+
csrf: () => import_csrf.csrf,
|
|
25
27
|
errorHandler: () => import_handler.errorHandler,
|
|
26
28
|
geolocation: () => import_geolocation.geolocation,
|
|
27
29
|
zValidator: () => import_validator.zValidator
|
|
@@ -31,10 +33,13 @@ var import_validator = require("./validator.cjs");
|
|
|
31
33
|
var import_handler = require("./handler.cjs");
|
|
32
34
|
var import_geolocation = require("./geolocation.cjs");
|
|
33
35
|
var import_authorizer = require("./authorizer.cjs");
|
|
36
|
+
var import_csrf = require("./csrf.cjs");
|
|
34
37
|
// Annotate the CommonJS export names for ESM import in node:
|
|
35
38
|
0 && (module.exports = {
|
|
36
39
|
Authorizer,
|
|
40
|
+
authorizer,
|
|
37
41
|
bigintId,
|
|
42
|
+
csrf,
|
|
38
43
|
errorHandler,
|
|
39
44
|
geolocation,
|
|
40
45
|
zValidator
|
package/dist/hono/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hono/index.ts"],"sourcesContent":["export { zValidator, bigintId } from './validator';\nexport { errorHandler } from './handler';\nexport { geolocation } from './geolocation';\nexport { Authorizer } from './authorizer';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAqC;AACrC,qBAA6B;AAC7B,yBAA4B;AAC5B,
|
|
1
|
+
{"version":3,"sources":["../../src/hono/index.ts"],"sourcesContent":["export { zValidator, bigintId } from './validator';\nexport { errorHandler } from './handler';\nexport { geolocation } from './geolocation';\nexport { Authorizer, authorizer, type AuthorizerConfig } from './authorizer';\nexport { csrf, type CSRFConfig, type CSRFIgnoreRule } from './csrf';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAAqC;AACrC,qBAA6B;AAC7B,yBAA4B;AAC5B,wBAA8D;AAC9D,kBAA2D;","names":[]}
|
package/dist/hono/index.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { bigintId, zValidator } from './validator.cjs';
|
|
2
2
|
export { errorHandler } from './handler.cjs';
|
|
3
3
|
export { geolocation } from './geolocation.cjs';
|
|
4
|
-
export { Authorizer } from './authorizer.cjs';
|
|
4
|
+
export { Authorizer, AuthorizerConfig, authorizer } from './authorizer.cjs';
|
|
5
|
+
export { CSRFConfig, CSRFIgnoreRule, csrf } from './csrf.cjs';
|
|
5
6
|
import 'zod/mini';
|
|
6
7
|
import 'hono';
|
|
7
8
|
import 'zod/v4/core';
|
package/dist/hono/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { bigintId, zValidator } from './validator.js';
|
|
2
2
|
export { errorHandler } from './handler.js';
|
|
3
3
|
export { geolocation } from './geolocation.js';
|
|
4
|
-
export { Authorizer } from './authorizer.js';
|
|
4
|
+
export { Authorizer, AuthorizerConfig, authorizer } from './authorizer.js';
|
|
5
|
+
export { CSRFConfig, CSRFIgnoreRule, csrf } from './csrf.js';
|
|
5
6
|
import 'zod/mini';
|
|
6
7
|
import 'hono';
|
|
7
8
|
import 'zod/v4/core';
|
package/dist/hono/index.mjs
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
import { zValidator, bigintId } from "./validator.mjs";
|
|
3
3
|
import { errorHandler } from "./handler.mjs";
|
|
4
4
|
import { geolocation } from "./geolocation.mjs";
|
|
5
|
-
import { Authorizer } from "./authorizer.mjs";
|
|
5
|
+
import { Authorizer, authorizer } from "./authorizer.mjs";
|
|
6
|
+
import { csrf } from "./csrf.mjs";
|
|
6
7
|
export {
|
|
7
8
|
Authorizer,
|
|
9
|
+
authorizer,
|
|
8
10
|
bigintId,
|
|
11
|
+
csrf,
|
|
9
12
|
errorHandler,
|
|
10
13
|
geolocation,
|
|
11
14
|
zValidator
|
package/dist/hono/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hono/index.ts"],"sourcesContent":["export { zValidator, bigintId } from './validator';\nexport { errorHandler } from './handler';\nexport { geolocation } from './geolocation';\nexport { Authorizer } from './authorizer';\n"],"mappings":";AAAA,SAAS,YAAY,gBAAgB;AACrC,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,
|
|
1
|
+
{"version":3,"sources":["../../src/hono/index.ts"],"sourcesContent":["export { zValidator, bigintId } from './validator';\nexport { errorHandler } from './handler';\nexport { geolocation } from './geolocation';\nexport { Authorizer, authorizer, type AuthorizerConfig } from './authorizer';\nexport { csrf, type CSRFConfig, type CSRFIgnoreRule } from './csrf';\n"],"mappings":";AAAA,SAAS,YAAY,gBAAgB;AACrC,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,YAAY,kBAAyC;AAC9D,SAAS,YAAkD;","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
// src/__tests__/ip.test.ts
|
|
4
|
-
var import_ip = require("../
|
|
3
|
+
// src/utils/__tests__/ip.test.ts
|
|
4
|
+
var import_ip = require("../ip.cjs");
|
|
5
5
|
describe("extractIpAddress", () => {
|
|
6
6
|
it("should extract the ipv4 address from the request", () => {
|
|
7
7
|
expect((0, import_ip.extractIpAddress)("127.0.0.1")).toBe("127.0.0.1");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/__tests__/ip.test.ts"],"sourcesContent":["import { extractIpAddress } from '../ip';\n\ndescribe('extractIpAddress', () => {\n it('should extract the ipv4 address from the request', () => {\n expect(extractIpAddress('127.0.0.1')).toBe('127.0.0.1');\n expect(extractIpAddress('192.168.0.1')).toBe('192.168.0.1');\n expect(extractIpAddress('127.0.0.1:8080')).toBe('127.0.0.1');\n expect(extractIpAddress('192.168.0.1:8080')).toBe('192.168.0.1');\n expect(extractIpAddress('127.0.0.1:8080/path')).toBe('127.0.0.1');\n });\n\n it('should extract the ipv6 address from the request', () => {\n expect(extractIpAddress('::1')).toBe('::1');\n expect(extractIpAddress('[::1]:8080')).toBe('::1');\n\n expect(extractIpAddress('2001:0db8:85a3:0000:0000:8a2e:0370:7334')).toBe(\n '2001:0db8:85a3:0000:0000:8a2e:0370:7334'\n );\n expect(extractIpAddress('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80')).toBe(\n '2001:db8:85a3::8a2e:370:7334' // remove leading zeros\n );\n expect(extractIpAddress('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80/path')).toBe(\n '2001:db8:85a3::8a2e:370:7334' // remove leading zeros\n );\n\n expect(extractIpAddress('2404:7ac0:614d:bba7:cf41:992e:98e:9186:60704')).toBe(\n '2404:7ac0:614d:bba7:cf41:992e:98e:9186'\n );\n });\n\n it('should return null if the ip address is not valid', () => {\n expect(extractIpAddress(null)).toBeNull();\n expect(extractIpAddress(undefined)).toBeNull();\n expect(extractIpAddress('invalid')).toBeNull();\n expect(extractIpAddress('example.com')).toBeNull();\n });\n});\n"],"mappings":";;;AAAA,gBAAiC;AAEjC,SAAS,oBAAoB,MAAM;AACjC,KAAG,oDAAoD,MAAM;AAC3D,eAAO,4BAAiB,WAAW,CAAC,EAAE,KAAK,WAAW;AACtD,eAAO,4BAAiB,aAAa,CAAC,EAAE,KAAK,aAAa;AAC1D,eAAO,4BAAiB,gBAAgB,CAAC,EAAE,KAAK,WAAW;AAC3D,eAAO,4BAAiB,kBAAkB,CAAC,EAAE,KAAK,aAAa;AAC/D,eAAO,4BAAiB,qBAAqB,CAAC,EAAE,KAAK,WAAW;AAAA,EAClE,CAAC;AAED,KAAG,oDAAoD,MAAM;AAC3D,eAAO,4BAAiB,KAAK,CAAC,EAAE,KAAK,KAAK;AAC1C,eAAO,4BAAiB,YAAY,CAAC,EAAE,KAAK,KAAK;AAEjD,eAAO,4BAAiB,yCAAyC,CAAC,EAAE;AAAA,MAClE;AAAA,IACF;AACA,eAAO,4BAAiB,8CAA8C,CAAC,EAAE;AAAA,MACvE;AAAA;AAAA,IACF;AACA,eAAO,4BAAiB,mDAAmD,CAAC,EAAE;AAAA,MAC5E;AAAA;AAAA,IACF;AAEA,eAAO,4BAAiB,8CAA8C,CAAC,EAAE;AAAA,MACvE;AAAA,IACF;AAAA,EACF,CAAC;AAED,KAAG,qDAAqD,MAAM;AAC5D,eAAO,4BAAiB,IAAI,CAAC,EAAE,SAAS;AACxC,eAAO,4BAAiB,MAAS,CAAC,EAAE,SAAS;AAC7C,eAAO,4BAAiB,SAAS,CAAC,EAAE,SAAS;AAC7C,eAAO,4BAAiB,aAAa,CAAC,EAAE,SAAS;AAAA,EACnD,CAAC;AACH,CAAC;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// src/__tests__/ip.test.ts
|
|
2
|
-
import { extractIpAddress } from "../
|
|
1
|
+
// src/utils/__tests__/ip.test.ts
|
|
2
|
+
import { extractIpAddress } from "../ip.mjs";
|
|
3
3
|
describe("extractIpAddress", () => {
|
|
4
4
|
it("should extract the ipv4 address from the request", () => {
|
|
5
5
|
expect(extractIpAddress("127.0.0.1")).toBe("127.0.0.1");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/__tests__/ip.test.ts"],"sourcesContent":["import { extractIpAddress } from '../ip';\n\ndescribe('extractIpAddress', () => {\n it('should extract the ipv4 address from the request', () => {\n expect(extractIpAddress('127.0.0.1')).toBe('127.0.0.1');\n expect(extractIpAddress('192.168.0.1')).toBe('192.168.0.1');\n expect(extractIpAddress('127.0.0.1:8080')).toBe('127.0.0.1');\n expect(extractIpAddress('192.168.0.1:8080')).toBe('192.168.0.1');\n expect(extractIpAddress('127.0.0.1:8080/path')).toBe('127.0.0.1');\n });\n\n it('should extract the ipv6 address from the request', () => {\n expect(extractIpAddress('::1')).toBe('::1');\n expect(extractIpAddress('[::1]:8080')).toBe('::1');\n\n expect(extractIpAddress('2001:0db8:85a3:0000:0000:8a2e:0370:7334')).toBe(\n '2001:0db8:85a3:0000:0000:8a2e:0370:7334'\n );\n expect(extractIpAddress('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80')).toBe(\n '2001:db8:85a3::8a2e:370:7334' // remove leading zeros\n );\n expect(extractIpAddress('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80/path')).toBe(\n '2001:db8:85a3::8a2e:370:7334' // remove leading zeros\n );\n\n expect(extractIpAddress('2404:7ac0:614d:bba7:cf41:992e:98e:9186:60704')).toBe(\n '2404:7ac0:614d:bba7:cf41:992e:98e:9186'\n );\n });\n\n it('should return null if the ip address is not valid', () => {\n expect(extractIpAddress(null)).toBeNull();\n expect(extractIpAddress(undefined)).toBeNull();\n expect(extractIpAddress('invalid')).toBeNull();\n expect(extractIpAddress('example.com')).toBeNull();\n });\n});\n"],"mappings":";AAAA,SAAS,wBAAwB;AAEjC,SAAS,oBAAoB,MAAM;AACjC,KAAG,oDAAoD,MAAM;AAC3D,WAAO,iBAAiB,WAAW,CAAC,EAAE,KAAK,WAAW;AACtD,WAAO,iBAAiB,aAAa,CAAC,EAAE,KAAK,aAAa;AAC1D,WAAO,iBAAiB,gBAAgB,CAAC,EAAE,KAAK,WAAW;AAC3D,WAAO,iBAAiB,kBAAkB,CAAC,EAAE,KAAK,aAAa;AAC/D,WAAO,iBAAiB,qBAAqB,CAAC,EAAE,KAAK,WAAW;AAAA,EAClE,CAAC;AAED,KAAG,oDAAoD,MAAM;AAC3D,WAAO,iBAAiB,KAAK,CAAC,EAAE,KAAK,KAAK;AAC1C,WAAO,iBAAiB,YAAY,CAAC,EAAE,KAAK,KAAK;AAEjD,WAAO,iBAAiB,yCAAyC,CAAC,EAAE;AAAA,MAClE;AAAA,IACF;AACA,WAAO,iBAAiB,8CAA8C,CAAC,EAAE;AAAA,MACvE;AAAA;AAAA,IACF;AACA,WAAO,iBAAiB,mDAAmD,CAAC,EAAE;AAAA,MAC5E;AAAA;AAAA,IACF;AAEA,WAAO,iBAAiB,8CAA8C,CAAC,EAAE;AAAA,MACvE;AAAA,IACF;AAAA,EACF,CAAC;AAED,KAAG,qDAAqD,MAAM;AAC5D,WAAO,iBAAiB,IAAI,CAAC,EAAE,SAAS;AACxC,WAAO,iBAAiB,MAAS,CAAC,EAAE,SAAS;AAC7C,WAAO,iBAAiB,SAAS,CAAC,EAAE,SAAS;AAC7C,WAAO,iBAAiB,aAAa,CAAC,EAAE,SAAS;AAAA,EACnD,CAAC;AACH,CAAC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shware/http",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "tsc --watch",
|
|
8
|
-
"build": "tsup",
|
|
9
|
-
"test": "jest",
|
|
10
|
-
"check-types": "tsc --noEmit",
|
|
11
|
-
"lint": "eslint . --fix --ext .js,.jsx,.ts,.tsx"
|
|
12
|
-
},
|
|
13
6
|
"repository": {
|
|
14
7
|
"type": "git",
|
|
15
8
|
"url": "git+https://github.com/ShwareHQ/shware-sdk.git"
|
|
@@ -45,14 +38,14 @@
|
|
|
45
38
|
"dist"
|
|
46
39
|
],
|
|
47
40
|
"devDependencies": {
|
|
48
|
-
"@repo/eslint-config": "workspace:*",
|
|
49
|
-
"@repo/typescript-config": "workspace:*",
|
|
50
41
|
"@types/jest": "^30.0.0",
|
|
51
42
|
"@types/node": "^24.3.1",
|
|
52
43
|
"@types/react": "^19.1.12",
|
|
53
44
|
"jest": "^30.1.3",
|
|
54
45
|
"ts-jest": "^29.4.1",
|
|
55
|
-
"typescript": "^5.9.2"
|
|
46
|
+
"typescript": "^5.9.2",
|
|
47
|
+
"@repo/eslint-config": "0.0.2",
|
|
48
|
+
"@repo/typescript-config": "0.0.0"
|
|
56
49
|
},
|
|
57
50
|
"dependencies": {
|
|
58
51
|
"zod": "^4.1.5"
|
|
@@ -76,5 +69,12 @@
|
|
|
76
69
|
"react": {
|
|
77
70
|
"optional": true
|
|
78
71
|
}
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"dev": "tsc --watch",
|
|
75
|
+
"build": "tsup",
|
|
76
|
+
"test": "jest",
|
|
77
|
+
"check-types": "tsc --noEmit",
|
|
78
|
+
"lint": "npx eslint . --fix --ext .js,.jsx,.ts,.tsx"
|
|
79
79
|
}
|
|
80
|
-
}
|
|
80
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/__tests__/ip.test.ts"],"sourcesContent":["import { extractIpAddress } from '../utils/ip';\n\ndescribe('extractIpAddress', () => {\n it('should extract the ipv4 address from the request', () => {\n expect(extractIpAddress('127.0.0.1')).toBe('127.0.0.1');\n expect(extractIpAddress('192.168.0.1')).toBe('192.168.0.1');\n expect(extractIpAddress('127.0.0.1:8080')).toBe('127.0.0.1');\n expect(extractIpAddress('192.168.0.1:8080')).toBe('192.168.0.1');\n expect(extractIpAddress('127.0.0.1:8080/path')).toBe('127.0.0.1');\n });\n\n it('should extract the ipv6 address from the request', () => {\n expect(extractIpAddress('::1')).toBe('::1');\n expect(extractIpAddress('[::1]:8080')).toBe('::1');\n\n expect(extractIpAddress('2001:0db8:85a3:0000:0000:8a2e:0370:7334')).toBe(\n '2001:0db8:85a3:0000:0000:8a2e:0370:7334'\n );\n expect(extractIpAddress('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80')).toBe(\n '2001:db8:85a3::8a2e:370:7334' // remove leading zeros\n );\n expect(extractIpAddress('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80/path')).toBe(\n '2001:db8:85a3::8a2e:370:7334' // remove leading zeros\n );\n\n expect(extractIpAddress('2404:7ac0:614d:bba7:cf41:992e:98e:9186:60704')).toBe(\n '2404:7ac0:614d:bba7:cf41:992e:98e:9186'\n );\n });\n\n it('should return null if the ip address is not valid', () => {\n expect(extractIpAddress(null)).toBeNull();\n expect(extractIpAddress(undefined)).toBeNull();\n expect(extractIpAddress('invalid')).toBeNull();\n expect(extractIpAddress('example.com')).toBeNull();\n });\n});\n"],"mappings":";;;AAAA,gBAAiC;AAEjC,SAAS,oBAAoB,MAAM;AACjC,KAAG,oDAAoD,MAAM;AAC3D,eAAO,4BAAiB,WAAW,CAAC,EAAE,KAAK,WAAW;AACtD,eAAO,4BAAiB,aAAa,CAAC,EAAE,KAAK,aAAa;AAC1D,eAAO,4BAAiB,gBAAgB,CAAC,EAAE,KAAK,WAAW;AAC3D,eAAO,4BAAiB,kBAAkB,CAAC,EAAE,KAAK,aAAa;AAC/D,eAAO,4BAAiB,qBAAqB,CAAC,EAAE,KAAK,WAAW;AAAA,EAClE,CAAC;AAED,KAAG,oDAAoD,MAAM;AAC3D,eAAO,4BAAiB,KAAK,CAAC,EAAE,KAAK,KAAK;AAC1C,eAAO,4BAAiB,YAAY,CAAC,EAAE,KAAK,KAAK;AAEjD,eAAO,4BAAiB,yCAAyC,CAAC,EAAE;AAAA,MAClE;AAAA,IACF;AACA,eAAO,4BAAiB,8CAA8C,CAAC,EAAE;AAAA,MACvE;AAAA;AAAA,IACF;AACA,eAAO,4BAAiB,mDAAmD,CAAC,EAAE;AAAA,MAC5E;AAAA;AAAA,IACF;AAEA,eAAO,4BAAiB,8CAA8C,CAAC,EAAE;AAAA,MACvE;AAAA,IACF;AAAA,EACF,CAAC;AAED,KAAG,qDAAqD,MAAM;AAC5D,eAAO,4BAAiB,IAAI,CAAC,EAAE,SAAS;AACxC,eAAO,4BAAiB,MAAS,CAAC,EAAE,SAAS;AAC7C,eAAO,4BAAiB,SAAS,CAAC,EAAE,SAAS;AAC7C,eAAO,4BAAiB,aAAa,CAAC,EAAE,SAAS;AAAA,EACnD,CAAC;AACH,CAAC;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/__tests__/ip.test.ts"],"sourcesContent":["import { extractIpAddress } from '../utils/ip';\n\ndescribe('extractIpAddress', () => {\n it('should extract the ipv4 address from the request', () => {\n expect(extractIpAddress('127.0.0.1')).toBe('127.0.0.1');\n expect(extractIpAddress('192.168.0.1')).toBe('192.168.0.1');\n expect(extractIpAddress('127.0.0.1:8080')).toBe('127.0.0.1');\n expect(extractIpAddress('192.168.0.1:8080')).toBe('192.168.0.1');\n expect(extractIpAddress('127.0.0.1:8080/path')).toBe('127.0.0.1');\n });\n\n it('should extract the ipv6 address from the request', () => {\n expect(extractIpAddress('::1')).toBe('::1');\n expect(extractIpAddress('[::1]:8080')).toBe('::1');\n\n expect(extractIpAddress('2001:0db8:85a3:0000:0000:8a2e:0370:7334')).toBe(\n '2001:0db8:85a3:0000:0000:8a2e:0370:7334'\n );\n expect(extractIpAddress('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80')).toBe(\n '2001:db8:85a3::8a2e:370:7334' // remove leading zeros\n );\n expect(extractIpAddress('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80/path')).toBe(\n '2001:db8:85a3::8a2e:370:7334' // remove leading zeros\n );\n\n expect(extractIpAddress('2404:7ac0:614d:bba7:cf41:992e:98e:9186:60704')).toBe(\n '2404:7ac0:614d:bba7:cf41:992e:98e:9186'\n );\n });\n\n it('should return null if the ip address is not valid', () => {\n expect(extractIpAddress(null)).toBeNull();\n expect(extractIpAddress(undefined)).toBeNull();\n expect(extractIpAddress('invalid')).toBeNull();\n expect(extractIpAddress('example.com')).toBeNull();\n });\n});\n"],"mappings":";AAAA,SAAS,wBAAwB;AAEjC,SAAS,oBAAoB,MAAM;AACjC,KAAG,oDAAoD,MAAM;AAC3D,WAAO,iBAAiB,WAAW,CAAC,EAAE,KAAK,WAAW;AACtD,WAAO,iBAAiB,aAAa,CAAC,EAAE,KAAK,aAAa;AAC1D,WAAO,iBAAiB,gBAAgB,CAAC,EAAE,KAAK,WAAW;AAC3D,WAAO,iBAAiB,kBAAkB,CAAC,EAAE,KAAK,aAAa;AAC/D,WAAO,iBAAiB,qBAAqB,CAAC,EAAE,KAAK,WAAW;AAAA,EAClE,CAAC;AAED,KAAG,oDAAoD,MAAM;AAC3D,WAAO,iBAAiB,KAAK,CAAC,EAAE,KAAK,KAAK;AAC1C,WAAO,iBAAiB,YAAY,CAAC,EAAE,KAAK,KAAK;AAEjD,WAAO,iBAAiB,yCAAyC,CAAC,EAAE;AAAA,MAClE;AAAA,IACF;AACA,WAAO,iBAAiB,8CAA8C,CAAC,EAAE;AAAA,MACvE;AAAA;AAAA,IACF;AACA,WAAO,iBAAiB,mDAAmD,CAAC,EAAE;AAAA,MAC5E;AAAA;AAAA,IACF;AAEA,WAAO,iBAAiB,8CAA8C,CAAC,EAAE;AAAA,MACvE;AAAA,IACF;AAAA,EACF,CAAC;AAED,KAAG,qDAAqD,MAAM;AAC5D,WAAO,iBAAiB,IAAI,CAAC,EAAE,SAAS;AACxC,WAAO,iBAAiB,MAAS,CAAC,EAAE,SAAS;AAC7C,WAAO,iBAAiB,SAAS,CAAC,EAAE,SAAS;AAC7C,WAAO,iBAAiB,aAAa,CAAC,EAAE,SAAS;AAAA,EACnD,CAAC;AACH,CAAC;","names":[]}
|
|
File without changes
|
|
File without changes
|