axiodb 22.17.1 → 22.19.1

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.
@@ -14,9 +14,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.default = createAxioDBControlServer;
16
16
  const fastify_1 = __importDefault(require("fastify"));
17
- const cors_1 = __importDefault(require("@fastify/cors"));
18
- const static_1 = __importDefault(require("@fastify/static"));
19
- const cookie_1 = __importDefault(require("@fastify/cookie"));
17
+ const Cors_utils_1 = __importDefault(require("../../utility/Cors.utils"));
18
+ const StaticFile_utils_1 = __importDefault(require("../../utility/StaticFile.utils"));
19
+ const Cookie_utils_1 = require("../../utility/Cookie.utils");
20
20
  const path_1 = __importDefault(require("path"));
21
21
  const fs_1 = __importDefault(require("fs"));
22
22
  const keys_1 = require("./keys");
@@ -44,17 +44,10 @@ function createAxioDBControlServer(AxioDBInstance_1) {
44
44
  });
45
45
  }
46
46
  }));
47
- yield AxioDBControlServer.register(cors_1.default, {
48
- origin: keys_1.CORS_CONFIG.ORIGIN,
49
- methods: keys_1.CORS_CONFIG.METHODS,
50
- allowedHeaders: keys_1.CORS_CONFIG.ALLOWED_HEADERS,
51
- credentials: keys_1.CORS_CONFIG.ALLOW_CREDENTIALS,
52
- exposedHeaders: keys_1.CORS_CONFIG.EXPOSED_HEADERS,
53
- maxAge: keys_1.CORS_CONFIG.MAX_AGE, // preflight cache duration
54
- });
47
+ (0, Cors_utils_1.default)(AxioDBControlServer);
55
48
  // Cookie support for session-based authentication (no signing secret needed -
56
49
  // the cookie value is meaningless without a matching entry in SessionStore's map)
57
- yield AxioDBControlServer.register(cookie_1.default);
50
+ (0, Cookie_utils_1.registerCookies)(AxioDBControlServer);
58
51
  AxioDBControlServer.addHook("onSend", (_request, reply) => __awaiter(this, void 0, void 0, function* () {
59
52
  for (const [header, value] of Object.entries(keys_1.SECURITY_HEADERS)) {
60
53
  reply.header(header, value);
@@ -70,11 +63,7 @@ function createAxioDBControlServer(AxioDBInstance_1) {
70
63
  }
71
64
  });
72
65
  if (guiEnabled) {
73
- yield AxioDBControlServer.register(static_1.default, {
74
- root: keys_1.staticPath,
75
- prefix: "/",
76
- decorateReply: false,
77
- });
66
+ (0, StaticFile_utils_1.default)(AxioDBControlServer, { root: keys_1.staticPath });
78
67
  AxioDBControlServer.get("/", (request, reply) => __awaiter(this, void 0, void 0, function* () {
79
68
  const indexPath = path_1.default.join(keys_1.staticPath, "index.html");
80
69
  const stream = fs_1.default.createReadStream(indexPath);
@@ -0,0 +1,34 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+ export interface CookieSerializeOptions {
3
+ path?: string;
4
+ domain?: string;
5
+ maxAge?: number;
6
+ expires?: Date;
7
+ httpOnly?: boolean;
8
+ secure?: boolean | "auto";
9
+ sameSite?: "lax" | "strict" | "none" | boolean;
10
+ }
11
+ export declare function parseCookieHeader(header: string): Record<string, string>;
12
+ export declare function serializeCookie(name: string, value: string, options?: CookieSerializeOptions): string;
13
+ declare module "fastify" {
14
+ interface FastifyRequest {
15
+ cookies: Record<string, string>;
16
+ }
17
+ interface FastifyReply {
18
+ setCookie(name: string, value: string, options?: CookieSerializeOptions): FastifyReply;
19
+ clearCookie(name: string, options?: Pick<CookieSerializeOptions, "path" | "domain">): FastifyReply;
20
+ }
21
+ }
22
+ /**
23
+ * Populates `request.cookies` and attaches the `setCookie` / `clearCookie`
24
+ * helpers to each reply.
25
+ *
26
+ * The reply helpers are assigned per-request rather than through `decorate`:
27
+ * a Fastify decorator lands on the instance, not on every reply object, so
28
+ * `reply.setCookie` would be undefined in route handlers.
29
+ */
30
+ export declare function registerCookies(fastify: FastifyInstanceLike): void;
31
+ interface FastifyInstanceLike {
32
+ addHook(name: string, hook: (request: FastifyRequest, reply: FastifyReply, done: (err?: Error) => void) => void): void;
33
+ }
34
+ export {};
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseCookieHeader = parseCookieHeader;
4
+ exports.serializeCookie = serializeCookie;
5
+ exports.registerCookies = registerCookies;
6
+ /**
7
+ * Cookie support for the control server.
8
+ *
9
+ * Sessions are server-side: the cookie carries only a `sid` that must match an
10
+ * entry in `SessionStore`, so the value itself needs no integrity protection.
11
+ * What this has to get right is correct parsing - a mis-split cookie would
12
+ * silently drop a valid session or authenticate the wrong one.
13
+ */
14
+ const COOKIE_PAIR = /; */;
15
+ function decode(value) {
16
+ if (!value.includes("%") && !value.includes("+")) {
17
+ return value;
18
+ }
19
+ try {
20
+ return decodeURIComponent(value);
21
+ }
22
+ catch (_a) {
23
+ // A malformed escape must not fail the whole request - hand back the raw
24
+ // value so it simply misses the session lookup instead.
25
+ return value;
26
+ }
27
+ }
28
+ function parseCookieHeader(header) {
29
+ const result = {};
30
+ for (const pair of header.split(COOKIE_PAIR)) {
31
+ const index = pair.indexOf("=");
32
+ if (index < 0) {
33
+ continue;
34
+ }
35
+ const name = pair.slice(0, index).trim();
36
+ // First occurrence wins, matching how browsers and the plugin behave.
37
+ if (name.length === 0 || name in result) {
38
+ continue;
39
+ }
40
+ result[name] = decode(pair.slice(index + 1).trim());
41
+ }
42
+ return result;
43
+ }
44
+ function serializeCookie(name, value, options = {}) {
45
+ var _a;
46
+ let cookie = `${name}=${encodeURIComponent(value)}`;
47
+ if (options.maxAge !== undefined) {
48
+ cookie += `; Max-Age=${Math.floor(options.maxAge)}`;
49
+ }
50
+ if (options.domain) {
51
+ cookie += `; Domain=${options.domain}`;
52
+ }
53
+ cookie += `; Path=${(_a = options.path) !== null && _a !== void 0 ? _a : "/"}`;
54
+ if (options.expires) {
55
+ cookie += `; Expires=${options.expires.toUTCString()}`;
56
+ }
57
+ if (options.httpOnly) {
58
+ cookie += "; HttpOnly";
59
+ }
60
+ if (options.secure) {
61
+ cookie += "; Secure";
62
+ }
63
+ if (options.sameSite && options.sameSite !== true) {
64
+ const mode = String(options.sameSite);
65
+ cookie += `; SameSite=${mode.charAt(0).toUpperCase()}${mode.slice(1)}`;
66
+ }
67
+ return cookie;
68
+ }
69
+ /**
70
+ * Populates `request.cookies` and attaches the `setCookie` / `clearCookie`
71
+ * helpers to each reply.
72
+ *
73
+ * The reply helpers are assigned per-request rather than through `decorate`:
74
+ * a Fastify decorator lands on the instance, not on every reply object, so
75
+ * `reply.setCookie` would be undefined in route handlers.
76
+ */
77
+ function registerCookies(fastify) {
78
+ fastify.addHook("onRequest", (request, reply, done) => {
79
+ const header = request.headers.cookie;
80
+ request.cookies = header ? parseCookieHeader(header) : {};
81
+ reply.setCookie = (name, value, options = {}) => {
82
+ // "auto" follows the connection, so a plain-HTTP dev server still receives a
83
+ // usable cookie while any TLS-terminated deployment gets the secure flag.
84
+ const secure = options.secure === "auto" ? request.protocol === "https" : options.secure;
85
+ reply.header("Set-Cookie", serializeCookie(name, value, Object.assign(Object.assign({}, options), { secure })));
86
+ return reply;
87
+ };
88
+ reply.clearCookie = (name, options = {}) => {
89
+ reply.header("Set-Cookie", serializeCookie(name, "", Object.assign(Object.assign({}, options), { maxAge: 0, expires: new Date(0) })));
90
+ return reply;
91
+ };
92
+ done();
93
+ });
94
+ }
@@ -0,0 +1,13 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ /**
3
+ * CORS for the control server, written against the same `CORS_CONFIG` the
4
+ * `@fastify/cors` plugin used.
5
+ *
6
+ * Two behaviours are deliberate:
7
+ * - The origin is echoed back verbatim, never `*`. Browsers reject a wildcard
8
+ * on a credentialed (cookie-bearing) request, and the GUI's session cookie is
9
+ * exactly that.
10
+ * - `Vary: Origin` is always sent, so a shared cache cannot serve one origin's
11
+ * response to another.
12
+ */
13
+ export default function registerCors(fastify: FastifyInstance): void;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.default = registerCors;
13
+ const keys_1 = require("../server/config/keys");
14
+ /**
15
+ * CORS for the control server, written against the same `CORS_CONFIG` the
16
+ * `@fastify/cors` plugin used.
17
+ *
18
+ * Two behaviours are deliberate:
19
+ * - The origin is echoed back verbatim, never `*`. Browsers reject a wildcard
20
+ * on a credentialed (cookie-bearing) request, and the GUI's session cookie is
21
+ * exactly that.
22
+ * - `Vary: Origin` is always sent, so a shared cache cannot serve one origin's
23
+ * response to another.
24
+ */
25
+ function registerCors(fastify) {
26
+ fastify.addHook("onRequest", (request, reply) => __awaiter(this, void 0, void 0, function* () {
27
+ const origin = request.headers.origin;
28
+ if (origin && keys_1.CORS_CONFIG.ORIGIN.includes(origin)) {
29
+ reply.header("Access-Control-Allow-Origin", origin);
30
+ reply.header("Access-Control-Allow-Credentials", String(keys_1.CORS_CONFIG.ALLOW_CREDENTIALS));
31
+ reply.header("Access-Control-Expose-Headers", keys_1.CORS_CONFIG.EXPOSED_HEADERS.join(", "));
32
+ }
33
+ reply.header("Vary", "Origin");
34
+ if (request.method !== "OPTIONS") {
35
+ return;
36
+ }
37
+ // Echo back what the browser asked for; falling back to our list would
38
+ // reject a legitimate request carrying an extra standard header.
39
+ const requested = request.headers["access-control-request-headers"];
40
+ reply.header("Access-Control-Allow-Headers", requested !== null && requested !== void 0 ? requested : keys_1.CORS_CONFIG.ALLOWED_HEADERS.join(", "));
41
+ reply.header("Access-Control-Allow-Methods", keys_1.CORS_CONFIG.METHODS.join(", "));
42
+ reply.header("Access-Control-Max-Age", String(keys_1.CORS_CONFIG.MAX_AGE));
43
+ // A preflight never reaches a route, so answer it here rather than needing
44
+ // a throwaway OPTIONS handler on every router.
45
+ yield reply.code(204).send();
46
+ }));
47
+ }
@@ -0,0 +1,15 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ /**
3
+ * Maps a URL path to a file inside `root`, or returns null when the result would
4
+ * escape it. Any traversal sequence resolves outside `root` and is rejected here.
5
+ */
6
+ export declare function resolveWithinRoot(root: string, urlPath: string): string | null;
7
+ export interface StaticOptions {
8
+ root: string;
9
+ }
10
+ /**
11
+ * Serves files from `root` for GET/HEAD requests that no route has claimed.
12
+ * Registered as an `onRequest` hook rather than a wildcard route so it composes
13
+ * with the existing router instead of competing with it.
14
+ */
15
+ export default function registerStatic(fastify: FastifyInstance, options: StaticOptions): void;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.resolveWithinRoot = resolveWithinRoot;
16
+ exports.default = registerStatic;
17
+ const fs_1 = __importDefault(require("fs"));
18
+ const path_1 = __importDefault(require("path"));
19
+ /**
20
+ * Static file serving for the control server.
21
+ *
22
+ * The job was never just "read a file" - it was mapping a request path to a file
23
+ * inside a fixed root without ever escaping it. That containment is the part
24
+ * worth keeping explicit, so it is written out here rather than left implicit:
25
+ * every candidate path is resolved and compared against the root, and the
26
+ * `onRequest` dot-dot guard in `config/server.ts` is a second layer in front.
27
+ */
28
+ const MIME_TYPES = {
29
+ ".html": "text/html; charset=utf-8",
30
+ ".js": "text/javascript; charset=utf-8",
31
+ ".mjs": "text/javascript; charset=utf-8",
32
+ ".css": "text/css; charset=utf-8",
33
+ ".json": "application/json; charset=utf-8",
34
+ ".svg": "image/svg+xml",
35
+ ".png": "image/png",
36
+ ".jpg": "image/jpeg",
37
+ ".jpeg": "image/jpeg",
38
+ ".gif": "image/gif",
39
+ ".ico": "image/x-icon",
40
+ ".webp": "image/webp",
41
+ ".woff": "font/woff",
42
+ ".woff2": "font/woff2",
43
+ ".ttf": "font/ttf",
44
+ ".eot": "application/vnd.ms-fontobject",
45
+ ".map": "application/json; charset=utf-8",
46
+ ".txt": "text/plain; charset=utf-8",
47
+ ".wasm": "application/wasm",
48
+ };
49
+ function contentTypeFor(filePath) {
50
+ var _a;
51
+ return (_a = MIME_TYPES[path_1.default.extname(filePath).toLowerCase()]) !== null && _a !== void 0 ? _a : "application/octet-stream";
52
+ }
53
+ /**
54
+ * Maps a URL path to a file inside `root`, or returns null when the result would
55
+ * escape it. Any traversal sequence resolves outside `root` and is rejected here.
56
+ */
57
+ function resolveWithinRoot(root, urlPath) {
58
+ let decoded;
59
+ try {
60
+ decoded = decodeURIComponent(urlPath);
61
+ }
62
+ catch (_a) {
63
+ return null;
64
+ }
65
+ const candidate = path_1.default.resolve(root, `.${path_1.default.posix.normalize(`/${decoded}`)}`);
66
+ const rootWithSep = root.endsWith(path_1.default.sep) ? root : root + path_1.default.sep;
67
+ if (candidate !== root && !candidate.startsWith(rootWithSep)) {
68
+ return null;
69
+ }
70
+ return candidate;
71
+ }
72
+ /**
73
+ * Serves files from `root` for GET/HEAD requests that no route has claimed.
74
+ * Registered as an `onRequest` hook rather than a wildcard route so it composes
75
+ * with the existing router instead of competing with it.
76
+ */
77
+ function registerStatic(fastify, options) {
78
+ const root = path_1.default.resolve(options.root);
79
+ fastify.addHook("onRequest", (request, reply) => __awaiter(this, void 0, void 0, function* () {
80
+ var _a;
81
+ if (request.method !== "GET" && request.method !== "HEAD") {
82
+ return;
83
+ }
84
+ // A reply already sent means a route or an earlier hook claimed the request.
85
+ if (reply.sent) {
86
+ return;
87
+ }
88
+ const urlPath = ((_a = request.raw.url) !== null && _a !== void 0 ? _a : "/").split("?", 1)[0];
89
+ const target = resolveWithinRoot(root, urlPath);
90
+ if (target === null) {
91
+ return;
92
+ }
93
+ let stats;
94
+ try {
95
+ stats = yield fs_1.default.promises.stat(target);
96
+ }
97
+ catch (_b) {
98
+ return;
99
+ }
100
+ // Directories fall through so the app can serve its own index route.
101
+ if (!stats.isFile()) {
102
+ return;
103
+ }
104
+ const type = contentTypeFor(target);
105
+ reply.header("Content-Type", type);
106
+ reply.header("Content-Length", String(stats.size));
107
+ if (request.method === "HEAD") {
108
+ yield reply.code(200).send();
109
+ return;
110
+ }
111
+ yield reply.type(type).send(fs_1.default.createReadStream(target));
112
+ }));
113
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axiodb",
3
- "version": "22.17.1",
3
+ "version": "22.19.1",
4
4
  "description": "The embedded database for Node.js. Replaces SQLite, LowDB, NeDB & raw JSON files with MongoDB-style queries, ACID transactions, zero native dependencies, and built-in InMemoryCache. No compilation, no platform issues. Perfect for desktop apps, CLI tools, and embedded systems.",
5
5
  "main": "./lib/config/DB.js",
6
6
  "types": "./lib/config/DB.d.ts",
@@ -59,10 +59,7 @@
59
59
  "url": "https://github.com/nexoral/AxioDB"
60
60
  },
61
61
  "dependencies": {
62
- "@fastify/cookie": "^10.0.1",
63
- "@fastify/cors": "^11.0.1",
64
62
  "@fastify/multipart": "^9.0.3",
65
- "@fastify/static": "^10.1.3",
66
63
  "fastify": "^5.8.5",
67
64
  "tar": "^7.5.16"
68
65
  },