@nimbus-dev/sdk 1.1.2
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/LICENSE +21 -0
- package/README.md +34 -0
- package/dist/audit-logger.d.ts +6 -0
- package/dist/audit-logger.d.ts.map +1 -0
- package/dist/audit-logger.js +18 -0
- package/dist/audit-logger.js.map +1 -0
- package/dist/contract-tests.d.ts +45 -0
- package/dist/contract-tests.d.ts.map +1 -0
- package/dist/contract-tests.js +191 -0
- package/dist/contract-tests.js.map +1 -0
- package/dist/crypto/app-store-connect-jwt.d.ts +19 -0
- package/dist/crypto/app-store-connect-jwt.d.ts.map +1 -0
- package/dist/crypto/app-store-connect-jwt.js +30 -0
- package/dist/crypto/app-store-connect-jwt.js.map +1 -0
- package/dist/crypto/canonical-json.d.ts +36 -0
- package/dist/crypto/canonical-json.d.ts.map +1 -0
- package/dist/crypto/canonical-json.js +75 -0
- package/dist/crypto/canonical-json.js.map +1 -0
- package/dist/crypto/jwt.d.ts +30 -0
- package/dist/crypto/jwt.d.ts.map +1 -0
- package/dist/crypto/jwt.js +30 -0
- package/dist/crypto/jwt.js.map +1 -0
- package/dist/crypto/service-account-token.d.ts +36 -0
- package/dist/crypto/service-account-token.d.ts.map +1 -0
- package/dist/crypto/service-account-token.js +96 -0
- package/dist/crypto/service-account-token.js.map +1 -0
- package/dist/crypto/verify-signature.d.ts +57 -0
- package/dist/crypto/verify-signature.d.ts.map +1 -0
- package/dist/crypto/verify-signature.js +102 -0
- package/dist/crypto/verify-signature.js.map +1 -0
- package/dist/distribution-channel.d.ts +34 -0
- package/dist/distribution-channel.d.ts.map +1 -0
- package/dist/distribution-channel.js +73 -0
- package/dist/distribution-channel.js.map +1 -0
- package/dist/hitl-request.d.ts +7 -0
- package/dist/hitl-request.d.ts.map +1 -0
- package/dist/hitl-request.js +15 -0
- package/dist/hitl-request.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc/index.d.ts +2 -0
- package/dist/ipc/index.d.ts.map +1 -0
- package/dist/ipc/index.js +2 -0
- package/dist/ipc/index.js.map +1 -0
- package/dist/ipc/ndjson-line-reader.d.ts +20 -0
- package/dist/ipc/ndjson-line-reader.d.ts.map +1 -0
- package/dist/ipc/ndjson-line-reader.js +56 -0
- package/dist/ipc/ndjson-line-reader.js.map +1 -0
- package/dist/server.d.ts +29 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +23 -0
- package/dist/server.js.map +1 -0
- package/dist/testing/index.d.ts +15 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/sandbox-contract.d.ts +83 -0
- package/dist/testing/sandbox-contract.d.ts.map +1 -0
- package/dist/testing/sandbox-contract.js +105 -0
- package/dist/testing/sandbox-contract.js.map +1 -0
- package/dist/testing/sandbox-probe.d.ts +23 -0
- package/dist/testing/sandbox-probe.d.ts.map +1 -0
- package/dist/testing/sandbox-probe.js +78 -0
- package/dist/testing/sandbox-probe.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
- package/src/audit-logger.test.ts +33 -0
- package/src/audit-logger.ts +23 -0
- package/src/contract-tests.test.ts +203 -0
- package/src/contract-tests.ts +220 -0
- package/src/crypto/app-store-connect-jwt.test.ts +80 -0
- package/src/crypto/app-store-connect-jwt.ts +42 -0
- package/src/crypto/canonical-json.test.ts +121 -0
- package/src/crypto/canonical-json.ts +73 -0
- package/src/crypto/jwt.test.ts +62 -0
- package/src/crypto/jwt.ts +45 -0
- package/src/crypto/service-account-token.test.ts +128 -0
- package/src/crypto/service-account-token.ts +116 -0
- package/src/crypto/verify-signature.test.ts +118 -0
- package/src/crypto/verify-signature.ts +138 -0
- package/src/distribution-channel.test.ts +107 -0
- package/src/distribution-channel.ts +105 -0
- package/src/hitl-request.ts +22 -0
- package/src/index.ts +59 -0
- package/src/ipc/index.ts +5 -0
- package/src/ipc/ndjson-line-reader.test.ts +64 -0
- package/src/ipc/ndjson-line-reader.ts +70 -0
- package/src/plugin-api-v1.test.ts +50 -0
- package/src/sdk.test.ts +23 -0
- package/src/server.test.ts +96 -0
- package/src/server.ts +39 -0
- package/src/testing/index.ts +18 -0
- package/src/testing/sandbox-contract.test.ts +146 -0
- package/src/testing/sandbox-contract.ts +155 -0
- package/src/testing/sandbox-probe.ts +87 -0
- package/src/types.ts +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nimbus Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @nimbus-dev/sdk
|
|
2
|
+
|
|
3
|
+
## What this is
|
|
4
|
+
|
|
5
|
+
MIT-licensed SDK for authoring Nimbus Model Context Protocol (MCP) connectors. This library provides TypeScript types, base classes, and request-routing primitives to build connectors that run on the Nimbus Gateway.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @nimbus-dev/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { McpServer } from "@nimbus-dev/sdk";
|
|
17
|
+
|
|
18
|
+
const server = new McpServer("my-connector");
|
|
19
|
+
|
|
20
|
+
server.registerTool("echo", "Echoes input", async (input) => {
|
|
21
|
+
return { result: input };
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
server.start();
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## See also
|
|
28
|
+
|
|
29
|
+
- [Nimbus Developer Guide](https://nimbus-agent.dev/)
|
|
30
|
+
- [Model Context Protocol Specification](https://modelcontextprotocol.io/)
|
|
31
|
+
|
|
32
|
+
## License
|
|
33
|
+
|
|
34
|
+
MIT
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type AuditEmit = (action: string, payload: Record<string, unknown>) => Promise<void>;
|
|
2
|
+
export interface AuditLogger {
|
|
3
|
+
log(action: string, payload: Record<string, unknown>): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export declare function createScopedAuditLogger(extensionId: string, emit: AuditEmit): AuditLogger;
|
|
6
|
+
//# sourceMappingURL=audit-logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-logger.d.ts","sourceRoot":"","sources":["../src/audit-logger.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5F,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE;AAED,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,WAAW,CAgBzF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function createScopedAuditLogger(extensionId, emit) {
|
|
2
|
+
if (!extensionId || extensionId.trim().length === 0) {
|
|
3
|
+
throw new Error("extensionId must be non-empty");
|
|
4
|
+
}
|
|
5
|
+
return {
|
|
6
|
+
async log(action, payload) {
|
|
7
|
+
if (!action || action.length === 0) {
|
|
8
|
+
throw new Error("action must be non-empty");
|
|
9
|
+
}
|
|
10
|
+
if (action.includes(":")) {
|
|
11
|
+
throw new Error("action must not contain a colon (scoping prefix is added automatically)");
|
|
12
|
+
}
|
|
13
|
+
const scoped = `${extensionId}:${action}`;
|
|
14
|
+
await emit(scoped, payload);
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=audit-logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-logger.js","sourceRoot":"","sources":["../src/audit-logger.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,uBAAuB,CAAC,WAAmB,EAAE,IAAe;IAC1E,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;IACD,OAAO;QACL,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO;YACvB,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;YAC9C,CAAC;YACD,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAC;YAC7F,CAAC;YACD,MAAM,MAAM,GAAG,GAAG,WAAW,IAAI,MAAM,EAAE,CAAC;YAC1C,MAAM,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC9B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ExtensionManifest } from "./types";
|
|
2
|
+
export declare class ExtensionContractError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Tool-name segments that indicate a tool fetches actual ROW / CELL / query-result
|
|
7
|
+
* data (including log EVENTS) — as opposed to schema/metadata. Used by
|
|
8
|
+
* {@link assertNoRowDataTools}.
|
|
9
|
+
*
|
|
10
|
+
* The service prefix of a tool name must be a single token (e.g. `bigquery_list`,
|
|
11
|
+
* not `big_query_list`) so that, for example, `bigquery` never splits into a
|
|
12
|
+
* spurious `query` segment.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ROW_DATA_TOOL_SEGMENTS: ReadonlySet<string>;
|
|
15
|
+
/** A registered MCP tool, reduced to the fields the no-row-data check inspects. */
|
|
16
|
+
export interface RowDataToolCandidate {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly description?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Tier-3 "no-row-data" contract assertion. A warehouse / logging / query connector
|
|
22
|
+
* must expose ONLY schema/metadata tools (list / get / search over datasets, tables,
|
|
23
|
+
* schemas, jobs, log groups, models, expectation suites …) and MUST NOT register any
|
|
24
|
+
* tool that pulls actual row / cell / query-result data into the local index.
|
|
25
|
+
*
|
|
26
|
+
* Enforcement is structural at the connector surface (NOT a runtime Gateway
|
|
27
|
+
* invariant): if the connector never registers a row/cell tool there is nothing to
|
|
28
|
+
* block at runtime. This assertion is the executable backstop — call it from the
|
|
29
|
+
* connector's contract test with its registered tool surface so that a future edit
|
|
30
|
+
* adding a `<svc>_run_query` / `<svc>_get_rows` / `<svc>_sample` / `<svc>_scan` tool
|
|
31
|
+
* fails CI. A connector that genuinely needs a live-gated row tool is a discrete
|
|
32
|
+
* `I17` design discussion, out of scope for the no-row-data tier.
|
|
33
|
+
*
|
|
34
|
+
* The check is name-based (tool descriptions are not scanned, to avoid false
|
|
35
|
+
* positives like "does not fetch rows"). Each tool name is split on non-alphanumeric
|
|
36
|
+
* boundaries and rejected if any segment is in {@link ROW_DATA_TOOL_SEGMENTS}.
|
|
37
|
+
*
|
|
38
|
+
* @throws {ExtensionContractError} if any tool name looks like a row/cell fetcher.
|
|
39
|
+
*/
|
|
40
|
+
export declare function assertNoRowDataTools(tools: ReadonlyArray<RowDataToolCandidate>, context?: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Validates a {@link ExtensionManifest} for CI / `nimbus test` (no network, no Gateway).
|
|
43
|
+
*/
|
|
44
|
+
export declare function runContractTests(manifest: ExtensionManifest): Promise<void>;
|
|
45
|
+
//# sourceMappingURL=contract-tests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-tests.d.ts","sourceRoot":"","sources":["../src/contract-tests.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAmGD;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAwBrD,CAAC;AAEH,mFAAmF;AACnF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AASD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,CAAC,oBAAoB,CAAC,EAC1C,OAAO,SAAc,GACpB,IAAI,CAmBN;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBjF"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { createScopedAuditLogger } from "./audit-logger";
|
|
2
|
+
import { isHitlRequest } from "./hitl-request";
|
|
3
|
+
export class ExtensionContractError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "ExtensionContractError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const PERMS = new Set(["read", "write", "delete"]);
|
|
10
|
+
const HITL = new Set(["write", "delete"]);
|
|
11
|
+
function isNonEmptyString(v) {
|
|
12
|
+
return typeof v === "string" && v.trim() !== "";
|
|
13
|
+
}
|
|
14
|
+
function validateRequiredStrings(manifest) {
|
|
15
|
+
const errors = [];
|
|
16
|
+
if (!isNonEmptyString(manifest.id)) {
|
|
17
|
+
errors.push("manifest.id is required");
|
|
18
|
+
}
|
|
19
|
+
if (!isNonEmptyString(manifest.displayName)) {
|
|
20
|
+
errors.push("manifest.displayName is required");
|
|
21
|
+
}
|
|
22
|
+
if (!isNonEmptyString(manifest.version)) {
|
|
23
|
+
errors.push("manifest.version is required");
|
|
24
|
+
}
|
|
25
|
+
if (!isNonEmptyString(manifest.description)) {
|
|
26
|
+
errors.push("manifest.description is required");
|
|
27
|
+
}
|
|
28
|
+
if (!isNonEmptyString(manifest.author)) {
|
|
29
|
+
errors.push("manifest.author is required");
|
|
30
|
+
}
|
|
31
|
+
if (!isNonEmptyString(manifest.entrypoint)) {
|
|
32
|
+
errors.push("manifest.entrypoint is required");
|
|
33
|
+
}
|
|
34
|
+
return errors;
|
|
35
|
+
}
|
|
36
|
+
function validateRuntime(manifest) {
|
|
37
|
+
if (manifest.runtime === "bun" || manifest.runtime === "node") {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
return ['manifest.runtime must be "bun" or "node"'];
|
|
41
|
+
}
|
|
42
|
+
function validatePermissions(manifest) {
|
|
43
|
+
const errors = [];
|
|
44
|
+
if (!Array.isArray(manifest.permissions)) {
|
|
45
|
+
errors.push("manifest.permissions must be an array");
|
|
46
|
+
return errors;
|
|
47
|
+
}
|
|
48
|
+
for (const p of manifest.permissions) {
|
|
49
|
+
if (!PERMS.has(p)) {
|
|
50
|
+
errors.push(`invalid manifest.permissions entry: ${String(p)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return errors;
|
|
54
|
+
}
|
|
55
|
+
function validateHitlRequired(manifest) {
|
|
56
|
+
const errors = [];
|
|
57
|
+
if (Array.isArray(manifest.hitlRequired)) {
|
|
58
|
+
for (const h of manifest.hitlRequired) {
|
|
59
|
+
if (!HITL.has(h)) {
|
|
60
|
+
errors.push(`invalid manifest.hitlRequired entry: ${String(h)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
errors.push("manifest.hitlRequired must be an array");
|
|
66
|
+
}
|
|
67
|
+
return errors;
|
|
68
|
+
}
|
|
69
|
+
function validateMinNimbusVersion(manifest) {
|
|
70
|
+
if (!isNonEmptyString(manifest.minNimbusVersion)) {
|
|
71
|
+
return ["manifest.minNimbusVersion is required"];
|
|
72
|
+
}
|
|
73
|
+
if (!/^\d+\.\d+\.\d+/.test(manifest.minNimbusVersion.trim())) {
|
|
74
|
+
return ["manifest.minNimbusVersion must start with semver x.y.z"];
|
|
75
|
+
}
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
function assertV1AuditLoggerShape(logger, extensionId) {
|
|
79
|
+
const ret = logger.log("test.action", {});
|
|
80
|
+
if (typeof ret.then !== "function") {
|
|
81
|
+
throw new ExtensionContractError(`AuditLogger.log must return a Promise (extension ${extensionId})`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function assertV1HitlRequestGuard() {
|
|
85
|
+
const good = { actionId: "x", summary: "y" };
|
|
86
|
+
if (!isHitlRequest(good)) {
|
|
87
|
+
throw new ExtensionContractError("isHitlRequest must accept a valid HitlRequest");
|
|
88
|
+
}
|
|
89
|
+
if (isHitlRequest({})) {
|
|
90
|
+
throw new ExtensionContractError("isHitlRequest must reject an empty object");
|
|
91
|
+
}
|
|
92
|
+
if (isHitlRequest({ actionId: "", summary: "y" })) {
|
|
93
|
+
throw new ExtensionContractError("isHitlRequest must reject empty actionId");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Tool-name segments that indicate a tool fetches actual ROW / CELL / query-result
|
|
98
|
+
* data (including log EVENTS) — as opposed to schema/metadata. Used by
|
|
99
|
+
* {@link assertNoRowDataTools}.
|
|
100
|
+
*
|
|
101
|
+
* The service prefix of a tool name must be a single token (e.g. `bigquery_list`,
|
|
102
|
+
* not `big_query_list`) so that, for example, `bigquery` never splits into a
|
|
103
|
+
* spurious `query` segment.
|
|
104
|
+
*/
|
|
105
|
+
export const ROW_DATA_TOOL_SEGMENTS = new Set([
|
|
106
|
+
"query",
|
|
107
|
+
"queries",
|
|
108
|
+
"row",
|
|
109
|
+
"rows",
|
|
110
|
+
"cell",
|
|
111
|
+
"cells",
|
|
112
|
+
"record",
|
|
113
|
+
"records",
|
|
114
|
+
"event",
|
|
115
|
+
"events",
|
|
116
|
+
"result",
|
|
117
|
+
"results",
|
|
118
|
+
"tabledata",
|
|
119
|
+
"scan",
|
|
120
|
+
"sample",
|
|
121
|
+
"samples",
|
|
122
|
+
"select",
|
|
123
|
+
"values",
|
|
124
|
+
"preview",
|
|
125
|
+
"head",
|
|
126
|
+
"dump",
|
|
127
|
+
"export",
|
|
128
|
+
"download",
|
|
129
|
+
]);
|
|
130
|
+
function toolNameSegments(name) {
|
|
131
|
+
return name
|
|
132
|
+
.toLowerCase()
|
|
133
|
+
.split(/[^a-z0-9]+/)
|
|
134
|
+
.filter((s) => s.length > 0);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Tier-3 "no-row-data" contract assertion. A warehouse / logging / query connector
|
|
138
|
+
* must expose ONLY schema/metadata tools (list / get / search over datasets, tables,
|
|
139
|
+
* schemas, jobs, log groups, models, expectation suites …) and MUST NOT register any
|
|
140
|
+
* tool that pulls actual row / cell / query-result data into the local index.
|
|
141
|
+
*
|
|
142
|
+
* Enforcement is structural at the connector surface (NOT a runtime Gateway
|
|
143
|
+
* invariant): if the connector never registers a row/cell tool there is nothing to
|
|
144
|
+
* block at runtime. This assertion is the executable backstop — call it from the
|
|
145
|
+
* connector's contract test with its registered tool surface so that a future edit
|
|
146
|
+
* adding a `<svc>_run_query` / `<svc>_get_rows` / `<svc>_sample` / `<svc>_scan` tool
|
|
147
|
+
* fails CI. A connector that genuinely needs a live-gated row tool is a discrete
|
|
148
|
+
* `I17` design discussion, out of scope for the no-row-data tier.
|
|
149
|
+
*
|
|
150
|
+
* The check is name-based (tool descriptions are not scanned, to avoid false
|
|
151
|
+
* positives like "does not fetch rows"). Each tool name is split on non-alphanumeric
|
|
152
|
+
* boundaries and rejected if any segment is in {@link ROW_DATA_TOOL_SEGMENTS}.
|
|
153
|
+
*
|
|
154
|
+
* @throws {ExtensionContractError} if any tool name looks like a row/cell fetcher.
|
|
155
|
+
*/
|
|
156
|
+
export function assertNoRowDataTools(tools, context = "connector") {
|
|
157
|
+
const offenders = [];
|
|
158
|
+
for (const tool of tools) {
|
|
159
|
+
if (typeof tool?.name !== "string" || tool.name.trim() === "") {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const hit = toolNameSegments(tool.name).find((s) => ROW_DATA_TOOL_SEGMENTS.has(s));
|
|
163
|
+
if (hit !== undefined) {
|
|
164
|
+
offenders.push(`${tool.name} (row-data segment "${hit}")`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (offenders.length > 0) {
|
|
168
|
+
throw new ExtensionContractError(`no-row-data contract violated: ${context} must expose only schema/metadata tools, ` +
|
|
169
|
+
`but these look like row/cell/result fetchers: ${offenders.join(", ")}. Remove the ` +
|
|
170
|
+
`tool, or — if a live-gated row tool is genuinely required — raise it as a discrete ` +
|
|
171
|
+
`I17 design discussion (out of scope for the no-row-data tier).`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Validates a {@link ExtensionManifest} for CI / `nimbus test` (no network, no Gateway).
|
|
176
|
+
*/
|
|
177
|
+
export async function runContractTests(manifest) {
|
|
178
|
+
const errors = [
|
|
179
|
+
...validateRequiredStrings(manifest),
|
|
180
|
+
...validateRuntime(manifest),
|
|
181
|
+
...validatePermissions(manifest),
|
|
182
|
+
...validateHitlRequired(manifest),
|
|
183
|
+
...validateMinNimbusVersion(manifest),
|
|
184
|
+
];
|
|
185
|
+
if (errors.length > 0) {
|
|
186
|
+
throw new ExtensionContractError(errors.join("; "));
|
|
187
|
+
}
|
|
188
|
+
assertV1HitlRequestGuard();
|
|
189
|
+
assertV1AuditLoggerShape(createScopedAuditLogger(manifest.id, async () => { }), manifest.id);
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=contract-tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-tests.js","sourceRoot":"","sources":["../src/contract-tests.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apple App Store Connect API authentication — a short-lived ES256 JWT.
|
|
3
|
+
*
|
|
4
|
+
* The App Store Connect API authenticates with a JWT bearer token minted from
|
|
5
|
+
* the developer's EC P-256 `.p8` private key (Apple caps the lifetime at 20
|
|
6
|
+
* min). Shared so the gateway sync and a connector's MCP server sign
|
|
7
|
+
* identically without duplicating the flow.
|
|
8
|
+
*
|
|
9
|
+
* See: https://developer.apple.com/documentation/appstoreconnectapi/generating-tokens-for-api-requests
|
|
10
|
+
*/
|
|
11
|
+
export interface AppStoreConnectJwtParams {
|
|
12
|
+
readonly issuerId: string;
|
|
13
|
+
readonly keyId: string;
|
|
14
|
+
/** Full `.p8` PEM text (`-----BEGIN PRIVATE KEY----- …`). */
|
|
15
|
+
readonly privateKeyPem: string;
|
|
16
|
+
}
|
|
17
|
+
/** Mint an ES256 JWT bearer token for the App Store Connect API. */
|
|
18
|
+
export declare function signAppStoreConnectJwt(params: AppStoreConnectJwtParams, nowMs?: number): string;
|
|
19
|
+
//# sourceMappingURL=app-store-connect-jwt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-store-connect-jwt.d.ts","sourceRoot":"","sources":["../../src/crypto/app-store-connect-jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAKD,oEAAoE;AACpE,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,wBAAwB,EAChC,KAAK,GAAE,MAAmB,GACzB,MAAM,CAcR"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apple App Store Connect API authentication — a short-lived ES256 JWT.
|
|
3
|
+
*
|
|
4
|
+
* The App Store Connect API authenticates with a JWT bearer token minted from
|
|
5
|
+
* the developer's EC P-256 `.p8` private key (Apple caps the lifetime at 20
|
|
6
|
+
* min). Shared so the gateway sync and a connector's MCP server sign
|
|
7
|
+
* identically without duplicating the flow.
|
|
8
|
+
*
|
|
9
|
+
* See: https://developer.apple.com/documentation/appstoreconnectapi/generating-tokens-for-api-requests
|
|
10
|
+
*/
|
|
11
|
+
import { signJwt } from "./jwt";
|
|
12
|
+
const AUDIENCE = "appstoreconnect-v1";
|
|
13
|
+
const TOKEN_TTL_SECONDS = 600;
|
|
14
|
+
/** Mint an ES256 JWT bearer token for the App Store Connect API. */
|
|
15
|
+
export function signAppStoreConnectJwt(params, nowMs = Date.now()) {
|
|
16
|
+
const nowSec = Math.floor(nowMs / 1000);
|
|
17
|
+
return signJwt({
|
|
18
|
+
header: { alg: "ES256", kid: params.keyId, typ: "JWT" },
|
|
19
|
+
payload: {
|
|
20
|
+
iss: params.issuerId,
|
|
21
|
+
iat: nowSec,
|
|
22
|
+
exp: nowSec + TOKEN_TTL_SECONDS,
|
|
23
|
+
aud: AUDIENCE,
|
|
24
|
+
},
|
|
25
|
+
privateKeyPem: params.privateKeyPem,
|
|
26
|
+
// ES256 = ECDSA over P-256; `ieee-p1363` gives the raw r||s JWS needs.
|
|
27
|
+
dsaEncoding: "ieee-p1363",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=app-store-connect-jwt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-store-connect-jwt.js","sourceRoot":"","sources":["../../src/crypto/app-store-connect-jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAShC,MAAM,QAAQ,GAAG,oBAAoB,CAAC;AACtC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B,oEAAoE;AACpE,MAAM,UAAU,sBAAsB,CACpC,MAAgC,EAChC,QAAgB,IAAI,CAAC,GAAG,EAAE;IAE1B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACxC,OAAO,OAAO,CAAC;QACb,MAAM,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE;QACvD,OAAO,EAAE;YACP,GAAG,EAAE,MAAM,CAAC,QAAQ;YACpB,GAAG,EAAE,MAAM;YACX,GAAG,EAAE,MAAM,GAAG,iBAAiB;YAC/B,GAAG,EAAE,QAAQ;SACd;QACD,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,uEAAuE;QACvE,WAAW,EAAE,YAAY;KAC1B,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic JSON canonicalization for extension manifests.
|
|
3
|
+
*
|
|
4
|
+
* Used as the input to Ed25519 manifest signing (T2 PR 2 / I16). The signed
|
|
5
|
+
* bytes are the manifest with the `signature` field stripped, re-serialized
|
|
6
|
+
* via the rules below. Signing and verifying both call into this module so
|
|
7
|
+
* the byte sequences match exactly.
|
|
8
|
+
*
|
|
9
|
+
* Rules:
|
|
10
|
+
* - Object keys sorted in lexicographic UTF-16 code-unit order (JS `Array.sort` default).
|
|
11
|
+
* - String VALUES Unicode-normalized to NFC so semantically-equal strings
|
|
12
|
+
* yield identical bytes regardless of how the source editor encoded them.
|
|
13
|
+
* Object KEYS are NOT normalized — the publisher signs them byte-for-byte
|
|
14
|
+
* as serialized.
|
|
15
|
+
* - Integer numbers only (manifests have no floating-point fields).
|
|
16
|
+
* - No whitespace; UTF-8 encoded.
|
|
17
|
+
* - Recursion capped at MAX_DEPTH (32) — real manifests have depth ≤ 4; the
|
|
18
|
+
* cap defends against a maliciously crafted manifest blowing the stack.
|
|
19
|
+
*
|
|
20
|
+
* Preconditions: callers pass values produced by `JSON.parse`. That domain
|
|
21
|
+
* guarantees no cycles, no undefined / function / symbol values, no NaN /
|
|
22
|
+
* Infinity. The thrown error classes below are defensive for callers that
|
|
23
|
+
* violate the precondition (e.g. constructing the input in-memory).
|
|
24
|
+
*/
|
|
25
|
+
export declare class NonIntegerNumberInManifest extends Error {
|
|
26
|
+
readonly name = "NonIntegerNumberInManifest";
|
|
27
|
+
}
|
|
28
|
+
export declare class UnsupportedManifestValueType extends Error {
|
|
29
|
+
readonly name = "UnsupportedManifestValueType";
|
|
30
|
+
}
|
|
31
|
+
export declare class ManifestNestedTooDeep extends Error {
|
|
32
|
+
readonly name = "ManifestNestedTooDeep";
|
|
33
|
+
}
|
|
34
|
+
export declare function canonicalize(value: unknown, depth?: number): string;
|
|
35
|
+
export declare function canonicalizeManifest(manifest: object): Uint8Array;
|
|
36
|
+
//# sourceMappingURL=canonical-json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-json.d.ts","sourceRoot":"","sources":["../../src/crypto/canonical-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,SAAkB,IAAI,gCAAgC;CACvD;AACD,qBAAa,4BAA6B,SAAQ,KAAK;IACrD,SAAkB,IAAI,kCAAkC;CACzD;AACD,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,SAAkB,IAAI,2BAA2B;CAClD;AAID,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,MAAM,CA6B9D;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAIjE"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic JSON canonicalization for extension manifests.
|
|
3
|
+
*
|
|
4
|
+
* Used as the input to Ed25519 manifest signing (T2 PR 2 / I16). The signed
|
|
5
|
+
* bytes are the manifest with the `signature` field stripped, re-serialized
|
|
6
|
+
* via the rules below. Signing and verifying both call into this module so
|
|
7
|
+
* the byte sequences match exactly.
|
|
8
|
+
*
|
|
9
|
+
* Rules:
|
|
10
|
+
* - Object keys sorted in lexicographic UTF-16 code-unit order (JS `Array.sort` default).
|
|
11
|
+
* - String VALUES Unicode-normalized to NFC so semantically-equal strings
|
|
12
|
+
* yield identical bytes regardless of how the source editor encoded them.
|
|
13
|
+
* Object KEYS are NOT normalized — the publisher signs them byte-for-byte
|
|
14
|
+
* as serialized.
|
|
15
|
+
* - Integer numbers only (manifests have no floating-point fields).
|
|
16
|
+
* - No whitespace; UTF-8 encoded.
|
|
17
|
+
* - Recursion capped at MAX_DEPTH (32) — real manifests have depth ≤ 4; the
|
|
18
|
+
* cap defends against a maliciously crafted manifest blowing the stack.
|
|
19
|
+
*
|
|
20
|
+
* Preconditions: callers pass values produced by `JSON.parse`. That domain
|
|
21
|
+
* guarantees no cycles, no undefined / function / symbol values, no NaN /
|
|
22
|
+
* Infinity. The thrown error classes below are defensive for callers that
|
|
23
|
+
* violate the precondition (e.g. constructing the input in-memory).
|
|
24
|
+
*/
|
|
25
|
+
export class NonIntegerNumberInManifest extends Error {
|
|
26
|
+
name = "NonIntegerNumberInManifest";
|
|
27
|
+
}
|
|
28
|
+
export class UnsupportedManifestValueType extends Error {
|
|
29
|
+
name = "UnsupportedManifestValueType";
|
|
30
|
+
}
|
|
31
|
+
export class ManifestNestedTooDeep extends Error {
|
|
32
|
+
name = "ManifestNestedTooDeep";
|
|
33
|
+
}
|
|
34
|
+
const MAX_DEPTH = 32;
|
|
35
|
+
export function canonicalize(value, depth = 0) {
|
|
36
|
+
if (depth > MAX_DEPTH)
|
|
37
|
+
throw new ManifestNestedTooDeep();
|
|
38
|
+
if (value === null)
|
|
39
|
+
return "null";
|
|
40
|
+
if (value === true)
|
|
41
|
+
return "true";
|
|
42
|
+
if (value === false)
|
|
43
|
+
return "false";
|
|
44
|
+
if (typeof value === "string") {
|
|
45
|
+
return JSON.stringify(value.normalize("NFC"));
|
|
46
|
+
}
|
|
47
|
+
if (typeof value === "number") {
|
|
48
|
+
if (!Number.isInteger(value))
|
|
49
|
+
throw new NonIntegerNumberInManifest();
|
|
50
|
+
return String(value);
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return `[${value.map((v) => canonicalize(v, depth + 1)).join(",")}]`;
|
|
54
|
+
}
|
|
55
|
+
if (typeof value === "object") {
|
|
56
|
+
const obj = value;
|
|
57
|
+
const keys = Object.keys(obj).sort((a, b) => {
|
|
58
|
+
if (a < b)
|
|
59
|
+
return -1;
|
|
60
|
+
if (a > b)
|
|
61
|
+
return 1;
|
|
62
|
+
return 0;
|
|
63
|
+
});
|
|
64
|
+
return ("{" +
|
|
65
|
+
keys.map((k) => `${JSON.stringify(k)}:${canonicalize(obj[k], depth + 1)}`).join(",") +
|
|
66
|
+
"}");
|
|
67
|
+
}
|
|
68
|
+
throw new UnsupportedManifestValueType();
|
|
69
|
+
}
|
|
70
|
+
export function canonicalizeManifest(manifest) {
|
|
71
|
+
const clone = { ...manifest };
|
|
72
|
+
delete clone["signature"];
|
|
73
|
+
return new TextEncoder().encode(canonicalize(clone));
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=canonical-json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-json.js","sourceRoot":"","sources":["../../src/crypto/canonical-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IACjC,IAAI,GAAG,4BAA4B,CAAC;CACvD;AACD,MAAM,OAAO,4BAA6B,SAAQ,KAAK;IACnC,IAAI,GAAG,8BAA8B,CAAC;CACzD;AACD,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC5B,IAAI,GAAG,uBAAuB,CAAC;CAClD;AAED,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,MAAM,UAAU,YAAY,CAAC,KAAc,EAAE,KAAK,GAAG,CAAC;IACpD,IAAI,KAAK,GAAG,SAAS;QAAE,MAAM,IAAI,qBAAqB,EAAE,CAAC;IACzD,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,OAAO,CAAC;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,0BAA0B,EAAE,CAAC;QACrE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACvE,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,KAAgC,CAAC;QAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YAC1C,IAAI,CAAC,GAAG,CAAC;gBAAE,OAAO,CAAC,CAAC,CAAC;YACrB,IAAI,CAAC,GAAG,CAAC;gBAAE,OAAO,CAAC,CAAC;YACpB,OAAO,CAAC,CAAC;QACX,CAAC,CAAC,CAAC;QACH,OAAO,CACL,GAAG;YACH,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YACpF,GAAG,CACJ,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,4BAA4B,EAAE,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,MAAM,KAAK,GAA4B,EAAE,GAAI,QAAoC,EAAE,CAAC;IACpF,OAAO,KAAK,CAAC,WAAW,CAAC,CAAC;IAC1B,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact JWS (JWT) signing primitive shared by connector auth flows.
|
|
3
|
+
*
|
|
4
|
+
* A first-party connector that authenticates with a signed JWT must sign in
|
|
5
|
+
* TWO places that cannot import each other across the package boundary — the
|
|
6
|
+
* gateway-side sync handler and the connector's own MCP server. Hosting the
|
|
7
|
+
* signer here (the SDK, which both may import) keeps a single source of truth.
|
|
8
|
+
*
|
|
9
|
+
* Uses `node:crypto` only — no runtime dependency, so the SDK stays dep-free.
|
|
10
|
+
*/
|
|
11
|
+
/** base64url-encode the JSON serialization of `value` (a JWT header/payload). */
|
|
12
|
+
export declare function base64UrlJson(value: unknown): string;
|
|
13
|
+
export interface SignJwtOptions {
|
|
14
|
+
readonly header: Record<string, unknown>;
|
|
15
|
+
readonly payload: Record<string, unknown>;
|
|
16
|
+
/** Full PEM text of the signing private key (`-----BEGIN PRIVATE KEY----- …`). */
|
|
17
|
+
readonly privateKeyPem: string;
|
|
18
|
+
/**
|
|
19
|
+
* ECDSA (e.g. ES256) requires `"ieee-p1363"` — the raw `r||s` encoding JWS
|
|
20
|
+
* mandates. Omit for RSA (e.g. RS256), which uses PKCS#1 v1.5 / DER.
|
|
21
|
+
*/
|
|
22
|
+
readonly dsaEncoding?: "ieee-p1363" | "der";
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sign a compact JWS — `base64url(header).base64url(payload).base64url(sig)` —
|
|
26
|
+
* over SHA-256. The digest name is passed explicitly ("sha256") rather than
|
|
27
|
+
* `null` so the signer works on Bun's BoringSSL, which has no default digest.
|
|
28
|
+
*/
|
|
29
|
+
export declare function signJwt(opts: SignJwtOptions): string;
|
|
30
|
+
//# sourceMappingURL=jwt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/crypto/jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,kFAAkF;IAClF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC;CAC7C;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CASpD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact JWS (JWT) signing primitive shared by connector auth flows.
|
|
3
|
+
*
|
|
4
|
+
* A first-party connector that authenticates with a signed JWT must sign in
|
|
5
|
+
* TWO places that cannot import each other across the package boundary — the
|
|
6
|
+
* gateway-side sync handler and the connector's own MCP server. Hosting the
|
|
7
|
+
* signer here (the SDK, which both may import) keeps a single source of truth.
|
|
8
|
+
*
|
|
9
|
+
* Uses `node:crypto` only — no runtime dependency, so the SDK stays dep-free.
|
|
10
|
+
*/
|
|
11
|
+
import crypto from "node:crypto";
|
|
12
|
+
/** base64url-encode the JSON serialization of `value` (a JWT header/payload). */
|
|
13
|
+
export function base64UrlJson(value) {
|
|
14
|
+
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Sign a compact JWS — `base64url(header).base64url(payload).base64url(sig)` —
|
|
18
|
+
* over SHA-256. The digest name is passed explicitly ("sha256") rather than
|
|
19
|
+
* `null` so the signer works on Bun's BoringSSL, which has no default digest.
|
|
20
|
+
*/
|
|
21
|
+
export function signJwt(opts) {
|
|
22
|
+
const signingInput = `${base64UrlJson(opts.header)}.${base64UrlJson(opts.payload)}`;
|
|
23
|
+
const data = Buffer.from(signingInput, "utf8");
|
|
24
|
+
const key = crypto.createPrivateKey(opts.privateKeyPem);
|
|
25
|
+
const signature = opts.dsaEncoding === undefined
|
|
26
|
+
? crypto.sign("sha256", data, key)
|
|
27
|
+
: crypto.sign("sha256", data, { key, dsaEncoding: opts.dsaEncoding });
|
|
28
|
+
return `${signingInput}.${signature.toString("base64url")}`;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=jwt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../src/crypto/jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,iFAAiF;AACjF,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC1E,CAAC;AAcD;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,IAAoB;IAC1C,MAAM,YAAY,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;IACpF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,MAAM,CAAC,gBAAgB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACxD,MAAM,SAAS,GACb,IAAI,CAAC,WAAW,KAAK,SAAS;QAC5B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC;QAClC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1E,OAAO,GAAG,YAAY,IAAI,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google service-account OAuth2 access tokens via the JWT-bearer grant.
|
|
3
|
+
*
|
|
4
|
+
* Google Cloud REST APIs (App Distribution, BigQuery, …) accept a short-lived
|
|
5
|
+
* OAuth2 access token. This mints one from a service-account key (the JSON the
|
|
6
|
+
* developer downloads) by signing an RS256 JWT assertion and exchanging it at
|
|
7
|
+
* the token endpoint — no `googleapis` dependency. Shared so the gateway sync
|
|
8
|
+
* and a connector's MCP server sign identically without duplicating the flow.
|
|
9
|
+
*
|
|
10
|
+
* See: https://developers.google.com/identity/protocols/oauth2/service-account
|
|
11
|
+
*/
|
|
12
|
+
export interface GoogleServiceAccount {
|
|
13
|
+
readonly clientEmail: string;
|
|
14
|
+
readonly privateKey: string;
|
|
15
|
+
readonly tokenUri: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parse a service-account key JSON string into the fields the JWT-bearer flow
|
|
19
|
+
* needs. Returns null on malformed JSON or a missing `client_email` /
|
|
20
|
+
* `private_key`; `token_uri` defaults to Google's endpoint when absent.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseServiceAccountJson(json: string): GoogleServiceAccount | null;
|
|
23
|
+
/**
|
|
24
|
+
* Sign an RS256 JWT assertion for the JWT-bearer grant. `nowMs` is injectable
|
|
25
|
+
* so tests can assert deterministic `iat`/`exp`; `scope` defaults to
|
|
26
|
+
* `cloud-platform`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function signServiceAccountAssertion(sa: GoogleServiceAccount, nowMs?: number, scope?: string): string;
|
|
29
|
+
export type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
30
|
+
/**
|
|
31
|
+
* Exchange a service-account assertion for an OAuth2 access token. `fetchFn` and
|
|
32
|
+
* `nowMs` are injectable for tests; the live path uses the global `fetch`.
|
|
33
|
+
* Returns null when the token endpoint declines the assertion.
|
|
34
|
+
*/
|
|
35
|
+
export declare function mintGoogleAccessToken(sa: GoogleServiceAccount, fetchFn?: FetchLike, nowMs?: number, scope?: string): Promise<string | null>;
|
|
36
|
+
//# sourceMappingURL=service-account-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-account-token.d.ts","sourceRoot":"","sources":["../../src/crypto/service-account-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAUH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAMD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAqBjF;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,EAAE,EAAE,oBAAoB,EACxB,KAAK,GAAE,MAAmB,EAC1B,KAAK,GAAE,MAA6B,GACnC,MAAM,CAaR;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvF;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,oBAAoB,EACxB,OAAO,GAAE,SAAyC,EAClD,KAAK,GAAE,MAAmB,EAC1B,KAAK,GAAE,MAA6B,GACnC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqBxB"}
|