@harperfast/harper-pro 5.0.6 → 5.0.7
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 +6 -5
- package/core/components/EntryHandler.ts +2 -4
- package/core/components/Scope.ts +1 -1
- package/core/components/componentLoader.ts +4 -11
- package/core/components/requestRestart.ts +2 -17
- package/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/TableSizeObject.js +25 -0
- package/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.js +34 -0
- package/core/package-lock.json +63 -971
- package/core/resources/DatabaseTransaction.ts +3 -8
- package/core/resources/Table.ts +17 -12
- package/core/resources/databases.ts +2 -2
- package/core/resources/graphql.ts +165 -163
- package/core/resources/indexes/HierarchicalNavigableSmallWorld.ts +3 -14
- package/core/resources/indexes/vector.ts +0 -17
- package/core/resources/loadEnv.ts +16 -20
- package/core/resources/login.ts +3 -4
- package/core/resources/roles.ts +65 -60
- package/core/security/auth.ts +14 -15
- package/core/security/jsLoader.ts +2 -14
- package/core/server/REST.ts +11 -10
- package/core/server/fastifyRoutes.ts +29 -30
- package/core/server/graphqlQuerying.ts +3 -4
- package/core/server/http.ts +1 -175
- package/core/server/mqtt.ts +2 -8
- package/core/server/serverHelpers/serverUtilities.ts +5 -2
- package/core/server/threads/threadServer.js +2 -30
- package/core/server/throttle.ts +0 -18
- package/core/utility/environment/environmentManager.js +4 -10
- package/core/utility/environment/systemInformation.js +355 -0
- package/core/utility/hdbTerms.ts +0 -1
- package/core/utility/operation_authorization.js +5 -2
- 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 +2 -4
- 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 +3 -11
- package/dist/core/components/componentLoader.js.map +1 -1
- package/dist/core/components/requestRestart.js +1 -12
- package/dist/core/components/requestRestart.js.map +1 -1
- package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/TableSizeObject.js +24 -0
- package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/TableSizeObject.js.map +1 -0
- package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.js +19 -18
- package/dist/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.js.map +1 -1
- package/dist/core/resources/DatabaseTransaction.js +1 -6
- package/dist/core/resources/DatabaseTransaction.js.map +1 -1
- package/dist/core/resources/Table.js +18 -14
- package/dist/core/resources/Table.js.map +1 -1
- package/dist/core/resources/databases.js +1 -2
- 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 +2 -14
- package/dist/core/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/core/resources/indexes/vector.js +0 -14
- package/dist/core/resources/indexes/vector.js.map +1 -1
- package/dist/core/resources/loadEnv.js +17 -20
- 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 +68 -64
- package/dist/core/resources/roles.js.map +1 -1
- package/dist/core/security/auth.js +15 -17
- package/dist/core/security/auth.js.map +1 -1
- package/dist/core/security/jsLoader.js +2 -16
- 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 +29 -30
- package/dist/core/server/fastifyRoutes.js.map +1 -1
- package/dist/core/server/graphqlQuerying.js +4 -5
- package/dist/core/server/graphqlQuerying.js.map +1 -1
- package/dist/core/server/http.js +0 -179
- package/dist/core/server/http.js.map +1 -1
- package/dist/core/server/mqtt.js +3 -5
- 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 +2 -26
- package/dist/core/server/threads/threadServer.js.map +1 -1
- package/dist/core/server/throttle.js +0 -17
- package/dist/core/server/throttle.js.map +1 -1
- package/dist/core/utility/environment/environmentManager.js +4 -9
- package/dist/core/utility/environment/environmentManager.js.map +1 -1
- package/dist/core/utility/environment/systemInformation.js +219 -359
- package/dist/core/utility/environment/systemInformation.js.map +1 -1
- package/dist/core/utility/hdbTerms.js +0 -1
- 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/npm-shrinkwrap.json +54 -974
- package/package.json +1 -2
- package/studio/web/assets/{index-qbLPhOzw.js → index-C0And10y.js} +2 -2
- package/studio/web/assets/{index-qbLPhOzw.js.map → index-C0And10y.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/core/dataLayer/harperBridge/TableSizeObject.ts +0 -35
- package/core/dataLayer/harperBridge/lmdbBridge/lmdbUtility/lmdbGetTableSize.ts +0 -24
- package/core/utility/environment/systemInformation.ts +0 -698
- package/dist/core/dataLayer/harperBridge/TableSizeObject.js +0 -32
- package/dist/core/dataLayer/harperBridge/TableSizeObject.js.map +0 -1
package/core/resources/roles.ts
CHANGED
|
@@ -9,72 +9,73 @@ 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
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
12
|
+
// eslint-disable-next-line no-unused-vars
|
|
13
|
+
export function start({ ensureTable }) {
|
|
14
|
+
return {
|
|
15
|
+
handleFile,
|
|
16
|
+
setupFile: handleFile,
|
|
17
|
+
};
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
async function handleFile(rolesContent) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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 };
|
|
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;
|
|
37
|
+
}
|
|
45
38
|
}
|
|
46
|
-
for (let
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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);
|
|
60
|
-
}
|
|
61
|
-
table.attribute_permissions = attributes;
|
|
62
|
-
delete table.attributes;
|
|
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 };
|
|
63
45
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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);
|
|
60
|
+
}
|
|
61
|
+
table.attribute_permissions = attributes;
|
|
62
|
+
delete table.attributes;
|
|
72
63
|
}
|
|
73
|
-
|
|
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;
|
|
74
|
+
}
|
|
74
75
|
}
|
|
76
|
+
role.role = role.id = roleName;
|
|
77
|
+
await ensureRole(role);
|
|
75
78
|
}
|
|
76
|
-
role.role = role.id = roleName;
|
|
77
|
-
await ensureRole(role);
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
async function ensureRole(role) {
|
|
@@ -91,3 +92,7 @@ async function ensureRole(role) {
|
|
|
91
92
|
}
|
|
92
93
|
return addRole(role);
|
|
93
94
|
}
|
|
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,9 +243,7 @@ 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 &&
|
|
247
|
-
request?._nodeRequest?.socket?.server?.bypassLocalAuth &&
|
|
248
|
-
request.ip === undefined) // allow operations API domain socket
|
|
246
|
+
(request?._nodeRequest?.socket?.server?._pipeName && request.ip === undefined) // allow socket domain
|
|
249
247
|
) {
|
|
250
248
|
request.user = await getSuperUser();
|
|
251
249
|
}
|
|
@@ -346,18 +344,19 @@ export async function authentication(request, nextHandler) {
|
|
|
346
344
|
return response;
|
|
347
345
|
}
|
|
348
346
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
347
|
+
let started;
|
|
348
|
+
export function start({ server, port, securePort }) {
|
|
349
|
+
server.http(authentication, port || securePort ? { port, securePort } : { port: 'all' });
|
|
350
|
+
// keep it cleaned out periodically
|
|
351
|
+
if (!started) {
|
|
352
|
+
started = true;
|
|
353
|
+
setInterval(() => {
|
|
354
|
+
authorizationCache = new Map();
|
|
355
|
+
}, env.get(CONFIG_PARAMS.AUTHENTICATION_CACHETTL)).unref();
|
|
356
|
+
user.addListener(() => {
|
|
357
|
+
authorizationCache = new Map();
|
|
358
|
+
});
|
|
359
|
+
}
|
|
361
360
|
}
|
|
362
361
|
// operations
|
|
363
362
|
export async function login(loginObject) {
|
|
@@ -181,13 +181,7 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
|
|
|
181
181
|
if (parts[0] === 'file:') {
|
|
182
182
|
return specifier;
|
|
183
183
|
}
|
|
184
|
-
|
|
185
|
-
if (referrer.startsWith('file:')) {
|
|
186
|
-
try {
|
|
187
|
-
resolveReferrer = pathToFileURL(realpathSync(fileURLToPath(referrer))).toString();
|
|
188
|
-
} catch {}
|
|
189
|
-
}
|
|
190
|
-
const resolved = createRequire(resolveReferrer).resolve(specifier);
|
|
184
|
+
const resolved = createRequire(referrer).resolve(specifier);
|
|
191
185
|
if (isAbsolute(resolved)) {
|
|
192
186
|
return pathToFileURL(resolved).toString();
|
|
193
187
|
}
|
|
@@ -212,13 +206,7 @@ async function loadModuleWithVM(moduleUrl: string, scope: ApplicationScope, useC
|
|
|
212
206
|
cjsModule.exports = parseJsonModule(source, url);
|
|
213
207
|
return cjsModule;
|
|
214
208
|
}
|
|
215
|
-
|
|
216
|
-
if (url.startsWith('file://')) {
|
|
217
|
-
try {
|
|
218
|
-
requireUrl = pathToFileURL(realpathSync(fileURLToPath(url))).toString();
|
|
219
|
-
} catch {}
|
|
220
|
-
}
|
|
221
|
-
const require = createRequire(requireUrl);
|
|
209
|
+
const require = createRequire(url);
|
|
222
210
|
|
|
223
211
|
const cjsRequire = (spec: string) => {
|
|
224
212
|
const resolvedUrl = resolveModule(spec, url);
|
package/core/server/REST.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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';
|
|
4
5
|
import { ServerError, ClientError } from '../utility/errors/hdbError.js';
|
|
5
6
|
import { Resources } from '../resources/Resources.ts';
|
|
6
7
|
import { Resource, missingMethod, allowedMethods } from '../resources/Resource.ts';
|
|
@@ -276,26 +277,26 @@ async function http(request: Context & Request, nextHandler) {
|
|
|
276
277
|
}
|
|
277
278
|
}
|
|
278
279
|
|
|
279
|
-
let started
|
|
280
|
+
let started;
|
|
280
281
|
let resources: Resources;
|
|
281
282
|
let addedMetrics;
|
|
282
283
|
let connectionCount = 0;
|
|
283
284
|
|
|
284
|
-
export function
|
|
285
|
-
httpOptions =
|
|
286
|
-
if (
|
|
285
|
+
export function start(options: ServerOptions & { path: string; port: number; server: any; resources: Resources }) {
|
|
286
|
+
httpOptions = options;
|
|
287
|
+
if (options.includeExpensiveRecordCountEstimates) {
|
|
287
288
|
// If they really want to enable expensive record count estimates
|
|
288
289
|
Request.prototype.includeExpensiveRecordCountEstimates = true;
|
|
289
290
|
}
|
|
290
|
-
resources = scope.resources;
|
|
291
291
|
if (started) return;
|
|
292
292
|
started = true;
|
|
293
|
-
|
|
293
|
+
resources = options.resources;
|
|
294
|
+
options.server.http(async (request: Request, nextHandler) => {
|
|
294
295
|
if (request.isWebSocket) return;
|
|
295
296
|
return http(request, nextHandler);
|
|
296
|
-
},
|
|
297
|
-
if (
|
|
298
|
-
|
|
297
|
+
}, options);
|
|
298
|
+
if (options.webSocket === false) return;
|
|
299
|
+
options.server.ws(async (ws, request, chainCompletion) => {
|
|
299
300
|
connectionCount++;
|
|
300
301
|
const incomingMessages = new IterableEventQueue();
|
|
301
302
|
if (!addedMetrics) {
|
|
@@ -381,7 +382,7 @@ export function handleApplication(scope: import('../components/Scope.ts').Scope)
|
|
|
381
382
|
);
|
|
382
383
|
}
|
|
383
384
|
ws.close();
|
|
384
|
-
},
|
|
385
|
+
}, options);
|
|
385
386
|
}
|
|
386
387
|
const HTTP_TO_WEBSOCKET_CLOSE_CODES = {
|
|
387
388
|
401: 3000,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dirname
|
|
1
|
+
import { dirname } from 'path';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
3
|
import fastify from 'fastify';
|
|
4
4
|
import fastifyCors from '@fastify/cors';
|
|
@@ -32,36 +32,35 @@ const routeFolders = new Set();
|
|
|
32
32
|
* @param filePath
|
|
33
33
|
* @param projectName
|
|
34
34
|
*/
|
|
35
|
-
export function
|
|
35
|
+
export function start(options) {
|
|
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 =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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;
|
|
37
|
+
const isHttps = options.securePort > 0;
|
|
38
|
+
return {
|
|
39
|
+
// eslint-disable-next-line no-unused-vars
|
|
40
|
+
async handleFile(jsContent, relativePath, filePath, projectName) {
|
|
41
|
+
if (!fastifyServer) {
|
|
42
|
+
fastifyServer = buildServer(isHttps);
|
|
43
|
+
server.http((await fastifyServer).server);
|
|
61
44
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
45
|
+
const resolvedServer = await fastifyServer;
|
|
46
|
+
const routeFolder = dirname(filePath);
|
|
47
|
+
let prefix = dirname(relativePath);
|
|
48
|
+
if (prefix.startsWith('/')) prefix = prefix.slice(1);
|
|
49
|
+
if (!routeFolders.has(routeFolder)) {
|
|
50
|
+
routeFolders.add(routeFolder);
|
|
51
|
+
try {
|
|
52
|
+
resolvedServer.register(buildRouteFolder(routeFolder, prefix));
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error.message === 'Root plugin has already booted')
|
|
55
|
+
harperLogger.warn(
|
|
56
|
+
`Could not load root fastify route for ${filePath}, this may require a restart to install properly`
|
|
57
|
+
);
|
|
58
|
+
else throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
ready,
|
|
63
|
+
};
|
|
65
64
|
}
|
|
66
65
|
/**
|
|
67
66
|
* Function called to start up server instance on a forked process - this method is called from customFunctionServer after process is
|
|
@@ -119,7 +118,7 @@ async function setUp() {
|
|
|
119
118
|
}
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
//
|
|
121
|
+
// eslint-disable-next-line require-await
|
|
123
122
|
function buildRouteFolder(routesFolder, projectName) {
|
|
124
123
|
return async function (cfServer) {
|
|
125
124
|
try {
|
|
@@ -570,9 +570,8 @@ async function graphqlQueryingHandler(request: Request) {
|
|
|
570
570
|
}
|
|
571
571
|
}
|
|
572
572
|
|
|
573
|
-
export function
|
|
574
|
-
|
|
575
|
-
scope.server.http(
|
|
573
|
+
export function start(options) {
|
|
574
|
+
options.server.http(
|
|
576
575
|
async (request, nextLayer) => {
|
|
577
576
|
if (!request.url.startsWith('/graphql')) {
|
|
578
577
|
return nextLayer(request);
|
|
@@ -696,6 +695,6 @@ export function handleApplication(scope: import('../components/Scope.ts').Scope)
|
|
|
696
695
|
throw error;
|
|
697
696
|
}
|
|
698
697
|
},
|
|
699
|
-
{ port, securePort }
|
|
698
|
+
{ port: options.port, securePort: options.securePort }
|
|
700
699
|
);
|
|
701
700
|
}
|
package/core/server/http.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
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';
|
|
6
5
|
import { Scope } from '../components/Scope.ts';
|
|
7
6
|
import { Socket } from 'node:net';
|
|
8
7
|
import harperLogger from '../utility/logging/harper_logger.js';
|
|
@@ -10,7 +9,7 @@ import { parentPort } from 'node:worker_threads';
|
|
|
10
9
|
import env from '../utility/environment/environmentManager.js';
|
|
11
10
|
import * as terms from '../utility/hdbTerms.ts';
|
|
12
11
|
import { getConfigPath } from '../config/configUtils.js';
|
|
13
|
-
import { getTicketKeys
|
|
12
|
+
import { getTicketKeys } from './threads/manageThreads.js';
|
|
14
13
|
import { createTLSSelector } from '../security/keys.js';
|
|
15
14
|
import { createSecureServer } from 'node:http2';
|
|
16
15
|
import { createServer as createSecureServerHttp1 } from 'node:https';
|
|
@@ -20,8 +19,6 @@ import { appendHeader, Headers } from './serverHelpers/Headers.ts';
|
|
|
20
19
|
import { Blob } from '../resources/blob.ts';
|
|
21
20
|
import { recordAction, recordActionBinary } from '../resources/analytics/write.ts';
|
|
22
21
|
import { Readable } from 'node:stream';
|
|
23
|
-
import { mkdirSync, writeFileSync, unlinkSync, readdirSync } from 'node:fs';
|
|
24
|
-
import { join } from 'node:path';
|
|
25
22
|
import { server, type ServerOptions, type HttpOptions, type UpgradeOptions, UpgradeListener } from './Server.ts';
|
|
26
23
|
import { setPortServerMap, SERVERS } from './serverRegistry.ts';
|
|
27
24
|
import { getComponentName } from '../components/componentLoader.ts';
|
|
@@ -39,77 +36,6 @@ const httpServers = {},
|
|
|
39
36
|
httpResponders = [];
|
|
40
37
|
let httpOptions: HttpOptions = {};
|
|
41
38
|
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
|
-
}
|
|
113
39
|
|
|
114
40
|
export function handleApplication(scope: Scope) {
|
|
115
41
|
httpOptions = scope.options.getAll() as HttpOptions;
|
|
@@ -517,46 +443,6 @@ function getHTTPServer(port: number, secure: boolean, options: ServerOptions) {
|
|
|
517
443
|
server.isSecure = true;
|
|
518
444
|
}
|
|
519
445
|
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
|
-
}
|
|
560
446
|
}
|
|
561
447
|
return httpServers[port];
|
|
562
448
|
}
|
|
@@ -698,66 +584,6 @@ function onWebSocket(listener: (ws: WebSocket) => void, options: OnWebSocketOpti
|
|
|
698
584
|
return servers;
|
|
699
585
|
}
|
|
700
586
|
|
|
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
|
-
|
|
761
587
|
function defaultNotFound(request, response) {
|
|
762
588
|
if (response.headersSent || response.writableEnded) return;
|
|
763
589
|
response.writeHead(404);
|
package/core/server/mqtt.ts
CHANGED
|
@@ -23,14 +23,7 @@ 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
|
|
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 ?? {};
|
|
26
|
+
export function start({ server, port, network, webSocket, securePort, requireAuthentication }) {
|
|
34
27
|
// here we basically normalize the different types of sockets to pass to our socket/message handler
|
|
35
28
|
if (!server.mqtt) {
|
|
36
29
|
server.mqtt = {
|
|
@@ -167,6 +160,7 @@ export function handleApplication(scope: import('../components/Scope.ts').Scope)
|
|
|
167
160
|
)
|
|
168
161
|
);
|
|
169
162
|
}
|
|
163
|
+
return serverInstances;
|
|
170
164
|
}
|
|
171
165
|
let addingMetrics,
|
|
172
166
|
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
|
|
22
|
+
import systemInformation from '../../utility/environment/systemInformation.js';
|
|
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,7 +363,10 @@ 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(
|
|
366
|
+
opFuncMap.set(
|
|
367
|
+
terms.OPERATIONS_ENUM.SYSTEM_INFORMATION,
|
|
368
|
+
new OperationFunctionObject(systemInformation.systemInformation)
|
|
369
|
+
);
|
|
367
370
|
opFuncMap.set(
|
|
368
371
|
terms.OPERATIONS_ENUM.DELETE_AUDIT_LOGS_BEFORE,
|
|
369
372
|
new OperationFunctionObject(executeJob, delete_.deleteAuditLogsBefore)
|