@harperfast/harper-pro 5.0.5 → 5.0.6
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/core/bin/status.js +2 -2
- package/core/bin/stop.js +5 -6
- package/core/components/EntryHandler.ts +4 -2
- package/core/components/Scope.ts +1 -1
- package/core/components/componentLoader.ts +11 -4
- package/core/components/requestRestart.ts +17 -2
- package/core/dataLayer/harperBridge/TableSizeObject.ts +35 -0
- package/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.ts +24 -0
- package/core/package-lock.json +1020 -101
- package/core/resources/DatabaseTransaction.ts +8 -3
- package/core/resources/Table.ts +12 -17
- package/core/resources/databases.ts +2 -2
- package/core/resources/graphql.ts +163 -165
- package/core/resources/indexes/HierarchicalNavigableSmallWorld.ts +14 -3
- package/core/resources/indexes/vector.ts +17 -0
- package/core/resources/loadEnv.ts +20 -16
- package/core/resources/login.ts +4 -3
- package/core/resources/roles.ts +60 -65
- package/core/security/auth.ts +15 -14
- package/core/security/jsLoader.ts +27 -9
- package/core/server/REST.ts +10 -11
- package/core/server/fastifyRoutes.ts +30 -29
- package/core/server/graphqlQuerying.ts +4 -3
- package/core/server/http.ts +175 -1
- package/core/server/mqtt.ts +8 -2
- package/core/server/serverHelpers/serverUtilities.ts +2 -5
- package/core/server/threads/threadServer.js +30 -2
- package/core/server/throttle.ts +18 -0
- package/core/utility/environment/environmentManager.js +10 -4
- package/core/utility/environment/systemInformation.ts +698 -0
- package/core/utility/hdbTerms.ts +1 -0
- package/core/utility/operation_authorization.js +2 -5
- package/dist/core/bin/status.js +2 -2
- package/dist/core/bin/status.js.map +1 -1
- package/dist/core/bin/stop.js +5 -5
- package/dist/core/bin/stop.js.map +1 -1
- package/dist/core/components/EntryHandler.js +4 -2
- package/dist/core/components/EntryHandler.js.map +1 -1
- package/dist/core/components/Scope.js +1 -1
- package/dist/core/components/Scope.js.map +1 -1
- package/dist/core/components/componentLoader.js +11 -3
- package/dist/core/components/componentLoader.js.map +1 -1
- package/dist/core/components/requestRestart.js +12 -1
- package/dist/core/components/requestRestart.js.map +1 -1
- package/dist/core/dataLayer/harperBridge/TableSizeObject.js +32 -0
- package/dist/core/dataLayer/harperBridge/TableSizeObject.js.map +1 -0
- package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.js +18 -19
- package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.js.map +1 -1
- package/dist/core/resources/DatabaseTransaction.js +6 -1
- package/dist/core/resources/DatabaseTransaction.js.map +1 -1
- package/dist/core/resources/Table.js +14 -18
- package/dist/core/resources/Table.js.map +1 -1
- package/dist/core/resources/databases.js +2 -1
- package/dist/core/resources/databases.js.map +1 -1
- package/dist/core/resources/graphql.js +176 -176
- package/dist/core/resources/graphql.js.map +1 -1
- package/dist/core/resources/indexes/HierarchicalNavigableSmallWorld.js +14 -2
- package/dist/core/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/core/resources/indexes/vector.js +14 -0
- package/dist/core/resources/indexes/vector.js.map +1 -1
- package/dist/core/resources/loadEnv.js +20 -17
- package/dist/core/resources/loadEnv.js.map +1 -1
- package/dist/core/resources/login.js +4 -4
- package/dist/core/resources/login.js.map +1 -1
- package/dist/core/resources/roles.js +64 -68
- package/dist/core/resources/roles.js.map +1 -1
- package/dist/core/security/auth.js +17 -15
- package/dist/core/security/auth.js.map +1 -1
- package/dist/core/security/jsLoader.js +29 -9
- package/dist/core/security/jsLoader.js.map +1 -1
- package/dist/core/server/REST.js +11 -11
- package/dist/core/server/REST.js.map +1 -1
- package/dist/core/server/fastifyRoutes.js +30 -29
- package/dist/core/server/fastifyRoutes.js.map +1 -1
- package/dist/core/server/graphqlQuerying.js +5 -4
- package/dist/core/server/graphqlQuerying.js.map +1 -1
- package/dist/core/server/http.js +179 -0
- package/dist/core/server/http.js.map +1 -1
- package/dist/core/server/mqtt.js +5 -3
- package/dist/core/server/mqtt.js.map +1 -1
- package/dist/core/server/serverHelpers/serverUtilities.js +2 -2
- package/dist/core/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/core/server/threads/threadServer.js +26 -2
- package/dist/core/server/threads/threadServer.js.map +1 -1
- package/dist/core/server/throttle.js +17 -0
- package/dist/core/server/throttle.js.map +1 -1
- package/dist/core/utility/environment/environmentManager.js +9 -4
- package/dist/core/utility/environment/environmentManager.js.map +1 -1
- package/dist/core/utility/environment/systemInformation.js +359 -219
- package/dist/core/utility/environment/systemInformation.js.map +1 -1
- package/dist/core/utility/hdbTerms.js +1 -0
- package/dist/core/utility/hdbTerms.js.map +1 -1
- package/dist/core/utility/operation_authorization.js +2 -2
- package/dist/core/utility/operation_authorization.js.map +1 -1
- package/dist/licensing/usageLicensing.js +1 -1
- package/dist/licensing/usageLicensing.js.map +1 -1
- package/licensing/usageLicensing.ts +1 -1
- package/npm-shrinkwrap.json +982 -62
- package/package.json +2 -1
- package/studio/web/assets/{index-D07pIqJt.js → index-qbLPhOzw.js} +2 -2
- package/studio/web/assets/{index-D07pIqJt.js.map → index-qbLPhOzw.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/TableSizeObject.js +0 -25
- package/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.js +0 -34
- package/core/utility/environment/systemInformation.js +0 -355
- package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/TableSizeObject.js +0 -24
- package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/TableSizeObject.js.map +0 -1
package/core/resources/roles.ts
CHANGED
|
@@ -9,73 +9,72 @@ const USERS_NOT_DBS = ['super_user', 'structure_user'];
|
|
|
9
9
|
* This is the component for handling role declarations in the Harper system. This will read roles.yaml for role
|
|
10
10
|
* definitions and ensure that they are created in the system database.
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
handleFile
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
export function handleApplication(scope: import('../components/Scope.ts').Scope) {
|
|
13
|
+
scope.handleEntry(async (entry) => {
|
|
14
|
+
if (entry.eventType === 'unlink') return;
|
|
15
|
+
return handleFile(entry.contents);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
19
|
+
/**
|
|
20
|
+
* This function will handle the roles.yaml file content that has been read, and ensure that the roles are translated to
|
|
21
|
+
* the right shape and created in the system database.
|
|
22
|
+
* @param rolesContent
|
|
23
|
+
*/
|
|
24
|
+
async function handleFile(rolesContent) {
|
|
25
|
+
let rolesToDefine = parseDocument(rolesContent.toString(), { simpleKeys: true }).toJSON();
|
|
26
|
+
for (let roleName in rolesToDefine) {
|
|
27
|
+
let role = rolesToDefine[roleName];
|
|
28
|
+
if (!role.permission) {
|
|
29
|
+
// we allow the permission object to be collapsed into the root object for convenience
|
|
30
|
+
role = {
|
|
31
|
+
permission: role,
|
|
32
|
+
};
|
|
33
|
+
if (role.permission.access) {
|
|
34
|
+
// this is the designed property object for user-defined flags and access levels
|
|
35
|
+
role.access = role.permission.access;
|
|
36
|
+
delete role.permission.access;
|
|
38
37
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
table.attribute_permissions = attributes;
|
|
62
|
-
delete table.attributes;
|
|
38
|
+
}
|
|
39
|
+
for (let dbName in role.permission) {
|
|
40
|
+
if (USERS_NOT_DBS.includes(dbName)) continue;
|
|
41
|
+
let db = role.permission[dbName];
|
|
42
|
+
if (!db.tables) {
|
|
43
|
+
// we allow the tables object to be collapsed into the root object for convenience
|
|
44
|
+
role.permission[dbName] = db = { tables: db };
|
|
45
|
+
}
|
|
46
|
+
for (let tableName in db.tables) {
|
|
47
|
+
let table = db.tables[tableName];
|
|
48
|
+
// ensure that all the flags are boolean
|
|
49
|
+
table.read = Boolean(table.read);
|
|
50
|
+
table.insert = Boolean(table.insert);
|
|
51
|
+
table.update = Boolean(table.update);
|
|
52
|
+
table.delete = Boolean(table.delete);
|
|
53
|
+
if (table.attributes) {
|
|
54
|
+
// allow attributes to be defined with an object, translating to an array
|
|
55
|
+
let attributes = [];
|
|
56
|
+
for (let attribute_name in table.attributes) {
|
|
57
|
+
let attribute = table.attributes[attribute_name];
|
|
58
|
+
attribute.attribute_name = attribute_name;
|
|
59
|
+
attributes.push(attribute);
|
|
63
60
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
throw new Error('attribute_permissions must be an array if defined');
|
|
67
|
-
for (let attribute of table.attribute_permissions) {
|
|
68
|
-
// ensure that all the flags are boolean
|
|
69
|
-
attribute.read = Boolean(attribute.read);
|
|
70
|
-
attribute.insert = Boolean(attribute.insert);
|
|
71
|
-
attribute.update = Boolean(attribute.update);
|
|
72
|
-
}
|
|
73
|
-
} else table.attribute_permissions = null;
|
|
61
|
+
table.attribute_permissions = attributes;
|
|
62
|
+
delete table.attributes;
|
|
74
63
|
}
|
|
64
|
+
if (table.attribute_permissions) {
|
|
65
|
+
if (!Array.isArray(table.attribute_permissions))
|
|
66
|
+
throw new Error('attribute_permissions must be an array if defined');
|
|
67
|
+
for (let attribute of table.attribute_permissions) {
|
|
68
|
+
// ensure that all the flags are boolean
|
|
69
|
+
attribute.read = Boolean(attribute.read);
|
|
70
|
+
attribute.insert = Boolean(attribute.insert);
|
|
71
|
+
attribute.update = Boolean(attribute.update);
|
|
72
|
+
}
|
|
73
|
+
} else table.attribute_permissions = null;
|
|
75
74
|
}
|
|
76
|
-
role.role = role.id = roleName;
|
|
77
|
-
await ensureRole(role);
|
|
78
75
|
}
|
|
76
|
+
role.role = role.id = roleName;
|
|
77
|
+
await ensureRole(role);
|
|
79
78
|
}
|
|
80
79
|
}
|
|
81
80
|
async function ensureRole(role) {
|
|
@@ -92,7 +91,3 @@ async function ensureRole(role) {
|
|
|
92
91
|
}
|
|
93
92
|
return addRole(role);
|
|
94
93
|
}
|
|
95
|
-
|
|
96
|
-
// we can define these on the main thread
|
|
97
|
-
export const startOnMainThread = start;
|
|
98
|
-
// useful for testing
|
package/core/security/auth.ts
CHANGED
|
@@ -243,7 +243,9 @@ export async function authentication(request, nextHandler) {
|
|
|
243
243
|
request.user = await server.getUser(session.user, null, request);
|
|
244
244
|
} else if (
|
|
245
245
|
(AUTHORIZE_LOCAL && (request.ip?.includes('127.0.0.') || request.ip == '::1')) ||
|
|
246
|
-
(request?._nodeRequest?.socket?.server?._pipeName &&
|
|
246
|
+
(request?._nodeRequest?.socket?.server?._pipeName &&
|
|
247
|
+
request?._nodeRequest?.socket?.server?.bypassLocalAuth &&
|
|
248
|
+
request.ip === undefined) // allow operations API domain socket
|
|
247
249
|
) {
|
|
248
250
|
request.user = await getSuperUser();
|
|
249
251
|
}
|
|
@@ -344,19 +346,18 @@ export async function authentication(request, nextHandler) {
|
|
|
344
346
|
return response;
|
|
345
347
|
}
|
|
346
348
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
349
|
+
setInterval(() => {
|
|
350
|
+
authorizationCache = new Map();
|
|
351
|
+
}, env.get(CONFIG_PARAMS.AUTHENTICATION_CACHETTL)).unref();
|
|
352
|
+
user.addListener(() => {
|
|
353
|
+
authorizationCache = new Map();
|
|
354
|
+
});
|
|
355
|
+
let started = false;
|
|
356
|
+
export function handleApplication(scope: import('../components/Scope.ts').Scope) {
|
|
357
|
+
if (started) return;
|
|
358
|
+
started = true;
|
|
359
|
+
const { port, securePort } = scope.options.getAll() as { port?: number; securePort?: number };
|
|
360
|
+
scope.server.http(authentication, port || securePort ? { port, securePort } : { port: 'all' });
|
|
360
361
|
}
|
|
361
362
|
// operations
|
|
362
363
|
export async function login(loginObject) {
|
|
@@ -181,7 +181,13 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
|
|
|
181
181
|
if (parts[0] === 'file:') {
|
|
182
182
|
return specifier;
|
|
183
183
|
}
|
|
184
|
-
|
|
184
|
+
let resolveReferrer = referrer;
|
|
185
|
+
if (referrer.startsWith('file:')) {
|
|
186
|
+
try {
|
|
187
|
+
resolveReferrer = pathToFileURL(realpathSync(fileURLToPath(referrer))).toString();
|
|
188
|
+
} catch {}
|
|
189
|
+
}
|
|
190
|
+
const resolved = createRequire(resolveReferrer).resolve(specifier);
|
|
185
191
|
if (isAbsolute(resolved)) {
|
|
186
192
|
return pathToFileURL(resolved).toString();
|
|
187
193
|
}
|
|
@@ -206,18 +212,30 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
|
|
|
206
212
|
cjsModule.exports = parseJsonModule(source, url);
|
|
207
213
|
return cjsModule;
|
|
208
214
|
}
|
|
209
|
-
|
|
215
|
+
let requireUrl = url;
|
|
216
|
+
if (url.startsWith('file://')) {
|
|
217
|
+
try {
|
|
218
|
+
requireUrl = pathToFileURL(realpathSync(fileURLToPath(url))).toString();
|
|
219
|
+
} catch {}
|
|
220
|
+
}
|
|
221
|
+
const require = createRequire(requireUrl);
|
|
210
222
|
|
|
211
223
|
const cjsRequire = (spec: string) => {
|
|
212
|
-
const
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
return loadCJS(resolvedPath, source).exports;
|
|
216
|
-
} else {
|
|
217
|
-
return require(spec);
|
|
224
|
+
const resolvedUrl = resolveModule(spec, url);
|
|
225
|
+
if (resolvedUrl === 'harper') {
|
|
226
|
+
return getHarperExports(scope);
|
|
218
227
|
}
|
|
228
|
+
if (resolvedUrl.startsWith('file://')) {
|
|
229
|
+
const source = readFileSync(new URL(resolvedUrl), { encoding: 'utf-8' });
|
|
230
|
+
return loadCJS(resolvedUrl, source).exports;
|
|
231
|
+
}
|
|
232
|
+
return require(resolvedUrl);
|
|
233
|
+
};
|
|
234
|
+
cjsRequire.resolve = (spec: string) => {
|
|
235
|
+
const resolvedUrl = resolveModule(spec, url);
|
|
236
|
+
if (resolvedUrl.startsWith('file://')) return fileURLToPath(resolvedUrl);
|
|
237
|
+
return resolvedUrl;
|
|
219
238
|
};
|
|
220
|
-
cjsRequire.resolve = require.resolve;
|
|
221
239
|
|
|
222
240
|
const cjsWrapper = `
|
|
223
241
|
(function(module, exports, require, __filename, __dirname) {
|
package/core/server/REST.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { serialize, serializeMessage, getDeserializer } from '../server/serverHelpers/contentTypes.ts';
|
|
2
2
|
import { addAnalyticsListener, recordAction, recordActionBinary } from '../resources/analytics/write.ts';
|
|
3
3
|
import * as harperLogger from '../utility/logging/harper_logger.js';
|
|
4
|
-
import { ServerOptions } from 'http';
|
|
5
4
|
import { ServerError, ClientError } from '../utility/errors/hdbError.js';
|
|
6
5
|
import { Resources } from '../resources/Resources.ts';
|
|
7
6
|
import { Resource, missingMethod, allowedMethods } from '../resources/Resource.ts';
|
|
@@ -277,26 +276,26 @@ async function http(request: Context & Request, nextHandler) {
|
|
|
277
276
|
}
|
|
278
277
|
}
|
|
279
278
|
|
|
280
|
-
let started;
|
|
279
|
+
let started = false;
|
|
281
280
|
let resources: Resources;
|
|
282
281
|
let addedMetrics;
|
|
283
282
|
let connectionCount = 0;
|
|
284
283
|
|
|
285
|
-
export function
|
|
286
|
-
httpOptions = options;
|
|
287
|
-
if (
|
|
284
|
+
export function handleApplication(scope: import('../components/Scope.ts').Scope) {
|
|
285
|
+
httpOptions = scope.options.getAll();
|
|
286
|
+
if ((httpOptions as any).includeExpensiveRecordCountEstimates) {
|
|
288
287
|
// If they really want to enable expensive record count estimates
|
|
289
288
|
Request.prototype.includeExpensiveRecordCountEstimates = true;
|
|
290
289
|
}
|
|
290
|
+
resources = scope.resources;
|
|
291
291
|
if (started) return;
|
|
292
292
|
started = true;
|
|
293
|
-
|
|
294
|
-
options.server.http(async (request: Request, nextHandler) => {
|
|
293
|
+
scope.server.http(async (request: Request, nextHandler) => {
|
|
295
294
|
if (request.isWebSocket) return;
|
|
296
295
|
return http(request, nextHandler);
|
|
297
|
-
},
|
|
298
|
-
if (
|
|
299
|
-
|
|
296
|
+
}, httpOptions);
|
|
297
|
+
if ((httpOptions as any).webSocket === false) return;
|
|
298
|
+
scope.server.ws(async (ws, request, chainCompletion) => {
|
|
300
299
|
connectionCount++;
|
|
301
300
|
const incomingMessages = new IterableEventQueue();
|
|
302
301
|
if (!addedMetrics) {
|
|
@@ -382,7 +381,7 @@ export function start(options: ServerOptions & { path: string; port: number; ser
|
|
|
382
381
|
);
|
|
383
382
|
}
|
|
384
383
|
ws.close();
|
|
385
|
-
},
|
|
384
|
+
}, httpOptions);
|
|
386
385
|
}
|
|
387
386
|
const HTTP_TO_WEBSOCKET_CLOSE_CODES = {
|
|
388
387
|
401: 3000,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dirname } from 'path';
|
|
1
|
+
import { dirname, basename } from 'path';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
3
|
import fastify from 'fastify';
|
|
4
4
|
import fastifyCors from '@fastify/cors';
|
|
@@ -32,35 +32,36 @@ const routeFolders = new Set();
|
|
|
32
32
|
* @param filePath
|
|
33
33
|
* @param projectName
|
|
34
34
|
*/
|
|
35
|
-
export function
|
|
35
|
+
export function handleApplication(scope: import('../components/Scope.ts').Scope) {
|
|
36
36
|
// if we have a secure port, need to use the secure HTTP server for fastify (it can be used for HTTP as well)
|
|
37
|
-
const isHttps = options.securePort > 0;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
37
|
+
const isHttps = (scope.options.getAll() as { securePort?: number }).securePort > 0;
|
|
38
|
+
scope.handleEntry(async (entry) => {
|
|
39
|
+
if (entry.eventType !== 'add') {
|
|
40
|
+
scope.requestRestart();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!fastifyServer) {
|
|
44
|
+
fastifyServer = buildServer(isHttps);
|
|
45
|
+
server.http((await fastifyServer).server);
|
|
46
|
+
}
|
|
47
|
+
const resolvedServer = await fastifyServer;
|
|
48
|
+
const routeFolder = dirname(entry.absolutePath);
|
|
49
|
+
let prefix = basename(scope.appName);
|
|
50
|
+
if (prefix.startsWith('/')) prefix = prefix.slice(1);
|
|
51
|
+
if (!routeFolders.has(routeFolder)) {
|
|
52
|
+
routeFolders.add(routeFolder);
|
|
53
|
+
try {
|
|
54
|
+
resolvedServer.register(buildRouteFolder(routeFolder, prefix));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error.message === 'Root plugin has already booted')
|
|
57
|
+
harperLogger.warn(
|
|
58
|
+
`Could not load root fastify route for ${entry.absolutePath}, this may require a restart to install properly`
|
|
59
|
+
);
|
|
60
|
+
else throw error;
|
|
60
61
|
}
|
|
61
|
-
}
|
|
62
|
-
ready
|
|
63
|
-
};
|
|
62
|
+
}
|
|
63
|
+
await ready();
|
|
64
|
+
});
|
|
64
65
|
}
|
|
65
66
|
/**
|
|
66
67
|
* Function called to start up server instance on a forked process - this method is called from customFunctionServer after process is
|
|
@@ -118,7 +119,7 @@ async function setUp() {
|
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
//
|
|
122
|
+
//
|
|
122
123
|
function buildRouteFolder(routesFolder, projectName) {
|
|
123
124
|
return async function (cfServer) {
|
|
124
125
|
try {
|
|
@@ -570,8 +570,9 @@ async function graphqlQueryingHandler(request: Request) {
|
|
|
570
570
|
}
|
|
571
571
|
}
|
|
572
572
|
|
|
573
|
-
export function
|
|
574
|
-
options.
|
|
573
|
+
export function handleApplication(scope: import('../components/Scope.ts').Scope) {
|
|
574
|
+
const { port, securePort } = scope.options.getAll() as { port?: number; securePort?: number };
|
|
575
|
+
scope.server.http(
|
|
575
576
|
async (request, nextLayer) => {
|
|
576
577
|
if (!request.url.startsWith('/graphql')) {
|
|
577
578
|
return nextLayer(request);
|
|
@@ -695,6 +696,6 @@ export function start(options) {
|
|
|
695
696
|
throw error;
|
|
696
697
|
}
|
|
697
698
|
},
|
|
698
|
-
{ port
|
|
699
|
+
{ port, securePort }
|
|
699
700
|
);
|
|
700
701
|
}
|
package/core/server/http.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* This module represents the HTTP component for Harper, and receives the HTTP options and uses them to configure
|
|
3
3
|
* HTTP servers
|
|
4
4
|
*/
|
|
5
|
+
import { currentThreadId } from '@harperfast/rocksdb-js';
|
|
5
6
|
import { Scope } from '../components/Scope.ts';
|
|
6
7
|
import { Socket } from 'node:net';
|
|
7
8
|
import harperLogger from '../utility/logging/harper_logger.js';
|
|
@@ -9,7 +10,7 @@ import { parentPort } from 'node:worker_threads';
|
|
|
9
10
|
import env from '../utility/environment/environmentManager.js';
|
|
10
11
|
import * as terms from '../utility/hdbTerms.ts';
|
|
11
12
|
import { getConfigPath } from '../config/configUtils.js';
|
|
12
|
-
import { getTicketKeys } from './threads/manageThreads.js';
|
|
13
|
+
import { getTicketKeys, getWorkerIndex } from './threads/manageThreads.js';
|
|
13
14
|
import { createTLSSelector } from '../security/keys.js';
|
|
14
15
|
import { createSecureServer } from 'node:http2';
|
|
15
16
|
import { createServer as createSecureServerHttp1 } from 'node:https';
|
|
@@ -19,6 +20,8 @@ import { appendHeader, Headers } from './serverHelpers/Headers.ts';
|
|
|
19
20
|
import { Blob } from '../resources/blob.ts';
|
|
20
21
|
import { recordAction, recordActionBinary } from '../resources/analytics/write.ts';
|
|
21
22
|
import { Readable } from 'node:stream';
|
|
23
|
+
import { mkdirSync, writeFileSync, unlinkSync, readdirSync } from 'node:fs';
|
|
24
|
+
import { join } from 'node:path';
|
|
22
25
|
import { server, type ServerOptions, type HttpOptions, type UpgradeOptions, UpgradeListener } from './Server.ts';
|
|
23
26
|
import { setPortServerMap, SERVERS } from './serverRegistry.ts';
|
|
24
27
|
import { getComponentName } from '../components/componentLoader.ts';
|
|
@@ -36,6 +39,77 @@ const httpServers = {},
|
|
|
36
39
|
httpResponders = [];
|
|
37
40
|
let httpOptions: HttpOptions = {};
|
|
38
41
|
export const universalHeaders: [string, string][] = [];
|
|
42
|
+
const udsCleanupPaths: { socketPath: string; yamlPath: string }[] = [];
|
|
43
|
+
|
|
44
|
+
export function registerUdsCleanupPaths(socketPath: string, yamlPath: string) {
|
|
45
|
+
udsCleanupPaths.push({ socketPath, yamlPath });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function cleanupUdsFiles() {
|
|
49
|
+
for (const { socketPath, yamlPath } of udsCleanupPaths) {
|
|
50
|
+
try {
|
|
51
|
+
unlinkSync(socketPath);
|
|
52
|
+
} catch {}
|
|
53
|
+
try {
|
|
54
|
+
unlinkSync(yamlPath);
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Write YAML metadata for a UDS mirror socket, describing the TLS certs from the corresponding secure server. */
|
|
60
|
+
export function writeUdsMetadata(yamlPath: string, port: number | string, secureServer: any) {
|
|
61
|
+
const contexts = secureServer.secureContexts;
|
|
62
|
+
let yaml = `pid: ${process.pid}\ntid: ${currentThreadId()}\nport: ${port}\n`;
|
|
63
|
+
yaml += `certificates:\n`;
|
|
64
|
+
if (contexts?.size > 0) {
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
for (const [, ctx] of contexts) {
|
|
67
|
+
if (seen.has(ctx.name)) continue;
|
|
68
|
+
seen.add(ctx.name);
|
|
69
|
+
yaml += ` - name: ${JSON.stringify(ctx.name)}\n`;
|
|
70
|
+
yaml += ` hostnames:\n`;
|
|
71
|
+
for (const [h, c] of contexts) {
|
|
72
|
+
if (c.name === ctx.name) yaml += ` - ${JSON.stringify(h)}\n`;
|
|
73
|
+
}
|
|
74
|
+
if (ctx.options.key_file) {
|
|
75
|
+
yaml += ` privateKeyFile: ${JSON.stringify(join(env.get(terms.CONFIG_PARAMS.ROOTPATH), 'keys', ctx.options.key_file))}\n`;
|
|
76
|
+
}
|
|
77
|
+
if (ctx.options.cert) {
|
|
78
|
+
yaml += ` certificate: |\n`;
|
|
79
|
+
for (const line of ctx.options.cert.trimEnd().split('\n')) {
|
|
80
|
+
yaml += ` ${line}\n`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (ctx.certificateAuthorities?.length > 0) {
|
|
84
|
+
yaml += ` certificateAuthorities:\n`;
|
|
85
|
+
for (const [, ca] of ctx.certificateAuthorities) {
|
|
86
|
+
yaml += ` - |\n`;
|
|
87
|
+
for (const line of ca.trimEnd().split('\n')) {
|
|
88
|
+
yaml += ` ${line}\n`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
writeFileSync(yamlPath, yaml);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
harperLogger.error('Error writing UDS metadata to ' + yamlPath, error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Clean all files in the sockets directory. Call from main thread on process startup. */
|
|
102
|
+
export function cleanupSocketsDirectory() {
|
|
103
|
+
if (!env.get(terms.CONFIG_PARAMS.TLS_UNIXDOMAINSOCKETS)) return;
|
|
104
|
+
const socketsDir = join(env.getHdbBasePath(), 'sockets');
|
|
105
|
+
try {
|
|
106
|
+
for (const file of readdirSync(socketsDir)) {
|
|
107
|
+
try {
|
|
108
|
+
unlinkSync(join(socketsDir, file));
|
|
109
|
+
} catch {}
|
|
110
|
+
}
|
|
111
|
+
} catch {}
|
|
112
|
+
}
|
|
39
113
|
|
|
40
114
|
export function handleApplication(scope: Scope) {
|
|
41
115
|
httpOptions = scope.options.getAll() as HttpOptions;
|
|
@@ -443,6 +517,46 @@ function getHTTPServer(port: number, secure: boolean, options: ServerOptions) {
|
|
|
443
517
|
server.isSecure = true;
|
|
444
518
|
}
|
|
445
519
|
registerServer(server, port);
|
|
520
|
+
|
|
521
|
+
// Operations API domain socket connections bypass auth (equivalent to local access)
|
|
522
|
+
if (isOperationsServer && String(port).includes('/')) server.bypassLocalAuth = true;
|
|
523
|
+
|
|
524
|
+
// Create a corresponding Unix Domain Socket mirror for secure ports
|
|
525
|
+
if (secure && env.get(terms.CONFIG_PARAMS.TLS_UNIXDOMAINSOCKETS)) {
|
|
526
|
+
const socketsDir = join(env.getHdbBasePath(), 'sockets');
|
|
527
|
+
mkdirSync(socketsDir, { recursive: true });
|
|
528
|
+
const socketName = `${getWorkerIndex()}-${port}`;
|
|
529
|
+
const udsPath = join(socketsDir, `${socketName}.sock`);
|
|
530
|
+
const yamlPath = join(socketsDir, `${socketName}.yaml`);
|
|
531
|
+
|
|
532
|
+
// Create a plain HTTP server (no TLS) with the same request handler
|
|
533
|
+
const udsServer = createServer(
|
|
534
|
+
{
|
|
535
|
+
keepAliveTimeout,
|
|
536
|
+
headersTimeout,
|
|
537
|
+
requestTimeout,
|
|
538
|
+
highWaterMark: 128 * 1024,
|
|
539
|
+
noDelay: true,
|
|
540
|
+
keepAlive: true,
|
|
541
|
+
keepAliveInitialDelay: 600,
|
|
542
|
+
maxHeaderSize: env.get(terms.CONFIG_PARAMS.HTTP_MAXHEADERSIZE),
|
|
543
|
+
},
|
|
544
|
+
(nodeRequest: IncomingMessage, nodeResponse: any) => {
|
|
545
|
+
const method = nodeRequest.method;
|
|
546
|
+
if (method === 'GET' || method === 'OPTIONS' || method === 'HEAD') requestHandler(nodeRequest, nodeResponse);
|
|
547
|
+
else throttledRequestHandler(nodeRequest, nodeResponse);
|
|
548
|
+
}
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
udsServer.isPerThreadSocket = true;
|
|
552
|
+
enableProxyProtocol(udsServer);
|
|
553
|
+
SERVERS[udsPath] = udsServer;
|
|
554
|
+
registerUdsCleanupPaths(udsPath, yamlPath);
|
|
555
|
+
|
|
556
|
+
const writeMetadata = () => writeUdsMetadata(yamlPath, port, server);
|
|
557
|
+
options.SNICallback.ready.then(writeMetadata);
|
|
558
|
+
server.secureContextsListeners.push(writeMetadata);
|
|
559
|
+
}
|
|
446
560
|
}
|
|
447
561
|
return httpServers[port];
|
|
448
562
|
}
|
|
@@ -584,6 +698,66 @@ function onWebSocket(listener: (ws: WebSocket) => void, options: OnWebSocketOpti
|
|
|
584
698
|
return servers;
|
|
585
699
|
}
|
|
586
700
|
|
|
701
|
+
// PROXY protocol v1 max header length per spec: 108 bytes
|
|
702
|
+
const PROXY_V1_MAX_HEADER = 108;
|
|
703
|
+
|
|
704
|
+
function enableProxyProtocol(httpServer) {
|
|
705
|
+
// In Node.js v24+, the HTTP parser's data path goes through the C++ stream layer
|
|
706
|
+
// and does not call socket.emit('data') via JavaScript method dispatch.
|
|
707
|
+
// Overriding socket.emit or socket.push has no effect on the HTTP parser's data intake.
|
|
708
|
+
//
|
|
709
|
+
// Instead: use process.nextTick inside the 'connection' handler to wrap the HTTP
|
|
710
|
+
// parser's 'data' listener after it has been registered (synchronously, by the HTTP
|
|
711
|
+
// parser's own 'connection' handler which runs right after ours).
|
|
712
|
+
// process.nextTick fires before any I/O callbacks, so it is guaranteed to run before
|
|
713
|
+
// the first network data chunk reaches the socket — making the interception race-free.
|
|
714
|
+
httpServer.prependListener('connection', (socket) => {
|
|
715
|
+
process.nextTick(() => {
|
|
716
|
+
// Capture the HTTP parser's 'data' listener(s) registered during this connection event.
|
|
717
|
+
const dataListeners = socket.listeners('data') as ((chunk: Buffer) => void)[];
|
|
718
|
+
if (dataListeners.length === 0) return;
|
|
719
|
+
socket.removeAllListeners('data');
|
|
720
|
+
|
|
721
|
+
let proxyDone = false;
|
|
722
|
+
socket.on('data', (chunk: Buffer) => {
|
|
723
|
+
if (!proxyDone) {
|
|
724
|
+
proxyDone = true;
|
|
725
|
+
// Fast path: PROXY v1 always starts with "PROXY " (0x50 0x52 0x4f 0x58 0x59 0x20)
|
|
726
|
+
if (
|
|
727
|
+
chunk.length >= 6 &&
|
|
728
|
+
chunk[0] === 0x50 &&
|
|
729
|
+
chunk[1] === 0x52 &&
|
|
730
|
+
chunk[2] === 0x4f &&
|
|
731
|
+
chunk[3] === 0x58 &&
|
|
732
|
+
chunk[4] === 0x59 &&
|
|
733
|
+
chunk[5] === 0x20
|
|
734
|
+
) {
|
|
735
|
+
const header = chunk.toString('latin1', 0, Math.min(PROXY_V1_MAX_HEADER, chunk.length));
|
|
736
|
+
const eol = header.indexOf('\r\n');
|
|
737
|
+
if (eol !== -1) {
|
|
738
|
+
// "PROXY TCP4 <src-ip> <dst-ip> <src-port> <dst-port>"
|
|
739
|
+
const parts = header.slice(0, eol).split(' ');
|
|
740
|
+
if (parts.length === 6) {
|
|
741
|
+
// Override the UDS socket's undefined remoteAddress/remotePort with the real client values.
|
|
742
|
+
Object.defineProperty(socket, 'remoteAddress', { value: parts[2], configurable: true });
|
|
743
|
+
Object.defineProperty(socket, 'remotePort', { value: parseInt(parts[4], 10), configurable: true });
|
|
744
|
+
}
|
|
745
|
+
// Forward only the bytes after the PROXY header to the HTTP parser.
|
|
746
|
+
const rest = chunk.subarray(eol + 2);
|
|
747
|
+
if (rest.length > 0) {
|
|
748
|
+
for (const listener of dataListeners) listener.call(socket, rest);
|
|
749
|
+
}
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
// Not a PROXY header (or already handled) — forward unchanged.
|
|
755
|
+
for (const listener of dataListeners) listener.call(socket, chunk);
|
|
756
|
+
});
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
|
|
587
761
|
function defaultNotFound(request, response) {
|
|
588
762
|
if (response.headersSent || response.writableEnded) return;
|
|
589
763
|
response.writeHead(404);
|
package/core/server/mqtt.ts
CHANGED
|
@@ -23,7 +23,14 @@ export function bypassAuth() {
|
|
|
23
23
|
const authorizeLocal = (remoteAddress: string) =>
|
|
24
24
|
AUTHORIZE_LOCAL && (remoteAddress.includes('127.0.0.') || remoteAddress === '::1');
|
|
25
25
|
|
|
26
|
-
export function
|
|
26
|
+
export function handleApplication(scope: import('../components/Scope.ts').Scope) {
|
|
27
|
+
const { network, webSocket, requireAuthentication } = scope.options.getAll() as {
|
|
28
|
+
network?: any;
|
|
29
|
+
webSocket?: any;
|
|
30
|
+
requireAuthentication?: boolean;
|
|
31
|
+
};
|
|
32
|
+
const server = scope.server;
|
|
33
|
+
const { port, securePort } = network ?? {};
|
|
27
34
|
// here we basically normalize the different types of sockets to pass to our socket/message handler
|
|
28
35
|
if (!server.mqtt) {
|
|
29
36
|
server.mqtt = {
|
|
@@ -160,7 +167,6 @@ export function start({ server, port, network, webSocket, securePort, requireAut
|
|
|
160
167
|
)
|
|
161
168
|
);
|
|
162
169
|
}
|
|
163
|
-
return serverInstances;
|
|
164
170
|
}
|
|
165
171
|
let addingMetrics,
|
|
166
172
|
numberOfConnections = 0;
|
|
@@ -19,7 +19,7 @@ import restart from '../../bin/restart.js';
|
|
|
19
19
|
import * as util from 'util';
|
|
20
20
|
import insert from '../../dataLayer/insert.js';
|
|
21
21
|
import globalSchema from '../../utility/globalSchema.js';
|
|
22
|
-
import systemInformation from '../../utility/environment/systemInformation.
|
|
22
|
+
import { systemInformation } from '../../utility/environment/systemInformation.ts';
|
|
23
23
|
import jobRunner from '../jobs/jobRunner.js';
|
|
24
24
|
import * as tokenAuthentication from '../../security/tokenAuthentication.ts';
|
|
25
25
|
import * as auth from '../../security/auth.ts';
|
|
@@ -363,10 +363,7 @@ function initializeOperationFunctionMap(): Map<OperationFunctionName, OperationF
|
|
|
363
363
|
opFuncMap.set(terms.OPERATIONS_ENUM.RESTART, new OperationFunctionObject(restart.restart));
|
|
364
364
|
opFuncMap.set(terms.OPERATIONS_ENUM.RESTART_SERVICE, new OperationFunctionObject(executeJob, restart.restartService));
|
|
365
365
|
opFuncMap.set(terms.OPERATIONS_ENUM.CATCHUP, new OperationFunctionObject(catchup));
|
|
366
|
-
opFuncMap.set(
|
|
367
|
-
terms.OPERATIONS_ENUM.SYSTEM_INFORMATION,
|
|
368
|
-
new OperationFunctionObject(systemInformation.systemInformation)
|
|
369
|
-
);
|
|
366
|
+
opFuncMap.set(terms.OPERATIONS_ENUM.SYSTEM_INFORMATION, new OperationFunctionObject(systemInformation));
|
|
370
367
|
opFuncMap.set(
|
|
371
368
|
terms.OPERATIONS_ENUM.DELETE_AUDIT_LOGS_BEFORE,
|
|
372
369
|
new OperationFunctionObject(executeJob, delete_.deleteAuditLogsBefore)
|