{"record":{"id":"5f1cd75784037f34","repo":"toeverything/AFFiNE","slug":"unknown-oauth-provider","errorCode":"unknown_oauth_provider","errorMessage":"Unknown authentication provider ${name}.","messagePattern":"Unknown authentication provider (.+?)\\.","errorType":"exception","errorClass":"UnknownOauthProvider","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/plugins/calendar/controller.ts","lineNumber":46,"sourceCode":"  constructor(\n    private readonly calendar: CalendarService,\n    private readonly oauth: CalendarOAuthService,\n    private readonly url: URLHelper\n  ) {}\n\n  @Post('/oauth/preflight')\n  @HttpCode(HttpStatus.OK)\n  async preflight(\n    @CurrentUser() user: CurrentUser,\n    @Body('provider') providerName?: CalendarProviderName,\n    @Body('redirect_uri') redirectUri?: string\n  ) {\n    if (!providerName) {\n      throw new MissingOauthQueryParameter({ name: 'provider' });\n    }\n\n    if (!this.calendar.isProviderAvailableFor(providerName, { oauth: true })) {\n      throw new UnknownOauthProvider({ name: providerName });\n    }\n\n    await this.calendar.assertCanLinkProvider(user.id, providerName);\n\n    const state = await this.oauth.saveOAuthState({\n      provider: providerName,\n      userId: user.id,\n      redirectUri,\n    });\n\n    const callbackUrl = this.calendar.getCallbackUrl();\n    const authUrl = this.calendar.getAuthUrl(providerName, state, callbackUrl);\n\n    return { url: authUrl };\n  }\n\n  @Public()\n  @Get('/oauth/callback')","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/plugins/calendar/controller.ts#L28-L64","documentation":"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').","triggerScenarios":"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.","commonSituations":"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.","solutions":["Send a provider value that is both a valid CalendarProviderName enum member AND supports OAuth — currently 'google'. Use the exact lowercase string.","If you intend CalDAV, use the CalDAV credential-linking flow instead of the OAuth preflight, since CalDAV does not support OAuth.","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).","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."],"exampleFix":"// before\nconst res = await fetch('/api/calendar/oauth/preflight', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ provider: 'caldav' }), // CalDAV has no OAuth\n});\n\n// after — use an OAuth-capable provider, lowercase\nconst res = await fetch('/api/calendar/oauth/preflight', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ provider: 'google' }),\n});","handlingStrategy":"type-guard","validationCode":"// Resolve which providers actually support OAuth before offering the flow.\nimport { CalendarProviderName } from './providers';\n\n// 'caldav' uses credentials, not OAuth — do not send it to /oauth/preflight.\nconst OAUTH_PROVIDERS: ReadonlyArray<CalendarProviderName> = [CalendarProviderName.Google];\n\nfunction pickOAuthProvider(name: string) {\n  const normalized = name.toLowerCase();\n  if (!OAUTH_PROVIDERS.includes(normalized as CalendarProviderName)) {\n    throw new Error(`Provider '${name}' does not support OAuth. Use ${OAUTH_PROVIDERS.join(', ')}.`);\n  }\n  return normalized as CalendarProviderName;\n}\n\nconst provider = pickOAuthProvider(userSelection);","typeGuard":"import { CalendarProviderName } from './providers';\n\nconst OAUTH_CAPABLE: ReadonlySet<CalendarProviderName> = new Set([CalendarProviderName.Google]);\n\nfunction supportsOAuth(value: unknown): value is CalendarProviderName {\n  return typeof value === 'string'\n    && Object.values(CalendarProviderName).includes(value as CalendarProviderName)\n    && OAUTH_CAPABLE.has(value as CalendarProviderName);\n}","tryCatchPattern":"try {\n  await startCalendarOAuth(providerName);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('unknown_oauth_provider')) {\n    notifyUser(`'${providerName}' is not an available OAuth provider.`);\n  } else {\n    throw err;\n  }\n}","preventionTips":["Fetch the server's advertised provider list at app start and only render OAuth buttons for providers whose supportsOAuth flag is true.","Use the lowercase enum string values ('google', 'caldav') verbatim — never capitalize or rename client-side.","Treat CalDAV as credentials-only; route it through the credential-linking API, not the OAuth preflight.","After a server upgrade, re-query the provider list in case a provider was added or OAuth support toggled."],"tags":["oauth","calendar","nestjs","typescript","provider-registry","http-400"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}