agent-relay 2.0.23 → 2.0.24

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 (168) hide show
  1. package/dist/src/cli/index.js +66 -13
  2. package/package.json +18 -52
  3. package/packages/api-types/package.json +1 -1
  4. package/packages/bridge/package.json +8 -8
  5. package/packages/cli-tester/package.json +1 -1
  6. package/packages/config/package.json +2 -2
  7. package/packages/continuity/package.json +1 -1
  8. package/packages/daemon/package.json +12 -12
  9. package/packages/hooks/package.json +4 -4
  10. package/packages/mcp/package.json +2 -2
  11. package/packages/memory/package.json +2 -2
  12. package/packages/policy/package.json +2 -2
  13. package/packages/protocol/package.json +1 -1
  14. package/packages/resiliency/package.json +1 -1
  15. package/packages/sdk/package.json +2 -2
  16. package/packages/spawner/package.json +1 -1
  17. package/packages/state/package.json +1 -1
  18. package/packages/storage/package.json +2 -2
  19. package/packages/telemetry/package.json +1 -1
  20. package/packages/trajectory/package.json +2 -2
  21. package/packages/user-directory/package.json +2 -2
  22. package/packages/utils/package.json +1 -1
  23. package/packages/wrapper/package.json +6 -6
  24. package/deploy/init-db.sql +0 -5
  25. package/deploy/scripts/setup-fly-workspaces.sh +0 -69
  26. package/deploy/scripts/setup-railway.sh +0 -75
  27. package/dist/src/cloud/index.d.ts +0 -8
  28. package/dist/src/cloud/index.js +0 -8
  29. package/packages/cloud/dist/api/admin.d.ts +0 -8
  30. package/packages/cloud/dist/api/admin.js +0 -225
  31. package/packages/cloud/dist/api/auth.d.ts +0 -20
  32. package/packages/cloud/dist/api/auth.js +0 -138
  33. package/packages/cloud/dist/api/billing.d.ts +0 -7
  34. package/packages/cloud/dist/api/billing.js +0 -564
  35. package/packages/cloud/dist/api/cli-pty-runner.d.ts +0 -53
  36. package/packages/cloud/dist/api/cli-pty-runner.js +0 -175
  37. package/packages/cloud/dist/api/codex-auth-helper.d.ts +0 -21
  38. package/packages/cloud/dist/api/codex-auth-helper.js +0 -327
  39. package/packages/cloud/dist/api/consensus.d.ts +0 -13
  40. package/packages/cloud/dist/api/consensus.js +0 -261
  41. package/packages/cloud/dist/api/coordinators.d.ts +0 -8
  42. package/packages/cloud/dist/api/coordinators.js +0 -750
  43. package/packages/cloud/dist/api/daemons.d.ts +0 -12
  44. package/packages/cloud/dist/api/daemons.js +0 -535
  45. package/packages/cloud/dist/api/email-auth.d.ts +0 -11
  46. package/packages/cloud/dist/api/email-auth.js +0 -347
  47. package/packages/cloud/dist/api/generic-webhooks.d.ts +0 -8
  48. package/packages/cloud/dist/api/generic-webhooks.js +0 -129
  49. package/packages/cloud/dist/api/git.d.ts +0 -8
  50. package/packages/cloud/dist/api/git.js +0 -269
  51. package/packages/cloud/dist/api/github-app.d.ts +0 -11
  52. package/packages/cloud/dist/api/github-app.js +0 -223
  53. package/packages/cloud/dist/api/middleware/planLimits.d.ts +0 -43
  54. package/packages/cloud/dist/api/middleware/planLimits.js +0 -202
  55. package/packages/cloud/dist/api/monitoring.d.ts +0 -11
  56. package/packages/cloud/dist/api/monitoring.js +0 -578
  57. package/packages/cloud/dist/api/nango-auth.d.ts +0 -9
  58. package/packages/cloud/dist/api/nango-auth.js +0 -741
  59. package/packages/cloud/dist/api/onboarding.d.ts +0 -15
  60. package/packages/cloud/dist/api/onboarding.js +0 -679
  61. package/packages/cloud/dist/api/policy.d.ts +0 -8
  62. package/packages/cloud/dist/api/policy.js +0 -229
  63. package/packages/cloud/dist/api/provider-env.d.ts +0 -26
  64. package/packages/cloud/dist/api/provider-env.js +0 -141
  65. package/packages/cloud/dist/api/providers.d.ts +0 -7
  66. package/packages/cloud/dist/api/providers.js +0 -574
  67. package/packages/cloud/dist/api/repos.d.ts +0 -8
  68. package/packages/cloud/dist/api/repos.js +0 -577
  69. package/packages/cloud/dist/api/sessions.d.ts +0 -11
  70. package/packages/cloud/dist/api/sessions.js +0 -302
  71. package/packages/cloud/dist/api/teams.d.ts +0 -7
  72. package/packages/cloud/dist/api/teams.js +0 -281
  73. package/packages/cloud/dist/api/test-helpers.d.ts +0 -10
  74. package/packages/cloud/dist/api/test-helpers.js +0 -745
  75. package/packages/cloud/dist/api/usage.d.ts +0 -7
  76. package/packages/cloud/dist/api/usage.js +0 -111
  77. package/packages/cloud/dist/api/webhooks.d.ts +0 -8
  78. package/packages/cloud/dist/api/webhooks.js +0 -645
  79. package/packages/cloud/dist/api/workspaces.d.ts +0 -25
  80. package/packages/cloud/dist/api/workspaces.js +0 -1799
  81. package/packages/cloud/dist/billing/index.d.ts +0 -9
  82. package/packages/cloud/dist/billing/index.js +0 -9
  83. package/packages/cloud/dist/billing/plans.d.ts +0 -39
  84. package/packages/cloud/dist/billing/plans.js +0 -245
  85. package/packages/cloud/dist/billing/service.d.ts +0 -80
  86. package/packages/cloud/dist/billing/service.js +0 -388
  87. package/packages/cloud/dist/billing/types.d.ts +0 -141
  88. package/packages/cloud/dist/billing/types.js +0 -7
  89. package/packages/cloud/dist/config.d.ts +0 -5
  90. package/packages/cloud/dist/config.js +0 -5
  91. package/packages/cloud/dist/db/bulk-ingest.d.ts +0 -89
  92. package/packages/cloud/dist/db/bulk-ingest.js +0 -268
  93. package/packages/cloud/dist/db/drizzle.d.ts +0 -290
  94. package/packages/cloud/dist/db/drizzle.js +0 -1422
  95. package/packages/cloud/dist/db/index.d.ts +0 -56
  96. package/packages/cloud/dist/db/index.js +0 -70
  97. package/packages/cloud/dist/db/schema.d.ts +0 -5117
  98. package/packages/cloud/dist/db/schema.js +0 -656
  99. package/packages/cloud/dist/index.d.ts +0 -11
  100. package/packages/cloud/dist/index.js +0 -38
  101. package/packages/cloud/dist/provisioner/index.d.ts +0 -207
  102. package/packages/cloud/dist/provisioner/index.js +0 -2118
  103. package/packages/cloud/dist/server.d.ts +0 -17
  104. package/packages/cloud/dist/server.js +0 -2055
  105. package/packages/cloud/dist/services/auto-scaler.d.ts +0 -152
  106. package/packages/cloud/dist/services/auto-scaler.js +0 -439
  107. package/packages/cloud/dist/services/capacity-manager.d.ts +0 -148
  108. package/packages/cloud/dist/services/capacity-manager.js +0 -449
  109. package/packages/cloud/dist/services/ci-agent-spawner.d.ts +0 -49
  110. package/packages/cloud/dist/services/ci-agent-spawner.js +0 -373
  111. package/packages/cloud/dist/services/cloud-message-bus.d.ts +0 -28
  112. package/packages/cloud/dist/services/cloud-message-bus.js +0 -19
  113. package/packages/cloud/dist/services/compute-enforcement.d.ts +0 -57
  114. package/packages/cloud/dist/services/compute-enforcement.js +0 -175
  115. package/packages/cloud/dist/services/coordinator.d.ts +0 -62
  116. package/packages/cloud/dist/services/coordinator.js +0 -389
  117. package/packages/cloud/dist/services/index.d.ts +0 -17
  118. package/packages/cloud/dist/services/index.js +0 -25
  119. package/packages/cloud/dist/services/intro-expiration.d.ts +0 -60
  120. package/packages/cloud/dist/services/intro-expiration.js +0 -252
  121. package/packages/cloud/dist/services/mention-handler.d.ts +0 -65
  122. package/packages/cloud/dist/services/mention-handler.js +0 -405
  123. package/packages/cloud/dist/services/nango.d.ts +0 -219
  124. package/packages/cloud/dist/services/nango.js +0 -424
  125. package/packages/cloud/dist/services/persistence.d.ts +0 -131
  126. package/packages/cloud/dist/services/persistence.js +0 -200
  127. package/packages/cloud/dist/services/planLimits.d.ts +0 -147
  128. package/packages/cloud/dist/services/planLimits.js +0 -335
  129. package/packages/cloud/dist/services/presence-registry.d.ts +0 -56
  130. package/packages/cloud/dist/services/presence-registry.js +0 -91
  131. package/packages/cloud/dist/services/scaling-orchestrator.d.ts +0 -159
  132. package/packages/cloud/dist/services/scaling-orchestrator.js +0 -502
  133. package/packages/cloud/dist/services/scaling-policy.d.ts +0 -121
  134. package/packages/cloud/dist/services/scaling-policy.js +0 -415
  135. package/packages/cloud/dist/services/ssh-security.d.ts +0 -31
  136. package/packages/cloud/dist/services/ssh-security.js +0 -63
  137. package/packages/cloud/dist/services/workspace-keepalive.d.ts +0 -76
  138. package/packages/cloud/dist/services/workspace-keepalive.js +0 -234
  139. package/packages/cloud/dist/shims/consensus.d.ts +0 -23
  140. package/packages/cloud/dist/shims/consensus.js +0 -5
  141. package/packages/cloud/dist/webhooks/index.d.ts +0 -24
  142. package/packages/cloud/dist/webhooks/index.js +0 -29
  143. package/packages/cloud/dist/webhooks/parsers/github.d.ts +0 -8
  144. package/packages/cloud/dist/webhooks/parsers/github.js +0 -234
  145. package/packages/cloud/dist/webhooks/parsers/index.d.ts +0 -23
  146. package/packages/cloud/dist/webhooks/parsers/index.js +0 -30
  147. package/packages/cloud/dist/webhooks/parsers/linear.d.ts +0 -9
  148. package/packages/cloud/dist/webhooks/parsers/linear.js +0 -258
  149. package/packages/cloud/dist/webhooks/parsers/slack.d.ts +0 -9
  150. package/packages/cloud/dist/webhooks/parsers/slack.js +0 -214
  151. package/packages/cloud/dist/webhooks/responders/github.d.ts +0 -8
  152. package/packages/cloud/dist/webhooks/responders/github.js +0 -73
  153. package/packages/cloud/dist/webhooks/responders/index.d.ts +0 -23
  154. package/packages/cloud/dist/webhooks/responders/index.js +0 -30
  155. package/packages/cloud/dist/webhooks/responders/linear.d.ts +0 -9
  156. package/packages/cloud/dist/webhooks/responders/linear.js +0 -149
  157. package/packages/cloud/dist/webhooks/responders/slack.d.ts +0 -20
  158. package/packages/cloud/dist/webhooks/responders/slack.js +0 -178
  159. package/packages/cloud/dist/webhooks/router.d.ts +0 -25
  160. package/packages/cloud/dist/webhooks/router.js +0 -504
  161. package/packages/cloud/dist/webhooks/rules-engine.d.ts +0 -24
  162. package/packages/cloud/dist/webhooks/rules-engine.js +0 -287
  163. package/packages/cloud/dist/webhooks/types.d.ts +0 -186
  164. package/packages/cloud/dist/webhooks/types.js +0 -8
  165. package/packages/cloud/package.json +0 -60
  166. package/scripts/run-migrations.js +0 -43
  167. package/scripts/setup-stripe-products.ts +0 -312
  168. package/scripts/verify-schema.js +0 -134
