Disable Authentication & Login Page Rework (#619)
* Implemented the ability to disable authentication on a server instance. Admins will require authentication, but non-admin accounts can be setup without any password requirements. * WIP for new login page. * Reworked code to handle disabled auth better. First time user flow is moved into the user login component. * Removed debug code * Removed home component, shakeout testing is complete. * remove a file accidently committed * Fixed a code smell from last PR * Code smells
This commit is contained in:
parent
83d76982f4
commit
a5b6bf1b52
36 changed files with 376 additions and 174 deletions
|
@ -7,10 +7,10 @@ using API.Constants;
|
|||
using API.DTOs;
|
||||
using API.DTOs.Account;
|
||||
using API.Entities;
|
||||
using API.Errors;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using AutoMapper;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
@ -31,13 +31,14 @@ namespace API.Controllers
|
|||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ILogger<AccountController> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IAccountService _accountService;
|
||||
|
||||
/// <inheritdoc />
|
||||
public AccountController(UserManager<AppUser> userManager,
|
||||
SignInManager<AppUser> signInManager,
|
||||
ITokenService tokenService, IUnitOfWork unitOfWork,
|
||||
ILogger<AccountController> logger,
|
||||
IMapper mapper)
|
||||
IMapper mapper, IAccountService accountService)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
|
@ -45,6 +46,7 @@ namespace API.Controllers
|
|||
_unitOfWork = unitOfWork;
|
||||
_logger = logger;
|
||||
_mapper = mapper;
|
||||
_accountService = accountService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -61,30 +63,10 @@ namespace API.Controllers
|
|||
if (resetPasswordDto.UserName != User.GetUsername() && !User.IsInRole(PolicyConstants.AdminRole))
|
||||
return Unauthorized("You are not permitted to this operation.");
|
||||
|
||||
// Validate Password
|
||||
foreach (var validator in _userManager.PasswordValidators)
|
||||
var errors = await _accountService.ChangeUserPassword(user, resetPasswordDto.Password);
|
||||
if (errors.Any())
|
||||
{
|
||||
var validationResult = await validator.ValidateAsync(_userManager, user, resetPasswordDto.Password);
|
||||
if (!validationResult.Succeeded)
|
||||
{
|
||||
return BadRequest(
|
||||
validationResult.Errors.Select(e => new ApiException(400, e.Code, e.Description)));
|
||||
}
|
||||
}
|
||||
|
||||
var result = await _userManager.RemovePasswordAsync(user);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
_logger.LogError("Could not update password");
|
||||
return BadRequest(result.Errors.Select(e => new ApiException(400, e.Code, e.Description)));
|
||||
}
|
||||
|
||||
|
||||
result = await _userManager.AddPasswordAsync(user, resetPasswordDto.Password);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
_logger.LogError("Could not update password");
|
||||
return BadRequest(result.Errors.Select(e => new ApiException(400, e.Code, e.Description)));
|
||||
return BadRequest(errors);
|
||||
}
|
||||
|
||||
_logger.LogInformation("{User}'s Password has been reset", resetPasswordDto.UserName);
|
||||
|
@ -110,6 +92,13 @@ namespace API.Controllers
|
|||
user.UserPreferences ??= new AppUserPreferences();
|
||||
user.ApiKey = HashUtil.ApiKey();
|
||||
|
||||
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
if (!settings.EnableAuthentication && !registerDto.IsAdmin)
|
||||
{
|
||||
_logger.LogInformation("User {UserName} is being registered as non-admin with no server authentication. Using default password.", registerDto.Username);
|
||||
registerDto.Password = AccountService.DefaultPassword;
|
||||
}
|
||||
|
||||
var result = await _userManager.CreateAsync(user, registerDto.Password);
|
||||
|
||||
if (!result.Succeeded) return BadRequest(result.Errors);
|
||||
|
@ -166,6 +155,14 @@ namespace API.Controllers
|
|||
|
||||
if (user == null) return Unauthorized("Invalid username");
|
||||
|
||||
var isAdmin = await _unitOfWork.UserRepository.IsUserAdmin(user);
|
||||
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
if (!settings.EnableAuthentication && !isAdmin)
|
||||
{
|
||||
_logger.LogDebug("User {UserName} is logging in with authentication disabled", loginDto.Username);
|
||||
loginDto.Password = AccountService.DefaultPassword;
|
||||
}
|
||||
|
||||
var result = await _signInManager
|
||||
.CheckPasswordSignInAsync(user, loginDto.Password, false);
|
||||
|
||||
|
|
|
@ -2,13 +2,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Constants;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers
|
||||
|
@ -19,13 +17,11 @@ namespace API.Controllers
|
|||
public class CollectionController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly UserManager<AppUser> _userManager;
|
||||
|
||||
/// <inheritdoc />
|
||||
public CollectionController(IUnitOfWork unitOfWork, UserManager<AppUser> userManager)
|
||||
public CollectionController(IUnitOfWork unitOfWork)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -36,7 +32,7 @@ namespace API.Controllers
|
|||
public async Task<IEnumerable<CollectionTagDto>> GetAllTags()
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
var isAdmin = await _userManager.IsInRoleAsync(user, PolicyConstants.AdminRole);
|
||||
var isAdmin = await _unitOfWork.UserRepository.IsUserAdmin(user);
|
||||
if (isAdmin)
|
||||
{
|
||||
return await _unitOfWork.CollectionTagRepository.GetAllTagDtosAsync();
|
||||
|
|
|
@ -26,7 +26,6 @@ namespace API.Controllers
|
|||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IDownloadService _downloadService;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly UserManager<AppUser> _userManager;
|
||||
private readonly ICacheService _cacheService;
|
||||
private readonly IReaderService _readerService;
|
||||
|
||||
|
@ -41,13 +40,12 @@ namespace API.Controllers
|
|||
private readonly ChapterSortComparer _chapterSortComparer = new ChapterSortComparer();
|
||||
|
||||
public OpdsController(IUnitOfWork unitOfWork, IDownloadService downloadService,
|
||||
IDirectoryService directoryService, UserManager<AppUser> userManager,
|
||||
ICacheService cacheService, IReaderService readerService)
|
||||
IDirectoryService directoryService, ICacheService cacheService,
|
||||
IReaderService readerService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_downloadService = downloadService;
|
||||
_directoryService = directoryService;
|
||||
_userManager = userManager;
|
||||
_cacheService = cacheService;
|
||||
_readerService = readerService;
|
||||
|
||||
|
@ -170,7 +168,7 @@ namespace API.Controllers
|
|||
return BadRequest("OPDS is not enabled on this server");
|
||||
var userId = await GetUser(apiKey);
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
var isAdmin = await _userManager.IsInRoleAsync(user, PolicyConstants.AdminRole);
|
||||
var isAdmin = await _unitOfWork.UserRepository.IsUserAdmin(user);
|
||||
|
||||
IEnumerable <CollectionTagDto> tags;
|
||||
if (isAdmin)
|
||||
|
@ -213,7 +211,7 @@ namespace API.Controllers
|
|||
return BadRequest("OPDS is not enabled on this server");
|
||||
var userId = await GetUser(apiKey);
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
var isAdmin = await _userManager.IsInRoleAsync(user, PolicyConstants.AdminRole);
|
||||
var isAdmin = await _unitOfWork.UserRepository.IsUserAdmin(user);
|
||||
|
||||
IEnumerable <CollectionTagDto> tags;
|
||||
if (isAdmin)
|
||||
|
|
|
@ -8,6 +8,8 @@ using API.Entities.Enums;
|
|||
using API.Extensions;
|
||||
using API.Helpers.Converters;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using Kavita.Common;
|
||||
using Kavita.Common.Extensions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -21,12 +23,14 @@ namespace API.Controllers
|
|||
private readonly ILogger<SettingsController> _logger;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ITaskScheduler _taskScheduler;
|
||||
private readonly IAccountService _accountService;
|
||||
|
||||
public SettingsController(ILogger<SettingsController> logger, IUnitOfWork unitOfWork, ITaskScheduler taskScheduler)
|
||||
public SettingsController(ILogger<SettingsController> logger, IUnitOfWork unitOfWork, ITaskScheduler taskScheduler, IAccountService accountService)
|
||||
{
|
||||
_logger = logger;
|
||||
_unitOfWork = unitOfWork;
|
||||
_taskScheduler = taskScheduler;
|
||||
_accountService = accountService;
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
|
@ -57,6 +61,7 @@ namespace API.Controllers
|
|||
|
||||
// We do not allow CacheDirectory changes, so we will ignore.
|
||||
var currentSettings = await _unitOfWork.SettingsRepository.GetSettingsAsync();
|
||||
var updateAuthentication = false;
|
||||
|
||||
foreach (var setting in currentSettings)
|
||||
{
|
||||
|
@ -93,6 +98,13 @@ namespace API.Controllers
|
|||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.EnableAuthentication && updateSettingsDto.EnableAuthentication + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.EnableAuthentication + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
updateAuthentication = true;
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.AllowStatCollection && updateSettingsDto.AllowStatCollection + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.AllowStatCollection + string.Empty;
|
||||
|
@ -110,12 +122,33 @@ namespace API.Controllers
|
|||
|
||||
if (!_unitOfWork.HasChanges()) return Ok("Nothing was updated");
|
||||
|
||||
if (!_unitOfWork.HasChanges() || !await _unitOfWork.CommitAsync())
|
||||
try
|
||||
{
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
if (updateAuthentication)
|
||||
{
|
||||
var users = await _unitOfWork.UserRepository.GetNonAdminUsersAsync();
|
||||
foreach (var user in users)
|
||||
{
|
||||
var errors = await _accountService.ChangeUserPassword(user, AccountService.DefaultPassword);
|
||||
if (!errors.Any()) continue;
|
||||
|
||||
await _unitOfWork.RollbackAsync();
|
||||
return BadRequest(errors);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Server authentication changed. Updated all non-admins to default password");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "There was an exception when updating server settings");
|
||||
await _unitOfWork.RollbackAsync();
|
||||
return BadRequest("There was a critical issue. Please try again.");
|
||||
}
|
||||
|
||||
|
||||
_logger.LogInformation("Server Settings updated");
|
||||
_taskScheduler.ScheduleTasks();
|
||||
return Ok(updateSettingsDto);
|
||||
|
@ -148,5 +181,12 @@ namespace API.Controllers
|
|||
var settingsDto = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
return Ok(settingsDto.EnableOpds);
|
||||
}
|
||||
|
||||
[HttpGet("authentication-enabled")]
|
||||
public async Task<ActionResult<bool>> GetAuthenticationEnabled()
|
||||
{
|
||||
var settingsDto = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
return Ok(settingsDto.EnableAuthentication);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Mvc;
|
|||
|
||||
namespace API.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
public class UsersController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
@ -39,6 +38,15 @@ namespace API.Controllers
|
|||
return Ok(await _unitOfWork.UserRepository.GetMembersAsync());
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("names")]
|
||||
public async Task<ActionResult<IEnumerable<MemberDto>>> GetUserNames()
|
||||
{
|
||||
var members = await _unitOfWork.UserRepository.GetMembersAsync();
|
||||
return Ok(members.Select(m => m.Username));
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("has-reading-progress")]
|
||||
public async Task<ActionResult<bool>> HasReadingProgress(int libraryId)
|
||||
{
|
||||
|
@ -47,6 +55,7 @@ namespace API.Controllers
|
|||
return Ok(await _unitOfWork.AppUserProgressRepository.UserHasProgress(library.Type, userId));
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("has-library-access")]
|
||||
public async Task<ActionResult<bool>> HasLibraryAccess(int libraryId)
|
||||
{
|
||||
|
@ -54,6 +63,7 @@ namespace API.Controllers
|
|||
return Ok(libs.Any(x => x.Id == libraryId));
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPost("update-preferences")]
|
||||
public async Task<ActionResult<UserPreferencesDto>> UpdatePreferences(UserPreferencesDto preferencesDto)
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue