Files
PS_Launcher/src/PSLauncher.Core/Licensing/ILicenseService.cs
j.foucher 07d58a8854 Licensing: 100-day offline cache with anti-rollback + cached server status
- CachedValidityDays bumped 7 → 100 days. Covers the "user goes online once
  every 3 months for updates" use case without repeatedly nagging for re-auth.
- LastValidationAt now uses response.ServerTime (signed Ed25519 by the server)
  rather than DateTime.UtcNow, so the client's local clock can't be used to
  forge a fresh validation date.
- LastSeenUtc monotonic counter, persisted on every launch as max(stored, now).
  Combined with a 1h grace window: if the user rolls their PC clock backward
  to extend the offline cache, the rollback is detected (now < LastSeen - 1h)
  and the cache is invalidated → forced re-validation next time online.
- CachedStatus persists the server-returned status (valid/expired/revoked/
  invalid/machine_limit_exceeded) so a revocation done while the user is
  offline still shows correctly when they next launch with cached data.
  Auto-override to "expired" if entitlement date has passed (handles the
  valid → expired transition without needing a server round-trip).
- ILicenseService.GetDecryptedKey() exposed for the new auto-revalidation flow.

This puts the trust boundary in the right place: cached data is informational
offline, the actual download authorization always re-validates against the
server (the gate to download is /api/download-url/, which checks the live
license state on each call). User can't fake offline-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:15:49 +02:00

29 lines
1.1 KiB
C#

using PSLauncher.Models;
namespace PSLauncher.Core.Licensing;
public interface ILicenseService
{
Task<LicenseValidationResponse> ValidateAsync(string licenseKey, CancellationToken ct);
LicenseValidationResponse? GetCached();
void SaveCached(string licenseKey, LicenseValidationResponse response);
void Clear();
bool HasLicense();
string GetMachineId();
bool CanDownloadVersion(LicenseValidationResponse? license, VersionManifest version);
/// <summary>
/// Demande au serveur une URL HMAC-signée valide 1 h pour le ZIP de cette version.
/// Retourne null si l'endpoint n'est pas dispo / pas configuré (ancien serveur),
/// auquel cas l'appelant retombe sur l'URL publique du manifest.
/// </summary>
Task<string?> GetSignedDownloadUrlAsync(string version, CancellationToken ct);
/// <summary>
/// Décrypte la clé de license stockée en cache (DPAPI CurrentUser). Retourne null
/// si pas de clé stockée ou si le déchiffrement échoue (cache déplacé vers une
/// autre machine ou autre user Windows).
/// </summary>
string? GetDecryptedKey();
}