v0.26.0 — Channels per-license + tag BÊTA sur versions
DEUX features liées :
1. CHANNELS : chaque license peut être attribuée à un manifest distinct
(« channel »). Permet de servir des versions différentes selon le
client. Le serveur lit ?channel=X et sert manifest/versions-{X}.json
avec fallback transparent sur versions.json. NULL = default.
2. BÊTA : nouveau flag isBeta + betaNotes par version dans le manifest.
Visible uniquement par les licenses avec can_see_betas=1. Affichage
d'une pill orange « BÊTA » sur la row + tooltip avec les notes pour
les testeurs. Les installations locales déjà présentes restent
visibles même si l'accès BÊTA est retiré ensuite (on n'efface pas
le disque du client).
DB :
002_channel_betas.sql ajoute channel + can_see_betas sur licenses.
Idempotent (ALTER TABLE IF NOT EXISTS), zero data migration.
Serveur PHP :
- ValidateLicense.php signe channel + canSeeBetas dans la réponse
(ordre des clés CRITIQUE pour matcher le canonical client).
- Manifest.php : whitelist regex anti-traversal sur ?channel=, fallback
silencieux sur versions.json si channel inconnu (évite leak de la
liste de channels par probing).
- SignManifest.php prend un channel optionnel → l'admin peut signer
chaque manifest indépendamment.
- admin/licenses.php : dropdown channel + checkbox bêta sur create,
bouton détails repliable par-row pour edit.
- admin/versions.php : channel switcher en tête, badge BÊTA sur chaque
row, dialog repliable « Bêta » avec checkbox + notes des testeurs.
Client C# :
- License.Channel + License.CanSeeBetas (dans le canonical signé).
- VersionManifest.IsBeta + BetaNotes.
- ManifestService prend un channelProvider via DI, lu depuis license
cachée à chaque fetch (lazy, pas de circular dep).
- MainViewModel.RebuildList filtre les versions IsBeta si !CanSeeBetas
(mais conserve les installées locales — on ne retire pas l'accès
rétroactivement à ce qui est déjà sur disque).
- VersionRowViewModel : props IsBeta / BetaNotes / BetaTooltip.
- MainWindow.xaml : pill orange à côté du n° version pour le featured
et les rows compactes, tooltip dynamique avec les notes testeurs.
Backward compat signature :
Anciennes licenses cachées (signées sans channel/canSeeBetas) sont
toujours validées via un fallback canonical legacy dans VerifySignature.
Sans ce fallback, le passage à v0.26 invaliderait toutes les caches
hors-ligne et bloquerait les users en mobilité.
Migration côté admin : jouer 002_channel_betas.sql sur la base, déployer
les fichiers PHP, créer manifest/versions-{channel}.json pour les
nouveaux channels (l'admin versions.php propose un input « Créer/utiliser
un nouveau channel »). Les licenses existantes restent en channel=NULL
= default = comportement actuel.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,7 @@ final class ValidateLicense
|
||||
|
||||
$stmt = $db->prepare(
|
||||
'SELECT id, license_key, owner_name, issued_at, download_entitlement_until,
|
||||
max_machines, revoked_at
|
||||
max_machines, channel, can_see_betas, revoked_at
|
||||
FROM licenses WHERE license_key = ? LIMIT 1'
|
||||
);
|
||||
$stmt->execute([$licenseKey]);
|
||||
@@ -82,6 +82,17 @@ final class ValidateLicense
|
||||
$serverTime = (new \DateTimeImmutable('now', $utc))->format($fmt);
|
||||
$expired = strtotime($lic['download_entitlement_until']) < time();
|
||||
|
||||
// ATTENTION : ordre des clés CRITIQUE pour la signature Ed25519.
|
||||
// Crypto::canonicalJson NE TRIE PAS — il prend l'ordre tel quel. Le client
|
||||
// C# (LicenseService.CanonicalBytesFor) reconstruit le même dictionnaire
|
||||
// dans le même ordre. Toute modif ici doit être miroir côté client.
|
||||
// channel : NULL en DB → null JSON (visible dans le canonical comme "channel":null).
|
||||
// can_see_betas : 0/1 en DB → bool JSON.
|
||||
$channel = isset($lic['channel']) && $lic['channel'] !== null && $lic['channel'] !== ''
|
||||
? (string)$lic['channel']
|
||||
: null;
|
||||
$canSeeBetas = (bool)($lic['can_see_betas'] ?? 0);
|
||||
|
||||
$payload = [
|
||||
'status' => $expired ? 'expired' : 'valid',
|
||||
'licenseId' => 'lic_' . $lic['id'],
|
||||
@@ -89,6 +100,8 @@ final class ValidateLicense
|
||||
'issuedAt' => $issuedAt,
|
||||
'downloadEntitlementUntil' => $entUntil,
|
||||
'maxMachines' => (int)$lic['max_machines'],
|
||||
'channel' => $channel,
|
||||
'canSeeBetas' => $canSeeBetas,
|
||||
'serverTime' => $serverTime,
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user