< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.UserController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/UserController.cs
Line coverage
47%
Covered lines: 73
Uncovered lines: 81
Coverable lines: 154
Total lines: 640
Line coverage: 47.4%
Branch coverage
27%
Covered branches: 22
Total branches: 80
Branch coverage: 27.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/13/2026 - 12:11:21 AM Line coverage: 70.1% (40/57) Branch coverage: 55% (11/20) Total lines: 6364/19/2026 - 12:14:27 AM Line coverage: 47.4% (73/154) Branch coverage: 30% (24/80) Total lines: 6365/13/2026 - 12:15:27 AM Line coverage: 47.4% (73/154) Branch coverage: 30% (24/80) Total lines: 6405/20/2026 - 12:15:44 AM Line coverage: 47.4% (73/154) Branch coverage: 27.5% (22/80) Total lines: 640 2/13/2026 - 12:11:21 AM Line coverage: 70.1% (40/57) Branch coverage: 55% (11/20) Total lines: 6364/19/2026 - 12:14:27 AM Line coverage: 47.4% (73/154) Branch coverage: 30% (24/80) Total lines: 6365/13/2026 - 12:15:27 AM Line coverage: 47.4% (73/154) Branch coverage: 30% (24/80) Total lines: 6405/20/2026 - 12:15:44 AM Line coverage: 47.4% (73/154) Branch coverage: 27.5% (22/80) Total lines: 640

Coverage delta

