Skip to content
Open
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
1c4fd6c
feat(auth-validator): [PM-22975] Client Version Validator - initial iโ€ฆ
Patrick-Pimentel-Bitwarden Nov 17, 2025
22e9e5b
mend
Patrick-Pimentel-Bitwarden Nov 17, 2025
1af2fba
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Nov 17, 2025
47a26bb
fix(auth-validator): [PM-22975] Client Version Validator - Minor toucโ€ฆ
Patrick-Pimentel-Bitwarden Nov 20, 2025
a82b31c
fix(auth-validator): [PM-22975] Client Version Validator - Fixing somโ€ฆ
Patrick-Pimentel-Bitwarden Nov 20, 2025
7d71ee2
fix(auth-validator): [PM-22975] Client Version Validator - Finished fโ€ฆ
Patrick-Pimentel-Bitwarden Nov 20, 2025
851f963
test(auth-validator): [PM-22975] Client Version Validator - Fixed tesโ€ฆ
Patrick-Pimentel-Bitwarden Nov 20, 2025
756ae5e
fix(auth-validator): [PM-22975] Client Version Validator - Fixed namiโ€ฆ
Patrick-Pimentel-Bitwarden Nov 20, 2025
7874ec7
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Nov 20, 2025
91af02b
test(auth-validator): [PM-22975] Client Version Validator - Fixed tests.
Patrick-Pimentel-Bitwarden Nov 20, 2025
59d9d7b
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Nov 21, 2025
7897485
fix(auth-validator): [PM-22975] Client Version Validator - Renamed vaโ€ฆ
Patrick-Pimentel-Bitwarden Nov 21, 2025
d0c5333
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Nov 24, 2025
e46425d
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Nov 24, 2025
d69842d
fix(auth-validator): [PM-22975] Client Version Validator - Added headโ€ฆ
Patrick-Pimentel-Bitwarden Nov 24, 2025
1681703
fix(auth-validator): [PM-22975] Client Version Validator - Trying to โ€ฆ
Patrick-Pimentel-Bitwarden Nov 24, 2025
cb146fc
fix(auth-validator): [PM-22975] Client Version Validator - Fixed tests
Patrick-Pimentel-Bitwarden Nov 24, 2025
e74682b
Merge remote-tracking branch 'origin' into auth/pm-22975/client-versiโ€ฆ
Patrick-Pimentel-Bitwarden Nov 26, 2025
53e6509
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Dec 1, 2025
b3b1b9b
fix(auth-validator): [PM-22975] Client Version Validator - misc changโ€ฆ
Patrick-Pimentel-Bitwarden Dec 1, 2025
8f89694
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Dec 2, 2025
8b8694e
test(auth-validator): [PM-22975] Client Version Validator - WIP changes.
Patrick-Pimentel-Bitwarden Dec 2, 2025
ed89cf8
fix(auth-validator): [PM-22975] Client Version Validator - Made enougโ€ฆ
Patrick-Pimentel-Bitwarden Dec 2, 2025
6696104
fix(auth-validator): [PM-22975] Client Version Validator - Fixed moreโ€ฆ
Patrick-Pimentel-Bitwarden Dec 2, 2025
aa4f8ab
test(auth-validator): [PM-22975] Client Version Validator - Fixed theโ€ฆ
Patrick-Pimentel-Bitwarden Dec 2, 2025
86bca81
fix(auth-validator): [PM-22975] Client Version Validator - Changed soโ€ฆ
Patrick-Pimentel-Bitwarden Dec 2, 2025
c1bc10b
fix(auth-validator): [PM-22975] Client Version Validator - Removed unโ€ฆ
Patrick-Pimentel-Bitwarden Dec 2, 2025
753670d
fix(auth-validator): [PM-22975] Client Version Validator - Took in feโ€ฆ
Patrick-Pimentel-Bitwarden Dec 3, 2025
544965e
test(auth-validator): [PM-22975] Client Version Validator - Fixed test
Patrick-Pimentel-Bitwarden Dec 3, 2025
f719763
fix(auth-validator): [PM-22975] Client Version Validator - Took in teโ€ฆ
Patrick-Pimentel-Bitwarden Dec 3, 2025
cff2f5d
fix(auth-validator): [PM-22975] Client Version Validator - Added moreโ€ฆ
Patrick-Pimentel-Bitwarden Dec 4, 2025
2ed458d
fix(auth-validator): [PM-22975] Client Version Validator - Removed onโ€ฆ
Patrick-Pimentel-Bitwarden Dec 4, 2025
d706796
fix(auth-validator): [PM-22975] Client Version Validator - Updated vaโ€ฆ
Patrick-Pimentel-Bitwarden Dec 4, 2025
2264056
fix(auth-validator): [PM-22975] Client Version Validator - Updated wiโ€ฆ
Patrick-Pimentel-Bitwarden Dec 8, 2025
55bfb71
test(auth-validator): [PM-22975] Client Version Validator - Added encโ€ฆ
Patrick-Pimentel-Bitwarden Dec 8, 2025
36e7b1c
test(auth-validator): [PM-22975] Client Version Validator - Added stuโ€ฆ
Patrick-Pimentel-Bitwarden Dec 8, 2025
1f8be3b
docs(auth-validator): [PM-22975] Client Version Validator - Updated cโ€ฆ
Patrick-Pimentel-Bitwarden Dec 8, 2025
998aeb1
fix(auth-validator): [PM-22975] Client Version Validator - Not havingโ€ฆ
Patrick-Pimentel-Bitwarden Dec 8, 2025
27c9e4d
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Dec 8, 2025
fb83df3
fix(auth-validator): [PM-22975] Client Version Validator - Minor toucโ€ฆ
Patrick-Pimentel-Bitwarden Dec 8, 2025
10cbfd8
fix(auth-validator): [PM-22975] Client Version Validator - Minor funcโ€ฆ
Patrick-Pimentel-Bitwarden Dec 8, 2025
79256d7
fix(auth-validator): [PM-22975] Client Version Validator - Minor chanโ€ฆ
Patrick-Pimentel-Bitwarden Dec 8, 2025
865e76f
fix(auth-validator): [PM-22975] Client Version Validator - Reorder ofโ€ฆ
Patrick-Pimentel-Bitwarden Dec 8, 2025
50298fb
fix(auth-validator): [PM-22975] Client Version Validator - Fixed testโ€ฆ
Patrick-Pimentel-Bitwarden Dec 11, 2025
ad68312
fix(auth-validator): [PM-22975] Client Version Validator - Added tickโ€ฆ
Patrick-Pimentel-Bitwarden Dec 11, 2025
3f0d7d2
test(auth-validator): [PM-22975] Client Version Validator - added oneโ€ฆ
Patrick-Pimentel-Bitwarden Dec 11, 2025
fc6a513
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Dec 12, 2025
06224d9
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Dec 15, 2025
6a5518a
fix(auth-validator): [PM-22975] Client Version Validator - Rename funโ€ฆ
Patrick-Pimentel-Bitwarden Dec 15, 2025
fcd4b24
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Dec 15, 2025
3736944
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Dec 17, 2025
6ddd8c7
Merge branch 'main' into auth/pm-22975/client-version-validator
JaredSnider-Bitwarden Dec 22, 2025
c7ea4ea
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Dec 23, 2025
3bd54e5
test(auth-validator): [PM-22975] Client Version Validator - Fixed tests.
Patrick-Pimentel-Bitwarden Dec 23, 2025
f44cd58
Merge remote-tracking branch 'origin' into auth/pm-22975/client-versiโ€ฆ
Patrick-Pimentel-Bitwarden Jan 2, 2026
9a1f91f
fix(auth-validator): [PM-22975] Client Version Validator - Fixed tests.
Patrick-Pimentel-Bitwarden Jan 2, 2026
a3f5a32
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Jan 2, 2026
19d43e9
Merge branch 'main' into auth/pm-22975/client-version-validator
Patrick-Pimentel-Bitwarden Jan 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Core/Context/ICurrentContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public interface ICurrentContext
Guid? OrganizationId { get; set; }
IdentityClientType IdentityClientType { get; set; }
string ClientId { get; set; }
Version ClientVersion { get; set; }
Version? ClientVersion { get; set; }
bool ClientVersionIsPrerelease { get; set; }

