@tknf/matchbox 0.2.6 → 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/with-defaults.d.ts +2 -1
- package/dist/with-defaults.js +17 -28
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { CgiContext, ConfigObject, MatchboxOptions, ModuleInfo, Page, SessionCookieOptions } from './cgi.js';
|
|
2
|
+
export { HtaccessConfig } from './htaccess/types.js';
|
|
3
|
+
export { parseHtaccess } from './htaccess/parser.js';
|
|
4
|
+
export { corsHeaders, securityHeaders } from './htaccess/headers.js';
|
|
2
5
|
export { createCgi } from './with-defaults.js';
|
|
3
6
|
import 'hono/types';
|
|
4
7
|
import 'hono';
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { basicAuth } from "hono/basic-auth";
|
|
2
|
+
function createBasicAuthMiddleware(htpasswdContent, realm = "Restricted Area") {
|
|
3
|
+
const credentials = htpasswdContent.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => {
|
|
4
|
+
const [username, password] = line.split(":");
|
|
5
|
+
return { username, password };
|
|
6
|
+
});
|
|
7
|
+
if (credentials.length === 0) {
|
|
8
|
+
return async (_c, next) => {
|
|
9
|
+
await next();
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
return async (c, next) => {
|
|
13
|
+
const handler = basicAuth({
|
|
14
|
+
verifyUser: (u, p) => credentials.some((cred) => cred.username === u && cred.password === p),
|
|
15
|
+
realm
|
|
16
|
+
});
|
|
17
|
+
return handler(c, next);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function applyBasicAuth(app, authMap) {
|
|
21
|
+
Object.entries(authMap).forEach(([dir, content]) => {
|
|
22
|
+
const authPath = dir === "/" ? "*" : `${dir.replace(/\/$/, "")}/*`;
|
|
23
|
+
app.use(authPath, createBasicAuthMiddleware(content));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
applyBasicAuth
|
|
28
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { HtaccessConfig } from '../htaccess/types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Apply htaccess middleware to Hono app in the correct order
|
|
6
|
+
*/
|
|
7
|
+
declare function applyHtaccessMiddleware(app: Hono, htaccessConfig: HtaccessConfig): void;
|
|
8
|
+
/**
|
|
9
|
+
* Apply error document middleware (must be applied last)
|
|
10
|
+
*/
|
|
11
|
+
declare function applyErrorDocumentMiddleware(app: Hono, htaccessConfig: HtaccessConfig): void;
|
|
12
|
+
|
|
13
|
+
export { applyErrorDocumentMiddleware, applyHtaccessMiddleware };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createRewriteMiddleware } from "../htaccess/rewrite.js";
|
|
2
|
+
import { createHeaderMiddleware } from "../htaccess/headers.js";
|
|
3
|
+
import { createErrorDocumentMiddleware } from "../htaccess/error-document.js";
|
|
4
|
+
import { createAccessControlMiddleware } from "../htaccess/access-control.js";
|
|
5
|
+
function applyHtaccessMiddleware(app, htaccessConfig) {
|
|
6
|
+
Object.entries(htaccessConfig).forEach(([dir, config]) => {
|
|
7
|
+
if (config.headers.length === 0) return;
|
|
8
|
+
const basePath = dir === "/" ? "" : dir.replace(/\/$/, "");
|
|
9
|
+
app.use(`${basePath}/*`, createHeaderMiddleware(config.headers));
|
|
10
|
+
});
|
|
11
|
+
Object.entries(htaccessConfig).forEach(([dir, config]) => {
|
|
12
|
+
const allRules = [
|
|
13
|
+
...config.rewriteRules,
|
|
14
|
+
...config.redirects.map((r) => ({
|
|
15
|
+
type: "rewrite",
|
|
16
|
+
pattern: `^${r.source}$`,
|
|
17
|
+
target: r.target,
|
|
18
|
+
flags: { redirect: r.code },
|
|
19
|
+
conditions: []
|
|
20
|
+
}))
|
|
21
|
+
];
|
|
22
|
+
if (allRules.length === 0) return;
|
|
23
|
+
const basePath = dir === "/" ? "" : dir.replace(/\/$/, "");
|
|
24
|
+
app.use(`${basePath}/*`, createRewriteMiddleware(allRules, basePath));
|
|
25
|
+
});
|
|
26
|
+
Object.entries(htaccessConfig).forEach(([dir, config]) => {
|
|
27
|
+
if (!config.accessControl) return;
|
|
28
|
+
const basePath = dir === "/" ? "" : dir.replace(/\/$/, "");
|
|
29
|
+
app.use(`${basePath}/*`, createAccessControlMiddleware(config.accessControl));
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function applyErrorDocumentMiddleware(app, htaccessConfig) {
|
|
33
|
+
Object.entries(htaccessConfig).forEach(([dir, config]) => {
|
|
34
|
+
if (config.errorDocuments.length === 0) return;
|
|
35
|
+
const basePath = dir === "/" ? "" : dir.replace(/\/$/, "");
|
|
36
|
+
app.use(`${basePath}/*`, createErrorDocumentMiddleware(config.errorDocuments, basePath));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
applyErrorDocumentMiddleware,
|
|
41
|
+
applyHtaccessMiddleware
|
|
42
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { applyBasicAuth } from './auth.js';
|
|
2
|
+
export { applyErrorDocumentMiddleware, applyHtaccessMiddleware } from './htaccess.js';
|
|
3
|
+
export { getSessionFromCookie, saveSessionToCookie } from './session.js';
|
|
4
|
+
export { applyProtectedFilesMiddleware } from './protected-files.js';
|
|
5
|
+
export { applyTrailingSlashMiddleware } from './trailing-slash.js';
|
|
6
|
+
import 'hono';
|
|
7
|
+
import '../htaccess/types.js';
|
|
8
|
+
import '../cgi.js';
|
|
9
|
+
import 'hono/types';
|
|
10
|
+
import 'hono/utils/html';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { applyBasicAuth } from "./auth.js";
|
|
2
|
+
import { applyHtaccessMiddleware, applyErrorDocumentMiddleware } from "./htaccess.js";
|
|
3
|
+
import { getSessionFromCookie, saveSessionToCookie } from "./session.js";
|
|
4
|
+
import { applyProtectedFilesMiddleware } from "./protected-files.js";
|
|
5
|
+
import { applyTrailingSlashMiddleware } from "./trailing-slash.js";
|
|
6
|
+
export {
|
|
7
|
+
applyBasicAuth,
|
|
8
|
+
applyErrorDocumentMiddleware,
|
|
9
|
+
applyHtaccessMiddleware,
|
|
10
|
+
applyProtectedFilesMiddleware,
|
|
11
|
+
applyTrailingSlashMiddleware,
|
|
12
|
+
getSessionFromCookie,
|
|
13
|
+
saveSessionToCookie
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const PROTECTED_FILES = [".htaccess", ".htpasswd", ".htdigest", ".htgroup"];
|
|
2
|
+
function applyProtectedFilesMiddleware(app) {
|
|
3
|
+
app.use("*", async (c, next) => {
|
|
4
|
+
const path = c.req.path;
|
|
5
|
+
const lastSegment = path.slice(path.lastIndexOf("/") + 1);
|
|
6
|
+
if (PROTECTED_FILES.some((file) => lastSegment === file)) {
|
|
7
|
+
return c.text("Forbidden", 403);
|
|
8
|
+
}
|
|
9
|
+
await next();
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
applyProtectedFilesMiddleware
|
|
14
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
import { SessionCookieOptions } from '../cgi.js';
|
|
3
|
+
import 'hono/types';
|
|
4
|
+
import 'hono/utils/html';
|
|
5
|
+
import '../htaccess/types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get session data from cookie
|
|
9
|
+
*/
|
|
10
|
+
declare function getSessionFromCookie(c: Context, sessionCookieName?: string): Record<string, unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* Save session data to cookie
|
|
13
|
+
*/
|
|
14
|
+
declare function saveSessionToCookie(c: Context, session: Record<string, unknown>, options?: SessionCookieOptions): void;
|
|
15
|
+
|
|
16
|
+
export { getSessionFromCookie, saveSessionToCookie };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getCookie, setCookie } from "hono/cookie";
|
|
2
|
+
const DEFAULT_SESSION_COOKIE_NAME = "_SESSION_ID";
|
|
3
|
+
function getSessionFromCookie(c, sessionCookieName) {
|
|
4
|
+
const cookieName = sessionCookieName || DEFAULT_SESSION_COOKIE_NAME;
|
|
5
|
+
const sessionCookie = getCookie(c, cookieName);
|
|
6
|
+
if (sessionCookie) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(decodeURIComponent(sessionCookie));
|
|
9
|
+
} catch {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
function saveSessionToCookie(c, session, options) {
|
|
16
|
+
const cookieName = options?.name || DEFAULT_SESSION_COOKIE_NAME;
|
|
17
|
+
const sessionValue = encodeURIComponent(JSON.stringify(session));
|
|
18
|
+
const sessionOptions = {
|
|
19
|
+
path: options?.path || "/",
|
|
20
|
+
httpOnly: true,
|
|
21
|
+
sameSite: options?.sameSite || "Lax"
|
|
22
|
+
};
|
|
23
|
+
if (options?.secure !== void 0) {
|
|
24
|
+
sessionOptions.secure = options.secure;
|
|
25
|
+
}
|
|
26
|
+
if (options?.domain) {
|
|
27
|
+
sessionOptions.domain = options.domain;
|
|
28
|
+
}
|
|
29
|
+
if (options?.maxAge !== void 0) {
|
|
30
|
+
sessionOptions.maxAge = options.maxAge;
|
|
31
|
+
}
|
|
32
|
+
setCookie(c, cookieName, sessionValue, sessionOptions);
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
getSessionFromCookie,
|
|
36
|
+
saveSessionToCookie
|
|
37
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
function applyTrailingSlashMiddleware(app) {
|
|
2
|
+
app.use("*", async (c, next) => {
|
|
3
|
+
const path = c.req.path;
|
|
4
|
+
if (!path.endsWith("/") && !path.includes(".")) {
|
|
5
|
+
return c.redirect(`${path}/`, 301);
|
|
6
|
+
}
|
|
7
|
+
await next();
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
applyTrailingSlashMiddleware
|
|
12
|
+
};
|
package/dist/with-defaults.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { MatchboxOptions } from './cgi.js';
|
|
2
1
|
import * as hono from 'hono';
|
|
3
2
|
import * as hono_types from 'hono/types';
|
|
3
|
+
import { MatchboxOptions } from './cgi.js';
|
|
4
4
|
import 'hono/utils/html';
|
|
5
|
+
import './htaccess/types.js';
|
|
5
6
|
|
|
6
7
|
declare const createCgi: (options?: MatchboxOptions) => hono.Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
7
8
|
|
package/dist/with-defaults.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createCgiWithPages } from "./cgi.js";
|
|
2
|
+
import { parseHtaccess } from "./htaccess/parser.js";
|
|
2
3
|
const loadPagesFromPublic = () => {
|
|
3
4
|
const modules = import.meta.glob("/public/**/*.cgi.{tsx,jsx}", {
|
|
4
5
|
eager: true
|
|
@@ -38,40 +39,28 @@ const loadPagesFromPublic = () => {
|
|
|
38
39
|
},
|
|
39
40
|
{}
|
|
40
41
|
);
|
|
41
|
-
const
|
|
42
|
+
const htaccessConfig = Object.keys(htaccessFiles).reduce((acc, key) => {
|
|
42
43
|
const dir = key.replace(basePathRegex, "").replace(/\.htaccess$/, "") || "/";
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
if (parts[0] === "Redirect") {
|
|
57
|
-
return {
|
|
58
|
-
type: "redirect",
|
|
59
|
-
code: parts[1],
|
|
60
|
-
source: parts[2],
|
|
61
|
-
target: parts[3]
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
return null;
|
|
65
|
-
}).filter(Boolean);
|
|
66
|
-
acc[dir] = rules;
|
|
44
|
+
const content = htaccessFiles[key];
|
|
45
|
+
try {
|
|
46
|
+
acc[dir] = parseHtaccess(content);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(`Error parsing .htaccess in ${dir}:`, error.message);
|
|
49
|
+
acc[dir] = {
|
|
50
|
+
rewriteRules: [],
|
|
51
|
+
redirects: [],
|
|
52
|
+
errorDocuments: [],
|
|
53
|
+
headers: []
|
|
54
|
+
};
|
|
55
|
+
}
|
|
67
56
|
return acc;
|
|
68
57
|
}, {});
|
|
69
|
-
return { pages, authMap,
|
|
58
|
+
return { pages, authMap, htaccessConfig };
|
|
70
59
|
};
|
|
71
60
|
const createCgi = (options) => {
|
|
72
61
|
const resolvedConfig = typeof __MATCHBOX_CONFIG__ === "undefined" ? {} : __MATCHBOX_CONFIG__;
|
|
73
|
-
const { pages, authMap,
|
|
74
|
-
return createCgiWithPages(pages, resolvedConfig, authMap,
|
|
62
|
+
const { pages, authMap, htaccessConfig } = loadPagesFromPublic();
|
|
63
|
+
return createCgiWithPages(pages, resolvedConfig, authMap, htaccessConfig, options);
|
|
75
64
|
};
|
|
76
65
|
export {
|
|
77
66
|
createCgi
|