Coverage delta 25 -25

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetUsers(...)100%11100%
GetPublicUsers()50%2266.66%
GetUserById(...)0%620%
DeleteUser()0%620%
AuthenticateUserByName()100%1186.66%
AuthenticateWithQuickConnect(...)100%210%
UpdateUserPassword()55%632052.38%
UpdateUser()0%7280%
UpdateUserPolicy()5.55%1551825%
UpdateUserConfiguration()0%4260%
CreateUserByName()50%2283.33%
ForgotPassword()0%2040%
ForgotPasswordPin()100%210%
GetCurrentUser()50%4471.42%
Get(...)50%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    {
 0157        var user = _userManager.GetUserById(userId);
 0158        if (user is null)
 159        {
 0160            return NotFound();
 161        }
 162
 0163        await _sessionManager.RevokeUserTokens(user.Id, null).ConfigureAwait(false);
 0164        await _playlistManager.RemovePlaylistsAsync(userId).ConfigureAwait(false);
 0165        await _userManager.DeleteUserAsync(userId).ConfigureAwait(false);
 0166        return NoContent();
 0167    }
 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    [Tags("Authentication")]
 212    public async Task<ActionResult<AuthenticationResult>> AuthenticateUserByName([FromBody, Required] AuthenticateUserBy
 213    {
 15214        var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
 215
 216        try
 217        {
 15218            var result = await _sessionManager.AuthenticateNewSession(new AuthenticationRequest
 15219            {
 15220                App = auth.Client,
 15221                AppVersion = auth.Version,
 15222                DeviceId = auth.DeviceId,
 15223                DeviceName = auth.Device,
 15224                Password = request.Pw,
 15225                RemoteEndPoint = HttpContext.GetNormalizedRemoteIP().ToString(),
 15226                Username = request.Username
 15227            }).ConfigureAwait(false);
 228
 15229            return result;
 230        }
 0231        catch (SecurityException e)
 232        {
 233            // rethrow adding IP address to message
 0234            throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e);
 235        }
 15236    }
 237
 238    /// <summary>
 239    /// Authenticates a user with quick connect.
 240    /// </summary>
 241    /// <param name="request">The <see cref="QuickConnectDto"/> request.</param>
 242    /// <response code="200">User authenticated.</response>
 243    /// <response code="400">Missing token.</response>
 244    /// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationRequest"/> with information about the new s
 245    [HttpPost("AuthenticateWithQuickConnect")]
 246    [ProducesResponseType(StatusCodes.Status200OK)]
 247    [Tags("Authentication")]
 248    public ActionResult<AuthenticationResult> AuthenticateWithQuickConnect([FromBody, Required] QuickConnectDto request)
 249    {
 250        try
 251        {
 0252            return _quickConnectManager.GetAuthorizedRequest(request.Secret);
 253        }
 0254        catch (SecurityException e)
 255        {
 256            // rethrow adding IP address to message
 0257            throw new SecurityException($"[{HttpContext.GetNormalizedRemoteIP()}] {e.Message}", e);
 258        }
 0259    }
 260
 261    /// <summary>
 262    /// Updates a user's password.
 263    /// </summary>
 264    /// <param name="userId">The user id.</param>
 265    /// <param name="request">The <see cref="UpdateUserPassword"/> request.</param>
 266    /// <response code="204">Password successfully reset.</response>
 267    /// <response code="403">User is not allowed to update the password.</response>
 268    /// <response code="404">User not found.</response>
 269    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotF
 270    [HttpPost("Password")]
 271    [Authorize]
 272    [ProducesResponseType(StatusCodes.Status204NoContent)]
 273    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 274    [ProducesResponseType(StatusCodes.Status404NotFound)]
 275    public async Task<ActionResult> UpdateUserPassword(
 276        [FromQuery] Guid? userId,
 277        [FromBody, Required] UpdateUserPassword request)
 278    {
 2279        var requestUserId = userId ?? User.GetUserId();
 2280        var user = _userManager.GetUserById(requestUserId);
 2281        if (user is null)
 282        {
 0283            return NotFound();
 284        }
 285
 2286        if (!RequestHelpers.AssertCanUpdateUser(User, user, true))
 287        {
 0288            return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the password.");
 289        }
 290
 2291        if (request.ResetPassword)
 292        {
 0293            await _userManager.ResetPassword(user.Id).ConfigureAwait(false);
 294        }
 295        else
 296        {
 2297            if (!User.IsInRole(UserRoles.Administrator) || (userId.HasValue && User.GetUserId().Equals(userId.Value)))
 298            {
 0299                var success = await _userManager.AuthenticateUser(
 0300                    user.Username,
 0301                    request.CurrentPw ?? string.Empty,
 0302                    HttpContext.GetNormalizedRemoteIP().ToString(),
 0303                    false).ConfigureAwait(false);
 304
 0305                if (success is null)
 306                {
 0307                    return StatusCode(StatusCodes.Status403Forbidden, "Invalid user or password entered.");
 308                }
 309            }
 310
 2311            await _userManager.ChangePassword(user.Id, request.NewPw ?? string.Empty).ConfigureAwait(false);
 312
 2313            var currentToken = User.GetToken();
 314
 2315            await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false);
 316        }
 317
 2318        return NoContent();
 2319    }
 320
 321    /// <summary>
 322    /// Updates a user's password.
 323    /// </summary>
 324    /// <param name="userId">The user id.</param>
 325    /// <param name="request">The <see cref="UpdateUserPassword"/> request.</param>
 326    /// <response code="204">Password successfully reset.</response>
 327    /// <response code="403">User is not allowed to update the password.</response>
 328    /// <response code="404">User not found.</response>
 329    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotF
 330    [HttpPost("{userId}/Password")]
 331    [Authorize]
 332    [ProducesResponseType(StatusCodes.Status204NoContent)]
 333    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 334    [ProducesResponseType(StatusCodes.Status404NotFound)]
 335    [Obsolete("Kept for backwards compatibility")]
 336    [ApiExplorerSettings(IgnoreApi = true)]
 337    public Task<ActionResult> UpdateUserPasswordLegacy(
 338        [FromRoute, Required] Guid userId,
 339        [FromBody, Required] UpdateUserPassword request)
 340        => UpdateUserPassword(userId, request);
 341
 342    /// <summary>
 343    /// Updates a user.
 344    /// </summary>
 345    /// <param name="userId">The user id.</param>
 346    /// <param name="updateUser">The updated user model.</param>
 347    /// <response code="204">User updated.</response>
 348    /// <response code="400">User information was not supplied.</response>
 349    /// <response code="403">User update forbidden.</response>
 350    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="
 351    [HttpPost]
 352    [Authorize]
 353    [ProducesResponseType(StatusCodes.Status204NoContent)]
 354    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 355    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 356    public async Task<ActionResult> UpdateUser(
 357        [FromQuery] Guid? userId,
 358        [FromBody, Required] UserDto updateUser)
 359    {
 0360        var requestUserId = userId ?? User.GetUserId();
 0361        var user = _userManager.GetUserById(requestUserId);
 0362        if (user is null)
 363        {
 0364            return NotFound();
 365        }
 366
 0367        if (!RequestHelpers.AssertCanUpdateUser(User, user, true))
 368        {
 0369            return StatusCode(StatusCodes.Status403Forbidden, "User update not allowed.");
 370        }
 371
 0372        if (!string.Equals(user.Username, updateUser.Name, StringComparison.Ordinal))
 373        {
 0374            await _userManager.RenameUser(user.Id, user.Username, updateUser.Name).ConfigureAwait(false);
 375        }
 376
 0377        await _userManager.UpdateConfigurationAsync(requestUserId, updateUser.Configuration).ConfigureAwait(false);
 378
 0379        return NoContent();
 0380    }
 381
 382    /// <summary>
 383    /// Updates a user.
 384    /// </summary>
 385    /// <param name="userId">The user id.</param>
 386    /// <param name="updateUser">The updated user model.</param>
 387    /// <response code="204">User updated.</response>
 388    /// <response code="400">User information was not supplied.</response>
 389    /// <response code="403">User update forbidden.</response>
 390    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="
 391    [HttpPost("{userId}")]
 392    [Authorize]
 393    [ProducesResponseType(StatusCodes.Status204NoContent)]
 394    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 395    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 396    [Obsolete("Kept for backwards compatibility")]
 397    [ApiExplorerSettings(IgnoreApi = true)]
 398    public Task<ActionResult> UpdateUserLegacy(
 399        [FromRoute, Required] Guid userId,
 400        [FromBody, Required] UserDto updateUser)
 401        => UpdateUser(userId, updateUser);
 402
 403    /// <summary>
 404    /// Updates a user policy.
 405    /// </summary>
 406    /// <param name="userId">The user id.</param>
 407    /// <param name="newPolicy">The new user policy.</param>
 408    /// <response code="204">User policy updated.</response>
 409    /// <response code="400">User policy was not supplied.</response>
 410    /// <response code="403">User policy update forbidden.</response>
 411    /// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="
 412    [HttpPost("{userId}/Policy")]
 413    [Authorize(Policy = Policies.RequiresElevation)]
 414    [ProducesResponseType(StatusCodes.Status204NoContent)]
 415    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 416    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 417    public async Task<ActionResult> UpdateUserPolicy(
 418        [FromRoute, Required] Guid userId,
 419        [FromBody, Required] UserPolicy newPolicy)
 420    {
 1421        var user = _userManager.GetUserById(userId);
 1422        if (user is null)
 423        {
 1424            return NotFound();
 425        }
 426
 427        // If removing admin access
 0428        if (!newPolicy.IsAdministrator && user.HasPermission(PermissionKind.IsAdministrator))
 429        {
 0430            if (_userManager.GetUsers().Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1)
 431            {
 0432                return StatusCode(StatusCodes.Status403Forbidden, "There must be at least one user in the system with ad
 433            }
 434        }
 435
 436        // If disabling
 0437        if (newPolicy.IsDisabled && user.HasPermission(PermissionKind.IsAdministrator))
 438        {
 0439            return StatusCode(StatusCodes.Status403Forbidden, "Administrators cannot be disabled.");
 440        }
 441
 442        // If disabling
 0443        if (newPolicy.IsDisabled && !user.HasPermission(PermissionKind.IsDisabled))
 444        {
 0445            if (_userManager.GetUsers().Count(i => !i.HasPermission(PermissionKind.IsDisabled)) == 1)
 446            {
 0447                return StatusCode(StatusCodes.Status403Forbidden, "There must be at least one enabled user in the system
 448            }
 449
 0450            var currentToken = User.GetToken();
 0451            await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false);
 452        }
 453
 0454        await _userManager.UpdatePolicyAsync(userId, newPolicy).ConfigureAwait(false);
 455
 0456        return NoContent();
 1457    }
 458
 459    /// <summary>
 460    /// Updates a user configuration.
 461    /// </summary>
 462    /// <param name="userId">The user id.</param>
 463    /// <param name="userConfig">The new user configuration.</param>
 464    /// <response code="204">User configuration updated.</response>
 465    /// <response code="403">User configuration update forbidden.</response>
 466    /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
 467    [HttpPost("Configuration")]
 468    [Authorize]
 469    [ProducesResponseType(StatusCodes.Status204NoContent)]
 470    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 471    public async Task<ActionResult> UpdateUserConfiguration(
 472        [FromQuery] Guid? userId,
 473        [FromBody, Required] UserConfiguration userConfig)
 474    {
 0475        var requestUserId = userId ?? User.GetUserId();
 0476        var user = _userManager.GetUserById(requestUserId);
 0477        if (user is null)
 478        {
 0479            return NotFound();
 480        }
 481
 0482        if (!RequestHelpers.AssertCanUpdateUser(User, user, true))
 483        {
 0484            return StatusCode(StatusCodes.Status403Forbidden, "User configuration update not allowed");
 485        }
 486
 0487        await _userManager.UpdateConfigurationAsync(requestUserId, userConfig).ConfigureAwait(false);
 488
 0489        return NoContent();
 0490    }
 491
 492    /// <summary>
 493    /// Updates a user configuration.
 494    /// </summary>
 495    /// <param name="userId">The user id.</param>
 496    /// <param name="userConfig">The new user configuration.</param>
 497    /// <response code="204">User configuration updated.</response>
 498    /// <response code="403">User configuration update forbidden.</response>
 499    /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
 500    [HttpPost("{userId}/Configuration")]
 501    [Authorize]
 502    [Obsolete("Kept for backwards compatibility")]
 503    [ApiExplorerSettings(IgnoreApi = true)]
 504    [ProducesResponseType(StatusCodes.Status204NoContent)]
 505    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 506    public Task<ActionResult> UpdateUserConfigurationLegacy(
 507        [FromRoute, Required] Guid userId,
 508        [FromBody, Required] UserConfiguration userConfig)
 509        => UpdateUserConfiguration(userId, userConfig);
 510
 511    /// <summary>
 512    /// Creates a user.
 513    /// </summary>
 514    /// <param name="request">The create user by name request body.</param>
 515    /// <response code="200">User created.</response>
 516    /// <returns>An <see cref="UserDto"/> of the new user.</returns>
 517    [HttpPost("New")]
 518    [Authorize(Policy = Policies.RequiresElevation)]
 519    [ProducesResponseType(StatusCodes.Status200OK)]
 520    public async Task<ActionResult<UserDto>> CreateUserByName([FromBody, Required] CreateUserByName request)
 521    {
 2522        var newUser = await _userManager.CreateUserAsync(request.Name).ConfigureAwait(false);
 523
 524        // no need to authenticate password for new user
 1525        if (request.Password is not null)
 526        {
 0527            await _userManager.ChangePassword(newUser.Id, request.Password).ConfigureAwait(false);
 528        }
 529
 1530        var result = _userManager.GetUserDto(newUser, HttpContext.GetNormalizedRemoteIP().ToString());
 531
 1532        return result;
 1533    }
 534
 535    /// <summary>
 536    /// Initiates the forgot password process for a local user.
 537    /// </summary>
 538    /// <param name="forgotPasswordRequest">The forgot password request containing the entered username.</param>
 539    /// <response code="200">Password reset process started.</response>
 540    /// <returns>A <see cref="Task"/> containing a <see cref="ForgotPasswordResult"/>.</returns>
 541    [HttpPost("ForgotPassword")]
 542    [ProducesResponseType(StatusCodes.Status200OK)]
 543    [Tags("Authentication")]
 544    public async Task<ActionResult<ForgotPasswordResult>> ForgotPassword([FromBody, Required] ForgotPasswordDto forgotPa
 545    {
 0546        var ip = HttpContext.GetNormalizedRemoteIP();
 0547        var isLocal = HttpContext.IsLocal()
 0548                      || _networkManager.IsInLocalNetwork(ip);
 549
 0550        if (!isLocal)
 551        {
 0552            _logger.LogWarning("Password reset process initiated from outside the local network with IP: {IP}", ip);
 553        }
 554
 0555        var result = await _userManager.StartForgotPasswordProcess(forgotPasswordRequest.EnteredUsername, isLocal).Confi
 556
 0557        return result;
 0558    }
 559
 560    /// <summary>
 561    /// Redeems a forgot password pin.
 562    /// </summary>
 563    /// <param name="forgotPasswordPinRequest">The forgot password pin request containing the entered pin.</param>
 564    /// <response code="200">Pin reset process started.</response>
 565    /// <returns>A <see cref="Task"/> containing a <see cref="PinRedeemResult"/>.</returns>
 566    [HttpPost("ForgotPassword/Pin")]
 567    [ProducesResponseType(StatusCodes.Status200OK)]
 568    [Tags("Authentication")]
 569    public async Task<ActionResult<PinRedeemResult>> ForgotPasswordPin([FromBody, Required] ForgotPasswordPinDto forgotP
 570    {
 0571        var result = await _userManager.RedeemPasswordResetPin(forgotPasswordPinRequest.Pin).ConfigureAwait(false);
 0572        return result;
 0573    }
 574
 575    /// <summary>
 576    /// Gets the user based on auth token.
 577    /// </summary>
 578    /// <response code="200">User returned.</response>
 579    /// <response code="400">Token is not owned by a user.</response>
 580    /// <returns>A <see cref="UserDto"/> for the authenticated user.</returns>
 581    [HttpGet("Me")]
 582    [Authorize]
 583    [ProducesResponseType(StatusCodes.Status200OK)]
 584    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 585    public ActionResult<UserDto> GetCurrentUser()
 586    {
 17587        var userId = User.GetUserId();
 17588        if (userId.IsEmpty())
 589        {
 0590            return BadRequest();
 591        }
 592
 17593        var user = _userManager.GetUserById(userId);
 17594        if (user is null)
 595        {
 0596            return BadRequest();
 597        }
 598
 17599        return _userManager.GetUserDto(user);
 600    }
 601
 602    private IEnumerable<UserDto> Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork)
 603    {
 2604        var users = _userManager.GetUsers();
 605
 2606        if (isDisabled.HasValue)
 607        {
 1608            users = users.Where(i => i.HasPermission(PermissionKind.IsDisabled) == isDisabled.Value);
 609        }
 610
 2611        if (isHidden.HasValue)
 612        {
 1613            users = users.Where(i => i.HasPermission(PermissionKind.IsHidden) == isHidden.Value);
 614        }
 615
 2616        if (filterByDevice)
 617        {
 0618            var deviceId = User.GetDeviceId();
 619
 0620            if (!string.IsNullOrWhiteSpace(deviceId))
 621            {
 0622                users = users.Where(i => _deviceManager.CanAccessDevice(i, deviceId));
 623            }
 624        }
 625
 2626        if (filterByNetwork)
 627        {
 0628            if (!_networkManager.IsInLocalNetwork(HttpContext.GetNormalizedRemoteIP()))
 629            {
 0630                users = users.Where(i => i.HasPermission(PermissionKind.EnableRemoteAccess));
 631            }
 632        }
 633
 2634        var result = users
 2635            .OrderBy(u => u.Username)
 2636            .Select(i => _userManager.GetUserDto(i, HttpContext.GetNormalizedRemoteIP().ToString()));
 637
 2638        return result;
 639    }
 640}