Random Bugs (#2531)
This commit is contained in:
parent
0c70e80420
commit
4e1c66331f
27 changed files with 232 additions and 178 deletions
|
@ -193,7 +193,7 @@ public class AccountController : BaseApiController
|
|||
{
|
||||
user = await _userManager.Users
|
||||
.Include(u => u.UserPreferences)
|
||||
.SingleOrDefaultAsync(x => x.NormalizedUserName == loginDto.Username.ToUpper());
|
||||
.SingleOrDefaultAsync(x => x.NormalizedUserName == loginDto.Username.ToUpperInvariant());
|
||||
}
|
||||
|
||||
_logger.LogInformation("{UserName} attempting to login from {IpAddress}", loginDto.Username, HttpContext.Connection.RemoteIpAddress?.ToString());
|
||||
|
@ -390,7 +390,8 @@ public class AccountController : BaseApiController
|
|||
return Ok(new InviteUserResponse
|
||||
{
|
||||
EmailLink = string.Empty,
|
||||
EmailSent = false
|
||||
EmailSent = false,
|
||||
InvalidEmail = true,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -484,6 +485,7 @@ public class AccountController : BaseApiController
|
|||
var errors = await _accountService.ValidateUsername(dto.Username);
|
||||
if (errors.Any()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "username-taken"));
|
||||
user.UserName = dto.Username;
|
||||
await _userManager.UpdateNormalizedUserNameAsync(user);
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
}
|
||||
|
||||
|
@ -689,7 +691,8 @@ public class AccountController : BaseApiController
|
|||
return Ok(new InviteUserResponse
|
||||
{
|
||||
EmailLink = emailLink,
|
||||
EmailSent = false
|
||||
EmailSent = false,
|
||||
InvalidEmail = true
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -974,13 +977,12 @@ public class AccountController : BaseApiController
|
|||
|
||||
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var emailLink = await _accountService.GenerateEmailLink(Request, token, "confirm-email", user.Email);
|
||||
_logger.LogCritical("[Email Migration]: Email Link: {Link}", emailLink);
|
||||
_logger.LogCritical("[Email Migration]: Token {UserName}: {Token}", user.UserName, token);
|
||||
_logger.LogCritical("[Email Migration]: Email Link for {UserName}: {Link}", user.UserName, emailLink);
|
||||
|
||||
if (!_emailService.IsValidEmail(user.Email))
|
||||
{
|
||||
_logger.LogCritical("[Email Migration]: User is trying to resend an invite flow, but their email ({Email}) isn't valid. No email will be send", user.Email);
|
||||
return Ok(await _localizationService.Translate(user.Id, "invalid-email"));
|
||||
_logger.LogCritical("[Email Migration]: User {UserName} is trying to resend an invite flow, but their email ({Email}) isn't valid. No email will be send", user.UserName, user.Email);
|
||||
return BadRequest(await _localizationService.Translate(user.Id, "invalid-email"));
|
||||
}
|
||||
|
||||
if (await _accountService.CheckIfAccessible(Request))
|
||||
|
@ -1003,7 +1005,7 @@ public class AccountController : BaseApiController
|
|||
return Ok(emailLink);
|
||||
}
|
||||
|
||||
return Ok(await _localizationService.Translate(user.Id, "not-accessible"));
|
||||
return BadRequest(await _localizationService.Translate(user.Id, "not-accessible"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1102,12 +1104,26 @@ public class AccountController : BaseApiController
|
|||
baseUrl = baseUrl.Replace("//", "/");
|
||||
}
|
||||
|
||||
if (baseUrl.StartsWith("/"))
|
||||
if (baseUrl.StartsWith('/'))
|
||||
{
|
||||
baseUrl = baseUrl.Substring(1, baseUrl.Length - 1);
|
||||
}
|
||||
}
|
||||
return Ok(origin + "/" + baseUrl + "api/opds/" + user!.ApiKey);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Is the user's current email valid or not
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("is-email-valid")]
|
||||
public async Task<ActionResult<bool>> IsEmailValid()
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId());
|
||||
if (user == null) return Unauthorized();
|
||||
if (string.IsNullOrEmpty(user.Email)) return Ok(false);
|
||||
|
||||
return Ok(_emailService.IsValidEmail(user.Email));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,4 +10,8 @@ public class InviteUserResponse
|
|||
/// Was an email sent (ie is this server accessible)
|
||||
/// </summary>
|
||||
public bool EmailSent { get; set; } = default!;
|
||||
/// <summary>
|
||||
/// When a user has an invalid email and is attempting to perform a flow.
|
||||
/// </summary>
|
||||
public bool InvalidEmail { get; set; } = false;
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
namespace API.DTOs.Account;
|
||||
|
||||
public class UpdateEmailResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Did the user not have an existing email
|
||||
/// </summary>
|
||||
/// <remarks>This informs the user to check the new email address</remarks>
|
||||
public bool HadNoExistingEmail { get; set; }
|
||||
/// <summary>
|
||||
/// Was an email sent (ie is this server accessible)
|
||||
/// </summary>
|
||||
public bool EmailSent { get; set; }
|
||||
}
|
|
@ -15,6 +15,7 @@ public class VolumeBuilder : IEntityBuilder<Volume>
|
|||
_volume = new Volume()
|
||||
{
|
||||
Name = volumeNumber,
|
||||
// TODO / BUG: Try to use float based Number which will allow Epub's with < 1 volumes to show in series detail
|
||||
Number = (int) Services.Tasks.Scanner.Parser.Parser.MinNumberFromRange(volumeNumber),
|
||||
Chapters = new List<Chapter>()
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Serilog;
|
||||
using System.Text.RegularExpressions;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting.Display;
|
||||
|
@ -49,6 +50,7 @@ public static class LogLevelOptions
|
|||
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Error)
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.WithThreadId()
|
||||
.Enrich.With(new ApiKeyEnricher())
|
||||
.WriteTo.Console(new MessageTemplateTextFormatter(outputTemplate))
|
||||
.WriteTo.File(LogFile,
|
||||
shared: true,
|
||||
|
@ -74,6 +76,7 @@ public static class LogLevelOptions
|
|||
if (e.Properties.ContainsKey("Path") && e.Properties["Path"].ToString().Replace("\"", string.Empty) == "/api/health") return false;
|
||||
if (e.Properties.ContainsKey("Path") && e.Properties["Path"].ToString().Replace("\"", string.Empty) == "/hubs/messages") return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -115,3 +118,24 @@ public static class LogLevelOptions
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
public partial class ApiKeyEnricher : ILogEventEnricher
|
||||
{
|
||||
public void Enrich(LogEvent e, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
var isRequestLoggingMiddleware = e.Properties.ContainsKey("SourceContext") &&
|
||||
e.Properties["SourceContext"].ToString().Replace("\"", string.Empty) ==
|
||||
"Serilog.AspNetCore.RequestLoggingMiddleware";
|
||||
if (!isRequestLoggingMiddleware) return;
|
||||
if (!e.Properties.ContainsKey("RequestPath") ||
|
||||
!e.Properties["RequestPath"].ToString().Contains("apiKey=")) return;
|
||||
|
||||
// Check if the log message contains "apiKey=" and censor it
|
||||
var censoredMessage = MyRegex().Replace(e.Properties["RequestPath"].ToString(), "apiKey=******REDACTED******");
|
||||
var enrichedProperty = propertyFactory.CreateProperty("RequestPath", censoredMessage);
|
||||
e.AddOrUpdateProperty(enrichedProperty);
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"\bapiKey=[^&\s]+\b")]
|
||||
private static partial Regex MyRegex();
|
||||
}
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Middleware;
|
||||
|
||||
public class CustomAuthHeaderMiddleware(RequestDelegate next)
|
||||
{
|
||||
// Hardcoded list of allowed IP addresses in CIDR format
|
||||
private readonly string[] allowedIpAddresses = { "192.168.1.0/24", "2001:db8::/32", "116.202.233.5", "104.21.81.112" };
|
||||
|
||||
|
||||
public async Task Invoke(HttpContext context, IUnitOfWork unitOfWork, ILogger<CustomAuthHeaderMiddleware> logger, ITokenService tokenService)
|
||||
{
|
||||
// Extract user information from the custom header
|
||||
string remoteUser = context.Request.Headers["Remote-User"];
|
||||
|
||||
// If header missing or user already authenticated, move on
|
||||
if (string.IsNullOrEmpty(remoteUser) || context.User.Identity is {IsAuthenticated: true})
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate IP address
|
||||
if (IsValidIpAddress(context.Connection.RemoteIpAddress))
|
||||
{
|
||||
// Perform additional authentication logic if needed
|
||||
// For now, you can log the authenticated user
|
||||
var user = await unitOfWork.UserRepository.GetUserByEmailAsync(remoteUser);
|
||||
if (user == null)
|
||||
{
|
||||
// Tell security log maybe?
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
|
||||
return;
|
||||
}
|
||||
// Check if the RemoteUser has an account on the server
|
||||
// if (!context.Request.Path.Equals("/login", StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// // Attach the Auth header and allow it to pass through
|
||||
// var token = await tokenService.CreateToken(user);
|
||||
// context.Request.Headers.Add("Authorization", $"Bearer {token}");
|
||||
// //context.Response.Redirect($"/login?apiKey={user.ApiKey}");
|
||||
// return;
|
||||
// }
|
||||
// Attach the Auth header and allow it to pass through
|
||||
var token = await tokenService.CreateToken(user);
|
||||
context.Request.Headers.Append("Authorization", $"Bearer {token}");
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
|
||||
await next(context);
|
||||
}
|
||||
|
||||
private bool IsValidIpAddress(IPAddress ipAddress)
|
||||
{
|
||||
// Check if the IP address is in the whitelist
|
||||
return allowedIpAddresses.Any(ipRange => IpAddressRange.Parse(ipRange).Contains(ipAddress));
|
||||
}
|
||||
}
|
||||
|
||||
// Helper class for IP address range parsing
|
||||
public class IpAddressRange
|
||||
{
|
||||
private readonly uint _startAddress;
|
||||
private readonly uint _endAddress;
|
||||
|
||||
private IpAddressRange(uint startAddress, uint endAddress)
|
||||
{
|
||||
_startAddress = startAddress;
|
||||
_endAddress = endAddress;
|
||||
}
|
||||
|
||||
public bool Contains(IPAddress address)
|
||||
{
|
||||
var ipAddressBytes = address.GetAddressBytes();
|
||||
var ipAddress = BitConverter.ToUInt32(ipAddressBytes.Reverse().ToArray(), 0);
|
||||
return ipAddress >= _startAddress && ipAddress <= _endAddress;
|
||||
}
|
||||
|
||||
public static IpAddressRange Parse(string ipRange)
|
||||
{
|
||||
var parts = ipRange.Split('/');
|
||||
var ipAddress = IPAddress.Parse(parts[0]);
|
||||
var maskBits = int.Parse(parts[1]);
|
||||
|
||||
var ipBytes = ipAddress.GetAddressBytes().Reverse().ToArray();
|
||||
var startAddress = BitConverter.ToUInt32(ipBytes, 0);
|
||||
var endAddress = startAddress | (uint.MaxValue >> maskBits);
|
||||
|
||||
return new IpAddressRange(startAddress, endAddress);
|
||||
}
|
||||
}
|
|
@ -73,7 +73,7 @@ public class AccountService : IAccountService
|
|||
basePart = serverSettings.HostName;
|
||||
if (!serverSettings.BaseUrl.Equals(Configuration.DefaultBaseUrl))
|
||||
{
|
||||
var removeCount = serverSettings.BaseUrl.EndsWith("/") ? 2 : 1;
|
||||
var removeCount = serverSettings.BaseUrl.EndsWith('/') ? 1 : 0;
|
||||
basePart += serverSettings.BaseUrl.Substring(0, serverSettings.BaseUrl.Length - removeCount);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -546,7 +546,6 @@ public class BookService : IBookService
|
|||
ExtractSortTitle(metadataItem, epubBook, info);
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -510,7 +510,6 @@ public class ProcessSeries : IProcessSeries
|
|||
|
||||
public void UpdateVolumes(Series series, IList<ParserInfo> parsedInfos, bool forceUpdate = false)
|
||||
{
|
||||
var startingVolumeCount = series.Volumes.Count;
|
||||
// Add new volumes and update chapters per volume
|
||||
var distinctVolumes = parsedInfos.DistinctVolumes();
|
||||
_logger.LogDebug("[ScannerService] Updating {DistinctVolumes} volumes on {SeriesName}", distinctVolumes.Count, series.Name);
|
||||
|
@ -582,10 +581,6 @@ public class ProcessSeries : IProcessSeries
|
|||
|
||||
series.Volumes = nonDeletedVolumes;
|
||||
}
|
||||
|
||||
// DO I need this anymore?
|
||||
_logger.LogDebug("[ScannerService] Updated {SeriesName} volumes from count of {StartingVolumeCount} to {VolumeCount}",
|
||||
series.Name, startingVolumeCount, series.Volumes.Count);
|
||||
}
|
||||
|
||||
public void UpdateChapters(Series series, Volume volume, IList<ParserInfo> parsedInfos, bool forceUpdate = false)
|
||||
|
|
|
@ -261,7 +261,6 @@ public class Startup
|
|||
|
||||
app.UseMiddleware<ExceptionMiddleware>();
|
||||
app.UseMiddleware<SecurityEventMiddleware>();
|
||||
app.UseMiddleware<CustomAuthHeaderMiddleware>();
|
||||
|
||||
|
||||
if (env.IsDevelopment())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue