@webtypen/webframez-core 0.3.82 → 0.3.85
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/bearer-auth-client.d.ts
CHANGED
|
@@ -5,9 +5,12 @@ export type BearerAuthTokens = {
|
|
|
5
5
|
refresh_expires_at: number;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
/** Override one request's transport (e.g. XHR upload progress); refreshes keep the original fetch and shared refresh lock. */
|
|
9
|
+
export type BearerAuthFetch = (input: Parameters<typeof fetch>[0], init?: RequestInit, requestFetch?: typeof fetch) => Promise<Response>;
|
|
10
|
+
|
|
8
11
|
export function createBearerAuthFetch(options: {
|
|
9
12
|
configuration: (url: URL) => { key: string; refreshUrl: string } | null;
|
|
10
13
|
load: (key: string) => Promise<BearerAuthTokens | null>;
|
|
11
14
|
clear?: (key: string) => Promise<void>;
|
|
12
15
|
save: (key: string, tokens: BearerAuthTokens) => Promise<void>;
|
|
13
|
-
}, originalFetch?: typeof fetch):
|
|
16
|
+
}, originalFetch?: typeof fetch): BearerAuthFetch;
|
package/bearer-auth-client.js
CHANGED
|
@@ -41,27 +41,27 @@ exports.createBearerAuthFetch = function createBearerAuthFetch(options, original
|
|
|
41
41
|
}
|
|
42
42
|
return pending.get(key);
|
|
43
43
|
};
|
|
44
|
-
return async (input, init) => {
|
|
44
|
+
return async (input, init, requestFetch = originalFetch) => {
|
|
45
45
|
let url;
|
|
46
46
|
try { url = new URL(typeof Request !== "undefined" && input instanceof Request ? input.url : String(input), globalThis.location?.href); }
|
|
47
|
-
catch { return
|
|
47
|
+
catch { return requestFetch(input, init); }
|
|
48
48
|
const configuration = options.configuration(url);
|
|
49
|
-
if (!configuration) return
|
|
49
|
+
if (!configuration) return requestFetch(input, init);
|
|
50
50
|
const refreshUrl = new URL(configuration.refreshUrl);
|
|
51
51
|
if (refreshUrl.origin !== url.origin) throw new Error("Bearer refresh must use the request origin.");
|
|
52
52
|
let session = await options.load(configuration.key);
|
|
53
|
-
if (!session || url.href === refreshUrl.href) return
|
|
53
|
+
if (!session || url.href === refreshUrl.href) return requestFetch(input, init);
|
|
54
54
|
if (session.auth_expires_at <= Date.now() + 30000) session = await refresh(configuration, session.auth_token) || session;
|
|
55
55
|
const headers = new Headers(init?.headers || (typeof Request !== "undefined" && input instanceof Request ? input.headers : undefined));
|
|
56
56
|
headers.set("Authorization", `Bearer ${session.auth_token}`);
|
|
57
57
|
const retryInput = typeof Request !== "undefined" && input instanceof Request ? input.clone() : input;
|
|
58
|
-
const response = await
|
|
58
|
+
const response = await requestFetch(input, { ...init, headers, credentials: "omit", redirect: "error" });
|
|
59
59
|
if (response.status !== 401) return response;
|
|
60
60
|
const failure = await response.clone().json().catch(() => null);
|
|
61
61
|
if (failure?.code !== "unauthorized") return response;
|
|
62
62
|
const renewed = await refresh(configuration, session.auth_token);
|
|
63
63
|
if (!renewed) return response;
|
|
64
64
|
headers.set("Authorization", `Bearer ${renewed.auth_token}`);
|
|
65
|
-
return
|
|
65
|
+
return requestFetch(retryInput, { ...init, headers, credentials: "omit", redirect: "error" });
|
|
66
66
|
};
|
|
67
67
|
};
|
|
@@ -17,7 +17,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
17
17
|
const path_1 = __importDefault(require("path"));
|
|
18
18
|
const os_1 = __importDefault(require("os"));
|
|
19
19
|
const crypto_1 = __importDefault(require("crypto"));
|
|
20
|
-
const
|
|
20
|
+
const ZipReader_1 = require("./Utils/ZipReader");
|
|
21
21
|
const Config_1 = require("../Config");
|
|
22
22
|
const DBConnection_1 = require("../Database/DBConnection");
|
|
23
23
|
const BackupOutputDrivers_1 = require("./BackupOutputDrivers");
|
|
@@ -705,7 +705,7 @@ class BackupManager {
|
|
|
705
705
|
}
|
|
706
706
|
extractArtifact(artifactPath, targetDir) {
|
|
707
707
|
return __awaiter(this, void 0, void 0, function* () {
|
|
708
|
-
yield (0,
|
|
708
|
+
yield (0, ZipReader_1.extractBackupZip)(artifactPath, targetDir);
|
|
709
709
|
fs_1.default.rmSync(path_1.default.join(targetDir, ".webframez-backup"), { recursive: true, force: true });
|
|
710
710
|
});
|
|
711
711
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
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.extractBackupZip = void 0;
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const stream_1 = require("stream");
|
|
19
|
+
const promises_1 = require("stream/promises");
|
|
20
|
+
const yauzl_1 = require("yauzl");
|
|
21
|
+
function ensureDirectory(directory) {
|
|
22
|
+
const parent = path_1.default.dirname(directory);
|
|
23
|
+
if (parent !== directory) {
|
|
24
|
+
ensureDirectory(parent);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
fs_1.default.mkdirSync(directory, { mode: 0o700 });
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.code !== "EEXIST") {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const info = fs_1.default.lstatSync(directory);
|
|
35
|
+
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
36
|
+
throw new Error(`Restore directory must not be a symlink or special file: ${directory}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function entryTarget(root, entry) {
|
|
40
|
+
const name = entry.fileName;
|
|
41
|
+
const parts = name.replace(/\/$/, "").split("/");
|
|
42
|
+
if (!name || name.includes("\\") || /[\x00-\x1f\x7f:]/.test(name) || parts.some((part) => !part || part === "." || part === "..")) {
|
|
43
|
+
throw new Error(`Unsafe ZIP entry path: ${JSON.stringify(name)}`);
|
|
44
|
+
}
|
|
45
|
+
const mode = (entry.externalFileAttributes >>> 16) & 0xffff;
|
|
46
|
+
const type = mode & 0o170000;
|
|
47
|
+
if (type !== 0 && type !== 0o100000 && type !== 0o040000) {
|
|
48
|
+
throw new Error(`ZIP symlinks and special files are forbidden: ${name}`);
|
|
49
|
+
}
|
|
50
|
+
const directory = name.endsWith("/") || type === 0o040000;
|
|
51
|
+
if (directory && entry.uncompressedSize !== 0) {
|
|
52
|
+
throw new Error(`ZIP directory contains file data: ${name}`);
|
|
53
|
+
}
|
|
54
|
+
const target = path_1.default.resolve(root, ...parts);
|
|
55
|
+
if (!target.startsWith(root + path_1.default.sep)) {
|
|
56
|
+
throw new Error(`ZIP entry escapes restore directory: ${name}`);
|
|
57
|
+
}
|
|
58
|
+
return { target, directory, mode: mode & 0o777 };
|
|
59
|
+
}
|
|
60
|
+
function extractEntry(zip, entry, target, mode) {
|
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
ensureDirectory(path_1.default.dirname(target));
|
|
63
|
+
try {
|
|
64
|
+
const existing = fs_1.default.lstatSync(target);
|
|
65
|
+
if (!existing.isFile() || existing.isSymbolicLink() || existing.nlink !== 1) {
|
|
66
|
+
throw new Error(`Restore destination is not a private regular file: ${target}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error.code !== "ENOENT") {
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Do not truncate until the opened inode is verified. O_NOFOLLOW rejects
|
|
75
|
+
// destination symlinks; nlink prevents modifying files shared outside restore.
|
|
76
|
+
const output = yield fs_1.default.promises.open(target, fs_1.default.constants.O_WRONLY | fs_1.default.constants.O_CREAT | fs_1.default.constants.O_NOFOLLOW | fs_1.default.constants.O_NONBLOCK, 0o600);
|
|
77
|
+
try {
|
|
78
|
+
const info = yield output.stat();
|
|
79
|
+
if (!info.isFile() || info.nlink !== 1) {
|
|
80
|
+
throw new Error(`Restore destination is not a private regular file: ${target}`);
|
|
81
|
+
}
|
|
82
|
+
yield output.truncate(0);
|
|
83
|
+
yield new Promise((resolve, reject) => {
|
|
84
|
+
zip.openReadStream(entry, (error, input) => {
|
|
85
|
+
if (error) {
|
|
86
|
+
reject(error);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Attach all stream error handlers synchronously, before an
|
|
90
|
+
// inflater can report a corrupt entry on the next tick (Node 22).
|
|
91
|
+
const sink = new stream_1.Writable({
|
|
92
|
+
write(chunk, _encoding, callback) {
|
|
93
|
+
output.writeFile(chunk).then(() => callback(), callback);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
(0, promises_1.pipeline)(input, sink).then(resolve, reject);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
yield output.chmod(mode || 0o600);
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
yield output.close();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Extract regular backup files only, sequentially, into an owner-controlled directory. */
|
|
107
|
+
function extractBackupZip(archive, destination) {
|
|
108
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
109
|
+
const root = path_1.default.resolve(destination);
|
|
110
|
+
ensureDirectory(root);
|
|
111
|
+
const zip = yield new Promise((resolve, reject) => {
|
|
112
|
+
(0, yauzl_1.open)(archive, { lazyEntries: true, validateEntrySizes: true, strictFileNames: true }, (error, file) => error ? reject(error) : resolve(file));
|
|
113
|
+
});
|
|
114
|
+
yield new Promise((resolve, reject) => {
|
|
115
|
+
const seen = new Set();
|
|
116
|
+
let finished = false;
|
|
117
|
+
const fail = (error) => {
|
|
118
|
+
if (!finished) {
|
|
119
|
+
finished = true;
|
|
120
|
+
zip.close();
|
|
121
|
+
reject(error);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
zip.on("error", fail);
|
|
125
|
+
zip.on("end", () => { finished = true; resolve(); });
|
|
126
|
+
zip.on("entry", (entry) => {
|
|
127
|
+
void (() => __awaiter(this, void 0, void 0, function* () {
|
|
128
|
+
const { target, directory, mode } = entryTarget(root, entry);
|
|
129
|
+
if (seen.has(target)) {
|
|
130
|
+
throw new Error(`Duplicate ZIP entry: ${entry.fileName}`);
|
|
131
|
+
}
|
|
132
|
+
seen.add(target);
|
|
133
|
+
if (directory) {
|
|
134
|
+
ensureDirectory(target);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
yield extractEntry(zip, entry, target, mode);
|
|
138
|
+
}
|
|
139
|
+
if (!finished) {
|
|
140
|
+
zip.readEntry();
|
|
141
|
+
}
|
|
142
|
+
}))().catch(fail);
|
|
143
|
+
});
|
|
144
|
+
zip.readEntry();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
exports.extractBackupZip = extractBackupZip;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webtypen/webframez-core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.85",
|
|
4
4
|
"description": "The core package of the typescript based webframez (web-framework) by webtypen.de",
|
|
5
5
|
"homepage": "https://webtypen.de/",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"@types/mime-types": "^2.1.4",
|
|
48
48
|
"@types/node": "^18.11.9",
|
|
49
49
|
"@types/qs": "^6.9.7",
|
|
50
|
-
"
|
|
50
|
+
"@types/yauzl": "^3.4.0",
|
|
51
|
+
"moment": "^2.31.0",
|
|
51
52
|
"ts-node": "^10.9.1",
|
|
52
53
|
"typescript": "^4.9.3"
|
|
53
54
|
},
|
|
@@ -55,11 +56,11 @@
|
|
|
55
56
|
"bcryptjs": "^3.0.3",
|
|
56
57
|
"busboy": "^1.6.0",
|
|
57
58
|
"dotenv": "^8.2.0",
|
|
58
|
-
"extract-zip": "^2.0.1",
|
|
59
59
|
"lodash": "^4.17.20",
|
|
60
60
|
"mime-types": "^2.1.35",
|
|
61
61
|
"moment-timezone": "^0.6.0",
|
|
62
|
-
"qs": "^6.
|
|
63
|
-
"querystring": "^0.2.1"
|
|
62
|
+
"qs": "^6.16.0",
|
|
63
|
+
"querystring": "^0.2.1",
|
|
64
|
+
"yauzl": "^3.4.0"
|
|
64
65
|
}
|
|
65
66
|
}
|