From d3df58046ca3135d0f7ca8d7c0ce83b42b111cde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Thu, 16 Apr 2026 17:01:55 +0200 Subject: [PATCH] chore(server): drop api-host branch in OAuth discovery (#19768) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Follow-up to #19755. Simplifies `OAuthDiscoveryController` by dropping the `authorization_endpoint → frontend base URL` branch that was there to make `api.twenty.com/mcp` paste-able in MCP clients. We've decided not to support pasting `api.twenty.com/mcp` — users can paste `app.twenty.com/mcp`, `.twenty.com/mcp`, or a custom domain, all of which serve both frontend and API. On those hosts, `authorization_endpoint` was already pointed at the same host as `issuer`, which is what we want. ## Change - Remove `isApiHost` helper and the `authorizeBase` branch — use `issuer` for `authorization_endpoint`. - Drop now-unused `TwentyConfigService` and `DomainServerConfigService` injections. - Drop duplicate `DomainServerConfigModule` import from `application-oauth.module.ts` (the module is no longer needed). Net diff: +1 / -22 across 2 files. ## Breaking change MCP clients configured with `https://api.twenty.com/mcp` will stop working. They should be reconfigured with the host matching the workspace they're connecting to (`.twenty.com/mcp`, `app.twenty.com/mcp`, or a custom domain). ## Test plan - [x] `yarn jest --testPathPatterns="mcp-auth.guard"` → 2/2 passing (unchanged) - [x] `tsc --noEmit` clean on modified files - [ ] Manual verification on staging: `app.twenty.com/mcp` and `.twenty.com/mcp` OAuth flow still works end-to-end Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 --- .../application-oauth.module.ts | 3 --- .../controllers/oauth-discovery.controller.ts | 20 +------------------ 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/packages/twenty-server/src/engine/core-modules/application/application-oauth/application-oauth.module.ts b/packages/twenty-server/src/engine/core-modules/application/application-oauth/application-oauth.module.ts index 6ef469b40a..712f834bbc 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-oauth/application-oauth.module.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-oauth/application-oauth.module.ts @@ -19,7 +19,6 @@ import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; -import { DomainServerConfigModule } from 'src/engine/core-modules/domain/domain-server-config/domain-server-config.module'; @Module({ imports: [ @@ -33,13 +32,11 @@ import { DomainServerConfigModule } from 'src/engine/core-modules/domain/domain- ApplicationCoreModule, ApplicationInstallModule, TokenModule, - DomainServerConfigModule, FeatureFlagModule, PermissionsModule, ThrottlerModule, TwentyConfigModule, WorkspaceCacheStorageModule, - DomainServerConfigModule, ], controllers: [ OAuthTokenController, diff --git a/packages/twenty-server/src/engine/core-modules/application/application-oauth/controllers/oauth-discovery.controller.ts b/packages/twenty-server/src/engine/core-modules/application/application-oauth/controllers/oauth-discovery.controller.ts index 8d760954b8..14dc2e61c2 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-oauth/controllers/oauth-discovery.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-oauth/controllers/oauth-discovery.controller.ts @@ -4,18 +4,13 @@ import { type Request } from 'express'; import { ALL_OAUTH_SCOPES } from 'src/engine/core-modules/application/application-oauth/constants/oauth-scopes'; import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service'; -import { DomainServerConfigService } from 'src/engine/core-modules/domain/domain-server-config/services/domain-server-config.service'; -import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard'; import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard'; -import { cleanServerUrl } from 'src/utils/clean-server-url'; import { TWENTY_CLI_APPLICATION_REGISTRATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-cli-application-registration.constant'; @Controller('.well-known') export class OAuthDiscoveryController { constructor( - private readonly twentyConfigService: TwentyConfigService, - private readonly domainServerConfigService: DomainServerConfigService, private readonly applicationRegistrationService: ApplicationRegistrationService, ) {} @@ -23,13 +18,6 @@ export class OAuthDiscoveryController { @UseGuards(PublicEndpointGuard, NoPermissionGuard) async getAuthorizationServerMetadata(@Req() request: Request) { const issuer = this.getRequestBaseUrl(request); - // /authorize is served by the frontend; SERVER_URL (API-only) has no such - // route, so we route the client to the default frontend base URL in that - // case. All other hosts (app.twenty.com, workspace subdomains, custom - // domains) serve both frontend and API. - const authorizeBase = this.isApiHost(request) - ? cleanServerUrl(this.domainServerConfigService.getBaseUrl().toString()) - : issuer; const cliRegistration = await this.applicationRegistrationService.findOneByUniversalIdentifier( @@ -38,7 +26,7 @@ export class OAuthDiscoveryController { return { issuer, - authorization_endpoint: `${authorizeBase}/authorize`, + authorization_endpoint: `${issuer}/authorize`, token_endpoint: `${issuer}/oauth/token`, registration_endpoint: `${issuer}/oauth/register`, revocation_endpoint: `${issuer}/oauth/revoke`, @@ -80,10 +68,4 @@ export class OAuthDiscoveryController { private getRequestBaseUrl(request: Request): string { return `${request.protocol}://${request.get('host')}`; } - - private isApiHost(request: Request): boolean { - const serverUrl = this.twentyConfigService.get('SERVER_URL'); - - return request.get('host') === new URL(serverUrl).host; - } }