Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kavita+ Reset License & Discord Integration #2516

Merged
merged 6 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 24 additions & 24 deletions API/Controllers/LicenseController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using API.Extensions;
using API.Services;
using API.Services.Plus;
using Kavita.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
Expand All @@ -17,22 +16,13 @@ namespace API.Controllers;

#nullable enable

public class LicenseController : BaseApiController
public class LicenseController(
IUnitOfWork unitOfWork,
ILogger<LicenseController> logger,
ILicenseService licenseService,
ILocalizationService localizationService)
: BaseApiController
{
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger<LicenseController> _logger;
private readonly ILicenseService _licenseService;
private readonly ILocalizationService _localizationService;

public LicenseController(IUnitOfWork unitOfWork, ILogger<LicenseController> logger,
ILicenseService licenseService, ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_logger = logger;
_licenseService = licenseService;
_localizationService = localizationService;
}

/// <summary>
/// Checks if the user's license is valid or not
/// </summary>
Expand All @@ -41,7 +31,7 @@ public LicenseController(IUnitOfWork unitOfWork, ILogger<LicenseController> logg
[ResponseCache(CacheProfileName = ResponseCacheProfiles.LicenseCache)]
public async Task<ActionResult<bool>> HasValidLicense(bool forceCheck = false)
{
return Ok(await _licenseService.HasActiveLicense(forceCheck));
return Ok(await licenseService.HasActiveLicense(forceCheck));
}

/// <summary>
Expand All @@ -54,22 +44,32 @@ public async Task<ActionResult<bool>> HasValidLicense(bool forceCheck = false)
public async Task<ActionResult<bool>> HasLicense()
{
return Ok(!string.IsNullOrEmpty(
(await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value));
(await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value));
}

[Authorize("RequireAdminRole")]
[HttpDelete]
[ResponseCache(CacheProfileName = ResponseCacheProfiles.LicenseCache)]
public async Task<ActionResult> RemoveLicense()
{
_logger.LogInformation("Removing license on file for Server");
var setting = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey);
logger.LogInformation("Removing license on file for Server");
var setting = await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey);
setting.Value = null;
_unitOfWork.SettingsRepository.Update(setting);
await _unitOfWork.CommitAsync();
unitOfWork.SettingsRepository.Update(setting);
await unitOfWork.CommitAsync();
return Ok();
}

[Authorize("RequireAdminRole")]
[HttpPost("reset")]
public async Task<ActionResult> ResetLicense(UpdateLicenseDto dto)
{
logger.LogInformation("Resetting license on file for Server");
if (await licenseService.ResetLicense(dto.License, dto.Email)) return Ok();

return BadRequest(localizationService.Translate(User.GetUserId(), "unable-to-reset-k+"));
}

/// <summary>
/// Updates server license
/// </summary>
Expand All @@ -81,11 +81,11 @@ public async Task<ActionResult> UpdateLicense(UpdateLicenseDto dto)
{
try
{
await _licenseService.AddLicense(dto.License.Trim(), dto.Email.Trim());
await licenseService.AddLicense(dto.License.Trim(), dto.Email.Trim(), dto.DiscordId);
}
catch (Exception ex)
{
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
return BadRequest(await localizationService.Translate(User.GetUserId(), ex.Message));
}
return Ok();
}
Expand Down
1 change: 1 addition & 0 deletions API/DTOs/License/EncryptLicenseDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ public class EncryptLicenseDto
public required string License { get; set; }
public required string InstallId { get; set; }
public required string EmailId { get; set; }
public string? DiscordId { get; set; }
}
8 changes: 8 additions & 0 deletions API/DTOs/License/ResetLicenseDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace API.DTOs.License;

public class ResetLicenseDto
{
public required string License { get; set; }
public required string InstallId { get; set; }
public required string EmailId { get; set; }
}
4 changes: 4 additions & 0 deletions API/DTOs/License/UpdateLicenseDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ public class UpdateLicenseDto
/// Email registered with Stripe
/// </summary>
public required string Email { get; set; }
/// <summary>
/// Optional DiscordId
/// </summary>
public string? DiscordId { get; set; }
}
1 change: 1 addition & 0 deletions API/I18N/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@

