@de-otio/chaoskb-server 0.2.0 → 0.2.1

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 (54) hide show
  1. package/dist/lib/constructs/blob-store.js +1 -1
  2. package/dist/lib/constructs/blob-store.js.map +1 -1
  3. package/dist/lib/handler/index.d.ts.map +1 -1
  4. package/dist/lib/handler/index.js +20 -4
  5. package/dist/lib/handler/index.js.map +1 -1
  6. package/dist/lib/handler/index.ts +19 -4
  7. package/dist/lib/handler/middleware/rate-limit.d.ts.map +1 -1
  8. package/dist/lib/handler/middleware/rate-limit.js +13 -9
  9. package/dist/lib/handler/middleware/rate-limit.js.map +1 -1
  10. package/dist/lib/handler/middleware/rate-limit.ts +14 -9
  11. package/dist/lib/handler/middleware/ssh-auth.d.ts.map +1 -1
  12. package/dist/lib/handler/middleware/ssh-auth.js +66 -6
  13. package/dist/lib/handler/middleware/ssh-auth.js.map +1 -1
  14. package/dist/lib/handler/middleware/ssh-auth.ts +74 -7
  15. package/dist/lib/handler/routes/audit.js +1 -1
  16. package/dist/lib/handler/routes/audit.js.map +1 -1
  17. package/dist/lib/handler/routes/audit.ts +1 -1
  18. package/dist/lib/handler/routes/blobs.d.ts.map +1 -1
  19. package/dist/lib/handler/routes/blobs.js +2 -3
  20. package/dist/lib/handler/routes/blobs.js.map +1 -1
  21. package/dist/lib/handler/routes/blobs.ts +2 -3
  22. package/dist/lib/handler/routes/devices.d.ts.map +1 -1
  23. package/dist/lib/handler/routes/devices.js +10 -6
  24. package/dist/lib/handler/routes/devices.js.map +1 -1
  25. package/dist/lib/handler/routes/devices.ts +11 -6
  26. package/dist/lib/handler/routes/github.d.ts +15 -2
  27. package/dist/lib/handler/routes/github.d.ts.map +1 -1
  28. package/dist/lib/handler/routes/github.js +96 -22
  29. package/dist/lib/handler/routes/github.js.map +1 -1
  30. package/dist/lib/handler/routes/github.ts +68 -35
  31. package/dist/lib/handler/routes/invites.d.ts.map +1 -1
  32. package/dist/lib/handler/routes/invites.js +11 -13
  33. package/dist/lib/handler/routes/invites.js.map +1 -1
  34. package/dist/lib/handler/routes/invites.ts +11 -13
  35. package/dist/lib/handler/routes/notifications.js +1 -1
  36. package/dist/lib/handler/routes/notifications.js.map +1 -1
  37. package/dist/lib/handler/routes/notifications.ts +1 -1
  38. package/dist/lib/handler/routes/projects.d.ts.map +1 -1
  39. package/dist/lib/handler/routes/projects.js.map +1 -1
  40. package/dist/lib/handler/routes/projects.ts +0 -1
  41. package/dist/lib/handler/routes/register.d.ts +1 -1
  42. package/dist/lib/handler/routes/register.d.ts.map +1 -1
  43. package/dist/lib/handler/routes/register.js +104 -58
  44. package/dist/lib/handler/routes/register.js.map +1 -1
  45. package/dist/lib/handler/routes/register.ts +113 -66
  46. package/dist/lib/handler/routes/restore.d.ts.map +1 -1
  47. package/dist/lib/handler/routes/restore.js +1 -2
  48. package/dist/lib/handler/routes/restore.js.map +1 -1
  49. package/dist/lib/handler/routes/restore.ts +1 -2
  50. package/dist/lib/handler/routes/rotation.d.ts.map +1 -1
  51. package/dist/lib/handler/routes/rotation.js +23 -2
  52. package/dist/lib/handler/routes/rotation.js.map +1 -1
  53. package/dist/lib/handler/routes/rotation.ts +30 -2
  54. package/package.json +1 -1
@@ -1,21 +1,24 @@
1
1
  import * as crypto from 'crypto';
