{"record":{"id":"cd5b709691ea98b4","repo":"calcom/cal.diy","slug":"you-must-be-logged-in-to-do-this-cd5b70","errorCode":null,"errorMessage":"You must be logged in to do this","messagePattern":"You must be logged in to do this","errorType":"http","errorClass":"HttpError","httpStatus":401,"severity":"warning","filePath":"packages/app-store/googlecalendar/api/callback.ts","lineNumber":43,"sourceCode":"\nasync function getHandler(req: NextApiRequest, res: NextApiResponse) {\n  const { code } = req.query;\n  const state = decodeOAuthState(req);\n\n  if (typeof code !== \"string\") {\n    if (state?.onErrorReturnTo || state?.returnTo) {\n      res.redirect(\n        getSafeRedirectUrl(state.onErrorReturnTo) ??\n          getSafeRedirectUrl(state?.returnTo) ??\n          `${WEBAPP_URL}/apps/installed`\n      );\n      return;\n    }\n    throw new HttpError({ statusCode: 400, message: \"`code` must be a string\" });\n  }\n\n  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,","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/app-store/googlecalendar/api/callback.ts#L25-L61","documentation":"Auth guard in the OAuth callback, run after the code check. Fires `HttpError` **401** when `req.session?.user?.id` is absent. Because this runs inside the redirect roundtrip to Google and back, the dominant cause is the session cookie not surviving that redirect chain (SameSite/third-party cookie blocking, or session expiry mid-flow).","triggerScenarios":"User starts Google install, is redirected to Google and back, but by the callback the session is gone: cookie blocked across the redirect, session TTL elapsed during a slow consent, or the callback completed in a different browser context than the one that started the flow.","commonSituations":"Safari/Chrome ITP blocking third-party cookies across the Google redirect; session cookie expiring during consent; user finishing consent on a different device/incognito tab; cookie domain mismatch between `WEBAPP_URL` and the callback host.","solutions":["Ensure session cookies use `SameSite=Lax` (or `SameSite=None; Secure`) so they survive the OAuth redirect chain.","On a 401 here, redirect to login and restart the OAuth flow rather than showing a bare error.","Confirm the callback is reached in the same browser session that hit `/add` (same cookie jar)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"// On a 401 from the callback, bounce to login and restart the OAuth flow\n// (callback is a top-level redirect target, so guard at the /add entry instead)\nasync function startGoogleOAuth() {\n  const res = await fetch(\"/api/integrations/googlecalendar/add\", { redirect: \"manual\" });\n  if (res.status === 401) {\n    window.location.href = `/auth/login?callbackUrl=${encodeURIComponent(\"/apps/installed\")}`;\n    return;\n  }\n  const { url } = await res.json();\n  window.location.href = url; // same browser session must complete the Google roundtrip\n}","preventionTips":["Use SameSite=Lax (or None; Secure) for session cookies so they survive the Google redirect chain.","Ensure the callback completes in the same browser session/cookie jar that started /add.","On 401 at the callback, redirect to login and restart OAuth rather than dead-ending."],"tags":["app-store","googlecalendar","oauth","session","cookies","http-401"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}