diff --git a/apps/api/src/integration-platform/controllers/oauth.controller.spec.ts b/apps/api/src/integration-platform/controllers/oauth.controller.spec.ts index 619cb13dc..cf3fdefc3 100644 --- a/apps/api/src/integration-platform/controllers/oauth.controller.spec.ts +++ b/apps/api/src/integration-platform/controllers/oauth.controller.spec.ts @@ -692,94 +692,6 @@ describe('OAuthController', () => { fetchSpy.mockRestore(); }); - it('persists installation_id on the connection for the GitHub App install flow', async () => { - const futureDate = new Date(Date.now() + 600000); - mockOAuthStateRepository.findByState.mockResolvedValue({ - state: 'valid_gh_state', - providerSlug: 'github-app', - organizationId: 'org_1', - userId: 'user_1', - codeVerifier: null, - redirectUrl: null, - expiresAt: futureDate, - }); - mockedGetManifest.mockReturnValue({ - id: 'github-app', - name: 'GitHub App', - category: 'Development', - auth: { - type: 'oauth2', - config: { - authorizeUrl: - 'https://github.com/apps/{APP_SLUG}/installations/new', - tokenUrl: 'https://github.com/login/oauth/access_token', - appInstallFlow: true, - }, - }, - capabilities: [], - isActive: true, - } as never); - mockOAuthCredentialsService.getCredentials.mockResolvedValue({ - clientId: 'client_123', - clientSecret: 'secret_456', - scopes: [], - source: 'platform', - }); - mockProviderRepository.findBySlug.mockResolvedValue({ - id: 'provider_gh', - }); - mockConnectionRepository.findByProviderAndOrg.mockResolvedValue({ - id: 'conn_gh', - metadata: {}, - variables: {}, - lastSyncAt: null, - }); - mockConnectionRepository.update.mockResolvedValue({ - id: 'conn_gh', - metadata: {}, - variables: {}, - lastSyncAt: null, - }); - mockConnectionService.activateConnection.mockResolvedValue({ - id: 'conn_gh', - }); - - const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue({ - ok: true, - status: 200, - json: () => Promise.resolve({ access_token: 'gh_user_token' }), - text: () => Promise.resolve(''), - } as unknown as Response); - - await controller.oauthCallback( - { - code: 'auth_code', - state: 'valid_gh_state', - installation_id: '987654', - setup_action: 'install', - }, - mockRequest, - mockResponse, - ); - - await new Promise((resolve) => setImmediate(resolve)); - - expect(mockConnectionRepository.update).toHaveBeenCalledWith( - 'conn_gh', - expect.objectContaining({ - metadata: expect.objectContaining({ - githubInstallationId: '987654', - githubSetupAction: 'install', - }), - }), - ); - const redirectUrl = (mockResponse.redirect as jest.Mock).mock.calls[0][0]; - expect(redirectUrl).toContain('success=true'); - expect(redirectUrl).toContain('provider=github-app'); - - fetchSpy.mockRestore(); - }); - describe('session defense-in-depth', () => { const futureDate = new Date(Date.now() + 600000); const validState = { diff --git a/apps/api/src/integration-platform/controllers/oauth.controller.ts b/apps/api/src/integration-platform/controllers/oauth.controller.ts index 4f3436dac..8007d2249 100644 --- a/apps/api/src/integration-platform/controllers/oauth.controller.ts +++ b/apps/api/src/integration-platform/controllers/oauth.controller.ts @@ -39,9 +39,6 @@ interface OAuthCallbackQuery { state: string; error?: string; error_description?: string; - // GitHub App installation flow returns these alongside `code`. - installation_id?: string; - setup_action?: string; } @Controller({ path: 'integrations/oauth', version: '1' }) @@ -425,27 +422,13 @@ export class OAuthController { }); } - // GitHub App installation flow: persist the installation id (and setup - // action) on the connection. The token stored above is a user-to-server - // token, but recording the installation id means a future server-to-server - // (installation access token) upgrade needs no re-connect. - if (query.installation_id) { - const metadata = - connection.metadata && - typeof connection.metadata === 'object' && - !Array.isArray(connection.metadata) - ? (connection.metadata as Record) - : {}; - connection = await this.connectionRepository.update(connection.id, { - metadata: { - ...metadata, - githubInstallationId: query.installation_id, - ...(query.setup_action - ? { githubSetupAction: query.setup_action } - : {}), - }, - }); - } + // NOTE: GitHub's App install flow returns an `installation_id` on this + // callback, but that value is attacker-spoofable — GitHub's own docs say + // not to rely on it — so we intentionally do NOT persist it here, and + // nothing reads it today. If/when GitHub App installation-token auth is + // added, resolve the installation via `GET /user/installations` with the + // user token (which also proves the user owns it) rather than trusting the + // value from this callback. await this.connectionService.activateConnection(connection.id);