Browse Source

Backport pull request #15254 from jellyfin/release-10.11.z

Update password reset to always return the same response structure

Original-merge: 4ad31418753840ca76c52fc2aa56fa1a4235ca87

Merged-by: crobibero <cody@robibe.ro>

Backported-by: Joshua M. Boniface <joshua@boniface.me>
thornbill 2 weeks ago
parent
commit
1ccd10863e

+ 23 - 17
Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.Security.Cryptography;
 using System.Text.Json;
@@ -92,33 +93,38 @@ namespace Jellyfin.Server.Implementations.Users
         }
 
         /// <inheritdoc />
-        public async Task<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork)
+        public async Task<ForgotPasswordResult> StartForgotPasswordProcess(User? user, string enteredUsername, bool isInNetwork)
         {
-            byte[] bytes = new byte[4];
-            RandomNumberGenerator.Fill(bytes);
-            string pin = BitConverter.ToString(bytes);
-
             DateTime expireTime = DateTime.UtcNow.AddMinutes(30);
-            string filePath = _passwordResetFileBase + user.Id + ".json";
-            SerializablePasswordReset spr = new SerializablePasswordReset
-            {
-                ExpirationDate = expireTime,
-                Pin = pin,
-                PinFile = filePath,
-                UserName = user.Username
-            };
+            var usernameHash = enteredUsername.ToUpperInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
+            var pinFile = _passwordResetFileBase + usernameHash + ".json";
 
-            FileStream fileStream = AsyncFile.Create(filePath);
-            await using (fileStream.ConfigureAwait(false))
+            if (user is not null && isInNetwork)
             {
-                await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false);
+                byte[] bytes = new byte[4];
+                RandomNumberGenerator.Fill(bytes);
+                string pin = BitConverter.ToString(bytes);
+
+                SerializablePasswordReset spr = new SerializablePasswordReset
+                {
+                    ExpirationDate = expireTime,
+                    Pin = pin,
+                    PinFile = pinFile,
+                    UserName = user.Username
+                };
+
+                FileStream fileStream = AsyncFile.Create(pinFile);
+                await using (fileStream.ConfigureAwait(false))
+                {
+                    await JsonSerializer.SerializeAsync(fileStream, spr).ConfigureAwait(false);
+                }
             }
 
             return new ForgotPasswordResult
             {
                 Action = ForgotPasswordAction.PinCode,
                 PinExpirationDate = expireTime,
-                PinFile = filePath
+                PinFile = pinFile
             };
         }
 

+ 12 - 12
Jellyfin.Server.Implementations/Users/UserManager.cs

@@ -508,23 +508,18 @@ namespace Jellyfin.Server.Implementations.Users
         public async Task<ForgotPasswordResult> StartForgotPasswordProcess(string enteredUsername, bool isInNetwork)
         {
             var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername);
+            var passwordResetProvider = GetPasswordResetProvider(user);
+
+            var result = await passwordResetProvider
+                .StartForgotPasswordProcess(user, enteredUsername, isInNetwork)
+                .ConfigureAwait(false);
 
             if (user is not null && isInNetwork)
             {
-                var passwordResetProvider = GetPasswordResetProvider(user);
-                var result = await passwordResetProvider
-                    .StartForgotPasswordProcess(user, isInNetwork)
-                    .ConfigureAwait(false);
-
                 await UpdateUserAsync(user).ConfigureAwait(false);
-                return result;
             }
 
-            return new ForgotPasswordResult
-            {
-                Action = ForgotPasswordAction.InNetworkRequired,
-                PinFile = string.Empty
-            };
+            return result;
         }
 
         /// <inheritdoc/>
@@ -760,8 +755,13 @@ namespace Jellyfin.Server.Implementations.Users
             return GetAuthenticationProviders(user)[0];
         }
 
-        private IPasswordResetProvider GetPasswordResetProvider(User user)
+        private IPasswordResetProvider GetPasswordResetProvider(User? user)
         {
+            if (user is null)
+            {
+                return _defaultPasswordResetProvider;
+            }
+
             return GetPasswordResetProviders(user)[0];
         }
 

+ 2 - 3
MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs

@@ -1,5 +1,3 @@
-#nullable disable
-
 #pragma warning disable CS1591
 
 using System;
@@ -15,11 +13,12 @@ namespace MediaBrowser.Controller.Authentication
 
         bool IsEnabled { get; }
 
-        Task<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork);
+        Task<ForgotPasswordResult> StartForgotPasswordProcess(User? user, string enteredUsername, bool isInNetwork);
 
         Task<PinRedeemResult> RedeemPasswordResetPin(string pin);
     }
 
+#nullable disable
     public class PasswordPinCreationResult
     {
         public string PinFile { get; set; }

+ 4 - 0
MediaBrowser.Model/Users/ForgotPasswordAction.cs

@@ -1,11 +1,15 @@
 #pragma warning disable CS1591
 
+using System;
+
 namespace MediaBrowser.Model.Users
 {
     public enum ForgotPasswordAction
     {
+        [Obsolete("Returning different actions represents a security concern.")]
         ContactAdmin = 0,
         PinCode = 1,
+        [Obsolete("Returning different actions represents a security concern.")]
         InNetworkRequired = 2
     }
 }