@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.
- package/dist/lib/constructs/blob-store.js +1 -1
- package/dist/lib/constructs/blob-store.js.map +1 -1
- package/dist/lib/handler/index.d.ts.map +1 -1
- package/dist/lib/handler/index.js +20 -4
- package/dist/lib/handler/index.js.map +1 -1
- package/dist/lib/handler/index.ts +19 -4
- package/dist/lib/handler/middleware/rate-limit.d.ts.map +1 -1
- package/dist/lib/handler/middleware/rate-limit.js +13 -9
- package/dist/lib/handler/middleware/rate-limit.js.map +1 -1
- package/dist/lib/handler/middleware/rate-limit.ts +14 -9
- package/dist/lib/handler/middleware/ssh-auth.d.ts.map +1 -1
- package/dist/lib/handler/middleware/ssh-auth.js +66 -6
- package/dist/lib/handler/middleware/ssh-auth.js.map +1 -1
- package/dist/lib/handler/middleware/ssh-auth.ts +74 -7
- package/dist/lib/handler/routes/audit.js +1 -1
- package/dist/lib/handler/routes/audit.js.map +1 -1
- package/dist/lib/handler/routes/audit.ts +1 -1
- package/dist/lib/handler/routes/blobs.d.ts.map +1 -1
- package/dist/lib/handler/routes/blobs.js +2 -3
- package/dist/lib/handler/routes/blobs.js.map +1 -1
- package/dist/lib/handler/routes/blobs.ts +2 -3
- package/dist/lib/handler/routes/devices.d.ts.map +1 -1
- package/dist/lib/handler/routes/devices.js +10 -6
- package/dist/lib/handler/routes/devices.js.map +1 -1
- package/dist/lib/handler/routes/devices.ts +11 -6
- package/dist/lib/handler/routes/github.d.ts +15 -2
- package/dist/lib/handler/routes/github.d.ts.map +1 -1
- package/dist/lib/handler/routes/github.js +96 -22
- package/dist/lib/handler/routes/github.js.map +1 -1
- package/dist/lib/handler/routes/github.ts +68 -35
- package/dist/lib/handler/routes/invites.d.ts.map +1 -1
- package/dist/lib/handler/routes/invites.js +11 -13
- package/dist/lib/handler/routes/invites.js.map +1 -1
- package/dist/lib/handler/routes/invites.ts +11 -13
- package/dist/lib/handler/routes/notifications.js +1 -1
- package/dist/lib/handler/routes/notifications.js.map +1 -1
- package/dist/lib/handler/routes/notifications.ts +1 -1
- package/dist/lib/handler/routes/projects.d.ts.map +1 -1
- package/dist/lib/handler/routes/projects.js.map +1 -1
- package/dist/lib/handler/routes/projects.ts +0 -1
- package/dist/lib/handler/routes/register.d.ts +1 -1
- package/dist/lib/handler/routes/register.d.ts.map +1 -1
- package/dist/lib/handler/routes/register.js +104 -58
- package/dist/lib/handler/routes/register.js.map +1 -1
- package/dist/lib/handler/routes/register.ts +113 -66
- package/dist/lib/handler/routes/restore.d.ts.map +1 -1
- package/dist/lib/handler/routes/restore.js +1 -2
- package/dist/lib/handler/routes/restore.js.map +1 -1
- package/dist/lib/handler/routes/restore.ts +1 -2
- package/dist/lib/handler/routes/rotation.d.ts.map +1 -1
- package/dist/lib/handler/routes/rotation.js +23 -2
- package/dist/lib/handler/routes/rotation.js.map +1 -1
- package/dist/lib/handler/routes/rotation.ts +30 -2
- package/package.json +1 -1
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import * as crypto from 'crypto';
|
|
2
|
-
import { DynamoDBDocumentClient, PutCommand,
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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: {
|
|
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
|
-
|
|
268
|
-
|
|
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: '
|
|
274
|
-
message:
|
|
299
|
+
error: 'github_verification_failed',
|
|
300
|
+
message: 'Could not verify key against this GitHub account',
|
|
275
301
|
}),
|
|
276
302
|
};
|
|
277
303
|
}
|
|
278
|
-
|
|
279
|
-
if (
|
|
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:
|
|
332
|
+
statusCode: 200,
|
|
282
333
|
headers: JSON_HEADERS,
|
|
283
|
-
body: JSON.stringify({
|
|
334
|
+
body: JSON.stringify({
|
|
335
|
+
status: 'auto_linked',
|
|
336
|
+
github: request.github,
|
|
337
|
+
}),
|
|
284
338
|
};
|
|
285
339
|
}
|
|
286
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
}
|