< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.UserController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/UserController.cs
Line coverage
70%
Covered lines: 40
Uncovered lines: 17
Coverable lines: 57
Total lines: 636
Line coverage: 70.1%
Branch coverage
55%
Covered branches: 11
Total branches: 20
Branch coverage: 55%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 10/25/2025 - 12:09:58 AM Line coverage: 70.1% (40/57) Branch coverage: 55% (11/20) Total lines: 6591/29/2026 - 12:13:32 AM Line coverage: 70.1% (40/57) Branch coverage: 55% (11/20) Total lines: 636 10/25/2025 - 12:09:58 AM Line coverage: 70.1% (40/57) Branch coverage: 55% (11/20) Total lines: 6591/29/2026 - 12:13:32 AM Line coverage: 70.1% (40/57) Branch coverage: 55% (11/20) Total lines: 636

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetUsers(...)100%11100%
GetPublicUsers()50%2266.66%
GetUserById(...)0%620%
AuthenticateWithQuickConnect(...)100%210%
GetCurrentUser()50%4471.42%
Get(...)66.66%161268.75%

File(s)

/srv/git/jellyfin/Jellyfin.Api/Controllers/UserController.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.ComponentModel.DataAnnotations;
 4using System.Linq;
 5using System.Threading.Tasks;
 6using Jellyfin.Api.Constants;
 7using Jellyfin.Api.Extensions;
 8using Jellyfin.Api.Helpers;
 9using Jellyfin.Api.Models.UserDtos;
 10using Jellyfin.Data;
 11using Jellyfin.Database.Implementations.Enums;
 12using Jellyfin.Extensions;
 13using MediaBrowser.Common.Api;
 14using MediaBrowser.Common.Extensions;
 15using MediaBrowser.Common.Net;
 16using MediaBrowser.Controller.Authentication;
 17using MediaBrowser.Controller.Configuration;
 18using MediaBrowser.Controller.Devices;
 19using MediaBrowser.Controller.Library;
 20using MediaBrowser.Controller.Net;
 21using MediaBrowser.Controller.Playlists;
 22using MediaBrowser.Controller.QuickConnect;
 23using MediaBrowser.Controller.Session;
 24using MediaBrowser.Model.Configuration;
 25using MediaBrowser.Model.Dto;
 26using MediaBrowser.Model.Users;
 27using Microsoft.AspNetCore.Authorization;
 28using Microsoft.AspNetCore.Http;
 29using Microsoft.AspNetCore.Mvc;
 30using Microsoft.Extensions.Logging;
 31
 32namespace Jellyfin.Api.Controllers;
 33
 34/// <summary>
 35/// User controller.
 36/// </summary>
 37[Route("Users")]
 38public class UserController : BaseJellyfinApiController
 39{
 40    private readonly IUserManager _userManager;
 41    private readonly ISessionManager _sessionManager;
 42    private readonly INetworkManager _networkManager;
 43    private readonly IDeviceManager _deviceManager;
 44    private readonly IAuthorizationContext _authContext;
 45    private readonly IServerConfigurationManager _config;
 46    private readonly ILogger _logger;
 47    private readonly IQuickConnect _quickConnectManager;
 48    private readonly IPlaylistManager _playlistManager;
 49
 50    /// <summary>
 51    /// Initializes a new instance of the <see cref="UserController"/> class.
 52    /// </summary>
 53    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 54    /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
 55    /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
 56    /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
 57    /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
 58    /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
 59    /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
 60    /// <param name="quickConnectManager">Instance of the <see cref="IQuickConnect"/> interface.</param>
 61    /// <param name="playlistManager">Instance of the <see cref="IPlaylistManager"/> interface.</param>
 4862    public UserController(
 4863        IUserManager userManager,
 4864        ISessionManager sessionManager,
 4865        INetworkManager networkManager,
 4866        IDeviceManager deviceManager,
 4867        IAuthorizationContext authContext,
 4868        IServerConfigurationManager config,
 4869        ILogger<UserController> logger,
 4870        IQuickConnect quickConnectManager,
 4871        IPlaylistManager playlistManager)
 72    {
 4873        _userManager = userManager;
 4874        _sessionManager = sessionManager;
 4875        _networkManager = networkManager;
 4876        _deviceManager = deviceManager;
 4877        _authContext = authContext;
 4878        _config = config;
 4879        _logger = logger;
 4880        _quickConnectManager = quickConnectManager;
 4881        _playlistManager = playlistManager;
 4882    }
 83
 84    /// <summary>
 85    /// Gets a list of users.
 86    /// </summary>
 87    /// <param name="isHidden">Optional filter by IsHidden=true or false.</param>
 88    /// <param name="isDisabled">Optional filter by IsDisabled=true or false.</param>
 89    /// <response code="200">Users returned.</response>
 90    /// <returns>An <see cref="IEnumerable{UserDto}"/> containing the users.</returns>
 91    [HttpGet]
 92    [Authorize]
 93    [ProducesResponseType(StatusCodes.Status200OK)]
 94    public ActionResult<IEnumerable<UserDto>> GetUsers(
 95        [FromQuery] bool? isHidden,
 96        [FromQuery] bool? isDisabled)
 97    {
 198        var users = Get(isHidden, isDisabled, false, false);
 199        return Ok(users);
 100    }
 101
 102    /// <summary>
 103    /// Gets a list of publicly visible users for display on a login screen.
 104    /// </summary>
 105    /// <response code="200">Public users returned.</response>
 106    /// <returns>An <see cref="IEnumerable{UserDto}"/> containing the public users.</returns>
 107    [HttpGet("Public")]
 108    [ProducesResponseType(StatusCodes.Status200OK)]
 109    public ActionResult<IEnumerable<UserDto>> GetPublicUsers()
 110    {
 111        // If the startup wizard hasn't been completed then just return all users
 1112        if (!_config.Configuration.IsStartupWizardCompleted)
 113        {
 1114            return Ok(Get(false, false, false, false));
 115        }
 116
 0117        return Ok(Get(false, false, true, true));
 118    }
 119
 120    /// <summary>
 121    /// Gets a user by Id.
 122    /// </summary>
 123    /// <param name="userId">The user id.</param>
 124    /// <response code="200">User returned.</response>
 125    /// <response code="404">User not found.</response>
 126    /// <returns>An <see cref="UserDto"/> with information about the user or a <see cref="NotFoundResult"/> if the user 
 127    [HttpGet("{userId}")]
 128    [Authorize(Policy = Policies.IgnoreParentalControl)]
 129    [ProducesResponseType(StatusCodes.Status200OK)]
 130    [ProducesResponseType(StatusCodes.Status404NotFound)]
 131    public ActionResult<UserDto> GetUserById([FromRoute, Required] Guid userId)
 132    {
 0133        var user = _userManager.GetUserById(userId);
 134
 0135        if (user is null)
 136        {
 0137            return NotFound("User not found");
 138        }
 139
 0140        var result = _userManager.GetUserDto(user, HttpContext.GetNormalizedRemoteIP().ToString());
 0141        return result;
 142    }
 143
 144    /// <summary>
 145    /// Deletes a user.
 146    /// </summary>
 147    /// <param name="userId">The user id.</param>
 148    /// <response code="204">User deleted.</response>
 149    /// <response code="404">User not found.</response>
 150    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="NotFoundResult"/> if the user was no
 151    [HttpDelete("{userId}")]
 152    [Authorize(Policy = Policies.RequiresElevation)]
 153    [ProducesResponseType(StatusCodes.Status204NoContent)]
 154    [ProducesResponseType(StatusCodes.Status404NotFound)]
 155    public async Task<ActionResult> DeleteUser([FromRoute, Required] Guid userId)
 156    {
 157        var user = _userManager.GetUserById(userId);
 158        if (user is null)
 159        {
 160            return NotFound();
 161        }
 162
 163        await _sessionManager.RevokeUserTokens(user.Id, null).ConfigureAwait(false);
 164        await _playlistManager.RemovePlaylistsAsync(userId).ConfigureAwait(false);
 165        await _userManager.DeleteUserAsync(userId).ConfigureAwait(false);
 166        return NoContent();
 167    }
 168
 169    /// <summary>
 170    /// Authenticates a user.
 171    /// </summary>
 172    /// <param name="userId">The user id.</param>
 173    /// <param name="pw">The password as plain text.</param>
 174    /// <response code="200">User authenticated.</response>
 175    /// <response code="403">Sha1-hashed password only is not allowed.</response>
 176    /// <response code="404">User not found.</response>
 177    /// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationResult"/>.</returns>
 178    [HttpPost("{userId}/Authenticate")]
 179    [ProducesResponseType(StatusCodes.Status200OK)]
 180    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 181    [ProducesResponseType(StatusCodes.Status404NotFound)]
 182    [ApiExplorerSettings(IgnoreApi = true)]
 183    [Obsolete("Authenticate with username instead")]
 184    public async Task<ActionResult<AuthenticationResult>> AuthenticateUser(
 185        [FromRoute, Required] Guid userId,
 186        [FromQuery, Required] string pw)
 187    {
 188        var user = _userManager.GetUserById(userId);
 189
 190        if (user is null)
 191        {
 192            return NotFound("User not found");
 193        }
 194
 195        AuthenticateUserByName request = new AuthenticateUserByName
 196        {
 197            Username = user.Username,
 198            Pw = pw
 199        };
 200        return await AuthenticateUserByName(request).ConfigureAwait(false);
 201    }
 202
 203    /// <summary>
 204    /// Authenticates a user by name.
 205    /// </summary>
 206    /// <param name="request">The <see cref="AuthenticateUserByName"/> request.</param>
 207    /// <response code="200">User authenticated.</response>
 208    /// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationRequest"/> with information about the new s
 209    [HttpPost("AuthenticateByName")]
 210    [ProducesResponseType(StatusCodes.Status200OK)]
 211    public async Task<ActionResult<AuthenticationResult>> AuthenticateUserByName([FromBody, Required] AuthenticateUserBy
 212    {
 213        var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
 214
 215        try
 216        {
 217            var result = await _sessionManager.AuthenticateNewSession(new AuthenticationRequest
 218            {
 219                App = auth.Client,
 220                AppVersion = auth.Version,
 221                DeviceId = auth.DeviceId,
 222                DeviceName = auth.Device,
 223                Password = request.Pw,
 224                RemoteEndPoint = HttpContext.GetNormalizedRemoteIP().ToString(),
 225                Username = request.Username
 226            }).ConfigureAwait(false);
 227
 228            return result;
 229        }
 230        catch (SecurityException e)
 231        {
 232            // rethrow adding IP address to message
 233            throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e);
 234        }
 235    }
 236
 237    /// <summary>
 238    /// Authenticates a user with quick connect.
 239    /// </summary>
 240    /// <param name="request">The <see cref="QuickConnectDto"/> request.</param>
 241    /// <response code="200">User authenticated.</response>
 242    /// <response code="400">Missing token.</response>
 243    /// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationRequest"/> with information about the new s
 244    [HttpPost("AuthenticateWithQuickConnect")]
 245    [ProducesResponseType(StatusCodes.Status200OK)]
 246    public ActionResult<AuthenticationResult> AuthenticateWithQuickConnect([FromBody, Required] QuickConnectDto request)
 247    {
 248        try
 249        {
 0250            return _quickConnectManager.GetAuthorizedRequest(request.Secret);
 251        }
 0252        catch (SecurityException e)
 253        {
 254            // rethrow adding IP address to message
 0255            throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e);
 256        }
 0257    }
 258
 259    /// <summary>
 260    /// Updates a user's password.
 261    /// </summary>
 262    /// <param name="userId">The user id.</param>
 263    /// <param name="request">The <see cref="UpdateUserPassword"/> request.</param>
 264    /// <response code="204">Password successfully reset.</response>
 265    /// <response code="403">User is not allowed to update the password.</response>
 266    /// <response code="404">User not found.</response>
 267    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotF
 268    [HttpPost("Password")]
 269    [Authorize]
 270    [ProducesResponseType(StatusCodes.Status204NoContent)]
 271    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 272    [ProducesResponseType(StatusCodes.Status404NotFound)]
 273    public async Task<ActionResult> UpdateUserPassword(
 274        [FromQuery] Guid? userId,
 275        [FromBody, Required] UpdateUserPassword request)
 276    {
 277        var requestUserId = userId ?? User.GetUserId();
 278        var user = _userManager.GetUserById(requestUserId);
 279        if (user is null)
 280        {
 281            return NotFound();
 282        }
 283
 284        if (!RequestHelpers.AssertCanUpdateUser(User, user, true))
 285        {
 286            return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the password.");
 287        }
 288
 289        if (request.ResetPassword)
 290        {
 291            await _userManager.ResetPassword(user).ConfigureAwait(false);
 292        }
 293        else
 294        {
 295            if (!User.IsInRole(UserRoles.Administrator) || (userId.HasValue && User.GetUserId().Equals(userId.Value)))
 296            {
 297                var success = await _userManager.AuthenticateUser(
 298                    user.Username,
 299                    request.CurrentPw ?? string.Empty,
 300                    HttpContext.GetNormalizedRemoteIP().ToString(),
 301                    false).ConfigureAwait(false);
 302
 303                if (success is null)
 304                {
 305                    return StatusCode(StatusCodes.Status403Forbidden, "Invalid user or password entered.");
 306                }
 307            }
 308
 309            await _userManager.ChangePassword(user, request.NewPw ?? string.Empty).ConfigureAwait(false);
 310
 311            var currentToken = User.GetToken();
 312
 313            await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false);
 314        }
 315
 316        return NoContent();
 317    }
 318
 319    /// <summary>
 320    /// Updates a user's password.
 321    /// </summary>
 322    /// <param name="userId">The user id.</param>
 323    /// <param name="request">The <see cref="UpdateUserPassword"/> request.</param>
 324    /// <response code="204">Password successfully reset.</response>
 325    /// <response code="403">User is not allowed to update the password.</response>
 326    /// <response code="404">User not found.</response>
 327    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotF
 328    [HttpPost("{userId}/Password")]
 329    [Authorize]
 330    [ProducesResponseType(StatusCodes.Status204NoContent)]
 331    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 332    [ProducesResponseType(StatusCodes.Status404NotFound)]
 333    [Obsolete("Kept for backwards compatibility")]
 334    [ApiExplorerSettings(IgnoreApi = true)]
 335    public Task<ActionResult> UpdateUserPasswordLegacy(
 336        [FromRoute, Required] Guid userId,
 337        [FromBody, Required] UpdateUserPassword request)
 338        => UpdateUserPassword(userId, request);
 339
 340    /// <summary>
 341    /// Updates a user.
 342    /// </summary>
 343    /// <param name="userId">The user id.</param>
 344    /// <param name="updateUser">The updated user model.</param>
 345    /// <response code="204">User updated.</response>
 346    /// <response code="400">User information was not supplied.</response>
 347    /// <response code="403">User update forbidden.</response>
 348    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="
 349    [HttpPost]
 350    [Authorize]
 351    [ProducesResponseType(StatusCodes.Status204NoContent)]
 352    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 353    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 354    public async Task<ActionResult> UpdateUser(
 355        [FromQuery] Guid? userId,
 356        [FromBody, Required] UserDto updateUser)
 357    {
 358        var requestUserId = userId ?? User.GetUserId();
 359        var user = _userManager.GetUserById(requestUserId);
 360        if (user is null)
 361        {
 362            return NotFound();
 363        }
 364
 365        if (!RequestHelpers.AssertCanUpdateUser(User, user, true))
 366        {
 367            return StatusCode(StatusCodes.Status403Forbidden, "User update not allowed.");
 368        }
 369
 370        if (!string.Equals(user.Username, updateUser.Name, StringComparison.Ordinal))
 371        {
 372            await _userManager.RenameUser(user, updateUser.Name).ConfigureAwait(false);
 373        }
 374
 375        await _userManager.UpdateConfigurationAsync(requestUserId, updateUser.Configuration).ConfigureAwait(false);
 376
 377        return NoContent();
 378    }
 379
 380    /// <summary>
 381    /// Updates a user.
 382    /// </summary>
 383    /// <param name="userId">The user id.</param>
 384    /// <param name="updateUser">The updated user model.</param>
 385    /// <response code="204">User updated.</response>
 386    /// <response code="400">User information was not supplied.</response>
 387    /// <response code="403">User update forbidden.</response>
 388    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="
 389    [HttpPost("{userId}")]
 390    [Authorize]
 391    [ProducesResponseType(StatusCodes.Status204NoContent)]
 392    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 393    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 394    [Obsolete("Kept for backwards compatibility")]
 395    [ApiExplorerSettings(IgnoreApi = true)]
 396    public Task<ActionResult> UpdateUserLegacy(
 397        [FromRoute, Required] Guid userId,
 398        [FromBody, Required] UserDto updateUser)
 399        => UpdateUser(userId, updateUser);
 400
 401    /// <summary>
 402    /// Updates a user policy.
 403    /// </summary>
 404    /// <param name="userId">The user id.</param>
 405    /// <param name="newPolicy">The new user policy.</param>
 406    /// <response code="204">User policy updated.</response>
 407    /// <response code="400">User policy was not supplied.</response>
 408    /// <response code="403">User policy update forbidden.</response>
 409    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="
 410    [HttpPost("{userId}/Policy")]
 411    [Authorize(Policy = Policies.RequiresElevation)]
 412    [ProducesResponseType(StatusCodes.Status204NoContent)]
 413    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 414    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 415    public async Task<ActionResult> UpdateUserPolicy(
 416        [FromRoute, Required] Guid userId,
 417        [FromBody, Required] UserPolicy newPolicy)
 418    {
 419        var user = _userManager.GetUserById(userId);
 420        if (user is null)
 421        {
 422            return NotFound();
 423        }
 424
 425        // If removing admin access
 426        if (!newPolicy.IsAdministrator && user.HasPermission(PermissionKind.IsAdministrator))
 427        {
 428            if (_userManager.Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1)
 429            {
 430                return StatusCode(StatusCodes.Status403Forbidden, "There must be at least one user in the system with ad
 431            }
 432        }
 433
 434        // If disabling
 435        if (newPolicy.IsDisabled && user.HasPermission(PermissionKind.IsAdministrator))
 436        {
 437            return StatusCode(StatusCodes.Status403Forbidden, "Administrators cannot be disabled.");
 438        }
 439
 440        // If disabling
 441        if (newPolicy.IsDisabled && !user.HasPermission(PermissionKind.IsDisabled))
 442        {
 443            if (_userManager.Users.Count(i => !i.HasPermission(PermissionKind.IsDisabled)) == 1)
 444            {
 445                return StatusCode(StatusCodes.Status403Forbidden, "There must be at least one enabled user in the system
 446            }
 447
 448            var currentToken = User.GetToken();
 449            await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false);
 450        }
 451
 452        await _userManager.UpdatePolicyAsync(userId, newPolicy).ConfigureAwait(false);
 453
 454        return NoContent();
 455    }
 456
 457    /// <summary>
 458    /// Updates a user configuration.
 459    /// </summary>
 460    /// <param name="userId">The user id.</param>
 461    /// <param name="userConfig">The new user configuration.</param>
 462    /// <response code="204">User configuration updated.</response>
 463    /// <response code="403">User configuration update forbidden.</response>
 464    /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
 465    [HttpPost("Configuration")]
 466    [Authorize]
 467    [ProducesResponseType(StatusCodes.Status204NoContent)]
 468    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 469    public async Task<ActionResult> UpdateUserConfiguration(
 470        [FromQuery] Guid? userId,
 471        [FromBody, Required] UserConfiguration userConfig)
 472    {
 473        var requestUserId = userId ?? User.GetUserId();
 474        var user = _userManager.GetUserById(requestUserId);
 475        if (user is null)
 476        {
 477            return NotFound();
 478        }
 479
 480        if (!RequestHelpers.AssertCanUpdateUser(User, user, true))
 481        {
 482            return StatusCode(StatusCodes.Status403Forbidden, "User configuration update not allowed");
 483        }
 484
 485        await _userManager.UpdateConfigurationAsync(requestUserId, userConfig).ConfigureAwait(false);
 486
 487        return NoContent();
 488    }
 489
 490    /// <summary>
 491    /// Updates a user configuration.
 492    /// </summary>
 493    /// <param name="userId">The user id.</param>
 494    /// <param name="userConfig">The new user configuration.</param>
 495    /// <response code="204">User configuration updated.</response>
 496    /// <response code="403">User configuration update forbidden.</response>
 497    /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
 498    [HttpPost("{userId}/Configuration")]
 499    [Authorize]
 500    [Obsolete("Kept for backwards compatibility")]
 501    [ApiExplorerSettings(IgnoreApi = true)]
 502    [ProducesResponseType(StatusCodes.Status204NoContent)]
 503    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 504    public Task<ActionResult> UpdateUserConfigurationLegacy(
 505        [FromRoute, Required] Guid userId,
 506        [FromBody, Required] UserConfiguration userConfig)
 507        => UpdateUserConfiguration(userId, userConfig);
 508
 509    /// <summary>
 510    /// Creates a user.
 511    /// </summary>
 512    /// <param name="request">The create user by name request body.</param>
 513    /// <response code="200">User created.</response>
 514    /// <returns>An <see cref="UserDto"/> of the new user.</returns>
 515    [HttpPost("New")]
 516    [Authorize(Policy = Policies.RequiresElevation)]
 517    [ProducesResponseType(StatusCodes.Status200OK)]
 518    public async Task<ActionResult<UserDto>> CreateUserByName([FromBody, Required] CreateUserByName request)
 519    {
 520        var newUser = await _userManager.CreateUserAsync(request.Name).ConfigureAwait(false);
 521
 522        // no need to authenticate password for new user
 523        if (request.Password is not null)
 524        {
 525            await _userManager.ChangePassword(newUser, request.Password).ConfigureAwait(false);
 526        }
 527
 528        var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIP().ToString());
 529
 530        return result;
 531    }
 532
 533    /// <summary>
 534    /// Initiates the forgot password process for a local user.
 535    /// </summary>
 536    /// <param name="forgotPasswordRequest">The forgot password request containing the entered username.</param>
 537    /// <response code="200">Password reset process started.</response>
 538    /// <returns>A <see cref="Task"/> containing a <see cref="ForgotPasswordResult"/>.</returns>
 539    [HttpPost("ForgotPassword")]
 540    [ProducesResponseType(StatusCodes.Status200OK)]
 541    public async Task<ActionResult<ForgotPasswordResult>> ForgotPassword([FromBody, Required] ForgotPasswordDto forgotPa
 542    {
 543        var ip = HttpContext.GetNormalizedRemoteIP();
 544        var isLocal = HttpContext.IsLocal()
 545                      || _networkManager.IsInLocalNetwork(ip);
 546
 547        if (!isLocal)
 548        {
 549            _logger.LogWarning("Password reset process initiated from outside the local network with IP: {IP}", ip);
 550        }
 551
 552        var result = await _userManager.StartForgotPasswordProcess(forgotPasswordRequest.EnteredUsername, isLocal).Confi
 553
 554        return result;
 555    }
 556
 557    /// <summary>
 558    /// Redeems a forgot password pin.
 559    /// </summary>
 560    /// <param name="forgotPasswordPinRequest">The forgot password pin request containing the entered pin.</param>
 561    /// <response code="200">Pin reset process started.</response>
 562    /// <returns>A <see cref="Task"/> containing a <see cref="PinRedeemResult"/>.</returns>
 563    [HttpPost("ForgotPassword/Pin")]
 564    [ProducesResponseType(StatusCodes.Status200OK)]
 565    public async Task<ActionResult<PinRedeemResult>> ForgotPasswordPin([FromBody, Required] ForgotPasswordPinDto forgotP
 566    {
 567        var result = await _userManager.RedeemPasswordResetPin(forgotPasswordPinRequest.Pin).ConfigureAwait(false);
 568        return result;
 569    }
 570
 571    /// <summary>
 572    /// Gets the user based on auth token.
 573    /// </summary>
 574    /// <response code="200">User returned.</response>
 575    /// <response code="400">Token is not owned by a user.</response>
 576    /// <returns>A <see cref="UserDto"/> for the authenticated user.</returns>
 577    [HttpGet("Me")]
 578    [Authorize]
 579    [ProducesResponseType(StatusCodes.Status200OK)]
 580    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 581    public ActionResult<UserDto> GetCurrentUser()
 582    {
 17583        var userId = User.GetUserId();
 17584        if (userId.IsEmpty())
 585        {
 0586            return BadRequest();
 587        }
 588
 17589        var user = _userManager.GetUserById(userId);
 17590        if (user is null)
 591        {
 0592            return BadRequest();
 593        }
 594
 17595        return _userManager.GetUserDto(user);
 596    }
 597
 598    private IEnumerable<UserDto> Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork)
 599    {
 2600        var users = _userManager.Users;
 601
 2602        if (isDisabled.HasValue)
 603        {
 1604            users = users.Where(i => i.HasPermission(PermissionKind.IsDisabled) == isDisabled.Value);
 605        }
 606
 2607        if (isHidden.HasValue)
 608        {
 1609            users = users.Where(i => i.HasPermission(PermissionKind.IsHidden) == isHidden.Value);
 610        }
 611
 2612        if (filterByDevice)
 613        {
 0614            var deviceId = User.GetDeviceId();
 615
 0616            if (!string.IsNullOrWhiteSpace(deviceId))
 617            {
 0618                users = users.Where(i => _deviceManager.CanAccessDevice(i, deviceId));
 619            }
 620        }
 621
 2622        if (filterByNetwork)
 623        {
 0624            if (!_networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP()))
 625            {
 0626                users = users.Where(i => i.HasPermission(PermissionKind.EnableRemoteAccess));
 627            }
 628        }
 629
 2630        var result = users
 2631            .OrderBy(u => u.Username)
 2632            .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIP().ToString()));
 633
 2634        return result;
 635    }
 636}