fix(auth): block OAuth linking for unverified accounts (#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)
This commit is contained in:
Pedro Castro
2026-01-09 11:04:39 +00:00
committed by GitHub
parent c380ea810c
commit 51639e38d7
3 changed files with 8 additions and 1 deletions
@@ -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}` : "");
};
@@ -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 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -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,