{"record":{"id":"1959b2a474514ad6","repo":"toeverything/AFFiNE","slug":"missing-oauth-query-parameter-1959b2","errorCode":"missing_oauth_query_parameter","errorMessage":"Missing query parameter `provider`.","messagePattern":"Missing query parameter `provider`\\.","errorType":"exception","errorClass":"MissingOauthQueryParameter","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/plugins/oauth/service.ts","lineNumber":119,"sourceCode":"    if (!state.token) state.token = stateStr;\n\n    if (\n      state.provider === OAuthProviderName.Apple &&\n      rawState &&\n      state.client &&\n      state.client !== 'web'\n    ) {\n      return {\n        type: 'handoff',\n        code: input.code,\n        provider: rawState.provider,\n        state,\n        stateToken: stateStr,\n      };\n    }\n\n    if (!state.provider) {\n      throw new MissingOauthQueryParameter({ name: 'provider' });\n    }\n\n    const provider = this.providerFactory.get(state.provider);\n\n    if (!provider) {\n      throw new UnknownOauthProvider({ name: state.provider ?? 'unknown' });\n    }\n\n    if (\n      state.provider !== OAuthProviderName.Apple &&\n      (!input.clientNonce ||\n        !state.clientNonce ||\n        state.clientNonce !== input.clientNonce)\n    ) {\n      throw new InvalidAuthState();\n    }\n\n    return {","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/oauth/service.ts#L101-L137","documentation":"MissingOauthQueryParameter({name:'provider'}) thrown at packages/backend/server/src/plugins/oauth/service.ts:119 when the server-side OAuthState record loaded from the challenge store has no provider field. Normally preflight always persists provider alongside client/clientNonce/pkce, so this indicates a corrupted, legacy, or hand-crafted state record rather than a client input problem.","triggerScenarios":"The oauth_state cache entry was written without a provider (custom code calling saveOAuthState with an incomplete object); state records created by an older server version being replayed after upgrade; direct calls to OAuthService.verifyCallback with a stateStr whose stored payload lacks provider.","commonSituations":"Version upgrade where the state payload schema changed and old cached states are still in Redis; a plugin or fork writing states directly into the challenge store; cache key collisions overwriting an oauth_state entry with an incomplete object.","solutions":["Start a new login flow (new preflight writes a complete state record).","If it persists, flush the stale oauth_state entries from Redis/cache after upgrading.","Audit custom code that calls saveOAuthState() — it must include provider: OAuthProviderName.","Check that all replicas run the same backend version so state payloads have a consistent shape."],"exampleFix":"// before (custom code)\nawait this.oauth.saveOAuthState({ client: 'web', redirectUri: '/' } as OAuthState);\n\n// after\nawait this.oauth.saveOAuthState({ provider: OAuthProviderName.OIDC, client: 'web', redirectUri: '/' });","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isCompleteState(state: OAuthState | null): state is OAuthState {\n  return !!state && typeof state.provider === 'string' && state.provider.length > 0;\n}","tryCatchPattern":"try {\n  await oauth.verifyCallback(input);\n} catch (err) {\n  if (err instanceof MissingOauthQueryParameter && err.data?.name === 'provider') {\n    // corrupted/legacy state record: flush stale oauth_state cache entries, restart login\n  }\n}","preventionTips":["Always include provider when calling saveOAuthState().","Flush stale oauth_state cache entries when upgrading server versions.","Keep all replicas on the same version so state payloads stay shape-compatible."],"tags":["oauth","state","provider","configuration"],"backgroundTag":"oauth-missing-provider","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-08-23T06:17:17.905Z"}