Task BuildAsync(HttpContext httpContext, GlobalSettings globalSettings);
Expand Down
39 changes: 38 additions & 1 deletion src/Core/Entities/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Bit.Core.Auth.Models;
using Bit.Core.Enums;
using Bit.Core.KeyManagement.Models.Data;
using Bit.Core.KeyManagement.Utilities;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Identity;

Expand Down Expand Up @@ -51,7 +52,7 @@ public class User : ITableObject<Guid>, IStorableSubscriber, IRevisable, ITwoFac
public string? Key { get; set; }
/// <summary>
/// The raw public key, without a signature from the user's signature key.
/// </summary>
/// </summary>
public string? PublicKey { get; set; }
/// <summary>
/// User key wrapped private key.
Expand Down Expand Up @@ -206,6 +207,42 @@ public int GetSecurityVersion()
return SecurityVersion ?? 1;
}

/// <summary>
/// Evaluates user state to determine if they are currently in a v2 encryption state.
/// </summary>
/// <returns>If the shape of their private key is v2 as well as has the proper security version then true, otherwise false</returns>
public bool HasV2Encryption()
{
return HasV2KeyShape() && IsSecurityVersionTwo();
}

private bool HasV2KeyShape()
{
if (string.IsNullOrEmpty(PrivateKey))
{
return false;
}

try
{
return EncryptionParsing.GetEncryptionType(PrivateKey) == EncryptionType.XChaCha20Poly1305_B64;
}
catch (ArgumentException)
{
// Invalid encryption string format - treat as not v2
return false;
}
}

