{"record":{"id":"40a78fca1191e003","repo":"toeverything/AFFiNE","slug":"calendar-provider-oauth-unsupported","errorCode":"calendar_provider_oauth_unsupported","errorMessage":"GraphQL bad request, code: calendar_provider_oauth_unsupported, Selected calendar provider does not support OAuth.","messagePattern":"GraphQL bad request, code: calendar_provider_oauth_unsupported, Selected calendar provider does not support OAuth\\.","errorType":"exception","errorClass":"GraphqlBadRequest","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/plugins/calendar/service.ts","lineNumber":607,"sourceCode":"    if (this.canCreateNewAccounts(provider)) {\n      return true;\n    }\n    if (!userId) {\n      return false;\n    }\n\n    const accounts = await this.models.calendarAccount.listByUser(userId);\n    return accounts.some(account => account.provider === provider);\n  }\n\n  getAuthUrl(\n    provider: CalendarProviderName,\n    state: string,\n    redirectUri: string\n  ) {\n    const instance = this.requireProvider(provider);\n    if (!instance.supportsOAuth) {\n      throw new GraphqlBadRequest({\n        code: 'calendar_provider_oauth_unsupported',\n        message: 'Selected calendar provider does not support OAuth.',\n      });\n    }\n    return instance.getAuthUrl(state, redirectUri);\n  }\n\n  private async assertCanPersistProviderAccount(\n    userId: string,\n    provider: CalendarProviderName,\n    providerAccountId: string\n  ) {\n    if (this.canCreateNewAccounts(provider)) {\n      return;\n    }\n\n    const account = await this.models.calendarAccount.getByProviderAccount(\n      userId,","sourceCodeStart":589,"sourceCodeEnd":625,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/591f874dad30887a80143a061a44bd3ca7ee3299/packages/backend/server/src/plugins/calendar/service.ts#L589-L625","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Route providers without OAuth to their own flow (CalDAV account discovery with username/password) instead of getAuthUrl.","Before calling getAuthUrl, gate the UI on the provider's advertised capabilities (supportsOAuth) returned by the provider-list API.","If you maintain the provider class and it does support OAuth, set supportsOAuth = true and implement getAuthUrl(state, redirectUri)."],"exampleFix":"// before\nconst url = await calendarService.getAuthUrl(CalendarProviderName.CalDAV, state, redirectUri);\n\n// after\nconst instance = calendarService.getProvider(provider);\nif (instance?.supportsOAuth) {\n  const url = await calendarService.getAuthUrl(provider, state, redirectUri);\n} else {\n  openCredentialDialog(provider); // e.g. CalDAV discovery flow\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function supportsOAuthFlow(\n  provider: CalendarProvider | undefined\n): provider is CalendarProvider & { getAuthUrl: (state: string, redirectUri: string) => string } {\n  return !!provider && provider.supportsOAuth === true;\n}","tryCatchPattern":"try {\n  const url = calendarService.getAuthUrl(provider, state, redirectUri);\n} catch (e) {\n  if (e?.extensions?.code === 'calendar_provider_oauth_unsupported') {\n    routeToCredentialFlow(provider);\n  } else throw e;\n}","preventionTips":["Drive the connect flow off the provider's advertised capabilities, not a hardcoded provider list.","Custom provider implementations must set supportsOAuth consistently with getAuthUrl availability.","Hide OAuth buttons for providers known to be credential-only (e.g. CalDAV)."],"tags":["calendar","oauth","graphql","provider-capabilities"],"backgroundTag":"oauth-unsupported-provider","analyzedSha":"591f874dad30887a80143a061a44bd3ca7ee3299","analyzedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}