borgmcp-server 0.1.1 → 0.1.5
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/README.md +70 -22
- package/THIRD_PARTY_NOTICES.md +1 -0
- package/dist/cli.js +62 -11
- package/dist/cli.js.map +1 -1
- package/dist/coordination-api.d.ts +3 -1
- package/dist/coordination-api.js +476 -78
- package/dist/coordination-api.js.map +1 -1
- package/dist/credentials.d.ts +13 -7
- package/dist/credentials.js +77 -15
- package/dist/credentials.js.map +1 -1
- package/dist/debug-log.d.ts +76 -0
- package/dist/debug-log.js +124 -0
- package/dist/debug-log.js.map +1 -0
- package/dist/enrollment.d.ts +3 -11
- package/dist/enrollment.js +21 -60
- package/dist/enrollment.js.map +1 -1
- package/dist/https-server.d.ts +5 -20
- package/dist/https-server.js +108 -50
- package/dist/https-server.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/message-taxonomy.d.ts +38 -0
- package/dist/message-taxonomy.js +218 -0
- package/dist/message-taxonomy.js.map +1 -0
- package/dist/migrations.d.ts +4 -0
- package/dist/migrations.js +99 -0
- package/dist/migrations.js.map +1 -1
- package/dist/operator-error.d.ts +2 -1
- package/dist/operator-error.js +23 -5
- package/dist/operator-error.js.map +1 -1
- package/dist/role-section.d.ts +14 -0
- package/dist/role-section.js +87 -0
- package/dist/role-section.js.map +1 -0
- package/dist/service.d.ts +14 -4
- package/dist/service.js +243 -25
- package/dist/service.js.map +1 -1
- package/dist/start-options.d.ts +5 -1
- package/dist/start-options.js +17 -3
- package/dist/start-options.js.map +1 -1
- package/dist/store.d.ts +106 -8
- package/dist/store.js +772 -120
- package/dist/store.js.map +1 -1
- package/npm-shrinkwrap.json +6 -7
- package/package.json +2 -2
- package/src/cli.ts +61 -11
- package/src/coordination-api.ts +490 -72
- package/src/credentials.ts +103 -19
- package/src/debug-log.ts +165 -0
- package/src/enrollment.ts +32 -78
- package/src/https-server.ts +113 -78
- package/src/index.ts +1 -1
- package/src/message-taxonomy.ts +284 -0
- package/src/migrations.ts +102 -0
- package/src/operator-error.ts +40 -6
- package/src/role-section.ts +108 -0
- package/src/service.ts +268 -27
- package/src/start-options.ts +21 -4
- package/src/store.ts +887 -142
- package/dist/protocol-draft.d.ts +0 -2
- package/dist/protocol-draft.js +0 -31
- package/dist/protocol-draft.js.map +0 -1
- package/src/protocol-draft.ts +0 -32
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug-log.js","sourceRoot":"","sources":["../src/debug-log.ts"],"names":[],"mappings":"AAqCA,MAAM,CAAC,MAAM,mBAAmB,GAAgB,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC;AAEzF,MAAM,UAAU,iBAAiB,CAAC,KAA2C;IAC3E,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,mBAAmB,CAAC;IACpD,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI,CAAC,KAAiB;YACpB,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;gBACtC,IAAI,SAAS,KAAK,IAAI;oBAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,SAAS,EAAE,CAAC,CAAC,CAAC;YAClF,CAAC;YAAC,MAAM,CAAC;gBACP,gEAAgE;YAClE,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CAAC,KAAiB;IACrC,MAAM,KAAK,GAAG,KAA2C,CAAC;IAC1D,QAAQ,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACvB,KAAK,SAAS;YACZ,OAAO;gBACL,KAAK,EAAE,SAAS;gBAChB,SAAS,EAAE,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,UAAU,CAAC;gBACxE,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;gBAC9C,cAAc,EAAE,SAAS,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC,YAAY,EAAE,UAAU,CAAC,EAAE,UAAU,CAAC;aAC1F,CAAC;QACJ,KAAK,SAAS,EAAE,CAAC;YACf,MAAM,SAAS,GAAG,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;YACtD,OAAO;gBACL,KAAK,EAAE,SAAS;gBAChB,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,SAAS,CAAC;gBACzD,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC;gBACzD,cAAc,EAAE,SAAS,CAAC,KAAK,CAAC,gBAAgB,CAAC,EAAE,YAAY,EAAE,SAAS,CAAC;gBAC3E,aAAa,EAAE,SAAS,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,aAAa,EAAE,aAAa,CAAC;gBAC9E,GAAG,SAAS;gBACZ,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC;gBAC/C,WAAW,EAAE,cAAc,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,gBAAgB,CAAC;aAC7E,CAAC;QACJ,CAAC;QACD,KAAK,WAAW;YACd,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC;QACzG,KAAK,iBAAiB;YACpB,OAAO;gBACL,KAAK,EAAE,iBAAiB;gBACxB,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC9B,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;gBAChC,GAAG,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;gBACtC,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;gBACxC,UAAU,EAAE,SAAS,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,WAAW,CAAC;gBAChF,eAAe,EAAE,SAAS,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC,MAAM;gBAC7D,mBAAmB,EAAE,SAAS,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;aAC3D,CAAC;QACJ,KAAK,eAAe;YAClB,OAAO;gBACL,KAAK,EAAE,eAAe;gBACtB,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC;gBACvD,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC9B,SAAS,EAAE,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC1C,cAAc,EAAE,cAAc,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC;gBAC9D,SAAS,EAAE,cAAc,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,gBAAgB,CAAC;gBACxE,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,KAAK,IAAI;aACvC,CAAC;QACJ,KAAK,WAAW;YACd,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;QACnM,KAAK,gBAAgB;YACnB,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;QACrJ,KAAK,eAAe;YAClB,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,YAAY,EAAE,cAAc,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,KAAK,IAAI,EAAE,CAAC;QAC5P,KAAK,iBAAiB;YACpB,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,cAAc,EAAE,cAAc,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC9O,KAAK,YAAY;YACf,OAAO;gBACL,KAAK,EAAE,YAAY;gBACnB,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,kBAAkB,EAAE,qBAAqB,CAAC;gBAC7E,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,OAAO,IAAI,KAAK,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvG,GAAG,YAAY,CAAC,WAAW,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC/C,GAAG,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC3C,GAAG,YAAY,CAAC,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;gBAC7C,GAAG,YAAY,CAAC,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;aAClD,CAAC;QACJ,KAAK,qBAAqB;YACxB,OAAO,EAAE,KAAK,EAAE,qBAAqB,EAAE,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,EAAE,kBAAkB,CAAC,EAAE,CAAC;QAC7I;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAC3D,MAAM,SAAS,GAAG,KAAgC,CAAC;IACnD,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,eAAe,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC7F,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,GAAG,YAAY,CAAC,cAAc,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;AACpF,CAAC;AAED,SAAS,IAAI,CAAC,KAAc;IAC1B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5F,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,YAAY,CAAC,GAAW,EAAE,KAAc;IAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IACzB,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AACvF,CAAC;AAED,SAAS,cAAc,CAAC,KAAc,EAAE,OAAe,EAAE,OAAe;IACtE,OAAO,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAK,KAAgB,IAAI,OAAO,IAAK,KAAgB,IAAI,OAAO;QAChG,CAAC,CAAC,KAAe;QACjB,CAAC,CAAC,OAAO,CAAC;AACd,CAAC;AAED,SAAS,SAAS,CAAyB,KAAc,EAAE,OAAqB,EAAE,QAAW;IAC3F,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAU,CAAC,CAAC,CAAC,CAAC,KAAU,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC3F,CAAC;AAED,MAAM,YAAY,GAAG,6EAA6E,CAAC;AACnG,MAAM,YAAY,GAA0B,CAAC,QAAQ,EAAE,UAAU,EAAE,qBAAqB,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;AAC9R,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAU,CAAC;AACjF,MAAM,YAAY,GAAG,CAAC,cAAc,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,CAAU,CAAC;AACvG,MAAM,aAAa,GAAG,CAAC,aAAa,EAAE,UAAU,EAAE,qBAAqB,CAAU,CAAC;AAClF,MAAM,kBAAkB,GAAG,CAAC,oBAAoB,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,CAAU,CAAC"}
|
package/dist/enrollment.d.ts
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
|
+
import { type EnrollmentExchangeRequest, type ProtocolEnvelope } from "borgmcp-shared/protocol";
|
|
1
2
|
import type { CredentialAuthority } from "./credentials.js";
|
|
2
|
-
|
|
3
|
-
readonly protocol_version: "1";
|
|
4
|
-
readonly request_id: string;
|
|
5
|
-
readonly payload: {
|
|
6
|
-
readonly invitation: string;
|
|
7
|
-
readonly retry_key: string;
|
|
8
|
-
readonly client_credential: string;
|
|
9
|
-
readonly client_name?: string;
|
|
10
|
-
};
|
|
11
|
-
}
|
|
3
|
+
type EnrollmentEnvelope = ProtocolEnvelope<EnrollmentExchangeRequest>;
|
|
12
4
|
export declare function createEnrollmentExchange(authority: CredentialAuthority): (body: unknown) => Promise<{
|
|
13
|
-
readonly status: 201 | 400 | 401 | 507;
|
|
5
|
+
readonly status: 201 | 400 | 401 | 426 | 507;
|
|
14
6
|
readonly body?: unknown;
|
|
15
7
|
}>;
|
|
16
8
|
export declare function decodeEnrollmentEnvelope(value: unknown): EnrollmentEnvelope;
|
package/dist/enrollment.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ErrorCode, PROTOCOL_VERSION, ProtocolContractError, createProtocolEnvelope, decodeEnrollmentExchangeRequestEnvelope, } from "borgmcp-shared/protocol";
|
|
1
2
|
import { StorageCapacityError } from "./store.js";
|
|
2
3
|
export function createEnrollmentExchange(authority) {
|
|
3
4
|
return async (body) => {
|
|
@@ -5,7 +6,14 @@ export function createEnrollmentExchange(authority) {
|
|
|
5
6
|
try {
|
|
6
7
|
envelope = decodeEnrollmentEnvelope(body);
|
|
7
8
|
}
|
|
8
|
-
catch {
|
|
9
|
+
catch (error) {
|
|
10
|
+
if (error instanceof ProtocolContractError &&
|
|
11
|
+
error.code === ErrorCode.UNSUPPORTED_PROTOCOL_VERSION) {
|
|
12
|
+
return {
|
|
13
|
+
status: 426,
|
|
14
|
+
body: errorEnvelope(ErrorCode.UNSUPPORTED_PROTOCOL_VERSION, "Unsupported protocol version.", safeRequestId(body)),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
9
17
|
return {
|
|
10
18
|
status: 400,
|
|
11
19
|
body: errorEnvelope("INVALID_INPUT", "Invalid enrollment request.", safeRequestId(body)),
|
|
@@ -38,21 +46,17 @@ export function createEnrollmentExchange(authority) {
|
|
|
38
46
|
}
|
|
39
47
|
return {
|
|
40
48
|
status: 201,
|
|
41
|
-
body: {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
client_id: response.clientId,
|
|
47
|
-
server_capabilities: response.serverCapabilities,
|
|
48
|
-
},
|
|
49
|
-
},
|
|
49
|
+
body: createProtocolEnvelope(envelope.request_id, {
|
|
50
|
+
purpose: response.purpose,
|
|
51
|
+
client_id: response.clientId,
|
|
52
|
+
server_capabilities: response.serverCapabilities,
|
|
53
|
+
}),
|
|
50
54
|
};
|
|
51
55
|
};
|
|
52
56
|
}
|
|
53
57
|
function errorEnvelope(code, message, requestId) {
|
|
54
58
|
return {
|
|
55
|
-
protocol_version:
|
|
59
|
+
protocol_version: PROTOCOL_VERSION,
|
|
56
60
|
...(requestId === undefined ? {} : { request_id: requestId }),
|
|
57
61
|
error: { code, message },
|
|
58
62
|
};
|
|
@@ -66,57 +70,14 @@ function safeRequestId(value) {
|
|
|
66
70
|
: undefined;
|
|
67
71
|
}
|
|
68
72
|
export function decodeEnrollmentEnvelope(value) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
throw new Error("Invalid enrollment request.");
|
|
72
|
-
const requestId = envelope["request_id"];
|
|
73
|
-
if (typeof requestId !== "string" || !/^[A-Za-z0-9._-]{8,128}$/u.test(requestId)) {
|
|
74
|
-
throw new Error("Invalid enrollment request.");
|
|
75
|
-
}
|
|
76
|
-
const payload = exactRecord(envelope["payload"], ["invitation", "retry_key", "client_credential"], ["client_name"]);
|
|
77
|
-
const invitation = payload["invitation"];
|
|
78
|
-
if (typeof invitation !== "string" || !/^[A-Za-z0-9_-]{43,1024}$/u.test(invitation)) {
|
|
79
|
-
throw new Error("Invalid enrollment request.");
|
|
80
|
-
}
|
|
81
|
-
const retryKey = payload["retry_key"];
|
|
82
|
-
if (typeof retryKey !== "string" ||
|
|
83
|
-
!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(retryKey)) {
|
|
84
|
-
throw new Error("Invalid enrollment request.");
|
|
85
|
-
}
|
|
86
|
-
const clientCredential = payload["client_credential"];
|
|
87
|
-
if (typeof clientCredential !== "string" ||
|
|
88
|
-
!/^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/u.test(clientCredential)) {
|
|
89
|
-
throw new Error("Invalid enrollment request.");
|
|
90
|
-
}
|
|
91
|
-
const clientName = payload["client_name"];
|
|
92
|
-
if (clientName !== undefined &&
|
|
93
|
-
(typeof clientName !== "string" || Buffer.byteLength(clientName) > 120 ||
|
|
94
|
-
!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/u.test(clientName))) {
|
|
95
|
-
throw new Error("Invalid enrollment request.");
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
protocol_version: "1",
|
|
99
|
-
request_id: requestId,
|
|
100
|
-
payload: clientName === undefined
|
|
101
|
-
? { invitation, retry_key: retryKey.toLowerCase(), client_credential: clientCredential }
|
|
102
|
-
: {
|
|
103
|
-
invitation,
|
|
104
|
-
retry_key: retryKey.toLowerCase(),
|
|
105
|
-
client_credential: clientCredential,
|
|
106
|
-
client_name: clientName,
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function exactRecord(value, required, optional = []) {
|
|
111
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
112
|
-
throw new Error("Invalid enrollment request.");
|
|
73
|
+
try {
|
|
74
|
+
return decodeEnrollmentExchangeRequestEnvelope(value);
|
|
113
75
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof ProtocolContractError &&
|
|
78
|
+
error.code === ErrorCode.UNSUPPORTED_PROTOCOL_VERSION)
|
|
79
|
+
throw error;
|
|
118
80
|
throw new Error("Invalid enrollment request.");
|
|
119
81
|
}
|
|
120
|
-
return record;
|
|
121
82
|
}
|
|
122
83
|
//# sourceMappingURL=enrollment.js.map
|
package/dist/enrollment.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enrollment.js","sourceRoot":"","sources":["../src/enrollment.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"enrollment.js","sourceRoot":"","sources":["../src/enrollment.ts"],"names":[],"mappings":"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"}
|
package/dist/https-server.d.ts
CHANGED
|
@@ -2,20 +2,7 @@ import type { Socket } from "node:net";
|
|
|
2
2
|
import { type BindOptionsInput } from "./network-policy.js";
|
|
3
3
|
import type { CoordinationRequest, CoordinationResponse } from "./coordination-api.js";
|
|
4
4
|
import type { Principal } from "./principal.js";
|
|
5
|
-
|
|
6
|
-
readonly protocol_version: string;
|
|
7
|
-
readonly package: {
|
|
8
|
-
readonly name: "borgmcp-shared";
|
|
9
|
-
readonly version: string;
|
|
10
|
-
};
|
|
11
|
-
readonly capabilities: readonly string[];
|
|
12
|
-
readonly limits: {
|
|
13
|
-
readonly max_request_bytes: number;
|
|
14
|
-
readonly max_log_message_bytes: number;
|
|
15
|
-
readonly max_read_page_size: number;
|
|
16
|
-
readonly max_replay_page_size: number;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
5
|
+
import { type DebugLogger } from "./debug-log.js";
|
|
19
6
|
export interface ServiceLimits {
|
|
20
7
|
readonly maxConnections: number;
|
|
21
8
|
readonly maxConnectionsPerAddress: number;
|
|
@@ -36,14 +23,13 @@ export interface ServiceLimits {
|
|
|
36
23
|
}
|
|
37
24
|
export declare const DEFAULT_SERVICE_LIMITS: ServiceLimits;
|
|
38
25
|
export interface RequestHandlerContext {
|
|
39
|
-
readonly protocolInfo: ProtocolInfoDocument;
|
|
40
|
-
readonly authorizeProtocol: (authorization: string | undefined, signal: AbortSignal) => Promise<boolean | "missing" | "invalid" | "revoked">;
|
|
41
26
|
readonly exchangeEnrollment?: (body: unknown) => Promise<{
|
|
42
|
-
readonly status: 201 | 400 | 401 | 507;
|
|
27
|
+
readonly status: 201 | 400 | 401 | 426 | 507;
|
|
43
28
|
readonly body?: unknown;
|
|
44
29
|
}>;
|
|
45
|
-
readonly authorizeCoordination?: (authorization: string | undefined, signal: AbortSignal) => Promise<Principal | "missing" | "invalid" | "revoked">;
|
|
30
|
+
readonly authorizeCoordination?: (authorization: string | undefined, signal: AbortSignal) => Promise<Principal | "missing" | "invalid" | "revoked" | "evicted">;
|
|
46
31
|
readonly handleCoordination?: (request: CoordinationRequest) => Promise<CoordinationResponse>;
|
|
32
|
+
readonly debugLogger: DebugLogger;
|
|
47
33
|
}
|
|
48
34
|
export interface HttpsServerOptions {
|
|
49
35
|
readonly bind?: BindOptionsInput;
|
|
@@ -52,12 +38,11 @@ export interface HttpsServerOptions {
|
|
|
52
38
|
readonly cert: string | Buffer;
|
|
53
39
|
readonly ca?: string | Buffer;
|
|
54
40
|
};
|
|
55
|
-
readonly protocolInfo: ProtocolInfoDocument;
|
|
56
|
-
readonly authorizeProtocol: RequestHandlerContext["authorizeProtocol"];
|
|
57
41
|
readonly exchangeEnrollment?: RequestHandlerContext["exchangeEnrollment"];
|
|
58
42
|
readonly authorizeCoordination?: RequestHandlerContext["authorizeCoordination"];
|
|
59
43
|
readonly handleCoordination?: RequestHandlerContext["handleCoordination"];
|
|
60
44
|
readonly limits?: ServiceLimits;
|
|
45
|
+
readonly debugLogger?: DebugLogger;
|
|
61
46
|
readonly testHooks?: {
|
|
62
47
|
readonly identifyRemoteAddress?: (socket: Socket) => string;
|
|
63
48
|
};
|
package/dist/https-server.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createServer } from "node:https";
|
|
2
2
|
import { createHash, X509Certificate } from "node:crypto";
|
|
3
|
+
import { ATTACH_PATH, CUBES_PATH, ENROLLMENT_EXCHANGE_PATH, ErrorCode, HEALTH_PATH, PROTOCOL_INFO_PATH, PROTOCOL_VERSION, createProtocolTagPreflight, } from "borgmcp-shared/protocol";
|
|
3
4
|
import { resolveBindOptions } from "./network-policy.js";
|
|
5
|
+
import { disabledDebugLogger } from "./debug-log.js";
|
|
4
6
|
export const DEFAULT_SERVICE_LIMITS = {
|
|
5
7
|
maxConnections: 100,
|
|
6
8
|
maxConnectionsPerAddress: 25,
|
|
@@ -40,8 +42,14 @@ export async function startHttpsServer(options) {
|
|
|
40
42
|
}, createRequestListener(handlerContext, limits, options.testHooks?.identifyRemoteAddress));
|
|
41
43
|
const acceptedSockets = applyServerLimits(server, limits);
|
|
42
44
|
server.on("secureConnection", (socket) => socket.disableRenegotiation());
|
|
43
|
-
server.on("tlsClientError", (_error, socket) =>
|
|
44
|
-
|
|
45
|
+
server.on("tlsClientError", (_error, socket) => {
|
|
46
|
+
handlerContext.debugLogger.emit({ event: "transport_rejection", reason: "tls_client_error" });
|
|
47
|
+
socket.destroy();
|
|
48
|
+
});
|
|
49
|
+
server.on("clientError", (_error, socket) => {
|
|
50
|
+
handlerContext.debugLogger.emit({ event: "transport_rejection", reason: "http_parser_error" });
|
|
51
|
+
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
52
|
+
});
|
|
45
53
|
server.on("checkContinue", (_request, response) => sendEmpty(response, 417, true));
|
|
46
54
|
try {
|
|
47
55
|
await listen(server, bind.port, bind.host);
|
|
@@ -70,8 +78,6 @@ export async function startHttpsServer(options) {
|
|
|
70
78
|
}
|
|
71
79
|
export function createRequestHandlerContext(options) {
|
|
72
80
|
return Object.freeze({
|
|
73
|
-
protocolInfo: options.protocolInfo,
|
|
74
|
-
authorizeProtocol: options.authorizeProtocol,
|
|
75
81
|
...(options.exchangeEnrollment === undefined
|
|
76
82
|
? {}
|
|
77
83
|
: { exchangeEnrollment: options.exchangeEnrollment }),
|
|
@@ -81,6 +87,7 @@ export function createRequestHandlerContext(options) {
|
|
|
81
87
|
...(options.handleCoordination === undefined
|
|
82
88
|
? {}
|
|
83
89
|
: { handleCoordination: options.handleCoordination }),
|
|
90
|
+
debugLogger: options.debugLogger ?? disabledDebugLogger,
|
|
84
91
|
});
|
|
85
92
|
}
|
|
86
93
|
function createRequestListener(context, limits, identifyRemoteAddress = (socket) => socket.remoteAddress ?? "unknown") {
|
|
@@ -88,6 +95,33 @@ function createRequestListener(context, limits, identifyRemoteAddress = (socket)
|
|
|
88
95
|
const credentialRateLimiter = new RequestRateLimiter(limits, limits.maxRequestsPerWindow);
|
|
89
96
|
const streamQuota = new ConcurrentQuota(limits.maxStreamsPerCredential);
|
|
90
97
|
return (request, response) => {
|
|
98
|
+
const startedAt = Date.now();
|
|
99
|
+
const trace = {
|
|
100
|
+
route: debugRoute(request.url),
|
|
101
|
+
method: debugMethod(request.method),
|
|
102
|
+
authentication: "not_required",
|
|
103
|
+
};
|
|
104
|
+
let debugEmitted = false;
|
|
105
|
+
const emitDebug = () => {
|
|
106
|
+
if (debugEmitted)
|
|
107
|
+
return;
|
|
108
|
+
debugEmitted = true;
|
|
109
|
+
const status = response.headersSent ? response.statusCode : 0;
|
|
110
|
+
context.debugLogger.emit({
|
|
111
|
+
event: "request",
|
|
112
|
+
route: trace.route,
|
|
113
|
+
method: trace.method,
|
|
114
|
+
authentication: trace.authentication,
|
|
115
|
+
authorization: trace.authentication === "accepted"
|
|
116
|
+
? status === 403 || status === 404 ? "denied_or_not_found" : "accepted"
|
|
117
|
+
: "not_checked",
|
|
118
|
+
...(trace.principal === undefined ? {} : { principal: trace.principal }),
|
|
119
|
+
status,
|
|
120
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
response.once("finish", emitDebug);
|
|
124
|
+
response.once("close", emitDebug);
|
|
91
125
|
const controller = new AbortController();
|
|
92
126
|
response.once("close", () => controller.abort());
|
|
93
127
|
let timer;
|
|
@@ -98,7 +132,7 @@ function createRequestListener(context, limits, identifyRemoteAddress = (socket)
|
|
|
98
132
|
}, limits.handlerTimeoutMs);
|
|
99
133
|
timer.unref();
|
|
100
134
|
});
|
|
101
|
-
const handled = handleRequest(request, response, context, limits, admissionLimiter, credentialRateLimiter, streamQuota, identifyRemoteAddress, controller.signal)
|
|
135
|
+
const handled = handleRequest(request, response, context, limits, admissionLimiter, credentialRateLimiter, streamQuota, identifyRemoteAddress, controller.signal, trace)
|
|
102
136
|
.then(() => "handled");
|
|
103
137
|
void Promise.race([handled, deadline])
|
|
104
138
|
.then((outcome) => {
|
|
@@ -112,7 +146,7 @@ function createRequestListener(context, limits, identifyRemoteAddress = (socket)
|
|
|
112
146
|
});
|
|
113
147
|
};
|
|
114
148
|
}
|
|
115
|
-
async function handleRequest(request, response, context, limits, admissionLimiter, credentialRateLimiter, streamQuota, identifyRemoteAddress, signal) {
|
|
149
|
+
async function handleRequest(request, response, context, limits, admissionLimiter, credentialRateLimiter, streamQuota, identifyRemoteAddress, signal, trace) {
|
|
116
150
|
const addressIdentity = `address:${identifyRemoteAddress(request.socket)}`;
|
|
117
151
|
const preAuthRetry = admissionLimiter.consume(addressIdentity);
|
|
118
152
|
if (preAuthRetry !== null) {
|
|
@@ -136,42 +170,23 @@ async function handleRequest(request, response, context, limits, admissionLimite
|
|
|
136
170
|
}
|
|
137
171
|
return;
|
|
138
172
|
}
|
|
139
|
-
if (path ===
|
|
173
|
+
if (path === HEALTH_PATH) {
|
|
140
174
|
if (requestBody.length !== 0)
|
|
141
175
|
return sendEmpty(response, 400, true);
|
|
142
176
|
sendEmpty(response, request.method === "GET" ? 204 : 405);
|
|
143
177
|
return;
|
|
144
178
|
}
|
|
145
|
-
if (path ===
|
|
179
|
+
if (path === PROTOCOL_INFO_PATH) {
|
|
146
180
|
if (requestBody.length !== 0)
|
|
147
181
|
return sendEmpty(response, 400, true);
|
|
148
|
-
const authorized = await context.authorizeProtocol(request.headers.authorization, signal);
|
|
149
|
-
if (signal.aborted)
|
|
150
|
-
return;
|
|
151
|
-
if (authorized !== true) {
|
|
152
|
-
const code = authorized === "revoked"
|
|
153
|
-
? "SESSION_REVOKED"
|
|
154
|
-
: authorized === "missing" || request.headers.authorization === undefined
|
|
155
|
-
? "AUTH_MISSING"
|
|
156
|
-
: "AUTH_INVALID";
|
|
157
|
-
sendJson(response, 401, protocolError(code, "Authentication failed."));
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
const credentialRetry = consumeAuthenticatedRateLimit(request.headers.authorization, credentialRateLimiter);
|
|
161
|
-
if (credentialRetry !== null)
|
|
162
|
-
return sendRateLimited(response, credentialRetry);
|
|
163
182
|
if (request.method !== "GET") {
|
|
164
183
|
sendEmpty(response, 405);
|
|
165
184
|
return;
|
|
166
185
|
}
|
|
167
|
-
sendJson(response, 200,
|
|
168
|
-
protocol_version: "1",
|
|
169
|
-
request_id: "protocol-info",
|
|
170
|
-
payload: context.protocolInfo,
|
|
171
|
-
});
|
|
186
|
+
sendJson(response, 200, createProtocolTagPreflight());
|
|
172
187
|
return;
|
|
173
188
|
}
|
|
174
|
-
if (path ===
|
|
189
|
+
if (path === ENROLLMENT_EXCHANGE_PATH) {
|
|
175
190
|
if (request.method !== "POST" || context.exchangeEnrollment === undefined) {
|
|
176
191
|
sendEmpty(response, 405);
|
|
177
192
|
return;
|
|
@@ -195,8 +210,9 @@ async function handleRequest(request, response, context, limits, admissionLimite
|
|
|
195
210
|
sendEmpty(response, result.status, result.status === 400);
|
|
196
211
|
else if (result.status === 400)
|
|
197
212
|
sendJson(response, 400, result.body, true);
|
|
198
|
-
else if (result.status === 401 || result.status === 507)
|
|
213
|
+
else if (result.status === 401 || result.status === 426 || result.status === 507) {
|
|
199
214
|
sendJson(response, result.status, result.body);
|
|
215
|
+
}
|
|
200
216
|
else
|
|
201
217
|
sendJson(response, 201, result.body);
|
|
202
218
|
return;
|
|
@@ -221,23 +237,29 @@ async function handleRequest(request, response, context, limits, admissionLimite
|
|
|
221
237
|
return;
|
|
222
238
|
}
|
|
223
239
|
const authentication = await authorizeCoordination(authorization, signal);
|
|
240
|
+
trace.authentication = typeof authentication === "string" ? authentication : "accepted";
|
|
224
241
|
if (signal.aborted)
|
|
225
242
|
return;
|
|
226
243
|
if (typeof authentication === "string") {
|
|
244
|
+
if (authentication === "evicted") {
|
|
245
|
+
sendJson(response, 410, protocolError(ErrorCode.DRONE_EVICTED, "Authentication failed."));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
227
248
|
const code = authentication === "revoked"
|
|
228
249
|
? "SESSION_REVOKED"
|
|
229
250
|
: authentication === "missing" || authorization === undefined ? "AUTH_MISSING" : "AUTH_INVALID";
|
|
230
251
|
sendJson(response, 401, protocolError(code, "Authentication failed."));
|
|
231
252
|
return;
|
|
232
253
|
}
|
|
254
|
+
trace.principal = authentication;
|
|
233
255
|
const clientIdentity = `client:${authentication.kind === "drone-session"
|
|
234
256
|
? authentication.clientId
|
|
235
257
|
: authentication.id}`;
|
|
236
258
|
const credentialRetry = credentialRateLimiter.consume(clientIdentity);
|
|
237
259
|
if (credentialRetry !== null)
|
|
238
260
|
return sendRateLimited(response, credentialRetry);
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
261
|
+
const query = parseCoordinationQuery(request.url, path);
|
|
262
|
+
if (query === INVALID_COORDINATION_QUERY) {
|
|
241
263
|
sendJson(response, 400, protocolError("INVALID_INPUT", "Invalid query parameters."), true);
|
|
242
264
|
return;
|
|
243
265
|
}
|
|
@@ -246,7 +268,7 @@ async function handleRequest(request, response, context, limits, admissionLimite
|
|
|
246
268
|
path,
|
|
247
269
|
principal: authentication,
|
|
248
270
|
...(decoded === undefined ? {} : { body: decoded }),
|
|
249
|
-
...
|
|
271
|
+
...query,
|
|
250
272
|
signal,
|
|
251
273
|
});
|
|
252
274
|
if (signal.aborted) {
|
|
@@ -273,6 +295,46 @@ async function handleRequest(request, response, context, limits, admissionLimite
|
|
|
273
295
|
}
|
|
274
296
|
sendEmpty(response, 404);
|
|
275
297
|
}
|
|
298
|
+
function debugMethod(method) {
|
|
299
|
+
return method === "GET" || method === "POST" || method === "PUT" ||
|
|
300
|
+
method === "PATCH" || method === "DELETE" ? method : "OTHER";
|
|
301
|
+
}
|
|
302
|
+
function debugRoute(rawUrl) {
|
|
303
|
+
const path = parseRequestPath(rawUrl);
|
|
304
|
+
if (path === null)
|
|
305
|
+
return "unknown";
|
|
306
|
+
if (path === HEALTH_PATH)
|
|
307
|
+
return "health";
|
|
308
|
+
if (path === PROTOCOL_INFO_PATH)
|
|
309
|
+
return "protocol";
|
|
310
|
+
if (path === ENROLLMENT_EXCHANGE_PATH)
|
|
311
|
+
return "enrollment_exchange";
|
|
312
|
+
if (path === ATTACH_PATH)
|
|
313
|
+
return "client_attach";
|
|
314
|
+
if (path === "/api/cubes")
|
|
315
|
+
return "cubes";
|
|
316
|
+
if (/^\/api\/cubes\/[0-9a-f-]{36}$/iu.test(path))
|
|
317
|
+
return "cube";
|
|
318
|
+
if (/^\/api\/cubes\/[0-9a-f-]{36}\/roles$/iu.test(path))
|
|
319
|
+
return "cube_roles";
|
|
320
|
+
if (/^\/api\/cubes\/[0-9a-f-]{36}\/roles\/[0-9a-f-]{36}$/iu.test(path))
|
|
321
|
+
return "cube_role";
|
|
322
|
+
if (/^\/api\/cubes\/[0-9a-f-]{36}\/roles\/[0-9a-f-]{36}\/section-patch$/iu.test(path))
|
|
323
|
+
return "cube_role_section_patch";
|
|
324
|
+
if (/^\/api\/cubes\/[0-9a-f-]{36}\/taxonomy-patch$/iu.test(path))
|
|
325
|
+
return "cube_taxonomy_patch";
|
|
326
|
+
if (/^\/api\/cubes\/[0-9a-f-]{36}\/drones$/iu.test(path))
|
|
327
|
+
return "cube_drones";
|
|
328
|
+
if (/^\/api\/cubes\/[0-9a-f-]{36}\/logs$/iu.test(path))
|
|
329
|
+
return "cube_logs";
|
|
330
|
+
if (/^\/api\/cubes\/[0-9a-f-]{36}\/acks$/iu.test(path))
|
|
331
|
+
return "cube_acks";
|
|
332
|
+
if (/^\/api\/cubes\/[0-9a-f-]{36}\/decisions$/iu.test(path))
|
|
333
|
+
return "cube_decisions";
|
|
334
|
+
if (/^\/api\/cubes\/[0-9a-f-]{36}\/stream$/iu.test(path))
|
|
335
|
+
return "cube_stream";
|
|
336
|
+
return "unknown";
|
|
337
|
+
}
|
|
276
338
|
async function closeRejectedStream(stream) {
|
|
277
339
|
const iterator = stream[Symbol.asyncIterator]();
|
|
278
340
|
try {
|
|
@@ -402,33 +464,33 @@ function waitForDrain(response) {
|
|
|
402
464
|
});
|
|
403
465
|
}
|
|
404
466
|
function protocolError(code, message) {
|
|
405
|
-
return { protocol_version:
|
|
467
|
+
return { protocol_version: PROTOCOL_VERSION, error: { code, message } };
|
|
406
468
|
}
|
|
407
469
|
const INVALID_COORDINATION_QUERY = Symbol("invalid-coordination-query");
|
|
408
|
-
function
|
|
470
|
+
function parseCoordinationQuery(value, path) {
|
|
409
471
|
if (value === undefined)
|
|
410
|
-
return
|
|
472
|
+
return {};
|
|
411
473
|
try {
|
|
412
474
|
const parsed = new URL(value, "https://local.invalid");
|
|
413
475
|
const keys = [...parsed.searchParams.keys()];
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (keys.some((key) => key !==
|
|
476
|
+
const allowed = path.endsWith("/stream") ? "cursor" : path.endsWith("/drones") ? "since" : null;
|
|
477
|
+
if (allowed === null)
|
|
478
|
+
return keys.length === 0 ? {} : INVALID_COORDINATION_QUERY;
|
|
479
|
+
if (keys.some((key) => key !== allowed))
|
|
418
480
|
return INVALID_COORDINATION_QUERY;
|
|
419
|
-
const values = parsed.searchParams.getAll(
|
|
481
|
+
const values = parsed.searchParams.getAll(allowed);
|
|
420
482
|
if (values.length === 0)
|
|
421
|
-
return
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
483
|
+
return {};
|
|
484
|
+
if (values.length !== 1 || values[0].length === 0)
|
|
485
|
+
return INVALID_COORDINATION_QUERY;
|
|
486
|
+
return allowed === "cursor" ? { cursor: values[0] } : { since: values[0] };
|
|
425
487
|
}
|
|
426
488
|
catch {
|
|
427
489
|
return INVALID_COORDINATION_QUERY;
|
|
428
490
|
}
|
|
429
491
|
}
|
|
430
492
|
function isCoordinationPath(path) {
|
|
431
|
-
return path ===
|
|
493
|
+
return path === ATTACH_PATH || path === CUBES_PATH ||
|
|
432
494
|
path?.startsWith("/api/cubes/") === true;
|
|
433
495
|
}
|
|
434
496
|
function applyServerLimits(server, limits) {
|
|
@@ -773,10 +835,6 @@ function credentialIdentity(authorization) {
|
|
|
773
835
|
return null;
|
|
774
836
|
return `credential:${createHash("sha256").update(authorization).digest("base64url")}`;
|
|
775
837
|
}
|
|
776
|
-
function consumeAuthenticatedRateLimit(authorization, rateLimiter) {
|
|
777
|
-
const identity = credentialIdentity(authorization);
|
|
778
|
-
return identity === null ? null : rateLimiter.consume(identity);
|
|
779
|
-
}
|
|
780
838
|
function listen(server, port, host) {
|
|
781
839
|
return new Promise((resolve, reject) => {
|
|
782
840
|
const onError = (error) => {
|