{"record":{"id":"48351dbc11818866","repo":"calcom/cal.diy","slug":"session-user-must-have-an-email","errorCode":null,"errorMessage":"Session user must have an email","messagePattern":"Session user must have an email","errorType":"http","errorClass":"HttpError","httpStatus":400,"severity":"error","filePath":"packages/app-store/dub/api/add.ts","lineNumber":20,"sourceCode":"\nimport { HttpError } from \"@calcom/lib/http-error\";\nimport { defaultHandler } from \"@calcom/lib/server/defaultHandler\";\nimport { defaultResponder } from \"@calcom/lib/server/defaultResponder\";\n\nimport getParsedAppKeysFromSlug from \"../../_utils/getParsedAppKeysFromSlug\";\nimport { dubAppKeysSchema, scopeString } from \"../lib/utils\";\n\nasync function handler(req: NextApiRequest, res: NextApiResponse) {\n  const loggedInUser = req.session?.user;\n\n  if (!loggedInUser) {\n    throw new HttpError({ statusCode: 401, message: \"You must be logged in to do this\" });\n  }\n\n  // Ideally this should never happen, as email is there in session user but typings aren't accurate it seems\n  // TODO: So, confirm and later fix the typings\n  if (!loggedInUser.email) {\n    throw new HttpError({ statusCode: 400, message: \"Session user must have an email\" });\n  }\n\n  const { teamId } = req.query;\n  const { client_id, redirect_uris } = await getParsedAppKeysFromSlug(\"dub\", dubAppKeysSchema);\n\n  const url = new URL(\"https://app.dub.co/oauth/authorize\");\n  url.searchParams.append(\"client_id\", client_id);\n  url.searchParams.append(\"redirect_uri\", redirect_uris);\n  url.searchParams.append(\"response_type\", \"code\");\n  url.searchParams.append(\"scope\", scopeString);\n  if (typeof teamId === \"string\" && !Number.isNaN(Number(teamId))) {\n    url.searchParams.append(\"state\", JSON.stringify({ teamId: Number(teamId) }));\n  }\n  const oauthUrl = url.toString();\n\n  return res.status(200).json({ url: oauthUrl });\n}\n","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/app-store/dub/api/add.ts#L2-L38","documentation":"Thrown as an HttpError (HTTP 400) by the Dub add handler when a logged-in session user has no email. NextAuth's session type does not guarantee email, and the Dub OAuth flow needs the user's email to attribute the link, so the handler explicitly asserts it. The inline TODO notes the typings are inaccurate.","triggerScenarios":"req.session.user is truthy but req.session.user.email is undefined or empty string. Possible with a custom NextAuth session strategy that omits email, a corrupt session JWT, or a user record missing an email in the database.","commonSituations":"Custom NextAuth callback that returns { id, name } without email; legacy user accounts with null email; session JWT decoded against a stale secret yielding partial claims.","solutions":["Ensure the NextAuth session callback includes email: return { ...session, user: { ...session.user, email: user.email } }.","Confirm the user record in the DB actually has an email address.","Verify NEXTAUTH_SECRET matches the one that issued the session (mismatch can produce partial sessions).","Re-authenticate the affected user to refresh the session JWT."],"exampleFix":"// before\nif (!loggedInUser.email) {\n  throw new HttpError({ statusCode: 400, message: \"Session user must have an email\" });\n}\n\n// after - surface to the user, prompt re-login\nif (!loggedInUser.email) {\n  throw new HttpError({ statusCode: 400, message: \"Your session is missing an email. Please log out and sign in again.\" });\n}","handlingStrategy":"validation","validationCode":"if (!loggedInUser || !loggedInUser.email) {\n  throw new HttpError({ statusCode: 400, message: \"Session user must have an email\" });\n}\n// Configure NextAuth session callback to always include email:\n// callbacks: { session({ session, user }) { return { ...session, user: { ...session.user, email: user.email } }; } }","typeGuard":"const hasSessionEmail = (u: unknown): u is { email: string } =>\n  typeof u === \"object\" && u !== null && typeof (u as any).email === \"string\" && (u as any).email.length > 0;","tryCatchPattern":"if (!loggedInUser.email) {\n  // prompt re-authentication rather than opaque 400\n  res.redirect(`${WEBAPP_URL}/auth/login?error=missing_email`);\n  return;\n}","preventionTips":["In the NextAuth session callback, always return user.email.","Backfill missing emails in the user table.","Keep NEXTAUTH_SECRET stable so session JWTs decode fully."],"tags":["dub","oauth","session","nextauth","validation"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}