@tknf/matchbox 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cgi.d.ts +10 -21
- package/dist/cgi.js +26 -97
- package/dist/htaccess/access-control.d.ts +9 -0
- package/dist/htaccess/access-control.js +91 -0
- package/dist/htaccess/error-document.d.ts +9 -0
- package/dist/htaccess/error-document.js +16 -0
- package/dist/htaccess/headers.d.ts +32 -0
- package/dist/htaccess/headers.js +98 -0
- package/dist/htaccess/index.d.ts +8 -0
- package/dist/htaccess/index.js +20 -0
- package/dist/htaccess/parser.d.ts +9 -0
- package/dist/htaccess/parser.js +365 -0
- package/dist/htaccess/rewrite.d.ts +13 -0
- package/dist/htaccess/rewrite.js +69 -0
- package/dist/htaccess/types.d.ts +156 -0
- package/dist/htaccess/types.js +0 -0
- package/dist/htaccess/utils.d.ts +21 -0
- package/dist/htaccess/utils.js +69 -0
- package/dist/html.d.ts +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -1
- package/dist/middleware/auth.d.ts +8 -0
- package/dist/middleware/auth.js +28 -0
- package/dist/middleware/htaccess.d.ts +13 -0
- package/dist/middleware/htaccess.js +42 -0
- package/dist/middleware/index.d.ts +10 -0
- package/dist/middleware/index.js +14 -0
- package/dist/middleware/protected-files.d.ts +8 -0
- package/dist/middleware/protected-files.js +14 -0
- package/dist/middleware/session.d.ts +16 -0
- package/dist/middleware/session.js +37 -0
- package/dist/middleware/trailing-slash.d.ts +8 -0
- package/dist/middleware/trailing-slash.js +12 -0
- package/dist/plugin.js +1 -1
- package/dist/with-defaults.d.ts +2 -1
- package/dist/with-defaults.js +17 -28
- package/package.json +1 -1
package/dist/cgi.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as hono_types from 'hono/types';
|
|
2
2
|
import { Context, Hono } from 'hono';
|
|
3
3
|
import { HtmlEscapedString } from 'hono/utils/html';
|
|
4
|
+
import { HtaccessConfig } from './htaccess/types.js';
|
|
4
5
|
|
|
5
|
-
type ConfigObject = Record<string,
|
|
6
|
+
type ConfigObject = Record<string, unknown>;
|
|
6
7
|
/**
|
|
7
8
|
* --- Session Cookie Configuration ---
|
|
8
9
|
*/
|
|
@@ -49,10 +50,10 @@ interface CgiContext<ConfigType = ConfigObject> {
|
|
|
49
50
|
$_GET: Record<string, string>;
|
|
50
51
|
$_POST: Record<string, string>;
|
|
51
52
|
$_FILES: Record<string, File | File[]>;
|
|
52
|
-
$_REQUEST: Record<string,
|
|
53
|
-
$_SESSION: Record<string,
|
|
53
|
+
$_REQUEST: Record<string, unknown>;
|
|
54
|
+
$_SESSION: Record<string, unknown>;
|
|
54
55
|
$_COOKIE: Record<string, string>;
|
|
55
|
-
$_ENV: Record<string,
|
|
56
|
+
$_ENV: Record<string, unknown>;
|
|
56
57
|
$_SERVER: {
|
|
57
58
|
REQUEST_METHOD: string;
|
|
58
59
|
REQUEST_URI: string;
|
|
@@ -61,7 +62,7 @@ interface CgiContext<ConfigType = ConfigObject> {
|
|
|
61
62
|
SCRIPT_NAME: string;
|
|
62
63
|
PATH_INFO: string;
|
|
63
64
|
QUERY_STRING: string;
|
|
64
|
-
[key: string]:
|
|
65
|
+
[key: string]: unknown;
|
|
65
66
|
};
|
|
66
67
|
config: ConfigType;
|
|
67
68
|
c: Context;
|
|
@@ -83,24 +84,12 @@ interface CgiContext<ConfigType = ConfigObject> {
|
|
|
83
84
|
type Page = {
|
|
84
85
|
urlPath: string;
|
|
85
86
|
dirPath: string | null;
|
|
86
|
-
component: (context: CgiContext) =>
|
|
87
|
+
component: (context: CgiContext) => unknown | Promise<unknown>;
|
|
87
88
|
};
|
|
88
|
-
|
|
89
|
-
type: "redirect";
|
|
90
|
-
code: string;
|
|
91
|
-
source: string;
|
|
92
|
-
target: string;
|
|
93
|
-
};
|
|
94
|
-
type RewriteRule = {
|
|
95
|
-
type: "rewrite";
|
|
96
|
-
pattern: string;
|
|
97
|
-
target: string;
|
|
98
|
-
flags: string;
|
|
99
|
-
};
|
|
100
|
-
type RewriteMap = Record<string, Array<RedirectRule | RewriteRule>>;
|
|
89
|
+
|
|
101
90
|
/**
|
|
102
91
|
* --- Matchbox Runtime Engine ---
|
|
103
92
|
*/
|
|
104
|
-
declare const createCgiWithPages: (pages: Page[], siteConfig?: ConfigObject, authMap?: Record<string, string>,
|
|
93
|
+
declare const createCgiWithPages: (pages: Page[], siteConfig?: ConfigObject, authMap?: Record<string, string>, htaccessConfig?: HtaccessConfig, options?: MatchboxOptions) => Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
105
94
|
|
|
106
|
-
export { type CgiContext, type ConfigObject, type MatchboxOptions, type ModuleInfo, type Page, type
|
|
95
|
+
export { type CgiContext, type ConfigObject, HtaccessConfig, type MatchboxOptions, type ModuleInfo, type Page, type SessionCookieOptions, createCgiWithPages };
|
package/dist/cgi.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import {
|
|
3
|
-
import { getCookie, setCookie } from "hono/cookie";
|
|
2
|
+
import { getCookie } from "hono/cookie";
|
|
4
3
|
import { generateCgiError, generateCgiInfo } from "./html.js";
|
|
4
|
+
import {
|
|
5
|
+
applyBasicAuth,
|
|
6
|
+
applyHtaccessMiddleware,
|
|
7
|
+
applyErrorDocumentMiddleware,
|
|
8
|
+
getSessionFromCookie,
|
|
9
|
+
saveSessionToCookie,
|
|
10
|
+
applyProtectedFilesMiddleware,
|
|
11
|
+
applyTrailingSlashMiddleware
|
|
12
|
+
} from "./middleware/index.js";
|
|
5
13
|
const isRedirectObject = (obj) => {
|
|
6
|
-
return obj && obj.__type === "redirect" && typeof obj.url === "string";
|
|
14
|
+
return typeof obj === "object" && obj !== null && "__type" in obj && obj.__type === "redirect" && "url" in obj && typeof obj.url === "string";
|
|
7
15
|
};
|
|
8
|
-
const createCgiWithPages = (pages, siteConfig = {}, authMap = {},
|
|
16
|
+
const createCgiWithPages = (pages, siteConfig = {}, authMap = {}, htaccessConfig = {}, options = {}) => {
|
|
9
17
|
const app = new Hono();
|
|
10
|
-
const SESS_KEY = options.sessionCookie?.name || "_SESSION_ID";
|
|
11
18
|
if (options.middleware && options.middleware.length > 0) {
|
|
12
19
|
for (const mw of options.middleware) {
|
|
13
20
|
app.use("*", async (c, next) => {
|
|
@@ -18,66 +25,12 @@ const createCgiWithPages = (pages, siteConfig = {}, authMap = {}, rewriteMap = {
|
|
|
18
25
|
});
|
|
19
26
|
}
|
|
20
27
|
}
|
|
21
|
-
|
|
22
|
-
app.use("*", async (c, next) => {
|
|
23
|
-
const path = c.req.path;
|
|
24
|
-
const lastSegment = path.slice(path.lastIndexOf("/") + 1);
|
|
25
|
-
if (protectedFiles.some((file) => lastSegment === file)) {
|
|
26
|
-
return c.text("Forbidden", 403);
|
|
27
|
-
}
|
|
28
|
-
await next();
|
|
29
|
-
});
|
|
28
|
+
applyProtectedFilesMiddleware(app);
|
|
30
29
|
if (options.enforceTrailingSlash) {
|
|
31
|
-
app
|
|
32
|
-
const path = c.req.path;
|
|
33
|
-
if (!path.endsWith("/") && !path.includes(".")) {
|
|
34
|
-
return c.redirect(`${path}/`, 301);
|
|
35
|
-
}
|
|
36
|
-
await next();
|
|
37
|
-
});
|
|
30
|
+
applyTrailingSlashMiddleware(app);
|
|
38
31
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
app.use(`${basePath}/*`, async (c, next) => {
|
|
42
|
-
const relPath = c.req.path.replace(basePath, "") || "/";
|
|
43
|
-
for (const rule of rules) {
|
|
44
|
-
if (rule.type === "redirect") {
|
|
45
|
-
if (relPath === rule.source) {
|
|
46
|
-
return c.redirect(
|
|
47
|
-
rule.target,
|
|
48
|
-
Number.parseInt(rule.code, 10) || 302
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
} else if (rule.type === "rewrite") {
|
|
52
|
-
const regex = new RegExp(rule.pattern);
|
|
53
|
-
if (regex.test(relPath)) {
|
|
54
|
-
const target = rule.target.startsWith("/") ? rule.target : `${basePath}/${rule.target}`;
|
|
55
|
-
if (rule.flags.includes("R")) {
|
|
56
|
-
const code = rule.flags.match(/R=(\d+)/)?.[1] || "302";
|
|
57
|
-
return c.redirect(target, Number.parseInt(code, 10));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
await next();
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
Object.entries(authMap).forEach(([dir, content]) => {
|
|
66
|
-
const credentials = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => {
|
|
67
|
-
const [username, password] = line.split(":");
|
|
68
|
-
return { username, password };
|
|
69
|
-
});
|
|
70
|
-
if (credentials.length > 0) {
|
|
71
|
-
const authPath = dir === "/" ? "*" : `${dir.replace(/\/$/, "")}/*`;
|
|
72
|
-
app.use(authPath, async (c, next) => {
|
|
73
|
-
const handler = basicAuth({
|
|
74
|
-
verifyUser: (u, p) => credentials.some((cred) => cred.username === u && cred.password === p),
|
|
75
|
-
realm: "Restricted Area"
|
|
76
|
-
});
|
|
77
|
-
return handler(c, next);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
});
|
|
32
|
+
applyHtaccessMiddleware(app, htaccessConfig);
|
|
33
|
+
applyBasicAuth(app, authMap);
|
|
81
34
|
pages.forEach(({ urlPath, dirPath, component }) => {
|
|
82
35
|
const routes = [urlPath, `${urlPath}/*`];
|
|
83
36
|
if (dirPath) {
|
|
@@ -96,7 +49,7 @@ const createCgiWithPages = (pages, siteConfig = {}, authMap = {}, rewriteMap = {
|
|
|
96
49
|
if (value instanceof File || Array.isArray(value) && value[0] instanceof File) {
|
|
97
50
|
$_FILES[key] = value;
|
|
98
51
|
} else {
|
|
99
|
-
$_POST[key] = value;
|
|
52
|
+
$_POST[key] = String(value);
|
|
100
53
|
}
|
|
101
54
|
}
|
|
102
55
|
const $_COOKIE = getCookie(c);
|
|
@@ -106,14 +59,10 @@ const createCgiWithPages = (pages, siteConfig = {}, authMap = {}, rewriteMap = {
|
|
|
106
59
|
...$_POST
|
|
107
60
|
};
|
|
108
61
|
const $_ENV = typeof process !== "undefined" && process.env ? process.env : c.env || {};
|
|
109
|
-
let $_SESSION =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
$_SESSION = JSON.parse(decodeURIComponent(sRaw));
|
|
114
|
-
} catch {
|
|
115
|
-
}
|
|
116
|
-
}
|
|
62
|
+
let $_SESSION = getSessionFromCookie(
|
|
63
|
+
c,
|
|
64
|
+
options.sessionCookie?.name
|
|
65
|
+
);
|
|
117
66
|
let responseStatus = 200;
|
|
118
67
|
const responseHeaders = {
|
|
119
68
|
"Content-Type": "text/html; charset=utf-8"
|
|
@@ -169,7 +118,7 @@ const createCgiWithPages = (pages, siteConfig = {}, authMap = {}, rewriteMap = {
|
|
|
169
118
|
}
|
|
170
119
|
},
|
|
171
120
|
get_version: () => {
|
|
172
|
-
return `MatchboxCGI/v${"0.
|
|
121
|
+
return `MatchboxCGI/v${"0.3.0"}`;
|
|
173
122
|
},
|
|
174
123
|
/**
|
|
175
124
|
* Returns information about all loaded CGI modules
|
|
@@ -184,48 +133,28 @@ const createCgiWithPages = (pages, siteConfig = {}, authMap = {}, rewriteMap = {
|
|
|
184
133
|
};
|
|
185
134
|
try {
|
|
186
135
|
const result = await component(context);
|
|
187
|
-
|
|
188
|
-
const sessionOptions = {
|
|
189
|
-
path: options.sessionCookie?.path || "/",
|
|
190
|
-
httpOnly: true,
|
|
191
|
-
sameSite: options.sessionCookie?.sameSite || "Lax"
|
|
192
|
-
};
|
|
193
|
-
if (options.sessionCookie?.secure !== void 0) {
|
|
194
|
-
sessionOptions.secure = options.sessionCookie.secure;
|
|
195
|
-
}
|
|
196
|
-
if (options.sessionCookie?.domain) {
|
|
197
|
-
sessionOptions.domain = options.sessionCookie.domain;
|
|
198
|
-
}
|
|
199
|
-
if (options.sessionCookie?.maxAge) {
|
|
200
|
-
sessionOptions.maxAge = options.sessionCookie.maxAge;
|
|
201
|
-
}
|
|
136
|
+
saveSessionToCookie(c, $_SESSION, options.sessionCookie);
|
|
202
137
|
if (isRedirectObject(result)) {
|
|
203
|
-
setCookie(c, SESS_KEY, sessionValue, sessionOptions);
|
|
204
138
|
return c.redirect(result.url, result.status);
|
|
205
139
|
}
|
|
206
140
|
if (result instanceof Response) {
|
|
207
|
-
setCookie(c, SESS_KEY, sessionValue, sessionOptions);
|
|
208
141
|
return result;
|
|
209
142
|
}
|
|
210
|
-
setCookie(c, SESS_KEY, sessionValue, sessionOptions);
|
|
211
143
|
Object.entries(responseHeaders).forEach(([key, value]) => {
|
|
212
144
|
c.header(key, value);
|
|
213
145
|
});
|
|
214
146
|
const contentType = responseHeaders["content-type"];
|
|
215
147
|
if (contentType?.includes("application/json")) {
|
|
216
|
-
return c.json(
|
|
217
|
-
// biome-ignore lint/suspicious/noExplicitAny: to JSON response
|
|
218
|
-
result ?? { success: true },
|
|
219
|
-
responseStatus
|
|
220
|
-
);
|
|
148
|
+
return c.json(result ?? { success: true }, responseStatus);
|
|
221
149
|
}
|
|
222
|
-
return c.html(result, responseStatus);
|
|
150
|
+
return c.html(String(result ?? ""), responseStatus);
|
|
223
151
|
} catch (error) {
|
|
224
152
|
return c.html(generateCgiError({ error, $_SERVER }), 500);
|
|
225
153
|
}
|
|
226
154
|
});
|
|
227
155
|
});
|
|
228
156
|
});
|
|
157
|
+
applyErrorDocumentMiddleware(app, htaccessConfig);
|
|
229
158
|
return app;
|
|
230
159
|
};
|
|
231
160
|
export {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
import { AccessControlConfig } from './types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create middleware for access control (Order/Allow/Deny)
|
|
6
|
+
*/
|
|
7
|
+
declare function createAccessControlMiddleware(config: AccessControlConfig): (c: Context, next: () => Promise<void>) => Promise<Response | void>;
|
|
8
|
+
|
|
9
|
+
export { createAccessControlMiddleware };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
function matchesIP(clientIP, ruleValue) {
|
|
2
|
+
const ips = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
|
|
3
|
+
for (const ip of ips) {
|
|
4
|
+
if (clientIP === ip) return true;
|
|
5
|
+
if (ip.includes("/")) {
|
|
6
|
+
const [network, bits] = ip.split("/");
|
|
7
|
+
const mask = parseInt(bits, 10);
|
|
8
|
+
if (isSameNetwork(clientIP, network, mask)) return true;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
function isSameNetwork(ip1, ip2, maskBits) {
|
|
14
|
+
const ip1Parts = ip1.split(".").map(Number);
|
|
15
|
+
const ip2Parts = ip2.split(".").map(Number);
|
|
16
|
+
let bits = maskBits;
|
|
17
|
+
for (let i = 0; i < 4; i++) {
|
|
18
|
+
if (bits <= 0) break;
|
|
19
|
+
const mask = bits >= 8 ? 255 : 256 - Math.pow(2, 8 - bits);
|
|
20
|
+
if ((ip1Parts[i] & mask) !== (ip2Parts[i] & mask)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
bits -= 8;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
function matchesHost(clientHost, ruleValue) {
|
|
28
|
+
const hosts = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
|
|
29
|
+
for (const host of hosts) {
|
|
30
|
+
if (clientHost === host) return true;
|
|
31
|
+
if (host.startsWith("*.")) {
|
|
32
|
+
const domain = host.slice(2);
|
|
33
|
+
if (clientHost.endsWith(domain)) return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function matchesRule(rule, c) {
|
|
39
|
+
switch (rule.type) {
|
|
40
|
+
case "all":
|
|
41
|
+
return true;
|
|
42
|
+
case "ip": {
|
|
43
|
+
const clientIP = c.req.header("x-forwarded-for")?.split(",")[0].trim() || c.req.header("x-real-ip") || c.env?.REMOTE_ADDR || "127.0.0.1";
|
|
44
|
+
return rule.value ? matchesIP(clientIP, rule.value) : false;
|
|
45
|
+
}
|
|
46
|
+
case "host": {
|
|
47
|
+
const clientHost = c.req.header("host") || "";
|
|
48
|
+
return rule.value ? matchesHost(clientHost, rule.value) : false;
|
|
49
|
+
}
|
|
50
|
+
case "env": {
|
|
51
|
+
if (!rule.value) return false;
|
|
52
|
+
const envVar = typeof rule.value === "string" ? rule.value : rule.value[0];
|
|
53
|
+
return c.env?.[envVar] !== void 0;
|
|
54
|
+
}
|
|
55
|
+
default:
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function evaluateAccessControl(config, c) {
|
|
60
|
+
const order = config.order || "allow,deny";
|
|
61
|
+
const allowMatches = config.allow.some((rule) => matchesRule(rule, c));
|
|
62
|
+
const denyMatches = config.deny.some((rule) => matchesRule(rule, c));
|
|
63
|
+
switch (order) {
|
|
64
|
+
case "allow,deny":
|
|
65
|
+
if (denyMatches) return false;
|
|
66
|
+
if (allowMatches) return true;
|
|
67
|
+
return false;
|
|
68
|
+
case "deny,allow":
|
|
69
|
+
if (allowMatches) return true;
|
|
70
|
+
if (denyMatches) return false;
|
|
71
|
+
return true;
|
|
72
|
+
case "mutual-failure":
|
|
73
|
+
if (denyMatches) return false;
|
|
74
|
+
if (allowMatches) return true;
|
|
75
|
+
return false;
|
|
76
|
+
default:
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function createAccessControlMiddleware(config) {
|
|
81
|
+
return async (c, next) => {
|
|
82
|
+
const allowed = evaluateAccessControl(config, c);
|
|
83
|
+
if (!allowed) {
|
|
84
|
+
return c.text("Forbidden", 403);
|
|
85
|
+
}
|
|
86
|
+
await next();
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
createAccessControlMiddleware
|
|
91
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
import { ErrorDocumentConfig } from './types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create middleware for error documents
|
|
6
|
+
*/
|
|
7
|
+
declare function createErrorDocumentMiddleware(errorDocs: ErrorDocumentConfig[], _basePath: string): (c: Context, next: () => Promise<void>) => Promise<Response | void>;
|
|
8
|
+
|
|
9
|
+
export { createErrorDocumentMiddleware };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function createErrorDocumentMiddleware(errorDocs, _basePath) {
|
|
2
|
+
return async (c, next) => {
|
|
3
|
+
await next();
|
|
4
|
+
const status = c.res.status;
|
|
5
|
+
if (status < 400) return;
|
|
6
|
+
const errorDoc = errorDocs.find((doc) => doc.statusCode === status);
|
|
7
|
+
if (!errorDoc) return;
|
|
8
|
+
if (errorDoc.target.startsWith("http://") || errorDoc.target.startsWith("https://")) {
|
|
9
|
+
return c.redirect(errorDoc.target);
|
|
10
|
+
}
|
|
11
|
+
return c.text(`Error ${status}: See ${errorDoc.target}`, status);
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
createErrorDocumentMiddleware
|
|
16
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
import { HeaderConfig } from './types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create middleware for header directives
|
|
6
|
+
*/
|
|
7
|
+
declare function createHeaderMiddleware(headers: HeaderConfig[]): (c: Context, next: () => Promise<void>) => Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Predefined security header helpers
|
|
10
|
+
*/
|
|
11
|
+
declare const securityHeaders: {
|
|
12
|
+
xFrameOptions: (value: "DENY" | "SAMEORIGIN" | string) => HeaderConfig;
|
|
13
|
+
xContentTypeOptions: () => HeaderConfig;
|
|
14
|
+
xssProtection: (enabled?: boolean) => HeaderConfig;
|
|
15
|
+
hsts: (maxAge?: number, includeSubdomains?: boolean) => HeaderConfig;
|
|
16
|
+
csp: (policy: string) => HeaderConfig;
|
|
17
|
+
referrerPolicy: (policy: string) => HeaderConfig;
|
|
18
|
+
permissionsPolicy: (policy: string) => HeaderConfig;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* CORS (Cross-Origin Resource Sharing) header helpers
|
|
22
|
+
*/
|
|
23
|
+
declare const corsHeaders: {
|
|
24
|
+
allowOrigin: (origin: string) => HeaderConfig;
|
|
25
|
+
allowMethods: (methods: string[]) => HeaderConfig;
|
|
26
|
+
allowHeaders: (headers: string[]) => HeaderConfig;
|
|
27
|
+
allowCredentials: (allow?: boolean) => HeaderConfig;
|
|
28
|
+
maxAge: (seconds: number) => HeaderConfig;
|
|
29
|
+
exposeHeaders: (headers: string[]) => HeaderConfig;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export { corsHeaders, createHeaderMiddleware, securityHeaders };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
function createHeaderMiddleware(headers) {
|
|
2
|
+
return async (c, next) => {
|
|
3
|
+
await next();
|
|
4
|
+
for (const header of headers) {
|
|
5
|
+
switch (header.action) {
|
|
6
|
+
case "set":
|
|
7
|
+
if (header.value) {
|
|
8
|
+
c.header(header.name, header.value);
|
|
9
|
+
}
|
|
10
|
+
break;
|
|
11
|
+
case "append":
|
|
12
|
+
if (header.value) {
|
|
13
|
+
const existing = c.res.headers.get(header.name);
|
|
14
|
+
const newValue = existing ? `${existing}, ${header.value}` : header.value;
|
|
15
|
+
c.header(header.name, newValue);
|
|
16
|
+
}
|
|
17
|
+
break;
|
|
18
|
+
case "unset":
|
|
19
|
+
c.res.headers.delete(header.name);
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const securityHeaders = {
|
|
26
|
+
xFrameOptions: (value) => ({
|
|
27
|
+
action: "set",
|
|
28
|
+
name: "X-Frame-Options",
|
|
29
|
+
value
|
|
30
|
+
}),
|
|
31
|
+
xContentTypeOptions: () => ({
|
|
32
|
+
action: "set",
|
|
33
|
+
name: "X-Content-Type-Options",
|
|
34
|
+
value: "nosniff"
|
|
35
|
+
}),
|
|
36
|
+
xssProtection: (enabled = true) => ({
|
|
37
|
+
action: "set",
|
|
38
|
+
name: "X-XSS-Protection",
|
|
39
|
+
value: enabled ? "1; mode=block" : "0"
|
|
40
|
+
}),
|
|
41
|
+
hsts: (maxAge = 31536e3, includeSubdomains = true) => ({
|
|
42
|
+
action: "set",
|
|
43
|
+
name: "Strict-Transport-Security",
|
|
44
|
+
value: includeSubdomains ? `max-age=${maxAge}; includeSubDomains` : `max-age=${maxAge}`
|
|
45
|
+
}),
|
|
46
|
+
csp: (policy) => ({
|
|
47
|
+
action: "set",
|
|
48
|
+
name: "Content-Security-Policy",
|
|
49
|
+
value: policy
|
|
50
|
+
}),
|
|
51
|
+
referrerPolicy: (policy) => ({
|
|
52
|
+
action: "set",
|
|
53
|
+
name: "Referrer-Policy",
|
|
54
|
+
value: policy
|
|
55
|
+
}),
|
|
56
|
+
permissionsPolicy: (policy) => ({
|
|
57
|
+
action: "set",
|
|
58
|
+
name: "Permissions-Policy",
|
|
59
|
+
value: policy
|
|
60
|
+
})
|
|
61
|
+
};
|
|
62
|
+
const corsHeaders = {
|
|
63
|
+
allowOrigin: (origin) => ({
|
|
64
|
+
action: "set",
|
|
65
|
+
name: "Access-Control-Allow-Origin",
|
|
66
|
+
value: origin
|
|
67
|
+
}),
|
|
68
|
+
allowMethods: (methods) => ({
|
|
69
|
+
action: "set",
|
|
70
|
+
name: "Access-Control-Allow-Methods",
|
|
71
|
+
value: methods.join(", ")
|
|
72
|
+
}),
|
|
73
|
+
allowHeaders: (headers) => ({
|
|
74
|
+
action: "set",
|
|
75
|
+
name: "Access-Control-Allow-Headers",
|
|
76
|
+
value: headers.join(", ")
|
|
77
|
+
}),
|
|
78
|
+
allowCredentials: (allow = true) => ({
|
|
79
|
+
action: "set",
|
|
80
|
+
name: "Access-Control-Allow-Credentials",
|
|
81
|
+
value: allow ? "true" : "false"
|
|
82
|
+
}),
|
|
83
|
+
maxAge: (seconds) => ({
|
|
84
|
+
action: "set",
|
|
85
|
+
name: "Access-Control-Max-Age",
|
|
86
|
+
value: String(seconds)
|
|
87
|
+
}),
|
|
88
|
+
exposeHeaders: (headers) => ({
|
|
89
|
+
action: "set",
|
|
90
|
+
name: "Access-Control-Expose-Headers",
|
|
91
|
+
value: headers.join(", ")
|
|
92
|
+
})
|
|
93
|
+
};
|
|
94
|
+
export {
|
|
95
|
+
corsHeaders,
|
|
96
|
+
createHeaderMiddleware,
|
|
97
|
+
securityHeaders
|
|
98
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { AccessControlConfig, AccessRule, AuthConfig, ConditionFlags, DirectoryConfig, ErrorDocumentConfig, HeaderConfig, HonoContext, HtaccessConfig, RedirectConfig, RequireConfig, RewriteCondition, RewriteFlags, RewriteResult, RewriteRuleConfig, VariableContext } from './types.js';
|
|
2
|
+
export { parseHtaccess } from './parser.js';
|
|
3
|
+
export { createRewriteMiddleware, evaluateConditions } from './rewrite.js';
|
|
4
|
+
export { corsHeaders, createHeaderMiddleware, securityHeaders } from './headers.js';
|
|
5
|
+
export { createErrorDocumentMiddleware } from './error-document.js';
|
|
6
|
+
export { createAccessControlMiddleware } from './access-control.js';
|
|
7
|
+
export { applyRewriteFlags, buildVariableContext, expandVariables } from './utils.js';
|
|
8
|
+
import 'hono';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
import { parseHtaccess } from "./parser.js";
|
|
3
|
+
import { createRewriteMiddleware, evaluateConditions } from "./rewrite.js";
|
|
4
|
+
import { createHeaderMiddleware, securityHeaders, corsHeaders } from "./headers.js";
|
|
5
|
+
import { createErrorDocumentMiddleware } from "./error-document.js";
|
|
6
|
+
import { createAccessControlMiddleware } from "./access-control.js";
|
|
7
|
+
import { buildVariableContext, expandVariables, applyRewriteFlags } from "./utils.js";
|
|
8
|
+
export {
|
|
9
|
+
applyRewriteFlags,
|
|
10
|
+
buildVariableContext,
|
|
11
|
+
corsHeaders,
|
|
12
|
+
createAccessControlMiddleware,
|
|
13
|
+
createErrorDocumentMiddleware,
|
|
14
|
+
createHeaderMiddleware,
|
|
15
|
+
createRewriteMiddleware,
|
|
16
|
+
evaluateConditions,
|
|
17
|
+
expandVariables,
|
|
18
|
+
parseHtaccess,
|
|
19
|
+
securityHeaders
|
|
20
|
+
};
|