@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,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitHubVerificationError = void 0;
4
+ exports.fetchGitHubKeys = fetchGitHubKeys;
5
+ exports.verifyKeyOnGitHub = verifyKeyOnGitHub;
6
+ exports.storeGitHubAssociation = storeGitHubAssociation;
7
+ exports.findTenantByGitHub = findTenantByGitHub;
8
+ exports.storeGitHubReverseLookup = storeGitHubReverseLookup;
9
+ exports._resetGitHubKeyCache = _resetGitHubKeyCache;
10
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
11
+ const logger_js_1 = require("../logger.js");
12
+ const JSON_HEADERS = { 'Content-Type': 'application/json' };
13
+ const githubKeyCache = new Map();
14
+ const CACHE_TTL_MS = 5 * 60 * 1000;
15
+ /**
16
+ * Fetch SSH public keys from GitHub for a username.
17
+ * Returns one key per line. Uses a 5-minute in-memory cache.
18
+ */
19
+ async function fetchGitHubKeys(username) {
20
+ const now = Date.now();
21
+ const cached = githubKeyCache.get(username);
22
+ if (cached && now < cached.expiresAt) {
23
+ return cached.keys;
24
+ }
25
+ const response = await fetch(`https://github.com/${encodeURIComponent(username)}.keys`, { signal: AbortSignal.timeout(10_000) });
26
+ if (response.status === 404) {
27
+ throw new GitHubVerificationError('github_user_not_found', `GitHub user "${username}" not found`);
28
+ }
29
+ if (!response.ok) {
30
+ throw new GitHubVerificationError('github_unreachable', `GitHub returned status ${response.status}`);
31
+ }
32
+ const text = await response.text();
33
+ const keys = text
34
+ .split('\n')
35
+ .map((line) => line.trim())
36
+ .filter((line) => line.length > 0);
37
+ githubKeyCache.set(username, { keys, expiresAt: now + CACHE_TTL_MS });
38
+ return keys;
39
+ }
40
+ /**
41
+ * Verify that a public key (in SSH authorized_keys format or base64) appears
42
+ * on a GitHub account.
43
+ */
44
+ async function verifyKeyOnGitHub(publicKeyBase64, githubUsername) {
45
+ const githubKeys = await fetchGitHubKeys(githubUsername);
46
+ for (const ghKey of githubKeys) {
47
+ const parts = ghKey.split(/\s+/);
48
+ if (parts.length >= 2 && parts[1] === publicKeyBase64) {
49
+ return true;
50
+ }
51
+ // Also match if the full key blob matches
52
+ if (ghKey === publicKeyBase64) {
53
+ return true;
54
+ }
55
+ }
56
+ return false;
57
+ }
58
+ class GitHubVerificationError extends Error {
59
+ code;
60
+ constructor(code, message) {
61
+ super(message);
62
+ this.code = code;
63
+ this.name = 'GitHubVerificationError';
64
+ }
65
+ }
66
+ exports.GitHubVerificationError = GitHubVerificationError;
67
+ /**
68
+ * Store the GitHub username association on a tenant.
69
+ * DynamoDB: PK: TENANT#{tenantId}, SK: GITHUB#{username}
70
+ */
71
+ async function storeGitHubAssociation(tenantId, githubUsername, ddb, tableName) {
72
+ const now = new Date().toISOString();
73
+ await ddb.send(new lib_dynamodb_1.PutCommand({
74
+ TableName: tableName,
75
+ Item: {
76
+ PK: `TENANT#${tenantId}`,
77
+ SK: `GITHUB#${githubUsername}`,
78
+ githubUsername,
79
+ createdAt: now,
80
+ },
81
+ }));
82
+ logger_js_1.logger.info('GitHub association stored', { tenantId, githubUsername });
83
+ }
84
+ /**
85
+ * Look up if a tenant is associated with a GitHub username.
86
+ * Returns the tenant ID if found, null otherwise.
87
+ */
88
+ async function findTenantByGitHub(githubUsername, ddb, tableName) {
89
+ // We need a reverse lookup: GITHUB#{username} -> tenantId
90
+ // Store a top-level record for this
91
+ const result = await ddb.send(new lib_dynamodb_1.GetCommand({
92
+ TableName: tableName,
93
+ Key: {
94
+ PK: `GITHUB#${githubUsername}`,
95
+ SK: 'META',
96
+ },
97
+ }));
98
+ return result.Item?.['tenantId'] ?? null;
99
+ }
100
+ /**
101
+ * Store a reverse lookup record: GITHUB#{username} -> tenantId
102
+ */
103
+ async function storeGitHubReverseLookup(githubUsername, tenantId, ddb, tableName) {
104
+ await ddb.send(new lib_dynamodb_1.PutCommand({
105
+ TableName: tableName,
106
+ Item: {
107
+ PK: `GITHUB#${githubUsername}`,
108
+ SK: 'META',
109
+ tenantId,
110
+ createdAt: new Date().toISOString(),
111
+ },
112
+ }));
113
+ }
114
+ // Export for testing
115
+ function _resetGitHubKeyCache() {
116
+ githubKeyCache.clear();
117
+ }
118
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../../../lib/handler/routes/github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,162 @@
1
+ import { DynamoDBDocumentClient, PutCommand, GetCommand } from '@aws-sdk/lib-dynamodb';
2
+ import { logger } from '../logger.js';
3
+
4
+ interface HandlerResponse {
5
+ statusCode: number;
6
+ body: string;
7
+ headers: Record<string, string>;
8
+ }
9
+
10
+ const JSON_HEADERS = { 'Content-Type': 'application/json' };
11
+
12
+ // In-memory cache for GitHub keys (5 min TTL)
13
+ interface CacheEntry {
14
+ keys: string[];
15
+ expiresAt: number;
16
+ }
17
+ const githubKeyCache = new Map<string, CacheEntry>();
18
+ const CACHE_TTL_MS = 5 * 60 * 1000;
19
+
20
+ /**
21
+ * Fetch SSH public keys from GitHub for a username.
22
+ * Returns one key per line. Uses a 5-minute in-memory cache.
23
+ */
24
+ export async function fetchGitHubKeys(username: string): Promise<string[]> {
25
+ const now = Date.now();
26
+ const cached = githubKeyCache.get(username);
27
+ if (cached && now < cached.expiresAt) {
28
+ return cached.keys;
29
+ }
30
+
31
+ const response = await fetch(
32
+ `https://github.com/${encodeURIComponent(username)}.keys`,
33
+ { signal: AbortSignal.timeout(10_000) },
34
+ );
35
+
36
+ if (response.status === 404) {
37
+ throw new GitHubVerificationError('github_user_not_found', `GitHub user "${username}" not found`);
38
+ }
39
+
40
+ if (!response.ok) {
41
+ throw new GitHubVerificationError('github_unreachable', `GitHub returned status ${response.status}`);
42
+ }
43
+
44
+ const text = await response.text();
45
+ const keys = text
46
+ .split('\n')
47
+ .map((line) => line.trim())
48
+ .filter((line) => line.length > 0);
49
+
50
+ githubKeyCache.set(username, { keys, expiresAt: now + CACHE_TTL_MS });
51
+ return keys;
52
+ }
53
+
54
+ /**
55
+ * Verify that a public key (in SSH authorized_keys format or base64) appears
56
+ * on a GitHub account.
57
+ */
58
+ export async function verifyKeyOnGitHub(
59
+ publicKeyBase64: string,
60
+ githubUsername: string,
61
+ ): Promise<boolean> {
62
+ const githubKeys = await fetchGitHubKeys(githubUsername);
63
+
64
+ for (const ghKey of githubKeys) {
65
+ const parts = ghKey.split(/\s+/);
66
+ if (parts.length >= 2 && parts[1] === publicKeyBase64) {
67
+ return true;
68
+ }
69
+ // Also match if the full key blob matches
70
+ if (ghKey === publicKeyBase64) {
71
+ return true;
72
+ }
73
+ }
74
+
75
+ return false;
76
+ }
77
+
78
+ export class GitHubVerificationError extends Error {
79
+ constructor(
80
+ public readonly code: string,
81
+ message: string,
82
+ ) {
83
+ super(message);
84
+ this.name = 'GitHubVerificationError';
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Store the GitHub username association on a tenant.
90
+ * DynamoDB: PK: TENANT#{tenantId}, SK: GITHUB#{username}
91
+ */
92
+ export async function storeGitHubAssociation(
93
+ tenantId: string,
94
+ githubUsername: string,
95
+ ddb: DynamoDBDocumentClient,
96
+ tableName: string,
97
+ ): Promise<void> {
98
+ const now = new Date().toISOString();
99
+ await ddb.send(
100
+ new PutCommand({
101
+ TableName: tableName,
102
+ Item: {
103
+ PK: `TENANT#${tenantId}`,
104
+ SK: `GITHUB#${githubUsername}`,
105
+ githubUsername,
106
+ createdAt: now,
107
+ },
108
+ }),
109
+ );
110
+ logger.info('GitHub association stored', { tenantId, githubUsername });
111
+ }
112
+
113
+ /**
114
+ * Look up if a tenant is associated with a GitHub username.
115
+ * Returns the tenant ID if found, null otherwise.
116
+ */
117
+ export async function findTenantByGitHub(
118
+ githubUsername: string,
119
+ ddb: DynamoDBDocumentClient,
120
+ tableName: string,
121
+ ): Promise<string | null> {
122
+ // We need a reverse lookup: GITHUB#{username} -> tenantId
123
+ // Store a top-level record for this
124
+ const result = await ddb.send(
125
+ new GetCommand({
126
+ TableName: tableName,
127
+ Key: {
128
+ PK: `GITHUB#${githubUsername}`,
129
+ SK: 'META',
130
+ },
131
+ }),
132
+ );
133
+
134
+ return result.Item?.['tenantId'] ?? null;
135
+ }
136
+
137
+ /**
138
+ * Store a reverse lookup record: GITHUB#{username} -> tenantId
139
+ */
140
+ export async function storeGitHubReverseLookup(
141
+ githubUsername: string,
142
+ tenantId: string,
143
+ ddb: DynamoDBDocumentClient,
144
+ tableName: string,
145
+ ): Promise<void> {
146
+ await ddb.send(
147
+ new PutCommand({
148
+ TableName: tableName,
149
+ Item: {
150
+ PK: `GITHUB#${githubUsername}`,
151
+ SK: 'META',
152
+ tenantId,
153
+ createdAt: new Date().toISOString(),
154
+ },
155
+ }),
156
+ );
157
+ }
158
+
159
+ // Export for testing
160
+ export function _resetGitHubKeyCache(): void {
161
+ githubKeyCache.clear();
162
+ }
@@ -0,0 +1,6 @@
1
+ export declare function handleHealth(): {
2
+ statusCode: number;
3
+ body: string;
4
+ headers: Record<string, string>;
5
+ };
6
+ //# sourceMappingURL=health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../../../lib/handler/routes/health.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,IAAI;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CASpG"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleHealth = handleHealth;
4
+ function handleHealth() {
5
+ return {
6
+ statusCode: 200,
7
+ headers: { 'Content-Type': 'application/json' },
8
+ body: JSON.stringify({
9
+ status: 'ok',
10
+ timestamp: new Date().toISOString(),
11
+ }),
12
+ };
13
+ }
14
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../../../lib/handler/routes/health.ts"],"names":[],"mappings":";;AAAA,oCASC;AATD,SAAgB,YAAY;IAC1B,OAAO;QACL,UAAU,EAAE,GAAG;QACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;KACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ export function handleHealth(): { statusCode: number; body: string; headers: Record<string, string> } {
2
+ return {
3
+ statusCode: 200,
4
+ headers: { 'Content-Type': 'application/json' },
5
+ body: JSON.stringify({
6
+ status: 'ok',
7
+ timestamp: new Date().toISOString(),
8
+ }),
9
+ };
10
+ }
@@ -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
+ /**
8
+ * POST /v1/invites - Create an invite
9
+ */
10
+ export declare function handleCreateInvite(tenantId: string, fingerprint: string, body: string | null | undefined, ddb: DynamoDBDocumentClient, tableName: string): Promise<HandlerResponse>;
11
+ /**
12
+ * GET /v1/invites - List pending invites for the authenticated user
13
+ */
14
+ export declare function handleListInvites(tenantId: string, fingerprint: string, ddb: DynamoDBDocumentClient, tableName: string): Promise<HandlerResponse>;
15
+ /**
16
+ * POST /v1/invites/{id}/accept - Accept an invite
17
+ */
18
+ export declare function handleAcceptInvite(tenantId: string, fingerprint: string, inviteId: string, ddb: DynamoDBDocumentClient, tableName: string): Promise<HandlerResponse>;
19
+ /**
20
+ * POST /v1/invites/{id}/decline - Decline an invite
21
+ */
22
+ export declare function handleDeclineInvite(tenantId: string, fingerprint: string, inviteId: string, body: string | null | undefined, ddb: DynamoDBDocumentClient, tableName: string): Promise<HandlerResponse>;
23
+ export {};
24
+ //# sourceMappingURL=invites.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invites.d.ts","sourceRoot":"","sources":["../../../../lib/handler/routes/invites.ts"],"names":[],"mappings":"AACA,OAAO,EACL,sBAAsB,EAKvB,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;AAgHD;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAyH1B;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAiC1B;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CA0G1B;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,GAAG,EAAE,sBAAsB,EAC3B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,CAAC,CAiG1B"}