toeverything/AFFiNE · error · UnknownOauthProvider

unknown_oauth_provider

unknown_oauth_provider

Error message

Unknown authentication provider ${name}.

What it means

Thrown at controller.ts:46 when CalendarService.isProviderAvailableFor(providerName, { oauth: true }) returns false. That method (service.ts:561) returns false for two distinct reasons: (a) no provider is registered under that name in CalendarProviderFactory's map, or (b) the provider is registered but its supportsOAuth flag is false. Only providers in the CalendarProviderName enum ('google', 'caldav') are recognized, and only those with supportsOAuth=true pass the oauth check. It is a user-facing invalid_input error (UnknownOauthProvider, code 'unknown_oauth_provider').

Source

Thrown at packages/backend/server/src/plugins/calendar/controller.ts:46

  constructor(
    private readonly calendar: CalendarService,
    private readonly oauth: CalendarOAuthService,
    private readonly url: URLHelper
  ) {}

  @Post('/oauth/preflight')
  @HttpCode(HttpStatus.OK)
  async preflight(
    @CurrentUser() user: CurrentUser,
    @Body('provider') providerName?: CalendarProviderName,
    @Body('redirect_uri') redirectUri?: string
  ) {
    if (!providerName) {
      throw new MissingOauthQueryParameter({ name: 'provider' });
    }

    if (!this.calendar.isProviderAvailableFor(providerName, { oauth: true })) {
      throw new UnknownOauthProvider({ name: providerName });
    }

    await this.calendar.assertCanLinkProvider(user.id, providerName);

    const state = await this.oauth.saveOAuthState({
      provider: providerName,
      userId: user.id,
      redirectUri,
    });

    const callbackUrl = this.calendar.getCallbackUrl();
    const authUrl = this.calendar.getAuthUrl(providerName, state, callbackUrl);

    return { url: authUrl };
  }

  @Public()
  @Get('/oauth/callback')

View on GitHub (pinned to 26c515e050)

Solutions

  1. Send a provider value that is both a valid CalendarProviderName enum member AND supports OAuth — currently 'google'. Use the exact lowercase string.
  2. If you intend CalDAV, use the CalDAV credential-linking flow instead of the OAuth preflight, since CalDAV does not support OAuth.
  3. Check server logs at boot for 'Calendar provider [google] registered.' — if missing, the Google provider module is disabled or failed to load; re-enable it in config (AFFiNE calendar.google.* settings).
  4. Before calling preflight, query the available providers (resolver exposes them via the providers field on the calendar resolver) and only offer OAuth for those that report supportsOAuth=true.

Example fix

// before
const res = await fetch('/api/calendar/oauth/preflight', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ provider: 'caldav' }), // CalDAV has no OAuth
});

// after — use an OAuth-capable provider, lowercase
const res = await fetch('/api/calendar/oauth/preflight', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ provider: 'google' }),
});
Defensive patterns

Strategy: type-guard

Validate before calling

// Resolve which providers actually support OAuth before offering the flow.
import { CalendarProviderName } from './providers';

// 'caldav' uses credentials, not OAuth — do not send it to /oauth/preflight.
const OAUTH_PROVIDERS: ReadonlyArray<CalendarProviderName> = [CalendarProviderName.Google];

function pickOAuthProvider(name: string) {
  const normalized = name.toLowerCase();
  if (!OAUTH_PROVIDERS.includes(normalized as CalendarProviderName)) {
    throw new Error(`Provider '${name}' does not support OAuth. Use ${OAUTH_PROVIDERS.join(', ')}.`);
  }
  return normalized as CalendarProviderName;
}

const provider = pickOAuthProvider(userSelection);

Type guard

import { CalendarProviderName } from './providers';

const OAUTH_CAPABLE: ReadonlySet<CalendarProviderName> = new Set([CalendarProviderName.Google]);

function supportsOAuth(value: unknown): value is CalendarProviderName {
  return typeof value === 'string'
    && Object.values(CalendarProviderName).includes(value as CalendarProviderName)
    && OAUTH_CAPABLE.has(value as CalendarProviderName);
}

Try / catch

try {
  await startCalendarOAuth(providerName);
} catch (err) {
  if (err instanceof Error && err.message.includes('unknown_oauth_provider')) {
    notifyUser(`'${providerName}' is not an available OAuth provider.`);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: POST /api/calendar/oauth/preflight with body { "provider": "caldav" } when CalDAV's supportsOAuth is false (it uses credential-based auth, not OAuth); or { "provider": "outlook" } / any string not in the enum, because providerFactory.get() returns undefined; or a typo like { "provider": "Google" } (capitalized) since the enum values are lowercase. The guard fires before OAuth state is saved.

Common situations: Client hardcodes a provider name that was removed or renamed in a server upgrade; provider plugins not registered at boot (e.g. Google provider module disabled via config so the factory map is empty); attempting the OAuth flow for CalDAV which is credentials-only; case mismatch between the enum ('google') and what the client sends.

Understand the failure class

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/5f1cd75784037f34. Report an issue: GitHub.