From d6fa6c240b0f7c85dfdea6f929c1f50584ec9215 Mon Sep 17 00:00:00 2001
From: Bernd Schoolmann <mail@quexten.com>
Date: Fri, 11 Apr 2025 11:53:34 +0200
Subject: [PATCH 1/4] Block legacy users on all clients over 2025.5

---
 src/Core/Constants.cs                                          | 1 +
 .../RequestValidators/CustomTokenRequestValidator.cs           | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs
index 2b53c52ab8b6..f97d78090d49 100644
--- a/src/Core/Constants.cs
+++ b/src/Core/Constants.cs
@@ -23,6 +23,7 @@ public static class Constants
 
     public const string Fido2KeyCipherMinimumVersion = "2023.10.0";
     public const string SSHKeyCipherMinimumVersion = "2024.12.0";
+    public const string DenyLegacyUserMinimumVersion = "2025.5.0";
 
     /// <summary>
     /// Used by IdentityServer to identify our own provider.
diff --git a/src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs b/src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs
index a7c6449ff611..f89f0a5c502b 100644
--- a/src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs
+++ b/src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs
@@ -26,6 +26,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
 {
     private readonly UserManager<User> _userManager;
     private readonly IUpdateInstallationCommand _updateInstallationCommand;
+    private readonly Version _denyLegacyUserMinimumVersion = new(Constants.DenyLegacyUserMinimumVersion);
 
     public CustomTokenRequestValidator(
         UserManager<User> userManager,
@@ -73,7 +74,7 @@ public async Task ValidateAsync(CustomTokenRequestValidationContext context)
         {
             // Force legacy users to the web for migration
             if (await _userService.IsLegacyUser(GetSubject(context)?.GetSubjectId()) &&
-                context.Result.ValidatedRequest.ClientId != "web")
+                (context.Result.ValidatedRequest.ClientId != "web" || CurrentContext.ClientVersion >= _denyLegacyUserMinimumVersion))
             {
                 await FailAuthForLegacyUserAsync(null, context);
                 return;

From d3da0682a16a772e752420051bed7da84e6297f7 Mon Sep 17 00:00:00 2001
From: Bernd Schoolmann <mail@quexten.com>
Date: Mon, 14 Apr 2025 11:07:30 +0200
Subject: [PATCH 2/4] Update message

---
 .../IdentityServer/RequestValidators/BaseRequestValidator.cs    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs b/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs
index 88691fa8f708..c9da7f751082 100644
--- a/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs
+++ b/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs
@@ -203,7 +203,7 @@ await _twoFactorAuthenticationValidator
     protected async Task FailAuthForLegacyUserAsync(User user, T context)
     {
         await BuildErrorResultAsync(
-            $"Encryption key migration is required. Please log in to the web vault at {_globalSettings.BaseServiceUri.VaultWithHash}",
+            $"Legacy encryption without a userkey is no longer supported. To recover your account, please contact support",
             false, context, user);
     }
 

From 841ac3939400316756f2e8c7a4d7e7039016c04c Mon Sep 17 00:00:00 2001
From: Bernd Schoolmann <mail@quexten.com>
Date: Mon, 14 Apr 2025 12:14:33 +0200
Subject: [PATCH 3/4] Fix test

---
 test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs b/test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs
index 589aac2842ef..bf3954db3acc 100644
--- a/test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs
+++ b/test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs
@@ -381,8 +381,7 @@ public async Task ValidateAsync_IsLegacyUser_FailAuthForLegacyUserAsync(
         // Assert
         Assert.True(context.GrantResult.IsError);
         var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
-        var expectedMessage = $"Encryption key migration is required. Please log in to the web " +
-                              $"vault at {_globalSettings.BaseServiceUri.VaultWithHash}";
+        var expectedMessage = "Legacy encryption without a userkey is no longer supported. To recover your account, please contact support";
         Assert.Equal(expectedMessage, errorResponse.Message);
     }
 

From df09d3ae9c4a1443e7d99659b45c59fee73119aa Mon Sep 17 00:00:00 2001
From: Bernd Schoolmann <mail@quexten.com>
Date: Mon, 14 Apr 2025 12:28:27 +0200
Subject: [PATCH 4/4] Fix test

---
 .../Identity.IntegrationTest/Endpoints/IdentityServerTests.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/Identity.IntegrationTest/Endpoints/IdentityServerTests.cs b/test/Identity.IntegrationTest/Endpoints/IdentityServerTests.cs
index 38a1518d14b9..56e1551cde0d 100644
--- a/test/Identity.IntegrationTest/Endpoints/IdentityServerTests.cs
+++ b/test/Identity.IntegrationTest/Endpoints/IdentityServerTests.cs
@@ -329,7 +329,7 @@ await _factory.RegisterAsync(new RegisterRequestModel
     }
 
     [Theory, BitAutoData]
-    public async Task TokenEndpoint_GrantTypeClientCredentials_AsLegacyUser_NotOnWebClient_Fails(string deviceId)
+    public async Task TokenEndpoint_GrantTypeClientCredentials_AsLegacyUser_Fails(string deviceId)
     {
         var server = _factory.WithWebHostBuilder(builder =>
         {
@@ -371,7 +371,7 @@ await server.PostAsync("/accounts/register", JsonContent.Create(new RegisterRequ
         var errorBody = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
         var error = AssertHelper.AssertJsonProperty(errorBody.RootElement, "ErrorModel", JsonValueKind.Object);
         var message = AssertHelper.AssertJsonProperty(error, "Message", JsonValueKind.String).GetString();
-        Assert.StartsWith("Encryption key migration is required.", message);
+        Assert.StartsWith("Legacy encryption without a userkey is no longer supported.", message);
     }