Update Reset method to not rely on externally available entity

This commit is contained in:
JPVenson
2026-05-01 11:05:33 +00:00
parent 1ae45519d0
commit 511f90d6d3
5 changed files with 23 additions and 29 deletions

View File

@@ -151,7 +151,7 @@ public class StartupController : BaseJellyfinApiController
if (!string.IsNullOrEmpty(startupUserDto.Password)) if (!string.IsNullOrEmpty(startupUserDto.Password))
{ {
await _userManager.ChangePassword(user, startupUserDto.Password).ConfigureAwait(false); await _userManager.ChangePassword(user.Id, startupUserDto.Password).ConfigureAwait(false);
} }
return NoContent(); return NoContent();

View File

@@ -288,7 +288,7 @@ public class UserController : BaseJellyfinApiController
if (request.ResetPassword) if (request.ResetPassword)
{ {
await _userManager.ResetPassword(user).ConfigureAwait(false); await _userManager.ResetPassword(user.Id).ConfigureAwait(false);
} }
else else
{ {
@@ -306,7 +306,7 @@ public class UserController : BaseJellyfinApiController
} }
} }
await _userManager.ChangePassword(user, request.NewPw ?? string.Empty).ConfigureAwait(false); await _userManager.ChangePassword(user.Id, request.NewPw ?? string.Empty).ConfigureAwait(false);
var currentToken = User.GetToken(); var currentToken = User.GetToken();
@@ -545,7 +545,7 @@ public class UserController : BaseJellyfinApiController
// no need to authenticate password for new user // no need to authenticate password for new user
if (request.Password is not null) if (request.Password is not null)
{ {
await _userManager.ChangePassword(newUser, request.Password).ConfigureAwait(false); await _userManager.ChangePassword(newUser.Id, request.Password).ConfigureAwait(false);
} }
var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIP().ToString()); var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIP().ToString());

View File

@@ -74,7 +74,7 @@ namespace Jellyfin.Server.Implementations.Users
var resetUser = userManager.GetUserByName(spr.UserName) var resetUser = userManager.GetUserByName(spr.UserName)
?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); ?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");
await userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); await userManager.ChangePassword(resetUser.Id, pin).ConfigureAwait(false);
usersReset.Add(resetUser.Username); usersReset.Add(resetUser.Username);
File.Delete(resetFile); File.Delete(resetFile);
} }

View File

@@ -335,43 +335,37 @@ namespace Jellyfin.Server.Implementations.Users
} }
/// <inheritdoc/> /// <inheritdoc/>
public Task ResetPassword(User user) public Task ResetPassword(Guid userId)
{ {
return ChangePassword(user, string.Empty); return ChangePassword(userId, string.Empty);
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task ChangePassword(User user, string newPassword) public async Task ChangePassword(Guid userId, string newPassword)
{ {
ArgumentNullException.ThrowIfNull(user); User dbUser = null!;
if (user.HasPermission(PermissionKind.IsAdministrator) && string.IsNullOrWhiteSpace(newPassword)) using (await _userLock.LockAsync(userId).ConfigureAwait(false))
{
throw new ArgumentException("Admin user passwords must not be empty", nameof(newPassword));
}
using (await _userLock.LockAsync(user.Id).ConfigureAwait(false))
{ {
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false)) await using (dbContext.ConfigureAwait(false))
{ {
// Reload the user inside the lock to get the current RowVersion. dbUser = await UserQuery(dbContext)
// The incoming user may have been loaded with AsNoTracking() before this
// lock was acquired; a concurrent write (e.g. a login updating LastLoginDate)
// could have incremented RowVersion in the database in the meantime.
// Since we now hold the per-user lock, no other write can race with us, so
// the freshly loaded RowVersion is guaranteed to be current.
var dbUser = await UserQuery(dbContext)
.AsTracking() .AsTracking()
.FirstOrDefaultAsync(u => u.Id == user.Id) .FirstOrDefaultAsync(u => u.Id == userId)
.ConfigureAwait(false) .ConfigureAwait(false)
?? throw new ResourceNotFoundException(nameof(user.Id)); ?? throw new ResourceNotFoundException(nameof(userId));
if (dbUser.HasPermission(PermissionKind.IsAdministrator) && string.IsNullOrWhiteSpace(newPassword))
{
throw new ArgumentException("Admin user passwords must not be empty", nameof(newPassword));
}
await GetAuthenticationProvider(dbUser).ChangePassword(dbUser, newPassword).ConfigureAwait(false); await GetAuthenticationProvider(dbUser).ChangePassword(dbUser, newPassword).ConfigureAwait(false);
await dbContext.SaveChangesAsync().ConfigureAwait(false); await dbContext.SaveChangesAsync().ConfigureAwait(false);
} }
} }
await _eventManager.PublishAsync(new UserPasswordChangedEventArgs(user)).ConfigureAwait(false); await _eventManager.PublishAsync(new UserPasswordChangedEventArgs(dbUser)).ConfigureAwait(false);
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -99,17 +99,17 @@ namespace MediaBrowser.Controller.Library
/// <summary> /// <summary>
/// Resets the password. /// Resets the password.
/// </summary> /// </summary>
/// <param name="user">The user.</param> /// <param name="userId">The users Id.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
Task ResetPassword(User user); Task ResetPassword(Guid userId);
/// <summary> /// <summary>
/// Changes the password. /// Changes the password.
/// </summary> /// </summary>
/// <param name="user">The user.</param> /// <param name="userId">The users id.</param>
/// <param name="newPassword">New password to use.</param> /// <param name="newPassword">New password to use.</param>
/// <returns>Awaitable task.</returns> /// <returns>Awaitable task.</returns>
Task ChangePassword(User user, string newPassword); Task ChangePassword(Guid userId, string newPassword);
/// <summary> /// <summary>
/// Gets the user dto. /// Gets the user dto.