@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,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"}