toeverything/AFFiNE · error · MissingOauthQueryParameter
missing_oauth_query_parameter
missing_oauth_query_parameter
Error message
Missing query parameter `${name}`. What it means
Thrown by the POST /api/calendar/oauth/preflight endpoint when the request body omits the 'provider' field. The error name is misleading — the code reads @Body('provider'), not a query string — but it is a generic OAuth-preflight guard reused across the codebase (also thrown for missing 'code' and 'state' on the callback). It is a user-facing bad_request error (MissingOauthQueryParameter, code 'missing_oauth_query_parameter') carrying the missing field name so the client can render a targeted message.
Source
Thrown at packages/backend/server/src/plugins/calendar/controller.ts:42
import { CalendarService } from './service';
@Controller('/api/calendar')
export class CalendarController {
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 };View on GitHub (pinned to 26c515e050)
Solutions
- POST a JSON body containing { "provider": "google" } (or "caldav") with header Content-Type: application/json to /api/calendar/oauth/preflight.
- If the client is sending provider as a URL query string, move it into the JSON request body — the @Body decorator will not read @Query.
- Verify the request is actually reaching the controller and not a validation pipe that strips unknown fields; ensure no global ValidationPipe with whitelist:true is dropping provider because the DTO field name differs.
- In frontend code, guard the submit handler so the button is disabled until a provider is chosen, eliminating the empty-body case.
Example fix
// before
fetch('/api/calendar/oauth/preflight', {
method: 'POST',
body: JSON.stringify({ redirect_uri }),
});
// after
fetch('/api/calendar/oauth/preflight', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ provider: 'google', redirect_uri }),
}); Defensive patterns
Strategy: validation
Validate before calling
// Run before calling /api/calendar/oauth/preflight
function buildPreflightBody(input: { provider?: string; redirect_uri?: string }) {
if (!input.provider || typeof input.provider !== 'string') {
throw new Error('provider is required and must be a non-empty string');
}
return { provider: input.provider, redirect_uri: input.redirect_uri };
}
const body = buildPreflightBody(formData);
await fetch('/api/calendar/oauth/preflight', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
}); Type guard
function isPreflightProvider(value: unknown): value is 'google' | 'caldav' {
return value === 'google' || value === 'caldav';
} Try / catch
try {
await startCalendarOAuth('google');
} catch (err) {
if (err instanceof Error && err.message.includes('missing_oauth_query_parameter') && err.message.includes('provider')) {
showFieldError('provider', 'Please select a calendar provider.');
} else {
throw err;
}
} Prevention
- Always set Content-Type: application/json on POST bodies so Nest's parser populates @Body fields.
- Disable the submit button until a provider is selected in the UI.
- Keep the provider option list in sync with the CalendarProviderName enum so the client can never send an unexpected value.
- Add a client-side schema check (e.g. zod) for the preflight request shape before sending.
When it happens
Trigger: A POST to /api/calendar/oauth/preflight whose JSON body lacks a 'provider' key, sends it as null/empty string, or sends a content-type that NestJS cannot parse into the @Body('provider') decorator. The guard at controller.ts:41 (`if (!providerName)`) fires before any provider lookup or state persistence, so the request fails fast with HTTP 400.
Common situations: Frontend form submitting before a provider is selected; a client built against an older API that sent provider as a query param (?provider=google) instead of in the body; a malformed fetch with the wrong Content-Type (e.g. text/plain) so Nest's body parser leaves provider undefined; automated tests that construct the preflight request without the field.
Related errors
- unknown_oauth_provider
- password_required
- A config file path is required
- invalid_app_config_input
- A migration name is required
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/773133a650a500ac.
Report an issue: GitHub.