@de-otio/chaoskb-server 0.2.0

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.
Files changed (157) hide show
  1. package/dist/lib/admin-handler/index.d.ts +22 -0
  2. package/dist/lib/admin-handler/index.d.ts.map +1 -0
  3. package/dist/lib/admin-handler/index.js +92 -0
  4. package/dist/lib/admin-handler/index.js.map +1 -0
  5. package/dist/lib/admin-handler/index.ts +123 -0
  6. package/dist/lib/admin-handler/routes/metrics.d.ts +11 -0
  7. package/dist/lib/admin-handler/routes/metrics.d.ts.map +1 -0
  8. package/dist/lib/admin-handler/routes/metrics.js +200 -0
  9. package/dist/lib/admin-handler/routes/metrics.js.map +1 -0
  10. package/dist/lib/admin-handler/routes/metrics.ts +234 -0
  11. package/dist/lib/admin-handler/routes/overview.d.ts +9 -0
  12. package/dist/lib/admin-handler/routes/overview.d.ts.map +1 -0
  13. package/dist/lib/admin-handler/routes/overview.js +110 -0
  14. package/dist/lib/admin-handler/routes/overview.js.map +1 -0
  15. package/dist/lib/admin-handler/routes/overview.ts +133 -0
  16. package/dist/lib/admin-handler/routes/tenants.d.ts +10 -0
  17. package/dist/lib/admin-handler/routes/tenants.d.ts.map +1 -0
  18. package/dist/lib/admin-handler/routes/tenants.js +108 -0
  19. package/dist/lib/admin-handler/routes/tenants.js.map +1 -0
  20. package/dist/lib/admin-handler/routes/tenants.ts +134 -0
  21. package/dist/lib/chaoskb-stack.d.ts +22 -0
  22. package/dist/lib/chaoskb-stack.d.ts.map +1 -0
  23. package/dist/lib/chaoskb-stack.js +60 -0
  24. package/dist/lib/chaoskb-stack.js.map +1 -0
  25. package/dist/lib/constructs/admin-api.d.ts +16 -0
  26. package/dist/lib/constructs/admin-api.d.ts.map +1 -0
  27. package/dist/lib/constructs/admin-api.js +93 -0
  28. package/dist/lib/constructs/admin-api.js.map +1 -0
  29. package/dist/lib/constructs/admin-dashboard.d.ts +18 -0
  30. package/dist/lib/constructs/admin-dashboard.d.ts.map +1 -0
  31. package/dist/lib/constructs/admin-dashboard.js +172 -0
  32. package/dist/lib/constructs/admin-dashboard.js.map +1 -0
  33. package/dist/lib/constructs/api.d.ts +17 -0
  34. package/dist/lib/constructs/api.d.ts.map +1 -0
  35. package/dist/lib/constructs/api.js +81 -0
  36. package/dist/lib/constructs/api.js.map +1 -0
  37. package/dist/lib/constructs/auth.d.ts +11 -0
  38. package/dist/lib/constructs/auth.d.ts.map +1 -0
  39. package/dist/lib/constructs/auth.js +18 -0
  40. package/dist/lib/constructs/auth.js.map +1 -0
  41. package/dist/lib/constructs/blob-store.d.ts +10 -0
  42. package/dist/lib/constructs/blob-store.d.ts.map +1 -0
  43. package/dist/lib/constructs/blob-store.js +31 -0
  44. package/dist/lib/constructs/blob-store.js.map +1 -0
  45. package/dist/lib/deploy-cli.d.ts +3 -0
  46. package/dist/lib/deploy-cli.d.ts.map +1 -0
  47. package/dist/lib/deploy-cli.js +49 -0
  48. package/dist/lib/deploy-cli.js.map +1 -0
  49. package/dist/lib/handler/index.d.ts +23 -0
  50. package/dist/lib/handler/index.d.ts.map +1 -0
  51. package/dist/lib/handler/index.js +276 -0
  52. package/dist/lib/handler/index.js.map +1 -0
  53. package/dist/lib/handler/index.ts +372 -0
  54. package/dist/lib/handler/logger.d.ts +16 -0
  55. package/dist/lib/handler/logger.d.ts.map +1 -0
  56. package/dist/lib/handler/logger.js +26 -0
  57. package/dist/lib/handler/logger.js.map +1 -0
  58. package/dist/lib/handler/logger.ts +36 -0
  59. package/dist/lib/handler/middleware/input-validation.d.ts +6 -0
  60. package/dist/lib/handler/middleware/input-validation.d.ts.map +1 -0
  61. package/dist/lib/handler/middleware/input-validation.js +36 -0
  62. package/dist/lib/handler/middleware/input-validation.js.map +1 -0
  63. package/dist/lib/handler/middleware/input-validation.ts +44 -0
  64. package/dist/lib/handler/middleware/rate-limit.d.ts +14 -0
  65. package/dist/lib/handler/middleware/rate-limit.d.ts.map +1 -0
  66. package/dist/lib/handler/middleware/rate-limit.js +94 -0
  67. package/dist/lib/handler/middleware/rate-limit.js.map +1 -0
  68. package/dist/lib/handler/middleware/rate-limit.ts +121 -0
  69. package/dist/lib/handler/middleware/ssh-auth.d.ts +48 -0
  70. package/dist/lib/handler/middleware/ssh-auth.d.ts.map +1 -0
  71. package/dist/lib/handler/middleware/ssh-auth.js +256 -0
  72. package/dist/lib/handler/middleware/ssh-auth.js.map +1 -0
  73. package/dist/lib/handler/middleware/ssh-auth.ts +300 -0
  74. package/dist/lib/handler/routes/audit.d.ts +24 -0
  75. package/dist/lib/handler/routes/audit.d.ts.map +1 -0
  76. package/dist/lib/handler/routes/audit.js +94 -0
  77. package/dist/lib/handler/routes/audit.js.map +1 -0
  78. package/dist/lib/handler/routes/audit.ts +101 -0
  79. package/dist/lib/handler/routes/blobs.d.ts +13 -0
  80. package/dist/lib/handler/routes/blobs.d.ts.map +1 -0
  81. package/dist/lib/handler/routes/blobs.js +298 -0
  82. package/dist/lib/handler/routes/blobs.js.map +1 -0
  83. package/dist/lib/handler/routes/blobs.ts +348 -0
  84. package/dist/lib/handler/routes/devices.d.ts +48 -0
  85. package/dist/lib/handler/routes/devices.d.ts.map +1 -0
  86. package/dist/lib/handler/routes/devices.js +394 -0
  87. package/dist/lib/handler/routes/devices.js.map +1 -0
  88. package/dist/lib/handler/routes/devices.ts +458 -0
  89. package/dist/lib/handler/routes/export.d.ts +9 -0
  90. package/dist/lib/handler/routes/export.d.ts.map +1 -0
  91. package/dist/lib/handler/routes/export.js +40 -0
  92. package/dist/lib/handler/routes/export.js.map +1 -0
  93. package/dist/lib/handler/routes/export.ts +55 -0
  94. package/dist/lib/handler/routes/github.d.ts +31 -0
  95. package/dist/lib/handler/routes/github.d.ts.map +1 -0
  96. package/dist/lib/handler/routes/github.js +118 -0
  97. package/dist/lib/handler/routes/github.js.map +1 -0
  98. package/dist/lib/handler/routes/github.ts +162 -0
  99. package/dist/lib/handler/routes/health.d.ts +6 -0
  100. package/dist/lib/handler/routes/health.d.ts.map +1 -0
  101. package/dist/lib/handler/routes/health.js +14 -0
  102. package/dist/lib/handler/routes/health.js.map +1 -0
  103. package/dist/lib/handler/routes/health.ts +10 -0
  104. package/dist/lib/handler/routes/invites.d.ts +24 -0
  105. package/dist/lib/handler/routes/invites.d.ts.map +1 -0
  106. package/dist/lib/handler/routes/invites.js +445 -0
  107. package/dist/lib/handler/routes/invites.js.map +1 -0
  108. package/dist/lib/handler/routes/invites.ts +527 -0
  109. package/dist/lib/handler/routes/notifications.d.ts +39 -0
  110. package/dist/lib/handler/routes/notifications.d.ts.map +1 -0
  111. package/dist/lib/handler/routes/notifications.js +150 -0
  112. package/dist/lib/handler/routes/notifications.js.map +1 -0
  113. package/dist/lib/handler/routes/notifications.ts +163 -0
  114. package/dist/lib/handler/routes/projects.d.ts +24 -0
  115. package/dist/lib/handler/routes/projects.d.ts.map +1 -0
  116. package/dist/lib/handler/routes/projects.js +47 -0
  117. package/dist/lib/handler/routes/projects.js.map +1 -0
  118. package/dist/lib/handler/routes/projects.ts +69 -0
  119. package/dist/lib/handler/routes/register.d.ts +19 -0
  120. package/dist/lib/handler/routes/register.d.ts.map +1 -0
  121. package/dist/lib/handler/routes/register.js +327 -0
  122. package/dist/lib/handler/routes/register.js.map +1 -0
  123. package/dist/lib/handler/routes/register.ts +363 -0
  124. package/dist/lib/handler/routes/restore.d.ts +9 -0
  125. package/dist/lib/handler/routes/restore.d.ts.map +1 -0
  126. package/dist/lib/handler/routes/restore.js +52 -0
  127. package/dist/lib/handler/routes/restore.js.map +1 -0
  128. package/dist/lib/handler/routes/restore.ts +73 -0
  129. package/dist/lib/handler/routes/revocation.d.ts +13 -0
  130. package/dist/lib/handler/routes/revocation.d.ts.map +1 -0
  131. package/dist/lib/handler/routes/revocation.js +63 -0
  132. package/dist/lib/handler/routes/revocation.js.map +1 -0
  133. package/dist/lib/handler/routes/revocation.ts +87 -0
  134. package/dist/lib/handler/routes/rotation.d.ts +24 -0
  135. package/dist/lib/handler/routes/rotation.d.ts.map +1 -0
  136. package/dist/lib/handler/routes/rotation.js +291 -0
  137. package/dist/lib/handler/routes/rotation.js.map +1 -0
  138. package/dist/lib/handler/routes/rotation.ts +336 -0
  139. package/dist/lib/handler/routes/tenants.d.ts +11 -0
  140. package/dist/lib/handler/routes/tenants.d.ts.map +1 -0
  141. package/dist/lib/handler/routes/tenants.js +181 -0
  142. package/dist/lib/handler/routes/tenants.js.map +1 -0
  143. package/dist/lib/handler/routes/tenants.ts +198 -0
  144. package/dist/lib/handler/routes/wrapped-key.d.ts +21 -0
  145. package/dist/lib/handler/routes/wrapped-key.d.ts.map +1 -0
  146. package/dist/lib/handler/routes/wrapped-key.js +76 -0
  147. package/dist/lib/handler/routes/wrapped-key.js.map +1 -0
  148. package/dist/lib/handler/routes/wrapped-key.ts +108 -0
  149. package/dist/lib/index.d.ts +7 -0
  150. package/dist/lib/index.d.ts.map +1 -0
  151. package/dist/lib/index.js +16 -0
  152. package/dist/lib/index.js.map +1 -0
  153. package/dist/vitest.config.d.ts +3 -0
  154. package/dist/vitest.config.d.ts.map +1 -0
  155. package/dist/vitest.config.js +18 -0
  156. package/dist/vitest.config.js.map +1 -0
  157. package/package.json +61 -0
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleCreateTenant = handleCreateTenant;
37
+ exports.handleListTenants = handleListTenants;
38
+ exports.handleDeleteTenant = handleDeleteTenant;
39
+ const crypto = __importStar(require("crypto"));
40
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
41
+ const logger_js_1 = require("../logger.js");
42
+ async function handleCreateTenant(body, tenantId, ddb, tableName) {
43
+ if (!body) {
44
+ return {
45
+ statusCode: 400,
46
+ headers: { 'Content-Type': 'application/json' },
47
+ body: JSON.stringify({ error: 'invalid_request', message: 'Request body is required' }),
48
+ };
49
+ }
50
+ let request;
51
+ try {
52
+ request = JSON.parse(body);
53
+ }
54
+ catch {
55
+ return {
56
+ statusCode: 400,
57
+ headers: { 'Content-Type': 'application/json' },
58
+ body: JSON.stringify({ error: 'invalid_request', message: 'Invalid JSON body' }),
59
+ };
60
+ }
61
+ if (!request.name || typeof request.name !== 'string') {
62
+ return {
63
+ statusCode: 400,
64
+ headers: { 'Content-Type': 'application/json' },
65
+ body: JSON.stringify({ error: 'invalid_request', message: 'name is required' }),
66
+ };
67
+ }
68
+ const projectTenantId = crypto.randomUUID().replace(/-/g, '').slice(0, 16);
69
+ const now = new Date().toISOString();
70
+ try {
71
+ await ddb.send(new lib_dynamodb_1.PutCommand({
72
+ TableName: tableName,
73
+ Item: {
74
+ PK: `TENANT#${tenantId}`,
75
+ SK: `PROJECT#${request.name}`,
76
+ projectTenantId,
77
+ name: request.name,
78
+ createdAt: now,
79
+ updatedAt: now,
80
+ },
81
+ ConditionExpression: 'attribute_not_exists(SK)',
82
+ }));
83
+ }
84
+ catch (err) {
85
+ if (err.name === 'ConditionalCheckFailedException') {
86
+ return {
87
+ statusCode: 409,
88
+ headers: { 'Content-Type': 'application/json' },
89
+ body: JSON.stringify({ error: 'already_exists', message: 'A project with this name already exists' }),
90
+ };
91
+ }
92
+ throw err;
93
+ }
94
+ logger_js_1.logger.info('Tenant project created', { tenantId, operation: 'CREATE_TENANT' });
95
+ return {
96
+ statusCode: 201,
97
+ headers: { 'Content-Type': 'application/json' },
98
+ body: JSON.stringify({ tenantId: projectTenantId, name: request.name }),
99
+ };
100
+ }
101
+ async function handleListTenants(tenantId, ddb, tableName) {
102
+ const result = await ddb.send(new lib_dynamodb_1.QueryCommand({
103
+ TableName: tableName,
104
+ KeyConditionExpression: 'PK = :pk AND begins_with(SK, :prefix)',
105
+ ExpressionAttributeValues: {
106
+ ':pk': `TENANT#${tenantId}`,
107
+ ':prefix': 'PROJECT#',
108
+ },
109
+ }));
110
+ const tenants = (result.Items ?? []).map((item) => ({
111
+ tenantId: item['projectTenantId'],
112
+ name: item['name'],
113
+ createdAt: item['createdAt'],
114
+ }));
115
+ return {
116
+ statusCode: 200,
117
+ headers: { 'Content-Type': 'application/json' },
118
+ body: JSON.stringify({ tenants }),
119
+ };
120
+ }
121
+ async function handleDeleteTenant(projectTenantId, tenantId, ddb, tableName) {
122
+ // First find the project record
123
+ const projects = await ddb.send(new lib_dynamodb_1.QueryCommand({
124
+ TableName: tableName,
125
+ KeyConditionExpression: 'PK = :pk AND begins_with(SK, :prefix)',
126
+ ExpressionAttributeValues: {
127
+ ':pk': `TENANT#${tenantId}`,
128
+ ':prefix': 'PROJECT#',
129
+ },
130
+ }));
131
+ const projectItem = (projects.Items ?? []).find((item) => item['projectTenantId'] === projectTenantId);
132
+ if (!projectItem) {
133
+ return {
134
+ statusCode: 404,
135
+ headers: { 'Content-Type': 'application/json' },
136
+ body: JSON.stringify({ error: 'not_found', message: 'Tenant not found' }),
137
+ };
138
+ }
139
+ // Delete all blobs in the project tenant's partition
140
+ const blobsResult = await ddb.send(new lib_dynamodb_1.QueryCommand({
141
+ TableName: tableName,
142
+ KeyConditionExpression: 'PK = :pk',
143
+ ExpressionAttributeValues: {
144
+ ':pk': `TENANT#${projectTenantId}`,
145
+ },
146
+ ProjectionExpression: 'PK, SK',
147
+ }));
148
+ const blobItems = blobsResult.Items ?? [];
149
+ // Batch delete in groups of 25
150
+ for (let i = 0; i < blobItems.length; i += 25) {
151
+ const batch = blobItems.slice(i, i + 25);
152
+ await ddb.send(new lib_dynamodb_1.BatchWriteCommand({
153
+ RequestItems: {
154
+ [tableName]: batch.map((item) => ({
155
+ DeleteRequest: {
156
+ Key: { PK: item['PK'], SK: item['SK'] },
157
+ },
158
+ })),
159
+ },
160
+ }));
161
+ }
162
+ // Delete the project record itself
163
+ await ddb.send(new lib_dynamodb_1.BatchWriteCommand({
164
+ RequestItems: {
165
+ [tableName]: [
166
+ {
167
+ DeleteRequest: {
168
+ Key: { PK: projectItem['PK'], SK: projectItem['SK'] },
169
+ },
170
+ },
171
+ ],
172
+ },
173
+ }));
174
+ logger_js_1.logger.info('Tenant deleted', { tenantId, operation: 'DELETE_TENANT' });
175
+ return {
176
+ statusCode: 200,
177
+ headers: { 'Content-Type': 'application/json' },
178
+ body: JSON.stringify({ deleted: true }),
179
+ };
180
+ }
181
+ //# sourceMappingURL=tenants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenants.js","sourceRoot":"","sources":["../../../../lib/handler/routes/tenants.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,198 @@
1
+ import * as crypto from 'crypto';
2
+ import {
3
+ DynamoDBDocumentClient,
4
+ PutCommand,
5
+ QueryCommand,
6
+ BatchWriteCommand,
7
+ } from '@aws-sdk/lib-dynamodb';
8
+ import { logger } from '../logger.js';
9
+
10
+ interface HandlerResponse {
11
+ statusCode: number;
12
+ body: string;
13
+ headers: Record<string, string>;
14
+ }
15
+
16
+ export async function handleCreateTenant(
17
+ body: string | null | undefined,
18
+ tenantId: string,
19
+ ddb: DynamoDBDocumentClient,
20
+ tableName: string,
21
+ ): Promise<HandlerResponse> {
22
+ if (!body) {
23
+ return {
24
+ statusCode: 400,
25
+ headers: { 'Content-Type': 'application/json' },
26
+ body: JSON.stringify({ error: 'invalid_request', message: 'Request body is required' }),
27
+ };
28
+ }
29
+
30
+ let request: { name?: string };
31
+ try {
32
+ request = JSON.parse(body);
33
+ } catch {
34
+ return {
35
+ statusCode: 400,
36
+ headers: { 'Content-Type': 'application/json' },
37
+ body: JSON.stringify({ error: 'invalid_request', message: 'Invalid JSON body' }),
38
+ };
39
+ }
40
+
41
+ if (!request.name || typeof request.name !== 'string') {
42
+ return {
43
+ statusCode: 400,
44
+ headers: { 'Content-Type': 'application/json' },
45
+ body: JSON.stringify({ error: 'invalid_request', message: 'name is required' }),
46
+ };
47
+ }
48
+
49
+ const projectTenantId = crypto.randomUUID().replace(/-/g, '').slice(0, 16);
50
+ const now = new Date().toISOString();
51
+
52
+ try {
53
+ await ddb.send(
54
+ new PutCommand({
55
+ TableName: tableName,
56
+ Item: {
57
+ PK: `TENANT#${tenantId}`,
58
+ SK: `PROJECT#${request.name}`,
59
+ projectTenantId,
60
+ name: request.name,
61
+ createdAt: now,
62
+ updatedAt: now,
63
+ },
64
+ ConditionExpression: 'attribute_not_exists(SK)',
65
+ }),
66
+ );
67
+ } catch (err: unknown) {
68
+ if ((err as { name?: string }).name === 'ConditionalCheckFailedException') {
69
+ return {
70
+ statusCode: 409,
71
+ headers: { 'Content-Type': 'application/json' },
72
+ body: JSON.stringify({ error: 'already_exists', message: 'A project with this name already exists' }),
73
+ };
74
+ }
75
+ throw err;
76
+ }
77
+
78
+ logger.info('Tenant project created', { tenantId, operation: 'CREATE_TENANT' });
79
+
80
+ return {
81
+ statusCode: 201,
82
+ headers: { 'Content-Type': 'application/json' },
83
+ body: JSON.stringify({ tenantId: projectTenantId, name: request.name }),
84
+ };
85
+ }
86
+
87
+ export async function handleListTenants(
88
+ tenantId: string,
89
+ ddb: DynamoDBDocumentClient,
90
+ tableName: string,
91
+ ): Promise<HandlerResponse> {
92
+ const result = await ddb.send(
93
+ new QueryCommand({
94
+ TableName: tableName,
95
+ KeyConditionExpression: 'PK = :pk AND begins_with(SK, :prefix)',
96
+ ExpressionAttributeValues: {
97
+ ':pk': `TENANT#${tenantId}`,
98
+ ':prefix': 'PROJECT#',
99
+ },
100
+ }),
101
+ );
102
+
103
+ const tenants = (result.Items ?? []).map((item) => ({
104
+ tenantId: item['projectTenantId'] as string,
105
+ name: item['name'] as string,
106
+ createdAt: item['createdAt'] as string,
107
+ }));
108
+
109
+ return {
110
+ statusCode: 200,
111
+ headers: { 'Content-Type': 'application/json' },
112
+ body: JSON.stringify({ tenants }),
113
+ };
114
+ }
115
+
116
+ export async function handleDeleteTenant(
117
+ projectTenantId: string,
118
+ tenantId: string,
119
+ ddb: DynamoDBDocumentClient,
120
+ tableName: string,
121
+ ): Promise<HandlerResponse> {
122
+ // First find the project record
123
+ const projects = await ddb.send(
124
+ new QueryCommand({
125
+ TableName: tableName,
126
+ KeyConditionExpression: 'PK = :pk AND begins_with(SK, :prefix)',
127
+ ExpressionAttributeValues: {
128
+ ':pk': `TENANT#${tenantId}`,
129
+ ':prefix': 'PROJECT#',
130
+ },
131
+ }),
132
+ );
133
+
134
+ const projectItem = (projects.Items ?? []).find(
135
+ (item) => item['projectTenantId'] === projectTenantId,
136
+ );
137
+
138
+ if (!projectItem) {
139
+ return {
140
+ statusCode: 404,
141
+ headers: { 'Content-Type': 'application/json' },
142
+ body: JSON.stringify({ error: 'not_found', message: 'Tenant not found' }),
143
+ };
144
+ }
145
+
146
+ // Delete all blobs in the project tenant's partition
147
+ const blobsResult = await ddb.send(
148
+ new QueryCommand({
149
+ TableName: tableName,
150
+ KeyConditionExpression: 'PK = :pk',
151
+ ExpressionAttributeValues: {
152
+ ':pk': `TENANT#${projectTenantId}`,
153
+ },
154
+ ProjectionExpression: 'PK, SK',
155
+ }),
156
+ );
157
+
158
+ const blobItems = blobsResult.Items ?? [];
159
+
160
+ // Batch delete in groups of 25
161
+ for (let i = 0; i < blobItems.length; i += 25) {
162
+ const batch = blobItems.slice(i, i + 25);
163
+ await ddb.send(
164
+ new BatchWriteCommand({
165
+ RequestItems: {
166
+ [tableName]: batch.map((item) => ({
167
+ DeleteRequest: {
168
+ Key: { PK: item['PK'], SK: item['SK'] },
169
+ },
170
+ })),
171
+ },
172
+ }),
173
+ );
174
+ }
175
+
176
+ // Delete the project record itself
177
+ await ddb.send(
178
+ new BatchWriteCommand({
179
+ RequestItems: {
180
+ [tableName]: [
181
+ {
182
+ DeleteRequest: {
183
+ Key: { PK: projectItem['PK'], SK: projectItem['SK'] },
184
+ },
185
+ },
186
+ ],
187
+ },
188
+ }),
189
+ );
190
+
191
+ logger.info('Tenant deleted', { tenantId, operation: 'DELETE_TENANT' });
192
+
193
+ return {
194
+ statusCode: 200,
195
+ headers: { 'Content-Type': 'application/json' },
196
+ body: JSON.stringify({ deleted: true }),
197
+ };
198
+ }
@@ -0,0 +1,21 @@
1
+ import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
2
+ interface HandlerResponse {
3
+ statusCode: number;
4
+ body: string;
5
+ headers: Record<string, string>;
6
+ }
7
+ /**
8
+ * Store a wrapped master key blob for the authenticated tenant.
9
+ *
10
+ * The blob is a crypto_box_seal output (Ed25519) or RSA-OAEP KEM+DEM output.
11
+ * The server cannot decrypt it — only the holder of the SSH private key can.
12
+ */
13
+ export declare function handlePutWrappedKey(tenantId: string, fingerprint: string, rawBody: string | null | undefined, isBase64Encoded: boolean, ddb: DynamoDBDocumentClient, tableName: string): Promise<HandlerResponse>;
14
+ /**
15
+ * Retrieve the wrapped master key blob for the authenticated tenant.
16
+ *
17
+ * Returns the blob as application/octet-stream.
18
+ */
19
+ export declare function handleGetWrappedKey(tenantId: string, fingerprint: string, ddb: DynamoDBDocumentClient, tableName: string): Promise<HandlerResponse>;
20
+ export {};
21
+ //# sourceMappingURL=wrapped-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapped-key.d.ts","sourceRoot":"","sources":["../../../../lib/handler/routes/wrapped-key.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EAGvB,MAAM,uBAAuB,CAAC;AAG/B,UAAU,eAAe;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAClC,eAAe,EAAE,OAAO,EACxB,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CA2C1B;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CA0B1B"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handlePutWrappedKey = handlePutWrappedKey;
4
+ exports.handleGetWrappedKey = handleGetWrappedKey;
5
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
6
+ const logger_js_1 = require("../logger.js");
7
+ /**
8
+ * Store a wrapped master key blob for the authenticated tenant.
9
+ *
10
+ * The blob is a crypto_box_seal output (Ed25519) or RSA-OAEP KEM+DEM output.
11
+ * The server cannot decrypt it — only the holder of the SSH private key can.
12
+ */
13
+ async function handlePutWrappedKey(tenantId, fingerprint, rawBody, isBase64Encoded, ddb, tableName) {
14
+ if (!rawBody) {
15
+ return {
16
+ statusCode: 400,
17
+ headers: { 'Content-Type': 'application/json' },
18
+ body: JSON.stringify({ error: 'invalid_request', message: 'Request body is required' }),
19
+ };
20
+ }
21
+ const bodyBuffer = isBase64Encoded
22
+ ? Buffer.from(rawBody, 'base64')
23
+ : Buffer.from(rawBody, 'utf-8');
24
+ // Max wrapped key size: 4 KB (wrapped master key is typically ~80 bytes for Ed25519, ~512 for RSA)
25
+ if (bodyBuffer.length > 4096) {
26
+ return {
27
+ statusCode: 400,
28
+ headers: { 'Content-Type': 'application/json' },
29
+ body: JSON.stringify({ error: 'invalid_request', message: 'Wrapped key too large (max 4KB)' }),
30
+ };
31
+ }
32
+ const now = new Date().toISOString();
33
+ await ddb.send(new lib_dynamodb_1.PutCommand({
34
+ TableName: tableName,
35
+ Item: {
36
+ PK: `TENANT#${tenantId}`,
37
+ SK: `WRAPPED_KEY#${fingerprint}`,
38
+ data: bodyBuffer.toString('base64'),
39
+ updatedAt: now,
40
+ },
41
+ }));
42
+ logger_js_1.logger.info('Wrapped key stored', { tenantId, fingerprint, size: bodyBuffer.length });
43
+ return {
44
+ statusCode: 200,
45
+ headers: { 'Content-Type': 'application/json' },
46
+ body: JSON.stringify({ status: 'stored' }),
47
+ };
48
+ }
49
+ /**
50
+ * Retrieve the wrapped master key blob for the authenticated tenant.
51
+ *
52
+ * Returns the blob as application/octet-stream.
53
+ */
54
+ async function handleGetWrappedKey(tenantId, fingerprint, ddb, tableName) {
55
+ const result = await ddb.send(new lib_dynamodb_1.GetCommand({
56
+ TableName: tableName,
57
+ Key: {
58
+ PK: `TENANT#${tenantId}`,
59
+ SK: `WRAPPED_KEY#${fingerprint}`,
60
+ },
61
+ }));
62
+ if (!result.Item) {
63
+ return {
64
+ statusCode: 404,
65
+ headers: { 'Content-Type': 'application/json' },
66
+ body: JSON.stringify({ error: 'not_found', message: 'No wrapped key found for this device' }),
67
+ };
68
+ }
69
+ const data = result.Item['data'];
70
+ return {
71
+ statusCode: 200,
72
+ headers: { 'Content-Type': 'application/octet-stream' },
73
+ body: data, // base64-encoded wrapped key blob
74
+ };
75
+ }
76
+ //# sourceMappingURL=wrapped-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapped-key.js","sourceRoot":"","sources":["../../../../lib/handler/routes/wrapped-key.ts"],"names":[],"mappings":";;AAmBA,kDAkDC;AAOD,kDA+BC;AA3GD,wDAI+B;AAC/B,4CAAsC;AAQtC;;;;;GAKG;AACI,KAAK,UAAU,mBAAmB,CACvC,QAAgB,EAChB,WAAmB,EACnB,OAAkC,EAClC,eAAwB,EACxB,GAA2B,EAC3B,SAAiB;IAEjB,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;YACL,UAAU,EAAE,GAAG;YACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC;SACxF,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,eAAe;QAChC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC;QAChC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAElC,mGAAmG;IACnG,IAAI,UAAU,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;QAC7B,OAAO;YACL,UAAU,EAAE,GAAG;YACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,iCAAiC,EAAE,CAAC;SAC/F,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,MAAM,GAAG,CAAC,IAAI,CACZ,IAAI,yBAAU,CAAC;QACb,SAAS,EAAE,SAAS;QACpB,IAAI,EAAE;YACJ,EAAE,EAAE,UAAU,QAAQ,EAAE;YACxB,EAAE,EAAE,eAAe,WAAW,EAAE;YAChC,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACnC,SAAS,EAAE,GAAG;SACf;KACF,CAAC,CACH,CAAC;IAEF,kBAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IAEtF,OAAO;QACL,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;KAC3C,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,mBAAmB,CACvC,QAAgB,EAChB,WAAmB,EACnB,GAA2B,EAC3B,SAAiB;IAEjB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CAC3B,IAAI,yBAAU,CAAC;QACb,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE;YACH,EAAE,EAAE,UAAU,QAAQ,EAAE;YACxB,EAAE,EAAE,eAAe,WAAW,EAAE;SACjC;KACF,CAAC,CACH,CAAC;IAEF,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO;YACL,UAAU,EAAE,GAAG;YACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,sCAAsC,EAAE,CAAC;SAC9F,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAW,CAAC;IAE3C,OAAO;QACL,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE;QACvD,IAAI,EAAE,IAAI,EAAE,kCAAkC;KAC/C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,108 @@
1
+ import {
2
+ DynamoDBDocumentClient,
3
+ PutCommand,
4
+ GetCommand,
5
+ } from '@aws-sdk/lib-dynamodb';
6
+ import { logger } from '../logger.js';
7
+
8
+ interface HandlerResponse {
9
+ statusCode: number;
10
+ body: string;
11
+ headers: Record<string, string>;
12
+ }
13
+
14
+ /**
15
+ * Store a wrapped master key blob for the authenticated tenant.
16
+ *
17
+ * The blob is a crypto_box_seal output (Ed25519) or RSA-OAEP KEM+DEM output.
18
+ * The server cannot decrypt it — only the holder of the SSH private key can.
19
+ */
20
+ export async function handlePutWrappedKey(
21
+ tenantId: string,
22
+ fingerprint: string,
23
+ rawBody: string | null | undefined,
24
+ isBase64Encoded: boolean,
25
+ ddb: DynamoDBDocumentClient,
26
+ tableName: string,
27
+ ): Promise<HandlerResponse> {
28
+ if (!rawBody) {
29
+ return {
30
+ statusCode: 400,
31
+ headers: { 'Content-Type': 'application/json' },
32
+ body: JSON.stringify({ error: 'invalid_request', message: 'Request body is required' }),
33
+ };
34
+ }
35
+
36
+ const bodyBuffer = isBase64Encoded
37
+ ? Buffer.from(rawBody, 'base64')
38
+ : Buffer.from(rawBody, 'utf-8');
39
+
40
+ // Max wrapped key size: 4 KB (wrapped master key is typically ~80 bytes for Ed25519, ~512 for RSA)
41
+ if (bodyBuffer.length > 4096) {
42
+ return {
43
+ statusCode: 400,
44
+ headers: { 'Content-Type': 'application/json' },
45
+ body: JSON.stringify({ error: 'invalid_request', message: 'Wrapped key too large (max 4KB)' }),
46
+ };
47
+ }
48
+
49
+ const now = new Date().toISOString();
50
+
51
+ await ddb.send(
52
+ new PutCommand({
53
+ TableName: tableName,
54
+ Item: {
55
+ PK: `TENANT#${tenantId}`,
56
+ SK: `WRAPPED_KEY#${fingerprint}`,
57
+ data: bodyBuffer.toString('base64'),
58
+ updatedAt: now,
59
+ },
60
+ }),
61
+ );
62
+
63
+ logger.info('Wrapped key stored', { tenantId, fingerprint, size: bodyBuffer.length });
64
+
65
+ return {
66
+ statusCode: 200,
67
+ headers: { 'Content-Type': 'application/json' },
68
+ body: JSON.stringify({ status: 'stored' }),
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Retrieve the wrapped master key blob for the authenticated tenant.
74
+ *
75
+ * Returns the blob as application/octet-stream.
76
+ */
77
+ export async function handleGetWrappedKey(
78
+ tenantId: string,
79
+ fingerprint: string,
80
+ ddb: DynamoDBDocumentClient,
81
+ tableName: string,
82
+ ): Promise<HandlerResponse> {
83
+ const result = await ddb.send(
84
+ new GetCommand({
85
+ TableName: tableName,
86
+ Key: {
87
+ PK: `TENANT#${tenantId}`,
88
+ SK: `WRAPPED_KEY#${fingerprint}`,
89
+ },
90
+ }),
91
+ );
92
+
93
+ if (!result.Item) {
94
+ return {
95
+ statusCode: 404,
96
+ headers: { 'Content-Type': 'application/json' },
97
+ body: JSON.stringify({ error: 'not_found', message: 'No wrapped key found for this device' }),
98
+ };
99
+ }
100
+
101
+ const data = result.Item['data'] as string;
102
+
103
+ return {
104
+ statusCode: 200,
105
+ headers: { 'Content-Type': 'application/octet-stream' },
106
+ body: data, // base64-encoded wrapped key blob
107
+ };
108
+ }
@@ -0,0 +1,7 @@
1
+ export { ChaosKBStack, type ChaosKBStackProps } from './chaoskb-stack.js';
2
+ export { BlobStore } from './constructs/blob-store.js';
3
+ export { Api } from './constructs/api.js';
4
+ export { Auth } from './constructs/auth.js';
5
+ export { AdminDashboard } from './constructs/admin-dashboard.js';
6
+ export { AdminApi } from './constructs/admin-api.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AdminApi = exports.AdminDashboard = exports.Auth = exports.Api = exports.BlobStore = exports.ChaosKBStack = void 0;
4
+ var chaoskb_stack_js_1 = require("./chaoskb-stack.js");
5
+ Object.defineProperty(exports, "ChaosKBStack", { enumerable: true, get: function () { return chaoskb_stack_js_1.ChaosKBStack; } });
6
+ var blob_store_js_1 = require("./constructs/blob-store.js");
7
+ Object.defineProperty(exports, "BlobStore", { enumerable: true, get: function () { return blob_store_js_1.BlobStore; } });
8
+ var api_js_1 = require("./constructs/api.js");
9
+ Object.defineProperty(exports, "Api", { enumerable: true, get: function () { return api_js_1.Api; } });
10
+ var auth_js_1 = require("./constructs/auth.js");
11
+ Object.defineProperty(exports, "Auth", { enumerable: true, get: function () { return auth_js_1.Auth; } });
12
+ var admin_dashboard_js_1 = require("./constructs/admin-dashboard.js");
13
+ Object.defineProperty(exports, "AdminDashboard", { enumerable: true, get: function () { return admin_dashboard_js_1.AdminDashboard; } });
14
+ var admin_api_js_1 = require("./constructs/admin-api.js");
15
+ Object.defineProperty(exports, "AdminApi", { enumerable: true, get: function () { return admin_api_js_1.AdminApi; } });
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/index.ts"],"names":[],"mappings":";;;AAAA,uDAA0E;AAAjE,gHAAA,YAAY,OAAA;AACrB,4DAAuD;AAA9C,0GAAA,SAAS,OAAA;AAClB,8CAA0C;AAAjC,6FAAA,GAAG,OAAA;AACZ,gDAA4C;AAAnC,+FAAA,IAAI,OAAA;AACb,sEAAiE;AAAxD,oHAAA,cAAc,OAAA;AACvB,0DAAqD;AAA5C,wGAAA,QAAQ,OAAA"}
@@ -0,0 +1,3 @@
1
+ declare const _default: import("vite", { with: { "resolution-mode": "import" } }).UserConfig;
2
+ export default _default;
3
+ //# sourceMappingURL=vitest.config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.config.d.ts","sourceRoot":"","sources":["../vitest.config.ts"],"names":[],"mappings":";AAEA,wBAaG"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_1 = require("vitest/config");
4
+ exports.default = (0, config_1.defineConfig)({
5
+ test: {
6
+ include: ['**/__tests__/**/*.test.ts'],
7
+ coverage: {
8
+ provider: 'v8',
9
+ reporter: ['text', 'lcov'],
10
+ thresholds: {
11
+ lines: 80,
12
+ branches: 80,
13
+ functions: 80,
14
+ },
15
+ },
16
+ },
17
+ });
18
+ //# sourceMappingURL=vitest.config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.config.js","sourceRoot":"","sources":["../vitest.config.ts"],"names":[],"mappings":";;AAAA,0CAA6C;AAE7C,kBAAe,IAAA,qBAAY,EAAC;IAC1B,IAAI,EAAE;QACJ,OAAO,EAAE,CAAC,2BAA2B,CAAC;QACtC,QAAQ,EAAE;YACR,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;YAC1B,UAAU,EAAE;gBACV,KAAK,EAAE,EAAE;gBACT,QAAQ,EAAE,EAAE;gBACZ,SAAS,EAAE,EAAE;aACd;SACF;KACF;CACF,CAAC,CAAC"}