"not-authenticated": "User is not authenticated",
"unable-to-register-k+": "Unable to register license due to error. Reach out to Kavita+ Support",
"unable-to-reset-k+": "Unable to reset Kavita+ license due to error. Reach out to Kavita+ Support",
"anilist-cred-expired": "AniList Credentials have expired or not set",
"scrobble-bad-payload": "Bad payload from Scrobble Provider",
"theme-doesnt-exist": "Theme file missing or invalid",
Expand Down
51 changes: 46 additions & 5 deletions API/Services/Plus/LicenseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ public interface ILicenseService
{
Task ValidateLicenseStatus();
Task RemoveLicense();
Task AddLicense(string license, string email);
Task AddLicense(string license, string email, string? discordId);
Task<bool> HasActiveLicense(bool forceCheck = false);
Task<bool> ResetLicense(string license, string email);
}

public class LicenseService : ILicenseService
Expand Down Expand Up @@ -87,7 +88,7 @@ private async Task<bool> IsLicenseValid(string license)
/// <param name="license"></param>
/// <param name="email"></param>
/// <returns></returns>
private async Task<string> RegisterLicense(string license, string email)
private async Task<string> RegisterLicense(string license, string email, string? discordId)
{
if (string.IsNullOrWhiteSpace(license) || string.IsNullOrWhiteSpace(email)) return string.Empty;
try
Expand All @@ -104,7 +105,8 @@ private async Task<string> RegisterLicense(string license, string email)
{
License = license.Trim(),
InstallId = HashUtil.ServerToken(),
EmailId = email.Trim()
EmailId = email.Trim(),
DiscordId = discordId?.Trim()
})
.ReceiveJson<RegisterLicenseResponseDto>();

Expand Down Expand Up @@ -164,10 +166,10 @@ public async Task RemoveLicense()
await provider.RemoveAsync(CacheKey);
}

public async Task AddLicense(string license, string email)
public async Task AddLicense(string license, string email, string? discordId)
{
var serverSetting = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey);
var lic = await RegisterLicense(license, email);
var lic = await RegisterLicense(license, email, discordId);
if (string.IsNullOrWhiteSpace(lic))
throw new KavitaException("unable-to-register-k+");
serverSetting.Value = lic;
Expand Down Expand Up @@ -199,4 +201,43 @@ public async Task<bool> HasActiveLicense(bool forceCheck = false)

return false;
}

public async Task<bool> ResetLicense(string license, string email)
{
try
{
var encryptedLicense = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey);
var response = await (Configuration.KavitaPlusApiUrl + "/api/license/reset")
.WithHeader("Accept", "application/json")
.WithHeader("User-Agent", "Kavita")
.WithHeader("x-license-key", encryptedLicense.Value)
.WithHeader("x-installId", HashUtil.ServerToken())
.WithHeader("x-kavita-version", BuildInfo.Version)
.WithHeader("Content-Type", "application/json")
.WithTimeout(TimeSpan.FromSeconds(Configuration.DefaultTimeOutSecs))
.PostJsonAsync(new ResetLicenseDto()
{
License = license.Trim(),
InstallId = HashUtil.ServerToken(),
EmailId = email
})
.ReceiveString();

if (string.IsNullOrEmpty(response))
{
var provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.License);
await provider.RemoveAsync(CacheKey);
return true;
}

_logger.LogError("An error happened during the request to Kavita+ API: {ErrorMessage}", response);
throw new KavitaException(response);
}
catch (FlurlHttpException e)
{
_logger.LogError(e, "An error happened during the request to Kavita+ API");
}

