{"record":{"id":"97a7a9993b6a7aa0","repo":"calcom/cal.diy","slug":"you-must-grant-all-permissions-to-use-this-integra","errorCode":null,"errorMessage":"You must grant all permissions to use this integration","messagePattern":"You must grant all permissions to use this integration","errorType":"http","errorClass":"HttpError","httpStatus":400,"severity":"warning","filePath":"packages/app-store/googlecalendar/api/callback.ts","lineNumber":60,"sourceCode":"  if (!req.session?.user?.id) {\n    throw new HttpError({ statusCode: 401, message: \"You must be logged in to do this\" });\n  }\n\n  const { client_id, client_secret } = await getGoogleAppKeys();\n\n  const redirect_uri = `${WEBAPP_URL_FOR_OAUTH}/api/integrations/googlecalendar/callback`;\n\n  const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri);\n\n  if (code) {\n    const token = await oAuth2Client.getToken(code);\n    const key = token.tokens;\n    const grantedScopes = token.tokens.scope?.split(\" \") ?? [];\n    // Check if we have granted all required permissions\n    const hasMissingRequiredScopes = GOOGLE_CALENDAR_SCOPES.some((scope) => !grantedScopes.includes(scope));\n    if (hasMissingRequiredScopes) {\n      if (!state?.fromApp) {\n        throw new HttpError({\n          statusCode: 400,\n          message: \"You must grant all permissions to use this integration\",\n        });\n      }\n      res.redirect(\n        getSafeRedirectUrl(state.onErrorReturnTo) ??\n          getSafeRedirectUrl(state?.returnTo) ??\n          `${WEBAPP_URL}/apps/installed`\n      );\n      return;\n    }\n\n    oAuth2Client.setCredentials(key);\n\n    const gcalCredentialData = buildCredentialCreateData({\n      userId: req.session.user.id,\n      key,\n      appId: \"google-calendar\",","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/app-store/googlecalendar/api/callback.ts#L42-L78","documentation":"After exchanging the code for tokens, the handler checks that every scope in `GOOGLE_CALENDAR_SCOPES` appears in `token.tokens.scope`. Google lets users uncheck individual scopes during consent; if any required calendar scope is missing, this throws `HttpError` **400** — but only when `!state?.fromApp`. When `fromApp` is set it redirects gracefully and returns instead. `add.ts` sets `prompt: \"consent\"` to force a fresh grant specifically to avoid stale partial grants.","triggerScenarios":"User unchecked one or more calendar scopes on Google's granular consent screen; Google granted only a subset; the app is unverified/testing and Google limited the granted scopes; consent cache returned an older, narrower grant than requested.","commonSituations":"End users deselecting 'See and download your calendars'; app in Google Cloud testing mode capping scopes; `GOOGLE_CALENDAR_SCOPES` changed but Google's consent cache returns the previous narrower grant; re-consent not forced.","solutions":["Inform the user all scopes are required and restart the flow — `add.ts` already uses `prompt: \"consent\"` to force a clean grant; ensure that parameter reaches every install entry point.","When starting OAuth, include `state.fromApp` or `state.onErrorReturnTo`/`returnTo` so partial-consent failures redirect gracefully instead of throwing.","Verify the app in Google Cloud Console so consent isn't scoped down, and confirm `GOOGLE_CALENDAR_SCOPES` matches the scopes configured there."],"exampleFix":"// before\nif (hasMissingRequiredScopes) {\n  if (!state?.fromApp) {\n    throw new HttpError({ statusCode: 400, message: \"You must grant all permissions to use this integration\" });\n  }\n  res.redirect(...); return;\n}\n// after - also honor onErrorReturnTo/returnTo, consistent with the code-check guard above\nif (hasMissingRequiredScopes) {\n  if (!state?.fromApp && !state?.onErrorReturnTo && !state?.returnTo) {\n    throw new HttpError({ statusCode: 400, message: \"You must grant all permissions to use this integration\" });\n  }\n  res.redirect(...); return;\n}","handlingStrategy":"validation","validationCode":null,"typeGuard":"// Verify the granted scopes cover everything required before treating the install as complete\nfunction hasAllRequiredScopes(granted: string[], required: readonly string[]): boolean {\n  return required.every((s) => granted.includes(s));\n}\n\n// usage after token exchange:\nconst granted = token.tokens.scope?.split(\" \") ?? [];\nif (!hasAllRequiredScopes(granted, GOOGLE_CALENDAR_SCOPES)) {\n  // re-prompt with prompt=consent rather than dead-ending\n}","tryCatchPattern":null,"preventionTips":["Always start OAuth with prompt=consent (as add.ts does) to force a fresh, full grant.","Carry state.fromApp or onErrorReturnTo/returnTo so partial-consent failures redirect gracefully.","Keep GOOGLE_CALENDAR_SCOPES in sync with the scopes configured on the Google Cloud app, and verify the app to avoid scope capping."],"tags":["app-store","googlecalendar","oauth","scopes","consent","http-400"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}