@@ -1,741 +0,0 @@
1
- /**
2
- * Nango Auth API Routes
3
- *
4
- * Handles GitHub OAuth via Nango with two-connection pattern:
5
- * - github: User login (identity)
6
- * - github-app-oauth: Repository access
7
- */
8
- import { Router } from 'express';
9
- import { randomUUID } from 'crypto';
10
- import { requireAuth } from './auth.js';
11
- import { db } from '../db/index.js';
12
- import { nangoService, NANGO_INTEGRATIONS } from '../services/nango.js';
13
- export const nangoAuthRouter = Router();
14
- /**
15
- * GET /api/auth/nango/status
16
- * Check if Nango is configured
17
- */
18
- nangoAuthRouter.get('/status', (req, res) => {
19
- try {
20
- res.json({
21
- configured: true,
22
- integrations: NANGO_INTEGRATIONS,
23
- });
24
- }
25
- catch (_error) {
26
- res.json({
27
- configured: false,
28
- message: 'Nango not configured',
29
- });
30
- }
31
- });
32
- /**
33
- * GET /api/auth/nango/login-session
34
- * Create a Nango connect session for GitHub login
35
- */
36
- nangoAuthRouter.get('/login-session', async (req, res) => {
37
- try {
38
- const tempUserId = randomUUID();
39
- const session = await nangoService.createConnectSession([NANGO_INTEGRATIONS.GITHUB_USER], { id: tempUserId });
40
- res.json({ sessionToken: session.token, tempUserId });
41
- }
42
- catch (error) {
43
- console.error('Error creating login session:', error);
44
- res.status(500).json({ error: 'Failed to create login session' });
45
- }
46
- });
47
- /**
48
- * GET /api/auth/nango/login-status/:connectionId
49
- * Poll for login completion after Nango connect UI
50
- */
51
- nangoAuthRouter.get('/login-status/:connectionId', async (req, res) => {
52
- const connectionId = req.params.connectionId;
53
- try {
54
- // Check if a user exists with this incoming connection
55
- const user = await db.users.findByIncomingConnectionId(connectionId);
56
- if (!user) {
57
- return res.json({ ready: false });
58
- }
59
- // Issue session
60
- req.session.userId = user.id;
61
- // Clear incoming connection ID
62
- await db.users.clearIncomingConnectionId(user.id);
63
- // Check if user has any repos connected
64
- const repos = await db.repositories.findByUserId(user.id);
65
- const hasRepos = repos.length > 0;
66
- // Check if user needs to provide an email
67
- const needsEmail = !user.email;
68
- res.json({
69
- ready: true,
70
- hasRepos,
71
- needsEmail,
72
- user: {
73
- id: user.id,
74
- githubUsername: user.githubUsername,
75
- email: user.email,
76
- avatarUrl: user.avatarUrl,
77
- plan: user.plan,
78
- },
79
- });
80
- }
81
- catch (error) {
82
- console.error('Error checking login status:', error);
83
- res.status(500).json({ error: 'Failed to check login status' });
84
- }
85
- });
86
- /**
87
- * GET /api/auth/nango/repo-session
88
- * Create a Nango connect session for GitHub App OAuth (repo access)
89
- * Requires authentication
90
- */
91
- nangoAuthRouter.get('/repo-session', requireAuth, async (req, res) => {
92
- const userId = req.session.userId;
93
- try {
94
- const user = await db.users.findById(userId);
95
- if (!user) {
96
- return res.status(404).json({ error: 'User not found' });
97
- }
98
- const session = await nangoService.createConnectSession([NANGO_INTEGRATIONS.GITHUB_APP], { id: user.id, email: user.email || undefined });
99
- res.json({ sessionToken: session.token });
100
- }
101
- catch (error) {
102
- console.error('Error creating repo session:', error);
103
- res.status(500).json({ error: 'Failed to create repo session' });
104
- }
105
- });
106
- /**
107
- * GET /api/auth/nango/repo-status/:connectionId
108
- * Poll for repo sync completion after GitHub App OAuth
109
- * Requires authentication
110
- */
111
- nangoAuthRouter.get('/repo-status/:connectionId', requireAuth, async (req, res) => {
112
- const userId = req.session.userId;
113
- const _connectionId = req.params.connectionId;
114
- try {
115
- const user = await db.users.findById(userId);
116
- if (!user) {
117
- return res.status(404).json({ error: 'User not found' });
118
- }
119
- // Check for pending org approval
120
- if (user.pendingInstallationRequest) {
121
- return res.json({
122
- ready: false,
123
- pendingApproval: true,
124
- message: 'Waiting for organization admin approval',
125
- });
126
- }
127
- // Check if repos have been synced
128
- const repos = await db.repositories.findByUserId(userId);
129
- const reposFromConnection = repos.filter(r => r.syncStatus === 'synced' && r.nangoConnectionId);
130
- if (reposFromConnection.length === 0) {
131
- return res.json({ ready: false });
132
- }
133
- // Check workspace status for frontend visibility
134
- const workspaces = await db.workspaces.findByUserId(userId);
135
- const primaryWorkspace = workspaces[0];
136
- res.json({
137
- ready: true,
138
- repos: reposFromConnection.map(r => ({
139
- id: r.id,
140
- fullName: r.githubFullName,
141
- isPrivate: r.isPrivate,
142
- defaultBranch: r.defaultBranch,
143
- })),
144
- workspace: primaryWorkspace ? {
145
- id: primaryWorkspace.id,
146
- name: primaryWorkspace.name,
147
- status: primaryWorkspace.status,
148
- publicUrl: primaryWorkspace.publicUrl,
149
- } : null,
150
- workspaceProvisioning: primaryWorkspace?.status === 'provisioning',
151
- });
152
- }
153
- catch (error) {
154
- console.error('Error checking repo status:', error);
155
- res.status(500).json({ error: 'Failed to check repo status' });
156
- }
157
- });
158
- // ============================================================================
159
- // Nango Webhook Handler
160
- // ============================================================================
161
- /**
162
- * POST /api/auth/nango/webhook
163
- * Handle Nango webhooks for auth and sync events
164
- */
165
- nangoAuthRouter.post('/webhook', async (req, res) => {
166
- const rawBody = req.rawBody || JSON.stringify(req.body);
167
- // Verify webhook signature if present
168
- const hasSignature = req.headers['x-nango-signature'] || req.headers['x-nango-hmac-sha256'];
169
- if (hasSignature) {
170
- if (!nangoService.verifyWebhookSignature(rawBody, req.headers)) {
171
- console.error('[nango-webhook] Invalid signature');
172
- return res.status(401).json({ error: 'Invalid signature' });
173
- }
174
- }
175
- const payload = req.body;
176
- console.log(`[nango-webhook] Received ${payload.type} event`);
177
- try {
178
- switch (payload.type) {
179
- case 'auth':
180
- await handleAuthWebhook(payload);
181
- break;
182
- case 'sync':
183
- console.log('[nango-webhook] Sync event received');
184
- break;
185
- case 'forward':
186
- await handleForwardWebhook(payload);
187
- break;
188
- default:
189
- console.log(`[nango-webhook] Unhandled event type: ${payload.type}`);
190
- }
191
- res.json({ success: true });
192
- }
193
- catch (error) {
194
- console.error('[nango-webhook] Error processing webhook:', error);
195
- res.status(500).json({ error: 'Failed to process webhook' });
196
- }
197
- });
198
- /**
199
- * Handle Nango auth webhook
200
- */
201
- async function handleAuthWebhook(payload) {
202
- const { connectionId, providerConfigKey, endUser } = payload;
203
- console.log(`[nango-webhook] Auth event for ${providerConfigKey} (${connectionId})`);
204
- if (providerConfigKey === NANGO_INTEGRATIONS.GITHUB_USER) {
205
- await handleLoginWebhook(connectionId, endUser);
206
- }
207
- else if (providerConfigKey === NANGO_INTEGRATIONS.GITHUB_APP) {
208
- await handleRepoAuthWebhook(connectionId, endUser);
209
- }
210
- }
211
- /**
212
- * Check user's repo access and auto-add them to workspaces
213
- * Uses GitHub user OAuth to query accessible repos and persists them to database
214
- */
215
- async function checkAndAutoAddToWorkspaces(userId, connectionId) {
216
- try {
217
- const user = await db.users.findById(userId);
218
- if (!user)
219
- return;
220
- console.log(`[nango-webhook] Checking workspace auto-add for ${user.githubUsername}`);
221
- // Query repos the user has access to via GitHub OAuth
222
- const { repositories } = await nangoService.listUserAccessibleRepos(connectionId, {
223
- perPage: 100,
224
- type: 'all',
225
- });
226
- const workspacesToJoin = new Set();
227
- // Check for workspace memberships - only persist repos that match existing workspaces
228
- for (const repo of repositories) {
229
- // Check if any user has this repo linked to a workspace
230
- const allRepoRecords = await db.repositories.findByGithubFullName(repo.fullName);
231
- let matchedWorkspaceId = null;
232
- for (const record of allRepoRecords) {
233
- if (record.workspaceId) {
234
- workspacesToJoin.add(record.workspaceId);
235
- matchedWorkspaceId = record.workspaceId; // Save the workspaceId to copy
236
- }
237
- }
238
- // Only persist repos that are linked to workspaces
239
- if (matchedWorkspaceId) {
240
- await db.repositories.upsert({
241
- userId: user.id,
242
- githubFullName: repo.fullName,
243
- githubId: repo.id,
244
- isPrivate: repo.isPrivate,
245
- defaultBranch: repo.defaultBranch,
246
- nangoConnectionId: connectionId,
247
- workspaceId: matchedWorkspaceId, // Copy the workspaceId
248
- syncStatus: 'synced',
249
- lastSyncedAt: new Date(),
250
- });
251
- }
252
- }
253
- // Auto-add user to workspaces
254
- for (const workspaceId of workspacesToJoin) {
255
- const existingMembership = await db.workspaceMembers.findMembership(workspaceId, userId);
256
- if (!existingMembership) {
257
- const workspace = await db.workspaces.findById(workspaceId);
258
- if (workspace) {
259
- console.log(`[nango-webhook] Auto-adding ${user.githubUsername} to workspace ${workspace.name}`);
260
- await db.workspaceMembers.addMember({
261
- workspaceId,
262
- userId,
263
- role: 'member',
264
- invitedBy: workspace.userId,
265
- });
266
- await db.workspaceMembers.acceptInvite(workspaceId, userId);
267
- }
268
- }
269
- }
270
- console.log(`[nango-webhook] Synced ${repositories.length} repos, auto-added ${user.githubUsername} to ${workspacesToJoin.size} workspaces`);
271
- }
272
- catch (error) {
273
- console.error(`[nango-webhook] Error checking workspace auto-add:`, error);
274
- // Non-fatal - don't throw
275
- }
276
- }
277
- /**
278
- * Fetch and sync user emails from GitHub
279
- * Requires 'user:email' scope to be configured in Nango's GitHub integration
280
- */
281
- async function syncGitHubEmails(userId, connectionId) {
282
- try {
283
- const emails = await nangoService.getGithubUserEmails(connectionId);
284
- if (emails.length === 0) {
285
- console.log(`[nango-webhook] No emails returned from GitHub (scope may not be granted)`);
286
- return null;
287
- }
288
- // Sync all verified emails to user_emails table
289
- const verifiedEmails = emails.filter(e => e.verified);
290
- if (verifiedEmails.length > 0) {
291
- await db.userEmails.syncFromGitHub(userId, verifiedEmails);
292
- console.log(`[nango-webhook] Synced ${verifiedEmails.length} verified emails for user ${userId}`);
293
- }
294
- // Return the primary verified email
295
- const primaryEmail = emails.find(e => e.primary && e.verified)?.email;
296
- return primaryEmail || null;
297
- }
298
- catch (error) {
299
- console.error('[nango-webhook] Error syncing GitHub emails:', error);
300
- return null;
301
- }
302
- }
303
- /**
304
- * Handle GitHub login webhook
305
- *
306
- * Three scenarios:
307
- * 1. New user - Create user record, keep connection as permanent
308
- * 2. Returning user with existing connection - Store incoming ID for polling, delete temp connection
309
- * 3. Existing user, first connection - Set connection ID as permanent
310
- */
311
- async function handleLoginWebhook(connectionId, _endUser) {
312
- // Get GitHub user info via Nango proxy
313
- const githubUser = await nangoService.getGithubUser(connectionId);
314
- const githubId = String(githubUser.id);
315
- // Check if user already exists by GitHub ID
316
- let existingUser = await db.users.findByGithubId(githubId);
317
- // If not found by GitHub ID, check by email (for email-signup users connecting GitHub)
318
- if (!existingUser && githubUser.email) {
319
- const userByEmail = await db.users.findByEmail(githubUser.email);
320
- if (userByEmail) {
321
- // Email-signup user is connecting their GitHub account
322
- console.log(`[nango-webhook] Linking GitHub to existing email user: ${githubUser.login} -> ${userByEmail.email}`);
323
- // Update the existing user with GitHub info
324
- await db.users.update(userByEmail.id, {
325
- githubId,
326
- githubUsername: githubUser.login,
327
- avatarUrl: githubUser.avatar_url || null,
328
- nangoConnectionId: connectionId,
329
- incomingConnectionId: connectionId,
330
- });
331
- // Sync GitHub emails
332
- await syncGitHubEmails(userByEmail.id, connectionId);
333
- // Update connection with user ID
334
- await nangoService.updateEndUser(connectionId, NANGO_INTEGRATIONS.GITHUB_USER, {
335
- id: userByEmail.id,
336
- email: userByEmail.email || undefined,
337
- });
338
- // Check for auto-add to workspaces
339
- await checkAndAutoAddToWorkspaces(userByEmail.id, connectionId);
340
- return;
341
- }
342
- }
343
- // SCENARIO 1: New user (no existing user by GitHub ID or email)
344
- if (!existingUser) {
345
- // First, get the primary email from GitHub API (requires user:email scope)
346
- // We'll create the user first, then sync emails
347
- const newUser = await db.users.upsert({
348
- githubId,
349
- githubUsername: githubUser.login,
350
- email: githubUser.email || null,
351
- avatarUrl: githubUser.avatar_url || null,
352
- nangoConnectionId: connectionId,
353
- incomingConnectionId: connectionId,
354
- });
355
- // Sync all GitHub emails and get primary email
356
- const primaryEmail = await syncGitHubEmails(newUser.id, connectionId);
357
- // If we got a primary email from the API and user doesn't have one, update it
358
- if (primaryEmail && !newUser.email) {
359
- await db.users.update(newUser.id, { email: primaryEmail });
360
- }
361
- // Update connection with real user ID
362
- await nangoService.updateEndUser(connectionId, NANGO_INTEGRATIONS.GITHUB_USER, {
363
- id: newUser.id,
364
- email: primaryEmail || newUser.email || undefined,
365
- });
366
- console.log(`[nango-webhook] New user created: ${githubUser.login}`);
367
- // Check for auto-add to workspaces based on repo access
368
- await checkAndAutoAddToWorkspaces(newUser.id, connectionId);
369
- return;
370
- }
371
- // SCENARIO 2: Returning user with existing connection - delete temp connection
372
- if (existingUser.nangoConnectionId && existingUser.nangoConnectionId !== connectionId) {
373
- console.log(`[nango-webhook] Returning user: ${githubUser.login}`, {
374
- permanentConnectionId: existingUser.nangoConnectionId,
375
- incomingConnectionId: connectionId,
376
- });
377
- // Store incoming connection ID for polling
378
- await db.users.update(existingUser.id, {
379
- incomingConnectionId: connectionId,
380
- githubUsername: githubUser.login,
381
- avatarUrl: githubUser.avatar_url || null,
382
- });
383
- // Sync GitHub emails using the temporary connection before we delete it
384
- await syncGitHubEmails(existingUser.id, connectionId);
385
- // Delete the temporary connection from Nango to prevent duplicates
386
- try {
387
- await nangoService.deleteConnection(connectionId, NANGO_INTEGRATIONS.GITHUB_USER);
388
- console.log(`[nango-webhook] Deleted temp connection for returning user`);
389
- }
390
- catch (error) {
391
- console.error(`[nango-webhook] Failed to delete temp connection:`, error);
392
- // Non-fatal - continue anyway
393
- }
394
- // Check for auto-add using permanent connection
395
- await checkAndAutoAddToWorkspaces(existingUser.id, existingUser.nangoConnectionId);
396
- return;
397
- }
398
- // SCENARIO 3: Existing user, first connection (or same connection)
399
- console.log(`[nango-webhook] First/same connection for existing user: ${githubUser.login}`);
400
- await db.users.update(existingUser.id, {
401
- nangoConnectionId: connectionId,
402
- incomingConnectionId: connectionId,
403
- githubUsername: githubUser.login,
404
- avatarUrl: githubUser.avatar_url || null,
405
- });
406
- // Sync GitHub emails
407
- const primaryEmail = await syncGitHubEmails(existingUser.id, connectionId);
408
- // Update connection with user ID
409
- await nangoService.updateEndUser(connectionId, NANGO_INTEGRATIONS.GITHUB_USER, {
410
- id: existingUser.id,
411
- email: primaryEmail || existingUser.email || undefined,
412
- });
413
- // Check for auto-add to workspaces
414
- await checkAndAutoAddToWorkspaces(existingUser.id, connectionId);
415
- }
416
- /**
417
- * Handle Nango forward webhook (GitHub events forwarded by Nango)
418
- */
419
- async function handleForwardWebhook(payload) {
420
- const githubPayload = payload.payload;
421
- console.log(`[nango-webhook] Forward event: action=${githubPayload.action} from ${payload.providerConfigKey}`);
422
- // Only process GitHub App events
423
- if (payload.providerConfigKey !== NANGO_INTEGRATIONS.GITHUB_APP) {
424
- console.log('[nango-webhook] Ignoring forward event from non-GitHub-App integration');
425
- return;
426
- }
427
- try {
428
- // Determine event type from payload structure
429
- if (githubPayload.installation && githubPayload.action === 'created' && githubPayload.repositories) {
430
- // Installation created event
431
- await handleInstallationForward(githubPayload, payload.connectionId);
432
- }
433
- else if (githubPayload.repositories_added || githubPayload.repositories_removed) {
434
- // Installation repositories added/removed
435
- await handleInstallationRepositoriesForward(githubPayload, payload.connectionId);
436
- }
437
- else {
438
- console.log(`[nango-webhook] Unhandled forward event structure: action=${githubPayload.action}`);
439
- }
440
- }
441
- catch (error) {
442
- console.error(`[nango-webhook] Error processing forward event:`, error);
443
- throw error;
444
- }
445
- }
446
- /**
447
- * Handle GitHub installation events forwarded by Nango
448
- */
449
- async function handleInstallationForward(body, connectionId) {
450
- const { action, installation, repositories, sender } = body;
451
- if (!installation || !sender)
452
- return;
453
- const installationId = String(installation.id);
454
- console.log(`[nango-webhook] Installation ${action}: ${installation.account.login} (${installationId})`);
455
- if (action === 'created') {
456
- // Find user by GitHub ID
457
- const user = await db.users.findByGithubId(String(sender.id));
458
- // Create/update installation record
459
- await db.githubInstallations.upsert({
460
- installationId,
461
- accountType: installation.account.type.toLowerCase(),
462
- accountLogin: installation.account.login,
463
- accountId: String(installation.account.id),
464
- installedById: user?.id ?? null,
465
- permissions: installation.permissions,
466
- events: installation.events,
467
- });
468
- // Sync repositories if provided
469
- if (repositories && user) {
470
- const dbInstallation = await db.githubInstallations.findByInstallationId(installationId);
471
- if (dbInstallation) {
472
- const workspacesToJoin = new Set();
473
- for (const repo of repositories) {
474
- const syncedRepo = await db.repositories.upsert({
475
- userId: user.id,
476
- githubFullName: repo.full_name,
477
- githubId: repo.id,
478
- isPrivate: repo.private,
479
- installationId: dbInstallation.id,
480
- nangoConnectionId: connectionId,
481
- syncStatus: 'synced',
482
- lastSyncedAt: new Date(),
483
- });
484
- // Check if repo is part of an existing workspace
485
- // Look for ANY user's record of this repo that has a workspaceId
486
- if (syncedRepo.workspaceId) {
487
- workspacesToJoin.add(syncedRepo.workspaceId);
488
- }
489
- else {
490
- // Check if other users have this repo linked to a workspace
491
- const allRepoRecords = await db.repositories.findByGithubFullName(repo.full_name);
492
- for (const otherRecord of allRepoRecords) {
493
- if (otherRecord.workspaceId && otherRecord.userId !== user.id) {
494
- workspacesToJoin.add(otherRecord.workspaceId);
495
- }
496
- }
497
- }
498
- }
499
- // Auto-join user to workspaces for repos they have access to
500
- for (const workspaceId of workspacesToJoin) {
501
- const existingMembership = await db.workspaceMembers.findMembership(workspaceId, user.id);
502
- if (!existingMembership) {
503
- const workspace = await db.workspaces.findById(workspaceId);
504
- if (workspace) {
505
- console.log(`[nango-webhook] Auto-adding ${user.githubUsername} to workspace ${workspace.name}`);
506
- await db.workspaceMembers.addMember({
507
- workspaceId,
508
- userId: user.id,
509
- role: 'member',
510
- invitedBy: workspace.userId,
511
- });
512
- await db.workspaceMembers.acceptInvite(workspaceId, user.id);
513
- }
514
- }
515
- }
516
- console.log(`[nango-webhook] Installation created for ${installation.account.login}, auto-joined ${workspacesToJoin.size} workspaces`);
517
- }
518
- }
519
- }
520
- }
521
- /**
522
- * Handle installation_repositories events forwarded by Nango
523
- */
524
- async function handleInstallationRepositoriesForward(body, connectionId) {
525
- const { action, installation, repositories_added, repositories_removed, sender } = body;
526
- console.log(`[nango-webhook] handleInstallationRepositoriesForward called`, {
527
- action,
528
- installation: installation?.id,
529
- sender: sender?.login,
530
- connectionId,
531
- repositories_added: repositories_added?.map(r => r.full_name),
532
- repositories_removed: repositories_removed?.map(r => r.full_name),
533
- });
534
- if (!installation || !sender) {
535
- console.log(`[nango-webhook] Missing installation or sender, skipping`);
536
- return;
537
- }
538
- const installationId = String(installation.id);
539
- console.log(`[nango-webhook] Repositories ${action} for ${installation.account.login}`);
540
- // Find installation in database
541
- const dbInstallation = await db.githubInstallations.findByInstallationId(installationId);
542
- if (!dbInstallation) {
543
- console.error(`[nango-webhook] Installation ${installationId} not found in database`);
544
- return;
545
- }
546
- console.log(`[nango-webhook] Found installation: ${dbInstallation.id}`);
547
- // Find user who triggered this
548
- const user = await db.users.findByGithubId(String(sender.id));
549
- if (!user) {
550
- console.error(`[nango-webhook] User ${sender.login} (github id: ${sender.id}) not found in database`);
551
- return;
552
- }
553
- console.log(`[nango-webhook] Found user: ${user.id} (${user.githubUsername})`);
554
- console.log(`[nango-webhook] Processing action: ${action}, repos_added: ${repositories_added?.length}, repos_removed: ${repositories_removed?.length}`);
555
- if (action === 'added' && repositories_added) {
556
- console.log(`[nango-webhook] Adding ${repositories_added.length} repos for user ${user.id}`);
557
- const workspacesToJoin = new Set();
558
- for (const repo of repositories_added) {
559
- console.log(`[nango-webhook] Upserting repo: ${repo.full_name}`);
560
- const syncedRepo = await db.repositories.upsert({
561
- userId: user.id,
562
- githubFullName: repo.full_name,
563
- githubId: repo.id,
564
- isPrivate: repo.private,
565
- installationId: dbInstallation.id,
566
- nangoConnectionId: connectionId,
567
- syncStatus: 'synced',
568
- lastSyncedAt: new Date(),
569
- });
570
- console.log(`[nango-webhook] Upserted repo: ${syncedRepo.id}, syncStatus: ${syncedRepo.syncStatus}, nangoConnectionId: ${syncedRepo.nangoConnectionId}`);
571
- // Check if repo is part of an existing workspace
572
- // Look for ANY user's record of this repo that has a workspaceId
573
- if (syncedRepo.workspaceId) {
574
- workspacesToJoin.add(syncedRepo.workspaceId);
575
- }
576
- else {
577
- // Check if other users have this repo linked to a workspace
578
- const allRepoRecords = await db.repositories.findByGithubFullName(repo.full_name);
579
- for (const otherRecord of allRepoRecords) {
580
- if (otherRecord.workspaceId && otherRecord.userId !== user.id) {
581
- workspacesToJoin.add(otherRecord.workspaceId);
582
- }
583
- }
584
- }
585
- }
586
- // Auto-join user to workspaces for repos they have access to
587
- for (const workspaceId of workspacesToJoin) {
588
- const existingMembership = await db.workspaceMembers.findMembership(workspaceId, user.id);
589
- if (!existingMembership) {
590
- const workspace = await db.workspaces.findById(workspaceId);
591
- if (workspace) {
592
- console.log(`[nango-webhook] Auto-adding ${user.githubUsername} to workspace ${workspace.name}`);
593
- await db.workspaceMembers.addMember({
594
- workspaceId,
595
- userId: user.id,
596
- role: 'member',
597
- invitedBy: workspace.userId,
598
- });
599
- await db.workspaceMembers.acceptInvite(workspaceId, user.id);
600
- }
601
- }
602
- }
603
- console.log(`[nango-webhook] Added ${repositories_added.length} repositories, auto-joined ${workspacesToJoin.size} workspaces`);
604
- }
605
- if (action === 'removed' && repositories_removed) {
606
- for (const repo of repositories_removed) {
607
- const repos = await db.repositories.findByUserId(user.id);
608
- const existingRepo = repos.find(r => r.githubFullName === repo.full_name);
609
- if (existingRepo) {
610
- await db.repositories.updateSyncStatus(existingRepo.id, 'access_removed');
611
- }
612
- }
613
- console.log(`[nango-webhook] Removed access to ${repositories_removed.length} repositories`);
614
- }
615
- }
616
- /**
617
- * Handle GitHub App OAuth webhook (repo access)
618
- */
619
- async function handleRepoAuthWebhook(connectionId, endUser) {
620
- let userId = endUser?.id;
621
- // Fallback: If endUser.id not in webhook, fetch connection metadata from Nango
622
- if (!userId) {
623
- console.log('[nango-webhook] No user ID in webhook payload, fetching from connection metadata...');
624
- try {
625
- const connection = await nangoService.getConnection(connectionId, NANGO_INTEGRATIONS.GITHUB_APP);
626
- userId = connection.end_user?.id;
627
- console.log(`[nango-webhook] Got user ID from connection: ${userId || 'not found'}`);
628
- }
629
- catch (err) {
630
- console.error('[nango-webhook] Failed to fetch connection metadata:', err);
631
- }
632
- }
633
- if (!userId) {
634
- console.error('[nango-webhook] No user ID found - cannot sync repos');
635
- return;
636
- }
637
- const user = await db.users.findById(userId);
638
- if (!user) {
639
- console.error(`[nango-webhook] User ${userId} not found`);
640
- return;
641
- }
642
- try {
643
- // Get the GitHub App installation ID
644
- const githubInstallationId = await nangoService.getGithubAppInstallationId(connectionId);
645
- let installationUuid = null;
646
- if (githubInstallationId) {
647
- // Find or create the github_installations record
648
- let installation = await db.githubInstallations.findByInstallationId(String(githubInstallationId));
649
- if (!installation) {
650
- // Create a new installation record
651
- // We need to get more info about the installation - for now use user info
652
- installation = await db.githubInstallations.upsert({
653
- installationId: String(githubInstallationId),
654
- accountType: 'user', // Could be 'organization' - we'd need to detect this
655
- accountLogin: user.githubUsername || 'unknown',
656
- accountId: user.githubId || 'unknown',
657
- installedById: user.id,
658
- permissions: {},
659
- events: [],
660
- });
661
- console.log(`[nango-webhook] Created installation record for ${githubInstallationId}`);
662
- }
663
- installationUuid = installation.id;
664
- }
665
- else {
666
- console.warn('[nango-webhook] Could not get installation ID from Nango connection');
667
- }
668
- // Fetch repos the user has access to
669
- const { repositories: repos } = await nangoService.listGithubAppRepos(connectionId);
670
- // Track workspaces to auto-join
671
- const workspacesToJoin = new Set();
672
- // Sync repos to database
673
- for (const repo of repos) {
674
- const syncedRepo = await db.repositories.upsert({
675
- userId: user.id,
676
- githubFullName: repo.full_name,
677
- githubId: repo.id,
678
- isPrivate: repo.private,
679
- defaultBranch: repo.default_branch,
680
- nangoConnectionId: connectionId,
681
- installationId: installationUuid,
682
- syncStatus: 'synced',
683
- lastSyncedAt: new Date(),
684
- });
685
- // Check if this repo is part of an existing workspace
686
- // Look for ANY user's record of this repo that has a workspaceId
687
- if (syncedRepo.workspaceId) {
688
- workspacesToJoin.add(syncedRepo.workspaceId);
689
- }
690
- else {
691
- // Check if other users have this repo linked to a workspace
692
- const allRepoRecords = await db.repositories.findByGithubFullName(repo.full_name);
693
- for (const otherRecord of allRepoRecords) {
694
- if (otherRecord.workspaceId && otherRecord.userId !== user.id) {
695
- workspacesToJoin.add(otherRecord.workspaceId);
696
- }
697
- }
698
- }
699
- }
700
- // Auto-join user to workspaces for repos they have access to
701
- for (const workspaceId of workspacesToJoin) {
702
- // Check if already a member
703
- const existingMembership = await db.workspaceMembers.findMembership(workspaceId, user.id);
704
- if (!existingMembership) {
705
- // Get workspace owner to use as invitedBy
706
- const workspace = await db.workspaces.findById(workspaceId);
707
- if (workspace) {
708
- console.log(`[nango-webhook] Auto-adding ${user.githubUsername} to workspace ${workspace.name}`);
709
- await db.workspaceMembers.addMember({
710
- workspaceId,
711
- userId: user.id,
712
- role: 'member',
713
- invitedBy: workspace.userId, // Workspace owner invited them
714
- });
715
- // Auto-accept since they have GitHub repo access
716
- await db.workspaceMembers.acceptInvite(workspaceId, user.id);
717
- }
718
- }
719
- }
720
- // Clear any pending installation request
721
- await db.users.clearPendingInstallationRequest(user.id);
722
- console.log(`[nango-webhook] Synced ${repos.length} repos for ${user.githubUsername} (installation: ${githubInstallationId || 'unknown'}), auto-joined ${workspacesToJoin.size} workspaces`);
723
- // Note: We intentionally do NOT auto-provision workspaces here.
724
- // Users should go through the onboarding flow at /app to:
725
- // 1. Name their workspace
726
- // 2. Choose which repos to include
727
- // 3. Understand what they're creating
728
- }
729
- catch (error) {
730
- const err = error;
731
- if (err.message?.includes('403')) {
732
- // Org approval pending
733
- await db.users.setPendingInstallationRequest(user.id);
734
- console.log(`[nango-webhook] Org approval pending for ${user.githubUsername}`);
735
- }
736
- else {
737
- throw error;
738
- }
739
- }
740
- }
741
- //# sourceMappingURL=nango-auth.js.map