@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.
- package/dist/lib/admin-handler/index.d.ts +22 -0
- package/dist/lib/admin-handler/index.d.ts.map +1 -0
- package/dist/lib/admin-handler/index.js +92 -0
- package/dist/lib/admin-handler/index.js.map +1 -0
- package/dist/lib/admin-handler/index.ts +123 -0
- package/dist/lib/admin-handler/routes/metrics.d.ts +11 -0
- package/dist/lib/admin-handler/routes/metrics.d.ts.map +1 -0
- package/dist/lib/admin-handler/routes/metrics.js +200 -0
- package/dist/lib/admin-handler/routes/metrics.js.map +1 -0
- package/dist/lib/admin-handler/routes/metrics.ts +234 -0
- package/dist/lib/admin-handler/routes/overview.d.ts +9 -0
- package/dist/lib/admin-handler/routes/overview.d.ts.map +1 -0
- package/dist/lib/admin-handler/routes/overview.js +110 -0
- package/dist/lib/admin-handler/routes/overview.js.map +1 -0
- package/dist/lib/admin-handler/routes/overview.ts +133 -0
- package/dist/lib/admin-handler/routes/tenants.d.ts +10 -0
- package/dist/lib/admin-handler/routes/tenants.d.ts.map +1 -0
- package/dist/lib/admin-handler/routes/tenants.js +108 -0
- package/dist/lib/admin-handler/routes/tenants.js.map +1 -0
- package/dist/lib/admin-handler/routes/tenants.ts +134 -0
- package/dist/lib/chaoskb-stack.d.ts +22 -0
- package/dist/lib/chaoskb-stack.d.ts.map +1 -0
- package/dist/lib/chaoskb-stack.js +60 -0
- package/dist/lib/chaoskb-stack.js.map +1 -0
- package/dist/lib/constructs/admin-api.d.ts +16 -0
- package/dist/lib/constructs/admin-api.d.ts.map +1 -0
- package/dist/lib/constructs/admin-api.js +93 -0
- package/dist/lib/constructs/admin-api.js.map +1 -0
- package/dist/lib/constructs/admin-dashboard.d.ts +18 -0
- package/dist/lib/constructs/admin-dashboard.d.ts.map +1 -0
- package/dist/lib/constructs/admin-dashboard.js +172 -0
- package/dist/lib/constructs/admin-dashboard.js.map +1 -0
- package/dist/lib/constructs/api.d.ts +17 -0
- package/dist/lib/constructs/api.d.ts.map +1 -0
- package/dist/lib/constructs/api.js +81 -0
- package/dist/lib/constructs/api.js.map +1 -0
- package/dist/lib/constructs/auth.d.ts +11 -0
- package/dist/lib/constructs/auth.d.ts.map +1 -0
- package/dist/lib/constructs/auth.js +18 -0
- package/dist/lib/constructs/auth.js.map +1 -0
- package/dist/lib/constructs/blob-store.d.ts +10 -0
- package/dist/lib/constructs/blob-store.d.ts.map +1 -0
- package/dist/lib/constructs/blob-store.js +31 -0
- package/dist/lib/constructs/blob-store.js.map +1 -0
- package/dist/lib/deploy-cli.d.ts +3 -0
- package/dist/lib/deploy-cli.d.ts.map +1 -0
- package/dist/lib/deploy-cli.js +49 -0
- package/dist/lib/deploy-cli.js.map +1 -0
- package/dist/lib/handler/index.d.ts +23 -0
- package/dist/lib/handler/index.d.ts.map +1 -0
- package/dist/lib/handler/index.js +276 -0
- package/dist/lib/handler/index.js.map +1 -0
- package/dist/lib/handler/index.ts +372 -0
- package/dist/lib/handler/logger.d.ts +16 -0
- package/dist/lib/handler/logger.d.ts.map +1 -0
- package/dist/lib/handler/logger.js +26 -0
- package/dist/lib/handler/logger.js.map +1 -0
- package/dist/lib/handler/logger.ts +36 -0
- package/dist/lib/handler/middleware/input-validation.d.ts +6 -0
- package/dist/lib/handler/middleware/input-validation.d.ts.map +1 -0
- package/dist/lib/handler/middleware/input-validation.js +36 -0
- package/dist/lib/handler/middleware/input-validation.js.map +1 -0
- package/dist/lib/handler/middleware/input-validation.ts +44 -0
- package/dist/lib/handler/middleware/rate-limit.d.ts +14 -0
- package/dist/lib/handler/middleware/rate-limit.d.ts.map +1 -0
- package/dist/lib/handler/middleware/rate-limit.js +94 -0
- package/dist/lib/handler/middleware/rate-limit.js.map +1 -0
- package/dist/lib/handler/middleware/rate-limit.ts +121 -0
- package/dist/lib/handler/middleware/ssh-auth.d.ts +48 -0
- package/dist/lib/handler/middleware/ssh-auth.d.ts.map +1 -0
- package/dist/lib/handler/middleware/ssh-auth.js +256 -0
- package/dist/lib/handler/middleware/ssh-auth.js.map +1 -0
- package/dist/lib/handler/middleware/ssh-auth.ts +300 -0
- package/dist/lib/handler/routes/audit.d.ts +24 -0
- package/dist/lib/handler/routes/audit.d.ts.map +1 -0
- package/dist/lib/handler/routes/audit.js +94 -0
- package/dist/lib/handler/routes/audit.js.map +1 -0
- package/dist/lib/handler/routes/audit.ts +101 -0
- package/dist/lib/handler/routes/blobs.d.ts +13 -0
- package/dist/lib/handler/routes/blobs.d.ts.map +1 -0
- package/dist/lib/handler/routes/blobs.js +298 -0
- package/dist/lib/handler/routes/blobs.js.map +1 -0
- package/dist/lib/handler/routes/blobs.ts +348 -0
- package/dist/lib/handler/routes/devices.d.ts +48 -0
- package/dist/lib/handler/routes/devices.d.ts.map +1 -0
- package/dist/lib/handler/routes/devices.js +394 -0
- package/dist/lib/handler/routes/devices.js.map +1 -0
- package/dist/lib/handler/routes/devices.ts +458 -0
- package/dist/lib/handler/routes/export.d.ts +9 -0
- package/dist/lib/handler/routes/export.d.ts.map +1 -0
- package/dist/lib/handler/routes/export.js +40 -0
- package/dist/lib/handler/routes/export.js.map +1 -0
- package/dist/lib/handler/routes/export.ts +55 -0
- package/dist/lib/handler/routes/github.d.ts +31 -0
- package/dist/lib/handler/routes/github.d.ts.map +1 -0
- package/dist/lib/handler/routes/github.js +118 -0
- package/dist/lib/handler/routes/github.js.map +1 -0
- package/dist/lib/handler/routes/github.ts +162 -0
- package/dist/lib/handler/routes/health.d.ts +6 -0
- package/dist/lib/handler/routes/health.d.ts.map +1 -0
- package/dist/lib/handler/routes/health.js +14 -0
- package/dist/lib/handler/routes/health.js.map +1 -0
- package/dist/lib/handler/routes/health.ts +10 -0
- package/dist/lib/handler/routes/invites.d.ts +24 -0
- package/dist/lib/handler/routes/invites.d.ts.map +1 -0
- package/dist/lib/handler/routes/invites.js +445 -0
- package/dist/lib/handler/routes/invites.js.map +1 -0
- package/dist/lib/handler/routes/invites.ts +527 -0
- package/dist/lib/handler/routes/notifications.d.ts +39 -0
- package/dist/lib/handler/routes/notifications.d.ts.map +1 -0
- package/dist/lib/handler/routes/notifications.js +150 -0
- package/dist/lib/handler/routes/notifications.js.map +1 -0
- package/dist/lib/handler/routes/notifications.ts +163 -0
- package/dist/lib/handler/routes/projects.d.ts +24 -0
- package/dist/lib/handler/routes/projects.d.ts.map +1 -0
- package/dist/lib/handler/routes/projects.js +47 -0
- package/dist/lib/handler/routes/projects.js.map +1 -0
- package/dist/lib/handler/routes/projects.ts +69 -0
- package/dist/lib/handler/routes/register.d.ts +19 -0
- package/dist/lib/handler/routes/register.d.ts.map +1 -0
- package/dist/lib/handler/routes/register.js +327 -0
- package/dist/lib/handler/routes/register.js.map +1 -0
- package/dist/lib/handler/routes/register.ts +363 -0
- package/dist/lib/handler/routes/restore.d.ts +9 -0
- package/dist/lib/handler/routes/restore.d.ts.map +1 -0
- package/dist/lib/handler/routes/restore.js +52 -0
- package/dist/lib/handler/routes/restore.js.map +1 -0
- package/dist/lib/handler/routes/restore.ts +73 -0
- package/dist/lib/handler/routes/revocation.d.ts +13 -0
- package/dist/lib/handler/routes/revocation.d.ts.map +1 -0
- package/dist/lib/handler/routes/revocation.js +63 -0
- package/dist/lib/handler/routes/revocation.js.map +1 -0
- package/dist/lib/handler/routes/revocation.ts +87 -0
- package/dist/lib/handler/routes/rotation.d.ts +24 -0
- package/dist/lib/handler/routes/rotation.d.ts.map +1 -0
- package/dist/lib/handler/routes/rotation.js +291 -0
- package/dist/lib/handler/routes/rotation.js.map +1 -0
- package/dist/lib/handler/routes/rotation.ts +336 -0
- package/dist/lib/handler/routes/tenants.d.ts +11 -0
- package/dist/lib/handler/routes/tenants.d.ts.map +1 -0
- package/dist/lib/handler/routes/tenants.js +181 -0
- package/dist/lib/handler/routes/tenants.js.map +1 -0
- package/dist/lib/handler/routes/tenants.ts +198 -0
- package/dist/lib/handler/routes/wrapped-key.d.ts +21 -0
- package/dist/lib/handler/routes/wrapped-key.d.ts.map +1 -0
- package/dist/lib/handler/routes/wrapped-key.js +76 -0
- package/dist/lib/handler/routes/wrapped-key.js.map +1 -0
- package/dist/lib/handler/routes/wrapped-key.ts +108 -0
- package/dist/lib/index.d.ts +7 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +16 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +18 -0
- package/dist/vitest.config.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,150 @@
|
|
|
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.createNotification = createNotification;
|
|
37
|
+
exports.handleGetNotifications = handleGetNotifications;
|
|
38
|
+
exports.handleDismissNotification = handleDismissNotification;
|
|
39
|
+
exports.resolveIpLocation = resolveIpLocation;
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
|
|
42
|
+
const logger_js_1 = require("../logger.js");
|
|
43
|
+
const JSON_HEADERS = { 'Content-Type': 'application/json' };
|
|
44
|
+
const TTL_30_DAYS = 30 * 24 * 60 * 60;
|
|
45
|
+
/**
|
|
46
|
+
* Create a notification for a tenant.
|
|
47
|
+
*
|
|
48
|
+
* DynamoDB: PK: TENANT#{tenantId}, SK: NOTIFICATION#{timestamp}#{suffix}
|
|
49
|
+
*/
|
|
50
|
+
async function createNotification(tenantId, type, deviceInfo, ddb, tableName) {
|
|
51
|
+
const now = new Date().toISOString();
|
|
52
|
+
const suffix = crypto.randomBytes(4).toString('hex');
|
|
53
|
+
const ttl = Math.floor(Date.now() / 1000) + TTL_30_DAYS;
|
|
54
|
+
await ddb.send(new lib_dynamodb_1.PutCommand({
|
|
55
|
+
TableName: tableName,
|
|
56
|
+
Item: {
|
|
57
|
+
PK: `TENANT#${tenantId}`,
|
|
58
|
+
SK: `NOTIFICATION#${now}#${suffix}`,
|
|
59
|
+
type,
|
|
60
|
+
deviceInfo,
|
|
61
|
+
acknowledged: false,
|
|
62
|
+
timestamp: now,
|
|
63
|
+
ttl,
|
|
64
|
+
},
|
|
65
|
+
}));
|
|
66
|
+
logger_js_1.logger.info('Notification created', { tenantId, type });
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* GET /v1/notifications — return unacknowledged notifications for tenant.
|
|
70
|
+
*/
|
|
71
|
+
async function handleGetNotifications(tenantId, ddb, tableName) {
|
|
72
|
+
const result = await ddb.send(new lib_dynamodb_1.QueryCommand({
|
|
73
|
+
TableName: tableName,
|
|
74
|
+
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :prefix)',
|
|
75
|
+
FilterExpression: 'acknowledged = :false',
|
|
76
|
+
ExpressionAttributeValues: {
|
|
77
|
+
':pk': `TENANT#${tenantId}`,
|
|
78
|
+
':prefix': 'NOTIFICATION#',
|
|
79
|
+
':false': false,
|
|
80
|
+
},
|
|
81
|
+
ScanIndexForward: false, // newest first
|
|
82
|
+
Limit: 50,
|
|
83
|
+
}));
|
|
84
|
+
const notifications = (result.Items ?? []).map((item) => ({
|
|
85
|
+
id: item['SK'],
|
|
86
|
+
type: item['type'],
|
|
87
|
+
deviceInfo: item['deviceInfo'],
|
|
88
|
+
timestamp: item['timestamp'],
|
|
89
|
+
}));
|
|
90
|
+
return {
|
|
91
|
+
statusCode: 200,
|
|
92
|
+
headers: JSON_HEADERS,
|
|
93
|
+
body: JSON.stringify({ notifications }),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* POST /v1/notifications/{id}/dismiss — mark notification as acknowledged.
|
|
98
|
+
*/
|
|
99
|
+
async function handleDismissNotification(tenantId, notificationId, ddb, tableName) {
|
|
100
|
+
try {
|
|
101
|
+
await ddb.send(new lib_dynamodb_1.UpdateCommand({
|
|
102
|
+
TableName: tableName,
|
|
103
|
+
Key: {
|
|
104
|
+
PK: `TENANT#${tenantId}`,
|
|
105
|
+
SK: notificationId,
|
|
106
|
+
},
|
|
107
|
+
UpdateExpression: 'SET acknowledged = :true',
|
|
108
|
+
ConditionExpression: 'attribute_exists(PK)',
|
|
109
|
+
ExpressionAttributeValues: {
|
|
110
|
+
':true': true,
|
|
111
|
+
},
|
|
112
|
+
}));
|
|
113
|
+
return {
|
|
114
|
+
statusCode: 200,
|
|
115
|
+
headers: JSON_HEADERS,
|
|
116
|
+
body: JSON.stringify({ status: 'dismissed' }),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
if (err.name === 'ConditionalCheckFailedException') {
|
|
121
|
+
return {
|
|
122
|
+
statusCode: 404,
|
|
123
|
+
headers: JSON_HEADERS,
|
|
124
|
+
body: JSON.stringify({ error: 'not_found', message: 'Notification not found' }),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* G3: Resolve IP address to approximate location.
|
|
132
|
+
*
|
|
133
|
+
* For Lambda, use a lightweight approach: extract country/region from
|
|
134
|
+
* CloudFront headers if available, otherwise return null.
|
|
135
|
+
* A full MaxMind integration can be added later.
|
|
136
|
+
*/
|
|
137
|
+
function resolveIpLocation(headers) {
|
|
138
|
+
// CloudFront sets these headers when the distribution is configured
|
|
139
|
+
const country = headers['cloudfront-viewer-country'] || headers['CloudFront-Viewer-Country'];
|
|
140
|
+
const city = headers['cloudfront-viewer-city'] || headers['CloudFront-Viewer-City'];
|
|
141
|
+
const region = headers['cloudfront-viewer-country-region-name'] || headers['CloudFront-Viewer-Country-Region-Name'];
|
|
142
|
+
if (city && country) {
|
|
143
|
+
return region ? `${city}, ${region}, ${country}` : `${city}, ${country}`;
|
|
144
|
+
}
|
|
145
|
+
if (country) {
|
|
146
|
+
return country;
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=notifications.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../../lib/handler/routes/notifications.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import {
|
|
3
|
+
DynamoDBDocumentClient,
|
|
4
|
+
PutCommand,
|
|
5
|
+
QueryCommand,
|
|
6
|
+
UpdateCommand,
|
|
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
|
+
const JSON_HEADERS = { 'Content-Type': 'application/json' };
|
|
17
|
+
const TTL_30_DAYS = 30 * 24 * 60 * 60;
|
|
18
|
+
|
|
19
|
+
export type NotificationType = 'device_linked' | 'device_revoked' | 'key_rotated';
|
|
20
|
+
|
|
21
|
+
export interface DeviceInfo {
|
|
22
|
+
hostname?: string;
|
|
23
|
+
platform?: string;
|
|
24
|
+
arch?: string;
|
|
25
|
+
osVersion?: string;
|
|
26
|
+
deviceModel?: string | null;
|
|
27
|
+
location?: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a notification for a tenant.
|
|
32
|
+
*
|
|
33
|
+
* DynamoDB: PK: TENANT#{tenantId}, SK: NOTIFICATION#{timestamp}#{suffix}
|
|
34
|
+
*/
|
|
35
|
+
export async function createNotification(
|
|
36
|
+
tenantId: string,
|
|
37
|
+
type: NotificationType,
|
|
38
|
+
deviceInfo: DeviceInfo,
|
|
39
|
+
ddb: DynamoDBDocumentClient,
|
|
40
|
+
tableName: string,
|
|
41
|
+
): Promise<void> {
|
|
42
|
+
const now = new Date().toISOString();
|
|
43
|
+
const suffix = crypto.randomBytes(4).toString('hex');
|
|
44
|
+
const ttl = Math.floor(Date.now() / 1000) + TTL_30_DAYS;
|
|
45
|
+
|
|
46
|
+
await ddb.send(
|
|
47
|
+
new PutCommand({
|
|
48
|
+
TableName: tableName,
|
|
49
|
+
Item: {
|
|
50
|
+
PK: `TENANT#${tenantId}`,
|
|
51
|
+
SK: `NOTIFICATION#${now}#${suffix}`,
|
|
52
|
+
type,
|
|
53
|
+
deviceInfo,
|
|
54
|
+
acknowledged: false,
|
|
55
|
+
timestamp: now,
|
|
56
|
+
ttl,
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
logger.info('Notification created', { tenantId, type });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* GET /v1/notifications — return unacknowledged notifications for tenant.
|
|
66
|
+
*/
|
|
67
|
+
export async function handleGetNotifications(
|
|
68
|
+
tenantId: string,
|
|
69
|
+
ddb: DynamoDBDocumentClient,
|
|
70
|
+
tableName: string,
|
|
71
|
+
): Promise<HandlerResponse> {
|
|
72
|
+
const result = await ddb.send(
|
|
73
|
+
new QueryCommand({
|
|
74
|
+
TableName: tableName,
|
|
75
|
+
KeyConditionExpression: 'PK = :pk AND begins_with(SK, :prefix)',
|
|
76
|
+
FilterExpression: 'acknowledged = :false',
|
|
77
|
+
ExpressionAttributeValues: {
|
|
78
|
+
':pk': `TENANT#${tenantId}`,
|
|
79
|
+
':prefix': 'NOTIFICATION#',
|
|
80
|
+
':false': false,
|
|
81
|
+
},
|
|
82
|
+
ScanIndexForward: false, // newest first
|
|
83
|
+
Limit: 50,
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const notifications = (result.Items ?? []).map((item) => ({
|
|
88
|
+
id: item['SK'],
|
|
89
|
+
type: item['type'],
|
|
90
|
+
deviceInfo: item['deviceInfo'],
|
|
91
|
+
timestamp: item['timestamp'],
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
statusCode: 200,
|
|
96
|
+
headers: JSON_HEADERS,
|
|
97
|
+
body: JSON.stringify({ notifications }),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* POST /v1/notifications/{id}/dismiss — mark notification as acknowledged.
|
|
103
|
+
*/
|
|
104
|
+
export async function handleDismissNotification(
|
|
105
|
+
tenantId: string,
|
|
106
|
+
notificationId: string,
|
|
107
|
+
ddb: DynamoDBDocumentClient,
|
|
108
|
+
tableName: string,
|
|
109
|
+
): Promise<HandlerResponse> {
|
|
110
|
+
try {
|
|
111
|
+
await ddb.send(
|
|
112
|
+
new UpdateCommand({
|
|
113
|
+
TableName: tableName,
|
|
114
|
+
Key: {
|
|
115
|
+
PK: `TENANT#${tenantId}`,
|
|
116
|
+
SK: notificationId,
|
|
117
|
+
},
|
|
118
|
+
UpdateExpression: 'SET acknowledged = :true',
|
|
119
|
+
ConditionExpression: 'attribute_exists(PK)',
|
|
120
|
+
ExpressionAttributeValues: {
|
|
121
|
+
':true': true,
|
|
122
|
+
},
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
statusCode: 200,
|
|
128
|
+
headers: JSON_HEADERS,
|
|
129
|
+
body: JSON.stringify({ status: 'dismissed' }),
|
|
130
|
+
};
|
|
131
|
+
} catch (err: unknown) {
|
|
132
|
+
if ((err as { name?: string }).name === 'ConditionalCheckFailedException') {
|
|
133
|
+
return {
|
|
134
|
+
statusCode: 404,
|
|
135
|
+
headers: JSON_HEADERS,
|
|
136
|
+
body: JSON.stringify({ error: 'not_found', message: 'Notification not found' }),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* G3: Resolve IP address to approximate location.
|
|
145
|
+
*
|
|
146
|
+
* For Lambda, use a lightweight approach: extract country/region from
|
|
147
|
+
* CloudFront headers if available, otherwise return null.
|
|
148
|
+
* A full MaxMind integration can be added later.
|
|
149
|
+
*/
|
|
150
|
+
export function resolveIpLocation(headers: Record<string, string>): string | null {
|
|
151
|
+
// CloudFront sets these headers when the distribution is configured
|
|
152
|
+
const country = headers['cloudfront-viewer-country'] || headers['CloudFront-Viewer-Country'];
|
|
153
|
+
const city = headers['cloudfront-viewer-city'] || headers['CloudFront-Viewer-City'];
|
|
154
|
+
const region = headers['cloudfront-viewer-country-region-name'] || headers['CloudFront-Viewer-Country-Region-Name'];
|
|
155
|
+
|
|
156
|
+
if (city && country) {
|
|
157
|
+
return region ? `${city}, ${region}, ${country}` : `${city}, ${country}`;
|
|
158
|
+
}
|
|
159
|
+
if (country) {
|
|
160
|
+
return country;
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
2
|
+
interface HandlerResponse {
|
|
3
|
+
statusCode: number;
|
|
4
|
+
body: string;
|
|
5
|
+
headers: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export interface SharedProjectMeta {
|
|
8
|
+
name: string;
|
|
9
|
+
role: string;
|
|
10
|
+
owner: string;
|
|
11
|
+
itemCount: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* GET /v1/projects/available
|
|
15
|
+
*
|
|
16
|
+
* Query all shared projects this tenant has access to (via MEMBER# records).
|
|
17
|
+
* Returns metadata only: [{ name, role, owner, itemCount }]
|
|
18
|
+
*
|
|
19
|
+
* For now, returns an empty array with the correct shape.
|
|
20
|
+
* Full multi-tenant membership lookup is Phase 2+.
|
|
21
|
+
*/
|
|
22
|
+
export declare function handleListAvailableProjects(tenantId: string, ddb: DynamoDBDocumentClient, tableName: string): Promise<HandlerResponse>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=projects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../../../lib/handler/routes/projects.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EAEvB,MAAM,uBAAuB,CAAC;AAI/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,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAmC1B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleListAvailableProjects = handleListAvailableProjects;
|
|
4
|
+
const rate_limit_js_1 = require("../middleware/rate-limit.js");
|
|
5
|
+
const logger_js_1 = require("../logger.js");
|
|
6
|
+
/**
|
|
7
|
+
* GET /v1/projects/available
|
|
8
|
+
*
|
|
9
|
+
* Query all shared projects this tenant has access to (via MEMBER# records).
|
|
10
|
+
* Returns metadata only: [{ name, role, owner, itemCount }]
|
|
11
|
+
*
|
|
12
|
+
* For now, returns an empty array with the correct shape.
|
|
13
|
+
* Full multi-tenant membership lookup is Phase 2+.
|
|
14
|
+
*/
|
|
15
|
+
async function handleListAvailableProjects(tenantId, ddb, tableName) {
|
|
16
|
+
const rateResult = await (0, rate_limit_js_1.checkRateLimit)(tenantId, 'GET', ddb, tableName);
|
|
17
|
+
if (!rateResult.allowed) {
|
|
18
|
+
return {
|
|
19
|
+
statusCode: 429,
|
|
20
|
+
headers: { 'Content-Type': 'application/json', ...(0, rate_limit_js_1.rateLimitHeaders)(rateResult) },
|
|
21
|
+
body: JSON.stringify({ error: 'rate_limited', message: 'Too many requests' }),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Phase 2+: Query MEMBER# records for this tenant's public key
|
|
25
|
+
// For now, return an empty array with the correct shape.
|
|
26
|
+
//
|
|
27
|
+
// Future implementation:
|
|
28
|
+
// const result = await ddb.send(new QueryCommand({
|
|
29
|
+
// TableName: tableName,
|
|
30
|
+
// IndexName: 'member-index',
|
|
31
|
+
// KeyConditionExpression: 'memberPK = :pk',
|
|
32
|
+
// ExpressionAttributeValues: { ':pk': `KEY#${tenantId}` },
|
|
33
|
+
// }));
|
|
34
|
+
// ... map results to SharedProjectMeta ...
|
|
35
|
+
const projects = [];
|
|
36
|
+
logger_js_1.logger.info('Listed available projects', {
|
|
37
|
+
tenantId,
|
|
38
|
+
operation: 'LIST_PROJECTS',
|
|
39
|
+
count: projects.length,
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
statusCode: 200,
|
|
43
|
+
headers: { 'Content-Type': 'application/json', ...(0, rate_limit_js_1.rateLimitHeaders)(rateResult) },
|
|
44
|
+
body: JSON.stringify({ projects }),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=projects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../../../../lib/handler/routes/projects.ts"],"names":[],"mappings":";;AA6BA,kEAuCC;AAhED,+DAA+E;AAC/E,4CAAsC;AAetC;;;;;;;;GAQG;AACI,KAAK,UAAU,2BAA2B,CAC/C,QAAgB,EAChB,GAA2B,EAC3B,SAAiB;IAEjB,MAAM,UAAU,GAAG,MAAM,IAAA,8BAAc,EAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;IACzE,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QACxB,OAAO;YACL,UAAU,EAAE,GAAG;YACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,IAAA,gCAAgB,EAAC,UAAU,CAAC,EAAE;YAChF,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC;SAC9E,CAAC;IACJ,CAAC;IAED,+DAA+D;IAC/D,yDAAyD;IACzD,EAAE;IACF,yBAAyB;IACzB,qDAAqD;IACrD,4BAA4B;IAC5B,iCAAiC;IACjC,gDAAgD;IAChD,+DAA+D;IAC/D,SAAS;IACT,6CAA6C;IAE7C,MAAM,QAAQ,GAAwB,EAAE,CAAC;IAEzC,kBAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE;QACvC,QAAQ;QACR,SAAS,EAAE,eAAe;QAC1B,KAAK,EAAE,QAAQ,CAAC,MAAM;KACvB,CAAC,CAAC;IAEH,OAAO;QACL,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,IAAA,gCAAgB,EAAC,UAAU,CAAC,EAAE;QAChF,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;KACnC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DynamoDBDocumentClient,
|
|
3
|
+
QueryCommand,
|
|
4
|
+
} from '@aws-sdk/lib-dynamodb';
|
|
5
|
+
import { checkRateLimit, rateLimitHeaders } from '../middleware/rate-limit.js';
|
|
6
|
+
import { logger } from '../logger.js';
|
|
7
|
+
|
|
8
|
+
interface HandlerResponse {
|
|
9
|
+
statusCode: number;
|
|
10
|
+
body: string;
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SharedProjectMeta {
|
|
15
|
+
name: string;
|
|
16
|
+
role: string;
|
|
17
|
+
owner: string;
|
|
18
|
+
itemCount: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* GET /v1/projects/available
|
|
23
|
+
*
|
|
24
|
+
* Query all shared projects this tenant has access to (via MEMBER# records).
|
|
25
|
+
* Returns metadata only: [{ name, role, owner, itemCount }]
|
|
26
|
+
*
|
|
27
|
+
* For now, returns an empty array with the correct shape.
|
|
28
|
+
* Full multi-tenant membership lookup is Phase 2+.
|
|
29
|
+
*/
|
|
30
|
+
export async function handleListAvailableProjects(
|
|
31
|
+
tenantId: string,
|
|
32
|
+
ddb: DynamoDBDocumentClient,
|
|
33
|
+
tableName: string,
|
|
34
|
+
): Promise<HandlerResponse> {
|
|
35
|
+
const rateResult = await checkRateLimit(tenantId, 'GET', ddb, tableName);
|
|
36
|
+
if (!rateResult.allowed) {
|
|
37
|
+
return {
|
|
38
|
+
statusCode: 429,
|
|
39
|
+
headers: { 'Content-Type': 'application/json', ...rateLimitHeaders(rateResult) },
|
|
40
|
+
body: JSON.stringify({ error: 'rate_limited', message: 'Too many requests' }),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Phase 2+: Query MEMBER# records for this tenant's public key
|
|
45
|
+
// For now, return an empty array with the correct shape.
|
|
46
|
+
//
|
|
47
|
+
// Future implementation:
|
|
48
|
+
// const result = await ddb.send(new QueryCommand({
|
|
49
|
+
// TableName: tableName,
|
|
50
|
+
// IndexName: 'member-index',
|
|
51
|
+
// KeyConditionExpression: 'memberPK = :pk',
|
|
52
|
+
// ExpressionAttributeValues: { ':pk': `KEY#${tenantId}` },
|
|
53
|
+
// }));
|
|
54
|
+
// ... map results to SharedProjectMeta ...
|
|
55
|
+
|
|
56
|
+
const projects: SharedProjectMeta[] = [];
|
|
57
|
+
|
|
58
|
+
logger.info('Listed available projects', {
|
|
59
|
+
tenantId,
|
|
60
|
+
operation: 'LIST_PROJECTS',
|
|
61
|
+
count: projects.length,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
statusCode: 200,
|
|
66
|
+
headers: { 'Content-Type': 'application/json', ...rateLimitHeaders(rateResult) },
|
|
67
|
+
body: JSON.stringify({ projects }),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
2
|
+
interface HandlerResponse {
|
|
3
|
+
statusCode: number;
|
|
4
|
+
body: string;
|
|
5
|
+
headers: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export declare function checkSignupsEnabled(paramName: string): Promise<boolean>;
|
|
8
|
+
export declare function _resetSignupsCache(): void;
|
|
9
|
+
/**
|
|
10
|
+
* GET /v1/register/challenge — generate a registration challenge nonce.
|
|
11
|
+
*
|
|
12
|
+
* Returns a 32-byte random nonce (base64-encoded) that must be signed by the
|
|
13
|
+
* client's SSH private key and submitted with the registration request.
|
|
14
|
+
* Challenge expires after 60 seconds and is single-use.
|
|
15
|
+
*/
|
|
16
|
+
export declare function handleChallenge(ddb: DynamoDBDocumentClient, tableName: string): Promise<HandlerResponse>;
|
|
17
|
+
export declare function handleRegister(body: string | null | undefined, ddb: DynamoDBDocumentClient, tableName: string, signupsParamName: string): Promise<HandlerResponse>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=register.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../../lib/handler/routes/register.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAyC,MAAM,uBAAuB,CAAC;AAmBtG,UAAU,eAAe;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAUD,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAkB7E;AAGD,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAkDD;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAyB1B;AAED,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,eAAe,CAAC,CAqN1B"}
|