calcom/cal.diy · error · HttpError

Session user must have an email

Error message

Session user must have an email

What it means

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.

Source

Thrown at packages/app-store/dub/api/add.ts:20

import { HttpError } from "@calcom/lib/http-error";
import { defaultHandler } from "@calcom/lib/server/defaultHandler";
import { defaultResponder } from "@calcom/lib/server/defaultResponder";

import getParsedAppKeysFromSlug from "../../_utils/getParsedAppKeysFromSlug";
import { dubAppKeysSchema, scopeString } from "../lib/utils";

async function handler(req: NextApiRequest, res: NextApiResponse) {
  const loggedInUser = req.session?.user;

  if (!loggedInUser) {
    throw new HttpError({ statusCode: 401, message: "You must be logged in to do this" });
  }

  // Ideally this should never happen, as email is there in session user but typings aren't accurate it seems
  // TODO: So, confirm and later fix the typings
  if (!loggedInUser.email) {
    throw new HttpError({ statusCode: 400, message: "Session user must have an email" });
  }

  const { teamId } = req.query;
  const { client_id, redirect_uris } = await getParsedAppKeysFromSlug("dub", dubAppKeysSchema);

  const url = new URL("https://app.dub.co/oauth/authorize");
  url.searchParams.append("client_id", client_id);
  url.searchParams.append("redirect_uri", redirect_uris);
  url.searchParams.append("response_type", "code");
  url.searchParams.append("scope", scopeString);
  if (typeof teamId === "string" && !Number.isNaN(Number(teamId))) {
    url.searchParams.append("state", JSON.stringify({ teamId: Number(teamId) }));
  }
  const oauthUrl = url.toString();

  return res.status(200).json({ url: oauthUrl });
}

View on GitHub (pinned to 176037d0af)

Solutions

  1. Ensure the NextAuth session callback includes email: return { ...session, user: { ...session.user, email: user.email } }.
  2. Confirm the user record in the DB actually has an email address.
  3. Verify NEXTAUTH_SECRET matches the one that issued the session (mismatch can produce partial sessions).
  4. Re-authenticate the affected user to refresh the session JWT.

Example fix

// before
if (!loggedInUser.email) {
  throw new HttpError({ statusCode: 400, message: "Session user must have an email" });
}

// after - surface to the user, prompt re-login
if (!loggedInUser.email) {
  throw new HttpError({ statusCode: 400, message: "Your session is missing an email. Please log out and sign in again." });
}
Defensive patterns

Strategy: validation

Validate before calling

if (!loggedInUser || !loggedInUser.email) {
  throw new HttpError({ statusCode: 400, message: "Session user must have an email" });
}
// Configure NextAuth session callback to always include email:
// callbacks: { session({ session, user }) { return { ...session, user: { ...session.user, email: user.email } }; } }

Type guard

const hasSessionEmail = (u: unknown): u is { email: string } =>
  typeof u === "object" && u !== null && typeof (u as any).email === "string" && (u as any).email.length > 0;

Try / catch

if (!loggedInUser.email) {
  // prompt re-authentication rather than opaque 400
  res.redirect(`${WEBAPP_URL}/auth/login?error=missing_email`);
  return;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/48351dbc11818866. Report an issue: GitHub.