/// <summary>
/// This technically is correct but all versions after 1 are considered v2 encryption. Leaving for now with
/// KM's blessing that when a new version comes along they will handle migration.
/// </summary>
private bool IsSecurityVersionTwo()
{
return SecurityVersion == 2;
}

/// <summary>
/// Serializes the C# object to the User.TwoFactorProviders property in JSON format.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions src/Core/Enums/EncryptionType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ public enum EncryptionType : byte
XChaCha20Poly1305_B64 = 7,

// asymmetric
[Obsolete("PM-29656 - Should probably be removed as it is not known to exist in the real world")]
Rsa2048_OaepSha256_B64 = 3,
Rsa2048_OaepSha1_B64 = 4,
[Obsolete("PM-29656 - Should probably be removed as it is not known to exist in the real world")]
Rsa2048_OaepSha256_HmacSha256_B64 = 5,
[Obsolete("PM-29656 - Should probably be removed as it is not known to exist in the real world")]
Rsa2048_OaepSha1_HmacSha256_B64 = 6
}
6 changes: 6 additions & 0 deletions src/Core/KeyManagement/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
๏ปฟnamespace Bit.Core.KeyManagement;

public static class Constants
{
public static readonly Version MinimumClientVersionForV2Encryption = new("2025.11.0");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ“ Hardcoded version requires code deployment to update - This hardcoded version requires a code deployment and release cycle to change.

Recommendations:

