1
0
mirror of https://github.com/bitwarden/server synced 2026-02-12 22:44:00 +00:00

[PM-30563] Change error response on Send Access token request (#6911)

* feat: remove invalid email response and instead return email and OTP required to protect against enumeration attacks.

* fix: fixing tests and dotnet format
This commit is contained in:
Ike
2026-02-04 09:42:32 -05:00
committed by GitHub
parent 52955d1860
commit 5afdfa6fd1
8 changed files with 27 additions and 27 deletions

View File

@@ -22,8 +22,7 @@ public class SendEmailOtpRequestValidator(
private static readonly Dictionary<string, string> _sendEmailOtpValidatorErrorDescriptions = new()
{
{ SendAccessConstants.EmailOtpValidatorResults.EmailRequired, $"{SendAccessConstants.TokenRequest.Email} is required." },
{ SendAccessConstants.EmailOtpValidatorResults.EmailOtpSent, "email otp sent." },
{ SendAccessConstants.EmailOtpValidatorResults.EmailInvalid, $"{SendAccessConstants.TokenRequest.Email} is invalid." },
{ SendAccessConstants.EmailOtpValidatorResults.EmailAndOtpRequired, $"{SendAccessConstants.TokenRequest.Email} and {SendAccessConstants.TokenRequest.Otp} are required." },
{ SendAccessConstants.EmailOtpValidatorResults.EmailOtpInvalid, $"{SendAccessConstants.TokenRequest.Email} otp is invalid." },
};
@@ -33,7 +32,7 @@ public class SendEmailOtpRequestValidator(
// get email
var email = request.Get(SendAccessConstants.TokenRequest.Email);
// It is an invalid request if the email is missing which indicated bad shape.
// It is an invalid request if the email is missing.
if (string.IsNullOrEmpty(email))
{
// Request is the wrong shape and doesn't contain an email field.
@@ -43,9 +42,16 @@ public class SendEmailOtpRequestValidator(
// email hash must be in the list of email hashes in the EmailOtp array
byte[] hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(email));
string hashEmailHex = Convert.ToHexString(hashBytes).ToUpperInvariant();
/*
* This is somewhat contradictory to our process where a poor shape means invalid_request and invalid
* data is invalid_grant.
* In this case the shape is correct and the data is invalid but to protect against enumeration we treat incorrect emails
* as invalid requests. The response for a request with a correct email which needs an OTP and a request
* that has an invalid email need to be the same otherwise an attacker could enumerate until a valid email is found.
*/
if (!authMethod.EmailHashes.Contains(hashEmailHex))
{
return BuildErrorResult(SendAccessConstants.EmailOtpValidatorResults.EmailInvalid);
return BuildErrorResult(SendAccessConstants.EmailOtpValidatorResults.EmailAndOtpRequired);
}
// get otp from request
@@ -70,7 +76,7 @@ public class SendEmailOtpRequestValidator(
token,
string.Format(SendAccessConstants.OtpEmail.Subject, token));
return BuildErrorResult(SendAccessConstants.EmailOtpValidatorResults.EmailOtpSent);
return BuildErrorResult(SendAccessConstants.EmailOtpValidatorResults.EmailAndOtpRequired);
}
// validate request otp
@@ -94,7 +100,7 @@ public class SendEmailOtpRequestValidator(
switch (error)
{
case SendAccessConstants.EmailOtpValidatorResults.EmailRequired:
case SendAccessConstants.EmailOtpValidatorResults.EmailOtpSent:
case SendAccessConstants.EmailOtpValidatorResults.EmailAndOtpRequired:
return new GrantValidationResult(TokenRequestErrors.InvalidRequest,
errorDescription: _sendEmailOtpValidatorErrorDescriptions[error],
new Dictionary<string, object>
@@ -102,7 +108,6 @@ public class SendEmailOtpRequestValidator(
{ SendAccessConstants.SendAccessError, error }
});
case SendAccessConstants.EmailOtpValidatorResults.EmailOtpInvalid:
case SendAccessConstants.EmailOtpValidatorResults.EmailInvalid:
return new GrantValidationResult(
TokenRequestErrors.InvalidGrant,
errorDescription: _sendEmailOtpValidatorErrorDescriptions[error],