Browse Source

fix(email-2fa): use device-identifier fallback when no password hash submitted

iOS clients call /api/two-factor/send-email-login with an email and
DeviceIdentifier but without masterPasswordHash or authRequestId after
receiving a 2FA-required response from the token endpoint. The previous
empty else-block allowed any caller to trigger a 2FA email for any
account knowing only the email address.

Replace the empty block with a device-identifier-based fallback:
- Look up the most-recently-active device via find_by_device_for_email2fa.
- Verify the device's associated user email matches the submitted email.
- Log (debug) when the fallback path is exercised for operator visibility.
- Reject with the original error when no device identifier is provided or
  when the device maps to a different account.

Fixes #7568
pull/7572/head
Arunabha-Mukhopadhyay 3 weeks ago
parent
commit
47fa7f52c8
  1. 40
      src/api/core/two_factor/email.rs

40
src/api/core/two_factor/email.rs

@ -86,11 +86,41 @@ async fn send_email_login(data: Json<SendEmailLoginData>, client_headers: Client
err!("AuthRequest doesn't exist", "Invalid device, IP or code") err!("AuthRequest doesn't exist", "Invalid device, IP or code")
} }
} else { } else {
// Allow email-only requests to trigger sending an email 2FA token. // Fallback for clients (e.g. iOS) that call this endpoint with an email but
// Mobile clients (e.g. iOS) may call this endpoint with only the email when // without a masterPasswordHash or authRequestId. This can happen when the
// the token endpoint indicated 2FA is required. In that flow the client // client receives a 2FA-required response from the token endpoint and then
// doesn't submit the master password hash or an auth request id, so // immediately calls send-email-login without re-submitting credentials.
// permit sending the email token based solely on the user's email. //
// If the client provided a device identifier, use it to look up the most
// recently active device for the account and verify it matches the submitted
// email. This preserves a meaningful security check while remaining
// compatible with these clients.
//
// If no device identifier is present either, reject the request to prevent
// unauthenticated actors from triggering emails for arbitrary accounts.
if let Some(device_identifier) = &data.device_identifier {
match User::find_by_device_for_email2fa(device_identifier, &conn).await {
Some(device_user) if device_user.email.to_lowercase() == email.to_lowercase() => {
// Device matches the requested email – allow the token to be sent.
// Log so operators can monitor how often this fallback path is used.
debug!(
"Email 2FA send-email-login: using device-identifier fallback for user '{}' \
(device: {}). No masterPasswordHash or authRequestId was provided.",
email, device_identifier
);
}
Some(_) => {
// Device exists but belongs to a different account – reject.
err!("Username or password is incorrect. Try again.")
}
None => {
// No device record found – cannot verify the caller.
err!("No password hash has been submitted.")
}
}
} else {
err!("No password hash has been submitted.")
}
} }
user user

Loading…
Cancel
Save