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