diff --git a/apps/web/app/(use-page-wrapper)/auth/error/page.tsx b/apps/web/app/(use-page-wrapper)/auth/error/page.tsx index 6beb145de3..765f80da24 100644 --- a/apps/web/app/(use-page-wrapper)/auth/error/page.tsx +++ b/apps/web/app/(use-page-wrapper)/auth/error/page.tsx @@ -49,6 +49,8 @@ const ServerPage = async ({ searchParams }: PageProps) => { return t("account_managed_by_identity_provider_error", { provider: providerName }); } else if (error === "saml-idp-not-authoritative") { return t("saml_idp_not_authoritative_error"); + } else if (error === "unverified-email") { + return t("unverified_email_oauth_error"); } return t("error_during_login") + (error ? ` Error code: ${error}` : ""); }; diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 4b37268f81..a28f369c92 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -4186,5 +4186,6 @@ "audit_logs_permission_denied": "You do not have permission to view audit logs for this booking.", "audit_logs_permission_check_error": "An error occurred while checking permissions.", "account_already_exists_please_login": "An account with this email already exists. Please log in to accept the invitation.", + "unverified_email_oauth_error": "Please verify your email address before signing in with Google or SAML. Sign in with your password to resend the verification email.", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } diff --git a/packages/features/auth/lib/next-auth-options.ts b/packages/features/auth/lib/next-auth-options.ts index 3c8b74cc06..a184cd284d 100644 --- a/packages/features/auth/lib/next-auth-options.ts +++ b/packages/features/auth/lib/next-auth-options.ts @@ -1075,6 +1075,11 @@ export const getOptions = ({ existingUserWithEmail.identityProvider === IdentityProvider.CAL && (idP === IdentityProvider.GOOGLE || idP === IdentityProvider.SAML) ) { + // Prevent account pre-hijacking: block OAuth linking for unverified accounts + if (!existingUserWithEmail.emailVerified) { + return "/auth/error?error=unverified-email"; + } + // Verify SAML IdP is authoritative before converting account if (idP === IdentityProvider.SAML) { const samlTenant = getSamlTenant(); @@ -1086,7 +1091,6 @@ export const getOptions = ({ await prisma.user.update({ where: { email: existingUserWithEmail.email }, - // also update email to the IdP email data: { email: user.email.toLowerCase(), identityProvider: idP,