@qnsp/tenant-sdk 0.3.1 → 0.3.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 +379 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +251 -3
- package/dist/index.js.map +1 -1
- package/dist/sdk-package-version.d.ts +2 -0
- package/dist/sdk-package-version.d.ts.map +1 -0
- package/dist/sdk-package-version.js +6 -0
- package/dist/sdk-package-version.js.map +1 -0
- package/package.json +27 -13
- package/.turbo/turbo-build.log +0 -4
- package/src/index.test.ts +0 -237
- package/src/index.ts +0 -1095
- package/src/observability.ts +0 -152
- package/src/validation.ts +0 -21
- package/tsconfig.build.json +0 -10
- package/tsconfig.json +0 -10
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -9
package/src/index.test.ts
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import { clearActivationCache } from "@qnsp/sdk-activation";
|
|
2
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
import { TenantClient } from "./index.js";
|
|
4
|
-
|
|
5
|
-
describe("TenantClient Security Tests", () => {
|
|
6
|
-
const mockFetch = vi.fn();
|
|
7
|
-
global.fetch = mockFetch;
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
clearActivationCache();
|
|
11
|
-
vi.clearAllMocks();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe("HTTPS Enforcement", () => {
|
|
15
|
-
it("should reject HTTP URLs in production", () => {
|
|
16
|
-
const originalEnv = process.env["NODE_ENV"];
|
|
17
|
-
process.env["NODE_ENV"] = "production";
|
|
18
|
-
|
|
19
|
-
expect(() => {
|
|
20
|
-
new TenantClient({
|
|
21
|
-
baseUrl: "http://example.com",
|
|
22
|
-
apiKey: "test-api-key",
|
|
23
|
-
});
|
|
24
|
-
}).toThrow("baseUrl must use HTTPS in production");
|
|
25
|
-
|
|
26
|
-
process.env["NODE_ENV"] = originalEnv;
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("should allow HTTP localhost in development", () => {
|
|
30
|
-
const originalEnv = process.env["NODE_ENV"];
|
|
31
|
-
process.env["NODE_ENV"] = "development";
|
|
32
|
-
|
|
33
|
-
expect(() => {
|
|
34
|
-
new TenantClient({
|
|
35
|
-
baseUrl: "http://localhost:3000",
|
|
36
|
-
apiKey: "test-api-key",
|
|
37
|
-
});
|
|
38
|
-
}).not.toThrow();
|
|
39
|
-
|
|
40
|
-
process.env["NODE_ENV"] = originalEnv;
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe("Input Validation", () => {
|
|
45
|
-
const client = new TenantClient({
|
|
46
|
-
baseUrl: "https://api.example.com",
|
|
47
|
-
apiKey: "test-api-key",
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("should reject invalid UUIDs", async () => {
|
|
51
|
-
await expect(client.getTenant("not-a-uuid")).rejects.toThrow("Invalid id");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("should reject SQL injection in tenantId", async () => {
|
|
55
|
-
await expect(client.getTenant("'; DROP TABLE tenants; --")).rejects.toThrow("Invalid id");
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("should reject path traversal in tenantId", async () => {
|
|
59
|
-
await expect(client.getTenant("../../etc/passwd")).rejects.toThrow("Invalid id");
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("should reject XSS attempts in tenantId", async () => {
|
|
63
|
-
await expect(client.getTenant("<script>alert('xss')</script>")).rejects.toThrow("Invalid id");
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
describe("Error Message Sanitization", () => {
|
|
68
|
-
const client = new TenantClient({
|
|
69
|
-
baseUrl: "https://api.example.com",
|
|
70
|
-
apiKey: "test-api-key",
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("should not expose sensitive data in error messages", async () => {
|
|
74
|
-
// activation mock (first network call per client instance)
|
|
75
|
-
mockFetch.mockResolvedValueOnce({
|
|
76
|
-
ok: true,
|
|
77
|
-
status: 200,
|
|
78
|
-
headers: new Headers(),
|
|
79
|
-
json: async () => ({
|
|
80
|
-
activated: true,
|
|
81
|
-
tenantId: "a1b2c3d4-e5f6-4789-8abc-def012345678",
|
|
82
|
-
tier: "dev-pro",
|
|
83
|
-
activationToken: "tok_test",
|
|
84
|
-
expiresInSeconds: 3600,
|
|
85
|
-
activatedAt: new Date().toISOString(),
|
|
86
|
-
limits: {
|
|
87
|
-
storageGB: 50,
|
|
88
|
-
apiCalls: 100_000,
|
|
89
|
-
enclavesEnabled: false,
|
|
90
|
-
aiTrainingEnabled: false,
|
|
91
|
-
aiInferenceEnabled: true,
|
|
92
|
-
sseEnabled: true,
|
|
93
|
-
vaultEnabled: true,
|
|
94
|
-
},
|
|
95
|
-
}),
|
|
96
|
-
});
|
|
97
|
-
mockFetch.mockResolvedValueOnce({
|
|
98
|
-
ok: false,
|
|
99
|
-
status: 500,
|
|
100
|
-
statusText: "Internal Server Error",
|
|
101
|
-
headers: new Headers(),
|
|
102
|
-
text: async () =>
|
|
103
|
-
JSON.stringify({
|
|
104
|
-
error: "Database connection failed",
|
|
105
|
-
stack: "at TenantService.getTenant()",
|
|
106
|
-
apiKey: "api_key_example_123",
|
|
107
|
-
password: "secret123",
|
|
108
|
-
}),
|
|
109
|
-
});
|
|
110
|
-
// second call in the IIFE below — activationPromise already resolved
|
|
111
|
-
mockFetch.mockResolvedValueOnce({
|
|
112
|
-
ok: false,
|
|
113
|
-
status: 500,
|
|
114
|
-
statusText: "Internal Server Error",
|
|
115
|
-
headers: new Headers(),
|
|
116
|
-
text: async () =>
|
|
117
|
-
JSON.stringify({
|
|
118
|
-
error: "Database connection failed",
|
|
119
|
-
stack: "at TenantService.getTenant()",
|
|
120
|
-
apiKey: "api_key_example_123",
|
|
121
|
-
password: "secret123",
|
|
122
|
-
}),
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
await expect(client.getTenant("123e4567-e89b-12d3-a456-426614174000")).rejects.toThrow(
|
|
126
|
-
"Tenant API error: 500 Internal Server Error",
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
const errorMessage = await (async () => {
|
|
130
|
-
try {
|
|
131
|
-
await client.getTenant("123e4567-e89b-12d3-a456-426614174000");
|
|
132
|
-
return "";
|
|
133
|
-
} catch (error) {
|
|
134
|
-
return error instanceof Error ? error.message : String(error);
|
|
135
|
-
}
|
|
136
|
-
})();
|
|
137
|
-
|
|
138
|
-
expect(errorMessage).not.toContain("api_key_example_123");
|
|
139
|
-
expect(errorMessage).not.toContain("secret123");
|
|
140
|
-
expect(errorMessage).not.toContain("Database connection failed");
|
|
141
|
-
expect(errorMessage).not.toContain("stack");
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
describe("Rate Limiting", () => {
|
|
146
|
-
const client = new TenantClient({
|
|
147
|
-
baseUrl: "https://api.example.com",
|
|
148
|
-
apiKey: "test-api-key",
|
|
149
|
-
maxRetries: 2,
|
|
150
|
-
retryDelayMs: 10,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("should retry on 429 with Retry-After header", async () => {
|
|
154
|
-
// activation mock consumed before mockImplementation kicks in
|
|
155
|
-
mockFetch.mockResolvedValueOnce({
|
|
156
|
-
ok: true,
|
|
157
|
-
status: 200,
|
|
158
|
-
headers: new Headers(),
|
|
159
|
-
json: async () => ({
|
|
160
|
-
activated: true,
|
|
161
|
-
tenantId: "a1b2c3d4-e5f6-4789-8abc-def012345678",
|
|
162
|
-
tier: "dev-pro",
|
|
163
|
-
activationToken: "tok_test",
|
|
164
|
-
expiresInSeconds: 3600,
|
|
165
|
-
activatedAt: new Date().toISOString(),
|
|
166
|
-
limits: {
|
|
167
|
-
storageGB: 50,
|
|
168
|
-
apiCalls: 100_000,
|
|
169
|
-
enclavesEnabled: false,
|
|
170
|
-
aiTrainingEnabled: false,
|
|
171
|
-
aiInferenceEnabled: true,
|
|
172
|
-
sseEnabled: true,
|
|
173
|
-
vaultEnabled: true,
|
|
174
|
-
},
|
|
175
|
-
}),
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
let attemptCount = 0;
|
|
179
|
-
mockFetch.mockImplementation(async () => {
|
|
180
|
-
attemptCount++;
|
|
181
|
-
if (attemptCount === 1) {
|
|
182
|
-
return {
|
|
183
|
-
ok: false,
|
|
184
|
-
status: 429,
|
|
185
|
-
statusText: "Too Many Requests",
|
|
186
|
-
headers: new Headers({ "Retry-After": "1" }),
|
|
187
|
-
text: async () => "",
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
return {
|
|
191
|
-
ok: true,
|
|
192
|
-
status: 200,
|
|
193
|
-
headers: new Headers(),
|
|
194
|
-
json: async () => ({
|
|
195
|
-
id: "123e4567-e89b-12d3-a456-426614174000",
|
|
196
|
-
name: "test-tenant",
|
|
197
|
-
slug: "test-tenant",
|
|
198
|
-
status: "active",
|
|
199
|
-
plan: "basic",
|
|
200
|
-
region: "us-east-1",
|
|
201
|
-
complianceTags: [],
|
|
202
|
-
metadata: {},
|
|
203
|
-
security: {
|
|
204
|
-
controlPlaneTokenSha256: null,
|
|
205
|
-
pqcSignatures: [],
|
|
206
|
-
hardwareProvider: null,
|
|
207
|
-
attestationStatus: null,
|
|
208
|
-
attestationProof: null,
|
|
209
|
-
},
|
|
210
|
-
domains: [],
|
|
211
|
-
createdAt: "2024-01-01T00:00:00Z",
|
|
212
|
-
updatedAt: "2024-01-01T00:00:00Z",
|
|
213
|
-
}),
|
|
214
|
-
};
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
const result = await client.getTenant("123e4567-e89b-12d3-a456-426614174000");
|
|
218
|
-
|
|
219
|
-
expect(result.id).toBe("123e4567-e89b-12d3-a456-426614174000");
|
|
220
|
-
expect(attemptCount).toBe(2);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it("should fail after max retries", async () => {
|
|
224
|
-
mockFetch.mockResolvedValue({
|
|
225
|
-
ok: false,
|
|
226
|
-
status: 429,
|
|
227
|
-
statusText: "Too Many Requests",
|
|
228
|
-
headers: new Headers(),
|
|
229
|
-
text: async () => "",
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
await expect(client.getTenant("123e4567-e89b-12d3-a456-426614174000")).rejects.toThrow(
|
|
233
|
-
"Rate limit exceeded after 2 retries",
|
|
234
|
-
);
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
});
|