return false;
}
}
31 changes: 14 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ your reading collection with your friends and family!
[![Release](https://img.shields.io/github/release/Kareadita/Kavita.svg?style=flat&maxAge=3600)](https://github.com/Kareadita/Kavita/releases)
[![License](https://img.shields.io/badge/license-GPLv3-blue.svg?style=flat)](https://github.com/Kareadita/Kavita/blob/master/LICENSE)
[![Downloads](https://img.shields.io/github/downloads/Kareadita/Kavita/total.svg?style=flat)](https://github.com/Kareadita/Kavita/releases)
[![Docker Pulls](https://img.shields.io/docker/pulls/kizaing/kavita.svg)](https://hub.docker.com/r/jvmilazz0/kavita)
[![Docker Pulls](https://img.shields.io/docker/pulls/jvmilazz0/kavita.svg)](https://hub.docker.com/r/jvmilazz0/kavita)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=Kareadita_Kavita&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=Kareadita_Kavita)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=Kareadita_Kavita&metric=security_rating)](https://sonarcloud.io/dashboard?id=Kareadita_Kavita)
[![Backers on Open Collective](https://opencollective.com/kavita/backers/badge.svg)](#backers)
Expand All @@ -35,12 +35,11 @@ your reading collection with your friends and family!


## Support
[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/KavitaManga/)
[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://discord.gg/eczRp9eeem)
[![GitHub - Bugs Only](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Kareadita/Kavita/issues)

## Demo
If you want to try out Kavita, we have a demo up:
If you want to try out Kavita, a demo is available:
[https://demo.kavitareader.com/](https://demo.kavitareader.com/)
```
Username: demouser
Expand All @@ -52,8 +51,6 @@ The easiest way to get started is to visit our Wiki which has up-to-date informa
install methods and platforms.
[https://wiki.kavitareader.com/en/install](https://wiki.kavitareader.com/en/install)

**Note: Kavita is under heavy development and is being updated all the time, so the tag for bleeding edge builds is `:nightly`. The `:latest` tag will be the latest stable release.**

## Feature Requests
Got a great idea? Throw it up on our [Feature Request site](https://feats.kavitareader.com/), [Feature Discord Channel](https://discord.com/channels/821879810934439936/1164375153493422122) or vote on another idea. Please check the [Project Board](https://github.com/Kareadita/Kavita/projects?type=classic) first for a list of planned features before you submit an idea.

Expand All @@ -71,7 +68,18 @@ expenses related to Kavita. Back us through [OpenCollective](https://opencollect

If you are interested, you can use the promo code `FIRSTTIME` for your initial signup for a 50% discount on the first month (2$). This can be thought of as donating to Kavita's development and getting some sweet features out of it.

**If you already contribute via OpenCollective, please reach out to me for a provisioned license.**
**If you already contribute via OpenCollective, please reach out to majora2007 for a provisioned license.**

## Localization
Thank you to [Weblate](https://hosted.weblate.org/engage/kavita/) who hosts our localization infrastructure pro-bono. If you want to see Kavita in your language, please help us localize.

<a href="https://hosted.weblate.org/engage/kavita/">
<img src="https://hosted.weblate.org/widget/kavita/horizontal-auto.svg" alt="Translation status" />
</a>

## PikaPods
If you are looking to try your hand at self-hosting but lack the machine, [PikaPods](https://www.pikapods.com/pods?run=kavita) is a great service that
allows you to easily spin up a server. 20% of app revenues are contributed back to Kavita via OpenCollective.


## Contributors
Expand Down Expand Up @@ -103,17 +111,6 @@ Thank you to [<img src="/Logo/jetbrains.svg" alt="" width="32"> JetBrains](http:
* [<img src="/Logo/rider.svg" alt="" width="32"> Rider](http://www.jetbrains.com/rider/)
* [<img src="/Logo/dottrace.svg" alt="" width="32"> dotTrace](http://www.jetbrains.com/dottrace/)

## Localization
Thank you to [Weblate](https://hosted.weblate.org/engage/kavita/) who hosts our localization infrastructure pro-bono. If you want to see Kavita in your language, please help us localize.

<a href="https://hosted.weblate.org/engage/kavita/">
<img src="https://hosted.weblate.org/widget/kavita/horizontal-auto.svg" alt="Translation status" />
</a>

## PikaPods
If you are looking to try your hand at self-hosting but lack the machine, [PikaPods](https://www.pikapods.com/pods?run=kavita) is a great service that
allows you to easily spin up a server. 20% of app revenues are contributed back to Kavita via OpenCollective.

### License

* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
Expand Down
Loading
Loading