Skip to content

Commit 51639e3

Browse files
authored
fix(auth): block OAuth linking for unverified accounts (calcom#26598)
* fix(auth): sanitize unverified accounts during OAuth linking - Add AccountSanitizationService for secure account cleanup - Clear webhooks, API keys, credentials, and sessions for unverified accounts - Reset password and 2FA settings during OAuth conversion - Nullify redirect URLs on event types Only affects accounts that never completed email verification * fix(auth): block OAuth linking for unverified accounts Replace sanitization with simpler blocking approach: - Unverified CAL accounts cannot link to OAuth (Google/SAML) - Add user-friendly error message with recovery path - Remove AccountSanitizationService (no data loss risk)
1 parent c380ea8 commit 51639e3

3 files changed

Lines changed: 8 additions & 1 deletion

File tree

apps/web/app/(use-page-wrapper)/auth/error/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ const ServerPage = async ({ searchParams }: PageProps) => {
4949
return t("account_managed_by_identity_provider_error", { provider: providerName });
5050
} else if (error === "saml-idp-not-authoritative") {
5151
return t("saml_idp_not_authoritative_error");
52+
} else if (error === "unverified-email") {
53+
return t("unverified_email_oauth_error");
5254
}
5355
return t("error_during_login") + (error ? ` Error code: ${error}` : "");
5456
};

apps/web/public/static/locales/en/common.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4186,5 +4186,6 @@
41864186
"audit_logs_permission_denied": "You do not have permission to view audit logs for this booking.",
41874187
"audit_logs_permission_check_error": "An error occurred while checking permissions.",
41884188
"account_already_exists_please_login": "An account with this email already exists. Please log in to accept the invitation.",
4189+
"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.",
41894190
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
41904191
}

packages/features/auth/lib/next-auth-options.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,11 @@ export const getOptions = ({
10751075
existingUserWithEmail.identityProvider === IdentityProvider.CAL &&
10761076
(idP === IdentityProvider.GOOGLE || idP === IdentityProvider.SAML)
10771077
) {
1078+
// Prevent account pre-hijacking: block OAuth linking for unverified accounts
1079+
if (!existingUserWithEmail.emailVerified) {
1080+
return "/auth/error?error=unverified-email";
1081+
}
1082+
10781083
// Verify SAML IdP is authoritative before converting account
10791084
if (idP === IdentityProvider.SAML) {
10801085
const samlTenant = getSamlTenant();
@@ -1086,7 +1091,6 @@ export const getOptions = ({
10861091

10871092
await prisma.user.update({
10881093
where: { email: existingUserWithEmail.email },
1089-
// also update email to the IdP email
10901094
data: {
10911095
email: user.email.toLowerCase(),
10921096
identityProvider: idP,

0 commit comments

Comments
 (0)