mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
More Fixes from Recent PRs (#1995)
* Added extra debugging for logout issue * Fixed the null issue with ISBN * Allow web links to be cleared out * More logging on refresh token * More key fallback when building Table of Contents * Added better fallback implementation for building table of contents based on the many different ways epubs are packed and referenced. * Updated dependencies * Fixed up refresh token refresh which was invalidating sessions for no reason. Added it to update last active time as well.
This commit is contained in:
parent
95df0a0825
commit
2ce4ddcaa4
@ -10,8 +10,8 @@
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
<PackageReference Include="NSubstitute" Version="4.4.0" />
|
||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="19.2.22" />
|
||||
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="19.2.22" />
|
||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="19.2.26" />
|
||||
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="19.2.26" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
@ -102,7 +102,7 @@
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.6" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.30.1" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="19.2.22" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="19.2.26" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageReference Include="VersOne.Epub" Version="3.3.0-alpha1" />
|
||||
</ItemGroup>
|
||||
|
@ -443,6 +443,9 @@ public class AccountController : BaseApiController
|
||||
if (!roleResult.Succeeded) return BadRequest(roleResult.Errors);
|
||||
}
|
||||
|
||||
// We might want to check if they had admin and no longer, if so:
|
||||
// await _userManager.UpdateSecurityStampAsync(user); to force them to re-authenticate
|
||||
|
||||
|
||||
var allLibraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).ToList();
|
||||
List<Library> libraries;
|
||||
|
@ -165,16 +165,16 @@ public class Program
|
||||
|
||||
var env = hostingContext.HostingEnvironment;
|
||||
|
||||
config.AddJsonFile("config/appsettings.json", optional: true, reloadOnChange: false)
|
||||
config.AddJsonFile("config/appsettings.json", optional: false, reloadOnChange: false)
|
||||
.AddJsonFile($"config/appsettings.{env.EnvironmentName}.json",
|
||||
optional: true, reloadOnChange: false);
|
||||
optional: false, reloadOnChange: false);
|
||||
})
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseKestrel((opts) =>
|
||||
{
|
||||
var ipAddresses = Configuration.IpAddresses;
|
||||
if (new OsInfo(Array.Empty<IOsVersionAdapter>()).IsDocker || string.IsNullOrEmpty(ipAddresses) || ipAddresses.Equals(Configuration.DefaultIpAddresses))
|
||||
if (new OsInfo().IsDocker || string.IsNullOrEmpty(ipAddresses) || ipAddresses.Equals(Configuration.DefaultIpAddresses))
|
||||
{
|
||||
opts.ListenAnyIP(HttpPort, options => { options.Protocols = HttpProtocols.Http1AndHttp2; });
|
||||
}
|
||||
@ -197,7 +197,4 @@ public class Program
|
||||
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -435,7 +435,7 @@ public class BookService : IBookService
|
||||
};
|
||||
ComicInfo.CleanComicInfo(info);
|
||||
|
||||
foreach (var identifier in epubBook.Schema.Package.Metadata.Identifiers.Where(id => id.Scheme.Equals("ISBN")))
|
||||
foreach (var identifier in epubBook.Schema.Package.Metadata.Identifiers.Where(id => !string.IsNullOrEmpty(id.Scheme) && id.Scheme.Equals("ISBN")))
|
||||
{
|
||||
if (string.IsNullOrEmpty(identifier.Identifier)) continue;
|
||||
var isbn = identifier.Identifier.Replace("urn:isbn:", string.Empty).Replace("isbn:", string.Empty);
|
||||
@ -494,7 +494,7 @@ public class BookService : IBookService
|
||||
break;
|
||||
case "title-type":
|
||||
break;
|
||||
// This is currently not possible until VersOne update's to allow EPUB 3 Title to have attributes
|
||||
// This is currently not possible until VersOne update's to allow EPUB 3 Title to have attributes (3.3 update)
|
||||
if (!metadataItem.Content.Equals("collection")) break;
|
||||
var titleId = metadataItem.Refines.Replace("#", string.Empty);
|
||||
var readingListElem = epubBook.Schema.Package.Metadata.MetaItems.FirstOrDefault(item =>
|
||||
@ -855,7 +855,7 @@ public class BookService : IBookService
|
||||
/// <param name="mappings"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
private static string CoalesceKey(EpubBookRef book, IDictionary<string, int> mappings, string key)
|
||||
private static string CoalesceKey(EpubBookRef book, IReadOnlyDictionary<string, int> mappings, string key)
|
||||
{
|
||||
if (mappings.ContainsKey(CleanContentKeys(key))) return key;
|
||||
|
||||
@ -866,6 +866,19 @@ public class BookService : IBookService
|
||||
key = correctedKey;
|
||||
}
|
||||
|
||||
var stepsBack = CountParentDirectory(book.Content.NavigationHtmlFile.FileName);
|
||||
if (mappings.TryGetValue(key, out _))
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
var modifiedKey = RemovePathSegments(key, stepsBack);
|
||||
if (mappings.TryGetValue(modifiedKey, out _))
|
||||
{
|
||||
return modifiedKey;
|
||||
}
|
||||
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
@ -904,7 +917,7 @@ public class BookService : IBookService
|
||||
{
|
||||
if (navigationItem.NestedItems.Count == 0)
|
||||
{
|
||||
CreateToCChapter(navigationItem, Array.Empty<BookChapterItem>(), chaptersList, mappings);
|
||||
CreateToCChapter(book, navigationItem, Array.Empty<BookChapterItem>(), chaptersList, mappings);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -913,19 +926,19 @@ public class BookService : IBookService
|
||||
foreach (var nestedChapter in navigationItem.NestedItems.Where(n => n.Link != null))
|
||||
{
|
||||
var key = CoalesceKey(book, mappings, nestedChapter.Link.ContentFileName);
|
||||
if (mappings.ContainsKey(key))
|
||||
if (mappings.TryGetValue(key, out var mapping))
|
||||
{
|
||||
nestedChapters.Add(new BookChapterItem
|
||||
{
|
||||
Title = nestedChapter.Title,
|
||||
Page = mappings[key],
|
||||
Page = mapping,
|
||||
Part = nestedChapter.Link.Anchor ?? string.Empty,
|
||||
Children = new List<BookChapterItem>()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CreateToCChapter(navigationItem, nestedChapters, chaptersList, mappings);
|
||||
CreateToCChapter(book, navigationItem, nestedChapters, chaptersList, mappings);
|
||||
}
|
||||
|
||||
if (chaptersList.Count != 0) return chaptersList;
|
||||
@ -964,6 +977,38 @@ public class BookService : IBookService
|
||||
return chaptersList;
|
||||
}
|
||||
|
||||
private static int CountParentDirectory(string path)
|
||||
{
|
||||
const string pattern = @"\.\./";
|
||||
var matches = Regex.Matches(path, pattern);
|
||||
|
||||
return matches.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes paths segments from the beginning of a path. Returns original path if any issues.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="segmentsToRemove"></param>
|
||||
/// <returns></returns>
|
||||
private static string RemovePathSegments(string path, int segmentsToRemove)
|
||||
{
|
||||
if (segmentsToRemove <= 0)
|
||||
return path;
|
||||
|
||||
var startIndex = 0;
|
||||
for (var i = 0; i < segmentsToRemove; i++)
|
||||
{
|
||||
var slashIndex = path.IndexOf('/', startIndex);
|
||||
if (slashIndex == -1)
|
||||
return path; // Not enough segments to remove
|
||||
|
||||
startIndex = slashIndex + 1;
|
||||
}
|
||||
|
||||
return path.Substring(startIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This returns a single page within the epub book. All html will be rewritten to be scoped within our reader,
|
||||
/// all css is scoped, etc.
|
||||
@ -1028,7 +1073,7 @@ public class BookService : IBookService
|
||||
throw new KavitaException("Could not find the appropriate html for that page");
|
||||
}
|
||||
|
||||
private static void CreateToCChapter(EpubNavigationItemRef navigationItem, IList<BookChapterItem> nestedChapters,
|
||||
private static void CreateToCChapter(EpubBookRef book, EpubNavigationItemRef navigationItem, IList<BookChapterItem> nestedChapters,
|
||||
ICollection<BookChapterItem> chaptersList, IReadOnlyDictionary<string, int> mappings)
|
||||
{
|
||||
if (navigationItem.Link == null)
|
||||
@ -1047,7 +1092,7 @@ public class BookService : IBookService
|
||||
}
|
||||
else
|
||||
{
|
||||
var groupKey = CleanContentKeys(navigationItem.Link.ContentFileName);
|
||||
var groupKey = CoalesceKey(book, mappings, navigationItem.Link.ContentFileName);
|
||||
if (mappings.ContainsKey(groupKey))
|
||||
{
|
||||
chaptersList.Add(new BookChapterItem
|
||||
|
@ -98,10 +98,10 @@ public class SeriesService : ISeriesService
|
||||
}
|
||||
|
||||
// This shouldn't be needed post v0.5.3 release
|
||||
if (string.IsNullOrEmpty(series.Metadata.Summary))
|
||||
{
|
||||
series.Metadata.Summary = string.Empty;
|
||||
}
|
||||
// if (string.IsNullOrEmpty(series.Metadata.Summary))
|
||||
// {
|
||||
// series.Metadata.Summary = string.Empty;
|
||||
// }
|
||||
|
||||
if (string.IsNullOrEmpty(updateSeriesMetadataDto.SeriesMetadata.Summary))
|
||||
{
|
||||
@ -120,7 +120,10 @@ public class SeriesService : ISeriesService
|
||||
series.Metadata.LanguageLocked = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(updateSeriesMetadataDto.SeriesMetadata?.WebLinks))
|
||||
if (string.IsNullOrEmpty(updateSeriesMetadataDto.SeriesMetadata?.WebLinks))
|
||||
{
|
||||
series.Metadata.WebLinks = string.Empty;
|
||||
} else
|
||||
{
|
||||
series.Metadata.WebLinks = string.Join(",", updateSeriesMetadataDto.SeriesMetadata?.WebLinks
|
||||
.Split(",")
|
||||
|
@ -5,10 +5,12 @@ using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.DTOs.Account;
|
||||
using API.Entities;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using static System.Security.Claims.ClaimTypes;
|
||||
using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;
|
||||
@ -27,13 +29,17 @@ public interface ITokenService
|
||||
public class TokenService : ITokenService
|
||||
{
|
||||
private readonly UserManager<AppUser> _userManager;
|
||||
private readonly ILogger<TokenService> _logger;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly SymmetricSecurityKey _key;
|
||||
private const string RefreshTokenName = "RefreshToken";
|
||||
|
||||
public TokenService(IConfiguration config, UserManager<AppUser> userManager)
|
||||
public TokenService(IConfiguration config, UserManager<AppUser> userManager, ILogger<TokenService> logger, IUnitOfWork unitOfWork)
|
||||
{
|
||||
|
||||
_userManager = userManager;
|
||||
_logger = logger;
|
||||
_unitOfWork = unitOfWork;
|
||||
_key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["TokenKey"] ?? string.Empty));
|
||||
}
|
||||
|
||||
@ -78,12 +84,28 @@ public class TokenService : ITokenService
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var tokenContent = tokenHandler.ReadJwtToken(request.Token);
|
||||
var username = tokenContent.Claims.FirstOrDefault(q => q.Type == JwtRegisteredClaimNames.Name)?.Value;
|
||||
if (string.IsNullOrEmpty(username)) return null;
|
||||
if (string.IsNullOrEmpty(username))
|
||||
{
|
||||
_logger.LogDebug("[RefreshToken] failed to validate due to not finding user in RefreshToken");
|
||||
return null;
|
||||
}
|
||||
var user = await _userManager.FindByNameAsync(username);
|
||||
if (user == null) return null; // This forces a logout
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogDebug("[RefreshToken] failed to validate due to not finding user in DB");
|
||||
return null;
|
||||
}
|
||||
|
||||
var validated = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, RefreshTokenName, request.RefreshToken);
|
||||
if (!validated) return null;
|
||||
await _userManager.UpdateSecurityStampAsync(user);
|
||||
if (!validated)
|
||||
{
|
||||
|
||||
_logger.LogDebug("[RefreshToken] failed to validate due to invalid refresh token");
|
||||
return null;
|
||||
}
|
||||
|
||||
user.UpdateLastActive();
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
return new TokenRequestDto()
|
||||
{
|
||||
@ -93,11 +115,13 @@ public class TokenService : ITokenService
|
||||
} catch (SecurityTokenExpiredException ex)
|
||||
{
|
||||
// Handle expired token
|
||||
_logger.LogError(ex, "Failed to validate refresh token");
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle other exceptions
|
||||
_logger.LogError(ex, "Failed to validate refresh token");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ public static class Configuration
|
||||
{
|
||||
public const string DefaultIpAddresses = "0.0.0.0,::";
|
||||
public const string DefaultBaseUrl = "/";
|
||||
public const int DefaultHttpPort = 5000;
|
||||
public const string DefaultXFrameOptions = "SAMEORIGIN";
|
||||
private static readonly string AppSettingsFilename = Path.Join("config", GetAppSettingFilename());
|
||||
|
||||
@ -307,7 +308,7 @@ public static class Configuration
|
||||
// ReSharper disable once MemberHidesStaticFromOuterClass
|
||||
public int Port { get; set; }
|
||||
// ReSharper disable once MemberHidesStaticFromOuterClass
|
||||
public string IpAddresses { get; set; }
|
||||
public string IpAddresses { get; set; } = string.Empty;
|
||||
// ReSharper disable once MemberHidesStaticFromOuterClass
|
||||
public string BaseUrl { get; set; }
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
"name": "GPL-3.0",
|
||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||
},
|
||||
"version": "0.7.2.7"
|
||||
"version": "0.7.2.8"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user