@miosa/sdk 1.2.1 → 1.2.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/README.md +1 -1
- package/dist/index.d.ts +84 -8
- package/dist/index.js +90 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +9 -2
- package/src/resources/admin.ts +11 -0
- package/src/resources/api-keys.ts +16 -0
- package/src/resources/custom_domains.ts +1 -1
- package/src/resources/deployments.test.ts +55 -0
- package/src/resources/deployments.ts +30 -1
- package/src/resources/governance.test.ts +355 -0
- package/src/resources/governance.ts +528 -0
- package/src/resources/sandboxes.test.ts +26 -0
- package/src/resources/sandboxes.ts +31 -0
- package/src/resources/tenant.ts +27 -0
- package/src/resources/webhooks.ts +69 -1
- package/src/types.ts +5 -5
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Webhooks resource — tenant outgoing event delivery.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
6
6
|
|
|
7
7
|
import type { HttpClient } from "../http.js";
|
|
8
8
|
|
|
@@ -66,6 +66,11 @@ export interface WebhookUpdateParams {
|
|
|
66
66
|
[key: string]: unknown;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
export interface WebhookSignatureVerifyOptions {
|
|
70
|
+
/** Maximum age for the webhook timestamp in seconds. Defaults to 300. */
|
|
71
|
+
toleranceSeconds?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
69
74
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
70
75
|
|
|
71
76
|
function unwrap<T>(payload: unknown): T {
|
|
@@ -101,9 +106,72 @@ function idempotencyKey(key?: string): string {
|
|
|
101
106
|
return key ?? randomUUID();
|
|
102
107
|
}
|
|
103
108
|
|
|
109
|
+
function parseSignatureHeader(header: string): {
|
|
110
|
+
timestamp: number;
|
|
111
|
+
signatures: string[];
|
|
112
|
+
} | null {
|
|
113
|
+
const parts = header.split(",").map((part) => part.trim());
|
|
114
|
+
let timestamp: number | null = null;
|
|
115
|
+
const signatures: string[] = [];
|
|
116
|
+
|
|
117
|
+
for (const part of parts) {
|
|
118
|
+
const [key, value] = part.split("=", 2);
|
|
119
|
+
if (!key || !value) continue;
|
|
120
|
+
if (key === "t") {
|
|
121
|
+
const parsed = Number(value);
|
|
122
|
+
if (Number.isFinite(parsed)) timestamp = parsed;
|
|
123
|
+
} else if (key === "v1") {
|
|
124
|
+
signatures.push(value);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (timestamp == null || signatures.length === 0) return null;
|
|
129
|
+
return { timestamp, signatures };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Verify a MIOSA webhook signature header.
|
|
134
|
+
*
|
|
135
|
+
* Header format: `t=<unix_seconds>,v1=<hex_hmac_sha256>`.
|
|
136
|
+
*/
|
|
137
|
+
export function verifySignature(
|
|
138
|
+
body: string | Buffer | Uint8Array,
|
|
139
|
+
header: string,
|
|
140
|
+
secret: string,
|
|
141
|
+
options: WebhookSignatureVerifyOptions = {},
|
|
142
|
+
): boolean {
|
|
143
|
+
const parsed = parseSignatureHeader(header);
|
|
144
|
+
if (!parsed) return false;
|
|
145
|
+
|
|
146
|
+
const toleranceSeconds = options.toleranceSeconds ?? 300;
|
|
147
|
+
const ageSeconds = Math.abs(Math.floor(Date.now() / 1000) - parsed.timestamp);
|
|
148
|
+
if (ageSeconds > toleranceSeconds) {
|
|
149
|
+
throw new Error("Webhook signature timestamp is too old");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const bodyBuffer = Buffer.isBuffer(body) ? body : Buffer.from(body);
|
|
153
|
+
const signedPayload = Buffer.concat([
|
|
154
|
+
Buffer.from(`${parsed.timestamp}.`),
|
|
155
|
+
bodyBuffer,
|
|
156
|
+
]);
|
|
157
|
+
const expected = createHmac("sha256", secret)
|
|
158
|
+
.update(signedPayload)
|
|
159
|
+
.digest("hex");
|
|
160
|
+
const expectedBuffer = Buffer.from(expected, "hex");
|
|
161
|
+
|
|
162
|
+
return parsed.signatures.some((signature) => {
|
|
163
|
+
const actualBuffer = Buffer.from(signature, "hex");
|
|
164
|
+
if (actualBuffer.length !== expectedBuffer.length) return false;
|
|
165
|
+
return timingSafeEqual(actualBuffer, expectedBuffer);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
104
169
|
// ── Main resource ─────────────────────────────────────────────────────────────
|
|
105
170
|
|
|
106
171
|
export class Webhooks {
|
|
172
|
+
static verifySignature = verifySignature;
|
|
173
|
+
static verify_signature = verifySignature;
|
|
174
|
+
|
|
107
175
|
constructor(private readonly http: HttpClient) {}
|
|
108
176
|
|
|
109
177
|
async list(params: WebhookListParams = {}): Promise<WebhookData[]> {
|
package/src/types.ts
CHANGED
|
@@ -78,9 +78,9 @@ export interface ComputerData {
|
|
|
78
78
|
id: ComputerId;
|
|
79
79
|
name: string;
|
|
80
80
|
/**
|
|
81
|
-
* URL-safe identifier used in preview URLs:
|
|
82
|
-
*
|
|
83
|
-
*
|
|
81
|
+
* URL-safe identifier used in preview URLs: `https://{port}-{slug}.sandbox.{preview_domain}`.
|
|
82
|
+
* Falls back to the computer id when no slug is assigned. The domain is the
|
|
83
|
+
* tenant's white-label `preview_domain` (server-provided) — never hardcode it.
|
|
84
84
|
*/
|
|
85
85
|
slug: string;
|
|
86
86
|
status: ComputerStatus;
|
|
@@ -91,9 +91,9 @@ export interface ComputerData {
|
|
|
91
91
|
metadata: Record<string, string>;
|
|
92
92
|
/** Controls who can access the HTTP preview URL. Defaults to `"public"`. */
|
|
93
93
|
visibility: ComputerVisibility;
|
|
94
|
-
/** Public ingress root, e.g. `https://<slug>.sandbox.<preview_domain
|
|
94
|
+
/** Public ingress root, e.g. `https://<slug>.sandbox.<preview_domain>` (server-provided). */
|
|
95
95
|
sandbox_url?: string;
|
|
96
|
-
/** Tenant's white-label preview/base domain
|
|
96
|
+
/** Tenant's white-label preview/base domain (e.g. `cliniciq.com`). Use to build preview URLs. */
|
|
97
97
|
preview_domain?: string;
|
|
98
98
|
/** KasmVNC URL for desktop templates. */
|
|
99
99
|
desktop_url?: string;
|