2
- import { DynamoDBDocumentClient, PutCommand, GetCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb';
2
+ import { DynamoDBDocumentClient, PutCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb';
3
3
  import { logAuditEvent } from './audit.js';
4
4
  import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
5
5
  import { logger } from '../logger.js';
6
6
  import {
7
7
  verifyKeyOnGitHub,
8
+ fetchGitHubKeysFresh,
9
+ keyAppearsInGitHubKeys,
8
10
  storeGitHubAssociation,
9
11
  storeGitHubReverseLookup,
10
12
  findTenantByGitHub,
11
- GitHubVerificationError,
12
13
  } from './github.js';
14
+ import { createNotification, resolveIpLocation, type DeviceInfo } from './notifications.js';
13
15
 
14
16
  interface RegisterRequest {
15
17
  publicKey: string;
16
18
  signedChallenge: string;
17
19
  challengeNonce: string;
18
20
  github?: string;
21
+ deviceInfo?: DeviceInfo;
19
22
  }
20
23
 
21
24
  interface HandlerResponse {
@@ -119,7 +122,7 @@ export async function handleChallenge(
119
122
  const nonce = crypto.randomBytes(32).toString('base64');
120
123
  const now = Math.floor(Date.now() / 1000);
121
124
  const ttl = now + CHALLENGE_EXPIRY_SECONDS + 60; // DynamoDB TTL: generous buffer
122
- const expiresAt = new Date((now + CHALLENGE_EXPIRY_SECONDS) * 1000).toISOString();
125
+ const expiresAtISO = new Date((now + CHALLENGE_EXPIRY_SECONDS) * 1000).toISOString();
123
126
 
124
127
  await ddb.send(
125
128
  new PutCommand({
@@ -127,8 +130,8 @@ export async function handleChallenge(
127
130
  Item: {
128
131
  PK: `CHALLENGE#${nonce}`,
129
132
  SK: 'META',
130
- expiresAt,
131
- ttl,
133
+ expiresAtISO,
134
+ expiresAt: ttl,
132
135
  },
133
136
  }),
134
137
  );
@@ -138,7 +141,7 @@ export async function handleChallenge(
138
141
  return {
139
142
  statusCode: 200,
140
143
  headers: JSON_HEADERS,
141
- body: JSON.stringify({ challenge: nonce, expiresAt }),
144
+ body: JSON.stringify({ challenge: nonce, expiresAt: expiresAtISO }),
142
145
  };
143
146
  }
144
147
 
@@ -147,6 +150,7 @@ export async function handleRegister(
147
150
  ddb: DynamoDBDocumentClient,
148
151
  tableName: string,
149
152
  signupsParamName: string,
153
+ headers: Record<string, string> = {},
150
154
  ): Promise<HandlerResponse> {
151
155
  // Check if signups are enabled
152
156
  const signupsEnabled = await checkSignupsEnabled(signupsParamName);
@@ -202,34 +206,35 @@ export async function handleRegister(
202
206
  };
203
207
  }
204
208
 
205
- // Look up and consume the challenge nonce (single-use)
206
- const challengeResult = await ddb.send(
207
- new GetCommand({
208
- TableName: tableName,
209
- Key: {
210
- PK: `CHALLENGE#${request.challengeNonce}`,
211
- SK: 'META',
212
- },
213
- }),
214
- );
215
-
216
- if (!challengeResult.Item) {
217
- return {
218
- statusCode: 400,
219
- headers: JSON_HEADERS,
220
- body: JSON.stringify({ error: 'invalid_challenge', message: 'Challenge not found or already used' }),
221
- };
222
- }
223
-
224
- // Check challenge expiry
225
- if (new Date(challengeResult.Item['expiresAt'] as string) < new Date()) {
226
- // Clean up expired challenge
227
- await ddb.send(
209
+ // Atomically consume the challenge nonce (single-use).
210
+ // Uses conditional delete to prevent TOCTOU race: only one request can consume a given nonce.
211
+ let challengeItem: Record<string, unknown> | undefined;
212
+ try {
213
+ const deleteResult = await ddb.send(
228
214
  new DeleteCommand({
229
215
  TableName: tableName,
230
- Key: { PK: `CHALLENGE#${request.challengeNonce}`, SK: 'META' },
216
+ Key: {
217
+ PK: `CHALLENGE#${request.challengeNonce}`,
218
+ SK: 'META',
219
+ },
220
+ ConditionExpression: 'attribute_exists(PK)',
221
+ ReturnValues: 'ALL_OLD',
231
222
  }),
232
223
  );
224
+ challengeItem = deleteResult.Attributes;
225
+ } catch (err: unknown) {
226
+ if ((err as { name?: string }).name === 'ConditionalCheckFailedException') {
227
+ return {
228
+ statusCode: 400,
229
+ headers: JSON_HEADERS,
230
+ body: JSON.stringify({ error: 'invalid_challenge', message: 'Challenge not found or already used' }),
231
+ };
232
+ }
233
+ throw err;
234
+ }
235
+
236
+ // Check challenge expiry on the consumed item
237
+ if (!challengeItem || new Date(challengeItem['expiresAtISO'] as string) < new Date()) {
233
238
  return {
234
239
  statusCode: 400,
235
240
  headers: JSON_HEADERS,
@@ -237,14 +242,6 @@ export async function handleRegister(
237
242
  };
238
243
  }
239
244
 
240
- // Consume the challenge (delete it — single-use)
241
- await ddb.send(
242
- new DeleteCommand({
243
- TableName: tableName,
244
- Key: { PK: `CHALLENGE#${request.challengeNonce}`, SK: 'META' },
245
- }),
246
- );
247
-
248
245
  // Verify the SSH signature of the challenge nonce against the public key
249
246
  const validSignature = verifyRegistrationSignature(
250
247
  request.publicKey,
@@ -263,45 +260,84 @@ export async function handleRegister(
263
260
 
264
261
  // GitHub verification (if --github was provided)
265
262
  if (request.github) {
263
+ let keyVerified = false;
266
264
  try {
267
- const keyOnGitHub = await verifyKeyOnGitHub(request.publicKey, request.github);
268
- if (!keyOnGitHub) {
265
+ keyVerified = await verifyKeyOnGitHub(request.publicKey, request.github);
266
+ } catch {
267
+ // GitHub unreachable or user not found — uniform response
268
+ }
269
+
270
+ if (!keyVerified) {
271
+ return {
272
+ statusCode: 400,
273
+ headers: JSON_HEADERS,
274
+ body: JSON.stringify({
275
+ error: 'github_verification_failed',
276
+ message: 'Could not verify key against this GitHub account',
277
+ }),
278
+ };
279
+ }
280
+
281
+ // Check if an existing tenant is associated with this GitHub username (auto-link)
282
+ const existingTenantId = await findTenantByGitHub(request.github, ddb, tableName);
283
+ if (existingTenantId) {
284
+ // Fresh-fetch GitHub keys (bypass cache) to ensure both device keys still appear
285
+ let freshKeys: string[];
286
+ try {
287
+ freshKeys = await fetchGitHubKeysFresh(request.github);
288
+ } catch {
289
+ // GitHub unreachable at auto-link time — fall back to normal registration
290
+ freshKeys = [];
291
+ }
292
+
293
+ // Verify the new device's key appears on the GitHub account (fresh data)
294
+ if (freshKeys.length > 0 && !keyAppearsInGitHubKeys(request.publicKey, freshKeys)) {
269
295
  return {
270
296
  statusCode: 400,
271
297
  headers: JSON_HEADERS,
272
298
  body: JSON.stringify({
273
- error: 'github_key_not_found',
274
- message: `Public key not found on GitHub account "${request.github}"`,
299
+ error: 'github_verification_failed',
300
+ message: 'Could not verify key against this GitHub account',
275
301
  }),
276
302
  };
277
303
  }
278
- } catch (err) {
279
- if (err instanceof GitHubVerificationError) {
304
+
305
+ if (freshKeys.length > 0) {
306
+ // Resolve location from CloudFront headers for the notification
307
+ const location = resolveIpLocation(headers);
308
+ const deviceInfo: DeviceInfo = {
309
+ ...request.deviceInfo,
310
+ location,
311
+ };
312
+
313
+ // Create notification for existing devices
314
+ await createNotification(existingTenantId, 'device_linked', deviceInfo, ddb, tableName);
315
+
316
+ // Audit event
317
+ await logAuditEvent(ddb, tableName, existingTenantId, {
318
+ eventType: 'device-linked',
319
+ fingerprint: '',
320
+ metadata: {
321
+ publicKey: request.publicKey,
322
+ github: request.github,
323
+ ...(deviceInfo.hostname && { hostname: deviceInfo.hostname }),
324
+ },
325
+ });
326
+
327
+ logger.info('GitHub auto-link: existing tenant found', {
328
+ existingTenantId,
329
+ github: request.github,
330
+ });
280
331
  return {
281
- statusCode: 400,
332
+ statusCode: 200,
282
333
  headers: JSON_HEADERS,
283
- body: JSON.stringify({ error: err.code, message: err.message }),
334
+ body: JSON.stringify({
335
+ status: 'auto_linked',
336
+ github: request.github,
337
+ }),
284
338
  };
285
339
  }
286
- throw err;
287
- }
288
-
289
- // Check if an existing tenant is associated with this GitHub username (auto-link)
290
- const existingTenantId = await findTenantByGitHub(request.github, ddb, tableName);
291
- if (existingTenantId) {
292
- logger.info('GitHub auto-link: existing tenant found', {
293
- existingTenantId,
294
- github: request.github,
295
- });
296
- return {
297
- statusCode: 200,
298
- headers: JSON_HEADERS,
299
- body: JSON.stringify({
300
- status: 'auto_linked',
301
- tenantId: existingTenantId,
302
- github: request.github,
303
- }),
304
- };
340
+ // If fresh keys unavailable, fall through to normal registration
305
341
  }
306
342
  }
307
343
 
@@ -328,8 +364,19 @@ export async function handleRegister(
328
364
 
329
365
  // Store GitHub association if provided
330
366
  if (request.github) {
367
+ const claimed = await storeGitHubReverseLookup(request.github, tenantId, ddb, tableName);
368
+ if (!claimed) {
369
+ // Another tenant already claimed this GitHub username — uniform error
370
+ return {
371
+ statusCode: 400,
372
+ headers: JSON_HEADERS,
373
+ body: JSON.stringify({
374
+ error: 'github_verification_failed',
375
+ message: 'Could not verify key against this GitHub account',
376
+ }),
377
+ };
378
+ }
331
379
  await storeGitHubAssociation(tenantId, request.github, ddb, tableName);
332
- await storeGitHubReverseLookup(request.github, tenantId, ddb, tableName);
333
380
  }
334
381
 
335
382
  await logAuditEvent(ddb, tableName, tenantId, {
@@ -1 +1 @@
1
- {"version":3,"file":"restore.d.ts","sourceRoot":"","sources":["../../../../lib/handler/routes/restore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAA6B,MAAM,uBAAuB,CAAC;AAG1F,UAAU,eAAe;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CA0D1B"}
1
+ {"version":3,"file":"restore.d.ts","sourceRoot":"","sources":["../../../../lib/handler/routes/restore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAA6B,MAAM,uBAAuB,CAAC;AAG1F,UAAU,eAAe;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAyD1B"}
@@ -31,8 +31,7 @@ async function handleRestore(blobId, tenantId, ddb, tableName) {
31
31
  await ddb.send(new lib_dynamodb_1.UpdateCommand({
32
32
  TableName: tableName,
33
33
  Key: { PK: `TENANT#${tenantId}`, SK: `BLOB#${blobId}` },
34
- UpdateExpression: 'REMOVE deletedAt, #ttl SET updatedAt = :updatedAt',
35
- ExpressionAttributeNames: { '#ttl': 'ttl' },
34
+ UpdateExpression: 'REMOVE deletedAt, expiresAt SET updatedAt = :updatedAt',
36
35
  ExpressionAttributeValues: { ':updatedAt': now },
37
36
  }));
38
37
  // Re-increment storage used
@@ -1 +1 @@
1
- {"version":3,"file":"restore.js","sourceRoot":"","sources":["../../../../lib/handler/routes/restore.ts"],"names":[],"mappings":";;AASA,sCA+DC;AAxED,wDAA0F;AAC1F,4CAAsC;AAQ/B,KAAK,UAAU,aAAa,CACjC,MAAc,EACd,QAAgB,EAChB,GAA2B,EAC3B,SAAiB;IAEjB,kDAAkD;IAClD,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,IAAI,CAC7B,IAAI,yBAAU,CAAC;QACb,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE,EAAE,EAAE,EAAE,UAAU,QAAQ,EAAE,EAAE,EAAE,EAAE,QAAQ,MAAM,EAAE,EAAE;QACvD,oBAAoB,EAAE,eAAe;QACrC,wBAAwB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;KAC3C,CAAC,CACH,CAAC;IAEF,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,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,gBAAgB,EAAE,CAAC;SACxE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QAChC,OAAO;YACL,UAAU,EAAE,GAAG;YACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC;SAC/E,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAY,IAAI,CAAC,CAAC;IACxD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,2BAA2B;IAC3B,MAAM,GAAG,CAAC,IAAI,CACZ,IAAI,4BAAa,CAAC;QAChB,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE,EAAE,EAAE,EAAE,UAAU,QAAQ,EAAE,EAAE,EAAE,EAAE,QAAQ,MAAM,EAAE,EAAE;QACvD,gBAAgB,EAAE,mDAAmD;QACrE,wBAAwB,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;QAC3C,yBAAyB,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE;KACjD,CAAC,CACH,CAAC;IAEF,4BAA4B;IAC5B,MAAM,GAAG,CAAC,IAAI,CACZ,IAAI,4BAAa,CAAC;QAChB,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE,EAAE,EAAE,EAAE,UAAU,QAAQ,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;QAC7C,gBAAgB,EAAE,iDAAiD;QACnE,yBAAyB,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE;KACjD,CAAC,CACH,CAAC;IAEF,kBAAM,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC;IAEjE,OAAO;QACL,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;KACrD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"restore.js","sourceRoot":"","sources":["../../../../lib/handler/routes/restore.ts"],"names":[],"mappings":";;AASA,sCA8DC;AAvED,wDAA0F;AAC1F,4CAAsC;AAQ/B,KAAK,UAAU,aAAa,CACjC,MAAc,EACd,QAAgB,EAChB,GAA2B,EAC3B,SAAiB;IAEjB,kDAAkD;IAClD,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,IAAI,CAC7B,IAAI,yBAAU,CAAC;QACb,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE,EAAE,EAAE,EAAE,UAAU,QAAQ,EAAE,EAAE,EAAE,EAAE,QAAQ,MAAM,EAAE,EAAE;QACvD,oBAAoB,EAAE,eAAe;QACrC,wBAAwB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;KAC3C,CAAC,CACH,CAAC;IAEF,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,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,gBAAgB,EAAE,CAAC;SACxE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QAChC,OAAO;YACL,UAAU,EAAE,GAAG;YACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC;SAC/E,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAY,IAAI,CAAC,CAAC;IACxD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,2BAA2B;IAC3B,MAAM,GAAG,CAAC,IAAI,CACZ,IAAI,4BAAa,CAAC;QAChB,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE,EAAE,EAAE,EAAE,UAAU,QAAQ,EAAE,EAAE,EAAE,EAAE,QAAQ,MAAM,EAAE,EAAE;QACvD,gBAAgB,EAAE,wDAAwD;QAC1E,yBAAyB,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE;KACjD,CAAC,CACH,CAAC;IAEF,4BAA4B;IAC5B,MAAM,GAAG,CAAC,IAAI,CACZ,IAAI,4BAAa,CAAC;QAChB,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE,EAAE,EAAE,EAAE,UAAU,QAAQ,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;QAC7C,gBAAgB,EAAE,iDAAiD;QACnE,yBAAyB,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE;KACjD,CAAC,CACH,CAAC;IAEF,kBAAM,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC;IAEjE,OAAO;QACL,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;KACrD,CAAC;AACJ,CAAC"}
@@ -47,8 +47,7 @@ export async function handleRestore(
47
47
  new UpdateCommand({
48
48
  TableName: tableName,
49
49
  Key: { PK: `TENANT#${tenantId}`, SK: `BLOB#${blobId}` },
50
- UpdateExpression: 'REMOVE deletedAt, #ttl SET updatedAt = :updatedAt',
51
- ExpressionAttributeNames: { '#ttl': 'ttl' },
50
+ UpdateExpression: 'REMOVE deletedAt, expiresAt SET updatedAt = :updatedAt',
52
51
  ExpressionAttributeValues: { ':updatedAt': now },
53
52
  }),
54
53
  );
@@ -1 +1 @@
1
- {"version":3,"file":"rotation.d.ts","sourceRoot":"","sources":["../../../../lib/handler/routes/rotation.ts"],"names":[],"mappings":"AACA,OAAO,EACL,sBAAsB,EAMvB,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;AA6BD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAClC,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAoH1B;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CA6F1B"}
1
+ {"version":3,"file":"rotation.d.ts","sourceRoot":"","sources":["../../../../lib/handler/routes/rotation.ts"],"names":[],"mappings":"AACA,OAAO,EACL,sBAAsB,EAMvB,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;AA6BD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAClC,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAoI1B;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CA6F1B"}
@@ -44,8 +44,7 @@ function fingerprintFromPublicKey(publicKeyBase64) {
44
44
  return crypto.createHash('sha256').update(Buffer.from(publicKeyBase64, 'base64')).digest('base64');
45
45
  }
46
46
  function tenantIdFromPublicKey(publicKeyBase64) {
47
- const hash = crypto.createHash('sha256').update(publicKeyBase64).digest('hex');
48
- return hash.slice(0, 32);
47
+ return crypto.createHash('sha256').update(publicKeyBase64).digest('hex').slice(0, 32);
49
48
  }
50
49
  function isValidBase64(value) {
51
50
  if (!value || value.length < 4 || value.length > 8192)
@@ -153,6 +152,19 @@ async function handleRotateStart(tenantId, oldFingerprint, rawBody, ddb, tableNa
153
152
  updatedAt: now,
154
153
  },
155
154
  }));
155
+ // Write a KEY_ALIAS record so auth middleware can resolve the new key's
156
+ // derived tenantId back to this tenant during rotation
157
+ const newKeyTenantId = tenantIdFromPublicKey(request.newPublicKey);
158
+ await ddb.send(new lib_dynamodb_1.PutCommand({
159
+ TableName: tableName,
160
+ Item: {
161
+ PK: `KEY_ALIAS#${newKeyTenantId}`,
162
+ SK: 'META',
163
+ originalTenantId: tenantId,
164
+ newPublicKey: request.newPublicKey,
165
+ createdAt: now,
166
+ },
167
+ }));
156
168
  logger_js_1.logger.info('Key rotation started', { tenantId, oldFingerprint, newFingerprint });
157
169
  return {
158
170
  statusCode: 200,
@@ -287,5 +299,14 @@ async function completeRotation(tenantId, rotation, ddb, tableName) {
287
299
  SK: 'ROTATION',
288
300
  },
289
301
  }));
302
+ // Delete the KEY_ALIAS lookup record for the new key
303
+ const newKeyTenantId = tenantIdFromPublicKey(newPublicKey);
304
+ await ddb.send(new lib_dynamodb_1.DeleteCommand({
305
+ TableName: tableName,
306
+ Key: {
307
+ PK: `KEY_ALIAS#${newKeyTenantId}`,
308
+ SK: 'META',
309
+ },
310
+ }));
290
311
  }
291
312
  //# sourceMappingURL=rotation.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rotation.js","sourceRoot":"","sources":["../../../../lib/handler/routes/rotation.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,8CA0HC;AASD,kDAkGC;AAxRD,+CAAiC;AACjC,wDAO+B;AAC/B,4CAAsC;AAatC,0EAA0E;AAC1E,MAAM,mBAAmB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEhD,SAAS,wBAAwB,CAAC,eAAuB;IACvD,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AACrG,CAAC;AAED,SAAS,qBAAqB,CAAC,eAAuB;IACpD,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/E,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,aAAa,CAAC,KAAa;IAClC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI;QAAE,OAAO,KAAK,CAAC;IACpE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAC7C,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,cAAsB,EACtB,OAAkC,EAClC,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,IAAI,OAA2B,CAAC;IAChC,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,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,mBAAmB,EAAE,CAAC;SACjF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QAClE,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,mDAAmD,EAAE,CAAC;SACjH,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QAChE,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,kDAAkD,EAAE,CAAC;SAChH,CAAC;IACJ,CAAC;IAED,0CAA0C;IAC1C,MAAM,gBAAgB,GAAG,MAAM,GAAG,CAAC,IAAI,CACrC,IAAI,yBAAU,CAAC;QACb,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE;YACH,EAAE,EAAE,UAAU,QAAQ,EAAE;YACxB,EAAE,EAAE,UAAU;SACf;KACF,CAAC,CACH,CAAC;IAEF,IAAI,gBAAgB,CAAC,IAAI,EAAE,CAAC;QAC1B,OAAO;YACL,UAAU,EAAE,GAAG;YACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,sBAAsB,EAAE,OAAO,EAAE,uCAAuC,EAAE,CAAC;SAC1G,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,GAAG,wBAAwB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACtE,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,wBAAwB;IACxB,MAAM,GAAG,CAAC,IAAI,CACZ,IAAI,yBAAU,CAAC;QACb,SAAS,EAAE,SAAS;QACpB,IAAI,EAAE;YACJ,EAAE,EAAE,UAAU,QAAQ,EAAE;YACxB,EAAE,EAAE,UAAU;YACd,KAAK,EAAE,SAAS;YAChB,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,cAAc;YACd,cAAc;YACd,SAAS,EAAE,GAAG;YACd,aAAa,EAAE,EAAE;SAClB;KACF,CAAC,CACH,CAAC;IAEF,+EAA+E;IAC/E,MAAM,GAAG,CAAC,IAAI,CACZ,IAAI,4BAAa,CAAC;QAChB,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE;YACH,EAAE,EAAE,UAAU,QAAQ,EAAE;YACxB,EAAE,EAAE,MAAM;SACX;QACD,gBAAgB,EAAE,mEAAmE;QACrF,yBAAyB,EAAE;YACzB,MAAM,EAAE,OAAO,CAAC,YAAY;YAC5B,QAAQ,EAAE,kBAAkB;YAC5B,MAAM,EAAE,GAAG;SACZ;KACF,CAAC,CACH,CAAC;IAEF,yCAAyC;IACzC,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,cAAc,EAAE;YACnC,IAAI,EAAE,OAAO,CAAC,WAAW;YACzB,SAAS,EAAE,GAAG;SACf;KACF,CAAC,CACH,CAAC;IAEF,kBAAM,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC,CAAC;IAElF,OAAO;QACL,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,MAAM,EAAE,kBAAkB;YAC1B,cAAc;YACd,cAAc;SACf,CAAC;KACH,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,mBAAmB,CACvC,QAAgB,EAChB,WAAmB,EACnB,GAA2B,EAC3B,SAAiB;IAEjB,0BAA0B;IAC1B,MAAM,cAAc,GAAG,MAAM,GAAG,CAAC,IAAI,CACnC,IAAI,yBAAU,CAAC;QACb,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE;YACH,EAAE,EAAE,UAAU,QAAQ,EAAE;YACxB,EAAE,EAAE,UAAU;SACf;KACF,CAAC,CACH,CAAC;IAEF,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QACzB,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,6BAA6B,EAAE,CAAC;SACrF,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC;IACrC,MAAM,aAAa,GAAa,QAAQ,CAAC,eAAe,CAAa,IAAI,EAAE,CAAC;IAE5E,sEAAsE;IACtE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QACzC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAEhC,MAAM,GAAG,CAAC,IAAI,CACZ,IAAI,4BAAa,CAAC;YAChB,SAAS,EAAE,SAAS;YACpB,GAAG,EAAE;gBACH,EAAE,EAAE,UAAU,QAAQ,EAAE;gBACxB,EAAE,EAAE,UAAU;aACf;YACD,gBAAgB,EAAE,wBAAwB;YAC1C,yBAAyB,EAAE;gBACzB,IAAI,EAAE,aAAa;aACpB;SACF,CAAC,CACH,CAAC;IACJ,CAAC;IAED,yCAAyC;IACzC,sDAAsD;IACtD,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAW,CAAC,CAAC,OAAO,EAAE,CAAC;IACtE,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,mBAAmB,CAAC;IAEpE,uEAAuE;IACvE,MAAM,YAAY,GAAG,MAAM,GAAG,CAAC,IAAI,CACjC,IAAI,2BAAY,CAAC;QACf,SAAS,EAAE,SAAS;QACpB,sBAAsB,EAAE,uCAAuC;QAC/D,yBAAyB,EAAE;YACzB,KAAK,EAAE,UAAU,QAAQ,EAAE;YAC3B,SAAS,EAAE,cAAc;SAC1B;QACD,MAAM,EAAE,OAAO;KAChB,CAAC,CACH,CAAC;IAEF,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,IAAI,CAAC,CAAC;IAC5C,MAAM,YAAY,GAAG,aAAa,CAAC,MAAM,IAAI,WAAW,CAAC;IAEzD,IAAI,YAAY,IAAI,cAAc,EAAE,CAAC;QACnC,iCAAiC;QACjC,MAAM,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;QAE3D,kBAAM,CAAC,IAAI,CAAC,wBAAwB,EAAE;YACpC,QAAQ;YACR,cAAc,EAAE,QAAQ,CAAC,gBAAgB,CAAC;YAC1C,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,eAAe;SACrD,CAAC,CAAC;QAEH,OAAO;YACL,UAAU,EAAE,GAAG;YACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,MAAM,EAAE,mBAAmB;gBAC3B,aAAa,EAAE,aAAa,CAAC,MAAM;gBACnC,YAAY,EAAE,WAAW;aAC1B,CAAC;SACH,CAAC;IACJ,CAAC;IAED,OAAO;QACL,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,MAAM,EAAE,uBAAuB;YAC/B,aAAa,EAAE,aAAa,CAAC,MAAM;YACnC,YAAY,EAAE,WAAW;SAC1B,CAAC;KACH,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,gBAAgB,CAC7B,QAAgB,EAChB,QAAiC,EACjC,GAA2B,EAC3B,SAAiB;IAEjB,MAAM,YAAY,GAAG,QAAQ,CAAC,cAAc,CAAW,CAAC;IACxD,MAAM,cAAc,GAAG,QAAQ,CAAC,gBAAgB,CAAW,CAAC;IAC5D,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,uEAAuE;IACvE,MAAM,GAAG,CAAC,IAAI,CACZ,IAAI,4BAAa,CAAC;QAChB,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE;YACH,EAAE,EAAE,UAAU,QAAQ,EAAE;YACxB,EAAE,EAAE,MAAM;SACX;QACD,gBAAgB,EAAE,0EAA0E;QAC5F,yBAAyB,EAAE;YACzB,KAAK,EAAE,YAAY;YACnB,MAAM,EAAE,GAAG;SACZ;KACF,CAAC,CACH,CAAC;IAEF,8BAA8B;IAC9B,MAAM,GAAG,CAAC,IAAI,CACZ,IAAI,4BAAa,CAAC;QAChB,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE;YACH,EAAE,EAAE,UAAU,QAAQ,EAAE;YACxB,EAAE,EAAE,eAAe,cAAc,EAAE;SACpC;KACF,CAAC,CACH,CAAC;IAEF,yBAAyB;IACzB,MAAM,GAAG,CAAC,IAAI,CACZ,IAAI,4BAAa,CAAC;QAChB,SAAS,EAAE,SAAS;QACpB,GAAG,EAAE;YACH,EAAE,EAAE,UAAU,QAAQ,EAAE;YACxB,EAAE,EAAE,UAAU;SACf;KACF,CAAC,CACH,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"rotation.js","sourceRoot":"","sources":["../../../../lib/handler/routes/rotation.ts"],"names":[],"mappings":"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"}
@@ -28,10 +28,10 @@ function fingerprintFromPublicKey(publicKeyBase64: string): string {
28
28
  }
29
29
 
30
30
  function tenantIdFromPublicKey(publicKeyBase64: string): string {
31
- const hash = crypto.createHash('sha256').update(publicKeyBase64).digest('hex');
32
- return hash.slice(0, 32);
31
+ return crypto.createHash('sha256').update(publicKeyBase64).digest('hex').slice(0, 32);
33
32
  }
34
33
 
34
+
35
35
  function isValidBase64(value: string): boolean {
36
36
  if (!value || value.length < 4 || value.length > 8192) return false;
37
37
  try {
@@ -160,6 +160,22 @@ export async function handleRotateStart(
160
160
  }),
161
161
  );
162
162
 
163
+ // Write a KEY_ALIAS record so auth middleware can resolve the new key's
164
+ // derived tenantId back to this tenant during rotation
165
+ const newKeyTenantId = tenantIdFromPublicKey(request.newPublicKey);
166
+ await ddb.send(
167
+ new PutCommand({
168
+ TableName: tableName,
169
+ Item: {
170
+ PK: `KEY_ALIAS#${newKeyTenantId}`,
171
+ SK: 'META',
172
+ originalTenantId: tenantId,
173
+ newPublicKey: request.newPublicKey,
174
+ createdAt: now,
175
+ },
176
+ }),
177
+ );
178
+
163
179
  logger.info('Key rotation started', { tenantId, oldFingerprint, newFingerprint });
164
180
 
165
181
  return {
@@ -333,4 +349,16 @@ async function completeRotation(
333
349
  },
334
350
  }),
335
351
  );
352
+
353
+ // Delete the KEY_ALIAS lookup record for the new key
354
+ const newKeyTenantId = tenantIdFromPublicKey(newPublicKey);
355
+ await ddb.send(
356
+ new DeleteCommand({
357
+ TableName: tableName,
358
+ Key: {
359
+ PK: `KEY_ALIAS#${newKeyTenantId}`,
360
+ SK: 'META',
361
+ },
362
+ }),
363
+ );
336
364
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@de-otio/chaoskb-server",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "ChaosKB server - CDK construct library for the hosted backend",
5
5
  "license": "MIT",
6
6
  "repository": {