  1. Add documentation - XML comments explaining:
    • Why this specific version (2025.11.0)
    • Which client releases support v2 encryption
    • Where/how to update when new clients are released
  2. Consider configuration-based approach - Move to GlobalSettings or feature flags for easier updates without redeployment
  3. Add monitoring - Ensure metrics exist to track blocked users for rollout planning

Example documentation:

/// <summary>
/// Minimum client version required for users with v2 encryption.
/// Update when v2 encryption support is added to new client releases.
/// This version (2025.11.0) corresponds to the first client release with v2 support.
/// See: https://bitwarden.atlassian.net/browse/PM-22975
/// </summary>

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Bit.Core.Enums;
using Bit.Core.KeyManagement.Models.Data;
using Bit.Core.KeyManagement.Repositories;
using Bit.Core.KeyManagement.Utilities;
using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
using Bit.Core.Services;
Expand Down Expand Up @@ -137,7 +138,7 @@ public async Task UpdateAccountKeysAsync(RotateUserAccountKeysData model, User u
}
else
{
if (GetEncryptionType(model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey) != EncryptionType.AesCbc256_HmacSha256_B64)
if (EncryptionParsing.GetEncryptionType(model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey) != EncryptionType.AesCbc256_HmacSha256_B64)
{
throw new InvalidOperationException("The provided account private key was not wrapped with AES-256-CBC-HMAC");
}
Expand Down Expand Up @@ -209,7 +210,7 @@ private bool IsV2EncryptionUserAsync(User user)
{
// Returns whether the user is a V2 user based on the private key's encryption type.
ArgumentNullException.ThrowIfNull(user);
var isPrivateKeyEncryptionV2 = GetEncryptionType(user.PrivateKey) == EncryptionType.XChaCha20Poly1305_B64;
var isPrivateKeyEncryptionV2 = EncryptionParsing.GetEncryptionType(user.PrivateKey) == EncryptionType.XChaCha20Poly1305_B64;
return isPrivateKeyEncryptionV2;
}

Expand Down Expand Up @@ -237,7 +238,7 @@ private static void ValidateV2Encryption(RotateUserAccountKeysData model)
{
throw new InvalidOperationException("Signature key pair data is required for V2 encryption.");
}
if (GetEncryptionType(model.AccountKeys.SignatureKeyPairData.WrappedSigningKey) != EncryptionType.XChaCha20Poly1305_B64)
if (EncryptionParsing.GetEncryptionType(model.AccountKeys.SignatureKeyPairData.WrappedSigningKey) != EncryptionType.XChaCha20Poly1305_B64)
{
throw new InvalidOperationException("The provided signing key data is not wrapped with XChaCha20-Poly1305.");
}
Expand All @@ -246,7 +247,7 @@ private static void ValidateV2Encryption(RotateUserAccountKeysData model)
throw new InvalidOperationException("The provided signature key pair data does not contain a valid verifying key.");
}

if (GetEncryptionType(model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey) != EncryptionType.XChaCha20Poly1305_B64)
if (EncryptionParsing.GetEncryptionType(model.AccountKeys.PublicKeyEncryptionKeyPairData.WrappedPrivateKey) != EncryptionType.XChaCha20Poly1305_B64)
{
throw new InvalidOperationException("The provided private key encryption key is not wrapped with XChaCha20-Poly1305.");
}
Expand All @@ -259,24 +260,4 @@ private static void ValidateV2Encryption(RotateUserAccountKeysData model)
throw new InvalidOperationException("No signed security state provider for V2 user");
}
}

/// <summary>
/// Helper method to convert an encryption type string to an enum value.
/// </summary>
private static EncryptionType GetEncryptionType(string encString)
{
var parts = encString.Split('.');
if (parts.Length == 1)
{
throw new ArgumentException("Invalid encryption type string.");
}
if (byte.TryParse(parts[0], out var encryptionTypeNumber))
{
if (Enum.IsDefined(typeof(EncryptionType), encryptionTypeNumber))
{
return (EncryptionType)encryptionTypeNumber;
}
}
throw new ArgumentException("Invalid encryption type string.");
}
}
28 changes: 28 additions & 0 deletions src/Core/KeyManagement/Utilities/EncryptionParsing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
๏ปฟusing Bit.Core.Enums;

namespace Bit.Core.KeyManagement.Utilities;

