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

Update password reset to always return the same response structure

Original-merge: 4ad3141875

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

Backported-by: Joshua M. Boniface <joshua@boniface.me>
This commit is contained in:
thornbill
2025-11-02 21:58:42 -05:00
committed by Joshua M. Boniface
parent 4258df4485
commit 1ccd10863e
4 changed files with 41 additions and 32 deletions

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text.Json; using System.Text.Json;
@@ -92,33 +93,38 @@ namespace Jellyfin.Server.Implementations.Users
} }
/// <inheritdoc /> /// <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); DateTime expireTime = DateTime.UtcNow.AddMinutes(30);
string filePath = _passwordResetFileBase + user.Id + ".json"; var usernameHash = enteredUsername.ToUpperInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
SerializablePasswordReset spr = new SerializablePasswordReset var pinFile = _passwordResetFileBase + usernameHash + ".json";
{
ExpirationDate = expireTime,
Pin = pin,
PinFile = filePath,
UserName = user.Username
};
FileStream fileStream = AsyncFile.Create(filePath); if (user is not null && isInNetwork)
await using (fileStream.ConfigureAwait(false))
{ {
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 return new ForgotPasswordResult
{ {
Action = ForgotPasswordAction.PinCode, Action = ForgotPasswordAction.PinCode,
PinExpirationDate = expireTime, PinExpirationDate = expireTime,
PinFile = filePath PinFile = pinFile
}; };
} }

View File

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

View File

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

View File

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