toeverything/AFFiNE · error · GraphqlBadRequest

calendar_provider_oauth_unsupported

calendar_provider_oauth_unsupported

Error message

GraphQL bad request, code: calendar_provider_oauth_unsupported, Selected calendar provider does not support OAuth.

What it means

Thrown by CalendarService.getAuthUrl when the requested calendar provider instance reports supportsOAuth = false. The OAuth login flow needs the provider to mint an authorization URL; providers that only do credential-based auth (e.g. CalDAV username/password) cannot serve this path, so the request is rejected as a bad request with code 'calendar_provider_oauth_unsupported'.

Source

Thrown at packages/backend/server/src/plugins/calendar/service.ts:607

    if (this.canCreateNewAccounts(provider)) {
      return true;
    }
    if (!userId) {
      return false;
    }

    const accounts = await this.models.calendarAccount.listByUser(userId);
    return accounts.some(account => account.provider === provider);
  }

  getAuthUrl(
    provider: CalendarProviderName,
    state: string,
    redirectUri: string
  ) {
    const instance = this.requireProvider(provider);
    if (!instance.supportsOAuth) {
      throw new GraphqlBadRequest({
        code: 'calendar_provider_oauth_unsupported',
        message: 'Selected calendar provider does not support OAuth.',
      });
    }
    return instance.getAuthUrl(state, redirectUri);
  }

  private async assertCanPersistProviderAccount(
    userId: string,
    provider: CalendarProviderName,
    providerAccountId: string
  ) {
    if (this.canCreateNewAccounts(provider)) {
      return;
    }

    const account = await this.models.calendarAccount.getByProviderAccount(
      userId,

View on GitHub (pinned to 591f874dad)

Solutions

  1. Route providers without OAuth to their own flow (CalDAV account discovery with username/password) instead of getAuthUrl.
  2. Before calling getAuthUrl, gate the UI on the provider's advertised capabilities (supportsOAuth) returned by the provider-list API.
  3. If you maintain the provider class and it does support OAuth, set supportsOAuth = true and implement getAuthUrl(state, redirectUri).

Example fix

// before
const url = await calendarService.getAuthUrl(CalendarProviderName.CalDAV, state, redirectUri);

// after
const instance = calendarService.getProvider(provider);
if (instance?.supportsOAuth) {
  const url = await calendarService.getAuthUrl(provider, state, redirectUri);
} else {
  openCredentialDialog(provider); // e.g. CalDAV discovery flow
}
Defensive patterns

Strategy: type-guard

Type guard

function supportsOAuthFlow(
  provider: CalendarProvider | undefined
): provider is CalendarProvider & { getAuthUrl: (state: string, redirectUri: string) => string } {
  return !!provider && provider.supportsOAuth === true;
}

Try / catch

try {
  const url = calendarService.getAuthUrl(provider, state, redirectUri);
} catch (e) {
  if (e?.extensions?.code === 'calendar_provider_oauth_unsupported') {
    routeToCredentialFlow(provider);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the calendar OAuth URL query/mutation with provider = 'caldav' or any provider whose implementation sets supportsOAuth to false; a custom provider plugin that forgot to implement getAuthUrl/supportsOAuth; frontend routing a generic 'Connect calendar' button to the OAuth flow for all providers.

Common situations: UI shows one connect flow for every provider and picks OAuth for CalDAV; adding a new custom calendar provider without OAuth support but reusing the generic OAuth entry point; stale client logic after a provider dropped OAuth support.

Related errors


AI-assisted analysis of toeverything/AFFiNE@591f874dad (2026-08-18). Data as JSON: /api/errors/40a78fca1191e003. Report an issue: GitHub.