public static class EncryptionParsing
{
/// <summary>
/// Helper method to convert an encryption type string to an enum value.
/// </summary>
public static EncryptionType GetEncryptionType(string? encString)
{
ArgumentNullException.ThrowIfNull(encString);

var parts = encString.Split('.');
if (parts.Length == 1)
{
throw new ArgumentException("Invalid encryption type string.");
}
if (byte.TryParse(parts[0], out var encryptionTypeNumber))
{
if (Enum.IsDefined(typeof(EncryptionType), encryptionTypeNumber))
{
return (EncryptionType)encryptionTypeNumber;
}
}
throw new ArgumentException("Invalid encryption type string.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public abstract class BaseRequestValidator<T> where T : class
private readonly IUserRepository _userRepository;
private readonly IAuthRequestRepository _authRequestRepository;
private readonly IMailService _mailService;
private readonly IClientVersionValidator _clientVersionValidator;

protected ICurrentContext CurrentContext { get; }
protected IPolicyService PolicyService { get; }
Expand Down Expand Up @@ -70,7 +71,8 @@ public BaseRequestValidator(
IPolicyRequirementQuery policyRequirementQuery,
IAuthRequestRepository authRequestRepository,
IMailService mailService,
IUserAccountKeysQuery userAccountKeysQuery
IUserAccountKeysQuery userAccountKeysQuery,
IClientVersionValidator clientVersionValidator
)
{
_userManager = userManager;
Expand All @@ -92,6 +94,7 @@ IUserAccountKeysQuery userAccountKeysQuery
_authRequestRepository = authRequestRepository;
_mailService = mailService;
_accountKeysQuery = userAccountKeysQuery;
_clientVersionValidator = clientVersionValidator;
}

protected async Task ValidateAsync(T context, ValidatedTokenRequest request,
Expand All @@ -111,7 +114,8 @@ await BuildSuccessResultAsync(validatorContext.User, context, validatorContext.D
}
else
{
// 1. We need to check if the user's master password hash is correct.
// 1. We need to check if the user is legitimate via the contextually appropriate mechanism
// (webauthn, password, custom token, etc.).
var valid = await ValidateContextAsync(context, validatorContext);
var user = validatorContext.User;
if (!valid)
Expand All @@ -122,6 +126,17 @@ await BuildSuccessResultAsync(validatorContext.User, context, validatorContext.D
return;
}

// 1.5 Now check the version number of the client. Do this after ValidateContextAsync so that
// we prevent account enumeration. If we were to do this before ValidateContextAsync, then attackers
// could use a known invalid client version and make a request for a user (before we know if they have
// demonstrated ownership of the account via correct credentials) and identify if they exist by getting
// an error response back from the validator saying the user is not compatible with the client.
var clientVersionValid = await ValidateClientVersionAsync(context, validatorContext);
if (!clientVersionValid)
{
return;
}

// 2. Decide if this user belongs to an organization that requires SSO.
// TODO: Clean up Feature Flag: Remove this if block: PM-28281
if (!_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired))
Expand Down Expand Up @@ -275,7 +290,8 @@ private Func<Task<bool>>[] DetermineValidationOrder(T context, ValidatedTokenReq
// validation to perform the recovery as part of scheme validation based on the request.
return
[
() => ValidateMasterPasswordAsync(context, validatorContext),
() => ValidateGrantSpecificContext(context, validatorContext),
() => ValidateClientVersionAsync(context, validatorContext),
() => ValidateTwoFactorAsync(context, request, validatorContext),
() => ValidateSsoAsync(context, request, validatorContext),
() => ValidateNewDeviceAsync(context, request, validatorContext),
Expand All @@ -288,7 +304,8 @@ private Func<Task<bool>>[] DetermineValidationOrder(T context, ValidatedTokenReq
// The typical validation scenario.
return
[
() => ValidateMasterPasswordAsync(context, validatorContext),
() => ValidateGrantSpecificContext(context, validatorContext),
() => ValidateClientVersionAsync(context, validatorContext),
() => ValidateSsoAsync(context, request, validatorContext),
() => ValidateTwoFactorAsync(context, request, validatorContext),
() => ValidateNewDeviceAsync(context, request, validatorContext),
Expand Down Expand Up @@ -341,12 +358,29 @@ private static async Task<bool> ProcessValidatorsAsync(params Func<Task<bool>>[]
}

/// <summary>
/// Validates the user's Master Password hash.
/// Validates whether the client version is compatible for the user attempting to authenticate.
/// </summary>
/// <returns>true if the scheme successfully passed validation, otherwise false.</returns>
private async Task<bool> ValidateClientVersionAsync(T context, CustomValidatorRequestContext validatorContext)
{
var ok = _clientVersionValidator.ValidateAsync(validatorContext.User, validatorContext);
if (ok)
{
return true;
}

SetValidationErrorResult(context, validatorContext);
await LogFailedLoginEvent(validatorContext.User, EventType.User_FailedLogIn);
return false;
}

/// <summary>
/// Validates the user's master password, webauthen, or custom token request via the appropriate context validator.
/// </summary>
/// <param name="context">The current request context.</param>
/// <param name="validatorContext"><see cref="Bit.Identity.IdentityServer.CustomValidatorRequestContext" /></param>
/// <returns>true if the scheme successfully passed validation, otherwise false.</returns>
private async Task<bool> ValidateMasterPasswordAsync(T context, CustomValidatorRequestContext validatorContext)
private async Task<bool> ValidateGrantSpecificContext(T context, CustomValidatorRequestContext validatorContext)
{
var valid = await ValidateContextAsync(context, validatorContext);
var user = validatorContext.User;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
๏ปฟusing Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.KeyManagement;
using Bit.Core.Models.Api;
using Duende.IdentityServer.Validation;

namespace Bit.Identity.IdentityServer.RequestValidators;

public interface IClientVersionValidator
{
bool ValidateAsync(User user, CustomValidatorRequestContext requestContext);
}

/// <summary>
/// This validator will use the Client Version on a request, which currently maps
/// to the "Bitwarden-Client-Version" header, to determine if a user meets minimum
/// required client version for issuing tokens on an old client. This is done to
/// incentivize users to get on an updated client when their password encryption
/// method has already been updated.
///
/// If the header is omitted, then the validator returns that this request is invalid.
/// </summary>
public class ClientVersionValidator(
ICurrentContext currentContext)
: IClientVersionValidator
{
private const string _upgradeMessage = "Please update your app to continue using Bitwarden";
private const string _noUserMessage = "No user found while trying to validate client version";
private const string _versionHeaderMissing = "No client version header found, required to prevent encryption errors. Please confirm your client is supplying the header: \"Bitwarden-Client-Version\"";

public bool ValidateAsync(User? user, CustomValidatorRequestContext requestContext)
{
// Do this nullish check because the base request validator currently is not
// strict null checking. Once that gets fixed then we can see about making
// the user not nullish checked. If they are null then the validator should fail.
if (user == null)
{
FillRequestContextWithErrorData(requestContext, "no_user", _noUserMessage);
return false;
}

Version? clientVersion = currentContext.ClientVersion;

// Deny access if the client version headers are missing.
// We want to establish a strict contract with clients that if they omit this header,
// then the server cannot guarantee that a client won't do harm to a user's data
// with stale encryption architecture.
if (clientVersion == null)
{
FillRequestContextWithErrorData(requestContext, "version_header_missing", _versionHeaderMissing);
return false;
}

// Determine the minimum version client that a user needs. If no V2 encryption detected then
// no validation needs to occur, which is why min version number can be null.
Version? minVersion = user.HasV2Encryption() ? Constants.MinimumClientVersionForV2Encryption : null;

// If min version is null then we know that the user had an encryption
// configuration that doesn't require a minimum version. Allowing through.
if (minVersion == null)
{
return true;
}

if (clientVersion < minVersion)
{
FillRequestContextWithErrorData(requestContext, "invalid_client_version", _upgradeMessage);
return false;
}

return true;
}

private void FillRequestContextWithErrorData(
CustomValidatorRequestContext requestContext,
string errorId,
string errorMessage)
{
requestContext.ValidationErrorResult = new ValidationResult
{
Error = errorId,
ErrorDescription = errorMessage,
IsError = true
};
requestContext.CustomResponse = new Dictionary<string, object>
{
{ "ErrorModel", new ErrorResponseModel(errorMessage) }
};
}
}


Loading
Loading