mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-01 04:34:49 -04:00
* Corrected tooltip for Cache * Ensure we sync the DB to what's in appsettings.json for Cache key. * Change the fingerprinting method for Windows installs exclusively to avoid churn due to how security updates are handled. * Hooked up the ability to see where reviews are from via an icon on the review card, rather than having to click or know that MAL has "external Review" as title. * Updated FAQ for Kavita+ to link directly to the FAQ * Added the ability for all ratings on a series to be shown to the user. Added favorite count on AL and MAL * Cleaned up so the check for Kavita+ license doesn't seem like it's running when no license is registered. * Tweaked the test instance buy link to test new product.
198 lines
7.2 KiB
C#
198 lines
7.2 KiB
C#
using System;
|
|
using System.Threading.Tasks;
|
|
using API.Constants;
|
|
using API.Data;
|
|
using API.DTOs.Account;
|
|
using API.DTOs.License;
|
|
using API.Entities.Enums;
|
|
using EasyCaching.Core;
|
|
using Flurl.Http;
|
|
using Hangfire;
|
|
using Kavita.Common;
|
|
using Kavita.Common.EnvironmentInfo;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace API.Services.Plus;
|
|
|
|
internal class RegisterLicenseResponseDto
|
|
{
|
|
public string EncryptedLicense { get; set; }
|
|
public bool Successful { get; set; }
|
|
public string ErrorMessage { get; set; }
|
|
}
|
|
|
|
public interface ILicenseService
|
|
{
|
|
Task ValidateLicenseStatus();
|
|
Task RemoveLicense();
|
|
Task AddLicense(string license, string email);
|
|
Task<bool> HasActiveLicense(bool forceCheck = false);
|
|
}
|
|
|
|
public class LicenseService : ILicenseService
|
|
{
|
|
private readonly IEasyCachingProviderFactory _cachingProviderFactory;
|
|
private readonly IUnitOfWork _unitOfWork;
|
|
private readonly ILogger<LicenseService> _logger;
|
|
private readonly TimeSpan _licenseCacheTimeout = TimeSpan.FromHours(8);
|
|
public const string Cron = "0 */4 * * *";
|
|
private const string CacheKey = "license";
|
|
|
|
|
|
public LicenseService(IEasyCachingProviderFactory cachingProviderFactory, IUnitOfWork unitOfWork, ILogger<LicenseService> logger)
|
|
{
|
|
_cachingProviderFactory = cachingProviderFactory;
|
|
_unitOfWork = unitOfWork;
|
|
_logger = logger;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Performs license lookup to API layer
|
|
/// </summary>
|
|
/// <param name="license"></param>
|
|
/// <returns></returns>
|
|
private async Task<bool> IsLicenseValid(string license)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(license)) return false;
|
|
try
|
|
{
|
|
var response = await (Configuration.KavitaPlusApiUrl + "/api/license/check")
|
|
.WithHeader("Accept", "application/json")
|
|
.WithHeader("User-Agent", "Kavita")
|
|
.WithHeader("x-license-key", license)
|
|
.WithHeader("x-installId", HashUtil.ServerToken())
|
|
.WithHeader("x-kavita-version", BuildInfo.Version)
|
|
.WithHeader("Content-Type", "application/json")
|
|
.WithTimeout(TimeSpan.FromSeconds(Configuration.DefaultTimeOutSecs))
|
|
.PostJsonAsync(new LicenseValidDto()
|
|
{
|
|
License = license,
|
|
InstallId = HashUtil.ServerToken()
|
|
})
|
|
.ReceiveString();
|
|
return bool.Parse(response);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.LogError(e, "An error happened during the request to Kavita+ API");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register the license with KavitaPlus
|
|
/// </summary>
|
|
/// <param name="license"></param>
|
|
/// <param name="email"></param>
|
|
/// <returns></returns>
|
|
private async Task<string> RegisterLicense(string license, string email)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(license) || string.IsNullOrWhiteSpace(email)) return string.Empty;
|
|
try
|
|
{
|
|
var response = await (Configuration.KavitaPlusApiUrl + "/api/license/register")
|
|
.WithHeader("Accept", "application/json")
|
|
.WithHeader("User-Agent", "Kavita")
|
|
.WithHeader("x-license-key", license)
|
|
.WithHeader("x-installId", HashUtil.ServerToken())
|
|
.WithHeader("x-kavita-version", BuildInfo.Version)
|
|
.WithHeader("Content-Type", "application/json")
|
|
.WithTimeout(TimeSpan.FromSeconds(Configuration.DefaultTimeOutSecs))
|
|
.PostJsonAsync(new EncryptLicenseDto()
|
|
{
|
|
License = license.Trim(),
|
|
InstallId = HashUtil.ServerToken(),
|
|
EmailId = email.Trim()
|
|
})
|
|
.ReceiveJson<RegisterLicenseResponseDto>();
|
|
|
|
if (response.Successful)
|
|
{
|
|
return response.EncryptedLicense;
|
|
}
|
|
|
|
_logger.LogError("An error happened during the request to Kavita+ API: {ErrorMessage}", response.ErrorMessage);
|
|
throw new KavitaException(response.ErrorMessage);
|
|
}
|
|
catch (FlurlHttpException e)
|
|
{
|
|
_logger.LogError(e, "An error happened during the request to Kavita+ API");
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks licenses and updates cache
|
|
/// </summary>
|
|
/// <remarks>Expected to be called at startup and on reoccurring basis</remarks>
|
|
public async Task ValidateLicenseStatus()
|
|
{
|
|
try
|
|
{
|
|
var license = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey);
|
|
if (string.IsNullOrEmpty(license.Value)) return;
|
|
|
|
_logger.LogInformation("Validating Kavita+ License");
|
|
var provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.License);
|
|
await provider.FlushAsync();
|
|
|
|
var isValid = await IsLicenseValid(license.Value);
|
|
await provider.SetAsync(CacheKey, isValid, _licenseCacheTimeout);
|
|
|
|
_logger.LogInformation("Validating Kavita+ License - Complete");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "There was an error talking with Kavita+ API for license validation. Rescheduling check in 30 mins");
|
|
BackgroundJob.Schedule(() => ValidateLicenseStatus(), TimeSpan.FromMinutes(30));
|
|
}
|
|
}
|
|
|
|
public async Task RemoveLicense()
|
|
{
|
|
var serverSetting = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey);
|
|
serverSetting.Value = string.Empty;
|
|
_unitOfWork.SettingsRepository.Update(serverSetting);
|
|
await _unitOfWork.CommitAsync();
|
|
var provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.License);
|
|
await provider.RemoveAsync(CacheKey);
|
|
}
|
|
|
|
public async Task AddLicense(string license, string email)
|
|
{
|
|
var serverSetting = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey);
|
|
var lic = await RegisterLicense(license, email);
|
|
if (string.IsNullOrWhiteSpace(lic))
|
|
throw new KavitaException("Unable to register license due to error. Reach out to Kavita+ Support");
|
|
serverSetting.Value = lic;
|
|
_unitOfWork.SettingsRepository.Update(serverSetting);
|
|
await _unitOfWork.CommitAsync();
|
|
}
|
|
|
|
public async Task<bool> HasActiveLicense(bool forceCheck = false)
|
|
{
|
|
var provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.License);
|
|
if (!forceCheck)
|
|
{
|
|
var cacheValue = await provider.GetAsync<bool>(CacheKey);
|
|
if (cacheValue.HasValue) return cacheValue.Value;
|
|
}
|
|
|
|
try
|
|
{
|
|
var serverSetting = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey);
|
|
var result = await IsLicenseValid(serverSetting.Value);
|
|
await provider.FlushAsync();
|
|
await provider.SetAsync(CacheKey, result, _licenseCacheTimeout);
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "There was an issue connecting to Kavita+");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|