hearth / license-client
Laravel helper package that verifies a license key against master-data.ro and stores the result locally.
Package info
github.com/master-data-ro/client-laravel-hearth.master-data.ro
pkg:composer/hearth/license-client
Requires
- php: >=8.0
README
Compatibilitate
- PHP:
^8.2(aliniat cu Laravel 11+). - Laravel:
11.x,12.xși13.x(constrângeri explicite pe componenteleilluminate/*folosite de pachet). - Auto-descoperire:
extra.laravel.providersdincomposer.jsonal pachetului (același mecanism pe 11–13). Dacăpackage:discovernu rulează pe server, vezi secțiunea de troubleshooting de mai jos.
Instalare / Installation
Versiune minimă recomandată: ^0.1.1. Dacă în composer.json ai ^0.1.0, Composer poate instala exact 0.1.0, unde nu există comanda license-client:status (și alte îmbunătățiri). Folosește:
composer require hearth/license-client:^0.1.1
sau, dacă pachetul e deja listat, schimbă constrângerea la ^0.1.1 (sau ^0.1.4) și rulează:
composer update hearth/license-client php artisan package:discover --ansi
Eroare: There are no commands defined in the "license-client" namespace
În hearth/license-client v0.1.0 nu există namespace-ul Artisan license-client: — este normal. Comenzile din pachet sunt doar cele legate de make:license-server (MakeLicenseServerCommand). Folosește tabelul din secțiunea Comenzi Artisan: make:license-server mai jos.
Dacă vrei și php artisan license-client:status (diagnostic), actualizează la ≥ 0.1.1 (vezi Instalare).
- Verifică versia instalată:
composer show hearth/license-client(sau încomposer.lockcâmpulversion). - Dacă rămâi pe
0.1.0, nu rulalicense-client:*; folosește doarmake:license-server. Pentru comenzilicense-client:*, schimbă constrângerea la^0.1.1șicomposer update hearth/license-client. - După update:
php artisan package:discoverșiphp artisan optimize:clear. Dacă providerul nu e descoperit, adaugă-l manual înbootstrap/providers.php(vezi mai jos).
Dacă după composer require site-ul merge fără licență
Laravel încarcă provider-ul pachetului doar după php artisan package:discover. La tine Composer raportează scripturi care nu există, de exemplu:
You made a reference to a non-existent script @/usr/local/bin/php artisan package:discover
În acest caz descoperirea pachetelor nu s-a rulat — provider-ul nu e înregistrat și middleware-ul nu se activează.
Pași:
- În
composer.jsonal aplicației (nu al pachetului), lascripts→post-autoload-dump, înlocuiește@/usr/local/bin/phpcu@php(sau calea reală către binarul PHP de pe server, ex./usr/bin/php). - Din directorul proiectului, rulează manual:
php artisan package:discover --ansi php artisan optimize:clear
Dacă package:discover a rulat dar încă nu vezi efect, șterge cache-ul vechi de manifest și redescoperă:
rm -f bootstrap/cache/packages.php php artisan package:discover --ansi php artisan optimize:clear
După actualizare la ≥ 0.1.1, poți rula diagnosticul:
php artisan license-client:status
404 pe /licenta sau pagina UI nu se încarcă
- Cache rute: dacă rulezi
php artisan route:cache, rutele pachetului trebuie incluse în acel cache. Dupăcomposer update/ prima instalare a pachetului, un cache vechi poate să nu conțină rutele — ruleazăphp artisan route:clear(sau regenereazăroute:cachedupă ce pachetul e instalat). - Subdirector: dacă aplicația e servită sub un path (ex.
https://exemplu.ro/myapp), seteazăAPP_URL(șiconfig('app.url')) la URL-ul complet cu acel path. Rutele UI se înregistrează atunci la…/myapp/licenta(nu la rădăcină domeniului). Cu pachet ≥ 0.1.1, comandaphp artisan license-client:statusafișează și „Cale UI activare licență”. - Provider manual: dacă tot nu merge, înregistrează provider-ul în
bootstrap/providers.php(Laravel 11, 12, 13):
Hearth\LicenseClient\LicenseServiceProvider::class,
Pentru testare locală, poți adăuga un repository de tip path:
"repositories": [ { "type": "vcs", "url": "https://github.com/master-data-ro/hearth-license-client.git" } ]
Comenzi Artisan: make:license-server
Această comandă există din v0.1.0 și rămâne comanda principală de verificare / salvare din CLI:
| Acțiune | Comandă |
|---|---|
Prima solicitare pe domeniu (cheie provizorie aleatoare; trimite APP_URL → host către Hearth) |
php artisan make:license-server CHEIE-ALEATOARE |
| Verificare / salvare cu cheia emisă de autoritate | php artisan make:license-server CHEIA-DEFINITIVA |
| Afișează licența salvată (decrypt, JSON în consolă) | php artisan make:license-server --show |
Opțional: --passphrase= pentru derivarea cheii de criptare (implicit se folosește APP_KEY / fluxul din Encryption).
Comenzi license-client:* (doar ≥ 0.1.1)
php artisan license-client:status— diagnostic (manifest, provider, cache rute, cale UI/licenta).php artisan license-client:probe— verificare one-shot fără browser: reachability JWKS, PEM, POST /api/verify (corp de probă), versiunea dincomposer.jsona pachetului, existențastorage/license.json, rezumatLicenseState, calea efectivăPackage::licenseActivationBasePath()(inclusiv prefix dinAPP_URL). Pentru CI / deploy, folosește--json(ieșire JSON stabilă pe stdout).php artisan license-client:sync— re-verifică licența salvată la autoritate și rescriestorage/license.json(fără cheie în argument; dacă nu există fișier, nu face nimic). Folosit și de scheduler.- Alte îmbunătățiri depind de versiune; vezi changelog / tag-uri pe GitHub.
Scheduler (cron la 5 minute)
Pachetul înregistrează automat în Laravel license-client:sync --quiet-sync la everyFiveMinutes(), cu withoutOverlapping(5) (minute), ca licența dezactivată la autoritate să se reflecte la client fără acțiune manuală.
Pe server trebuie rulat scheduler-ul Laravel (o dată pe minut), de exemplu:
* * * * * cd /calea/proiectului && php artisan schedule:run >> /dev/null 2>&1
Pe instalare autoritate (același host ca Package::authorityUrl() sau există storage/keys/private.pem), comanda nu apelează autoritatea (iese imediat).
Manual: php artisan license-client:sync (mesaje în consolă) sau php artisan license-client:sync --quiet-sync.
Utilizare / Usage
- Prima solicitare: pe server, din rădăcina proiectului, rulează
php artisan make:license-servercu o cheie aleatoare (orice șir unic); autoritatea primește domeniul dinAPP_URL. După emitere, aceeași comandă cu cheia primită salvează licența definitivă.
php artisan make:license-server cheie-random
# … după ce primiți cheia de la Hearth / furnizor:
php artisan make:license-server CHEIA-EMISA
Dacă site-ul nu se blochează fără licență și ai ≥ 0.1.1, rulează php artisan license-client:status. Pe 0.1.0 verifică package:discover, bootstrap/providers.php și existența storage/license.json manual sau cu make:license-server --show.
-
La succes, pachetul salvează
storage/license.json(criptat). -
Interfață web Bootstrap 5 (autonomă, fără
layouts.app): ruta/licenta(sau cu prefix dinAPP_URL) — în versiunile recente; pe 0.1.0 folosiți CLI-ul. Solicitare pe domeniu: buton care trimitePOST /licenta/solicita(cheie provizorie generată pe server dacă nu există încă una salvată; altfel reîntreabă autoritatea pentru stadiu). Alternativ:make:license-server+ cheie aleatoare din SSH. Activare cu cheia emisă: formular sau din nou comanda. Nu există link public către portalul autorității; endpoint-urile rămân în cod (Package). -
Middleware-ul
EnsureHasValidLicenseeste înregistrat automat de provider (global pe kernel). Fără licență validă, cererile HTML sunt redirecționate spre pagina de activare; răspunsurile JSON pot primi 403 culicense_code(excepții: consolă, mod autoritate, rute whitelist — vezi Notă enforcement).
Cum funcționează (Principiul "ping-pong")
- Clientul trimite către autoritate (hearth.master-data.ro) domeniul (din
APP_URL) și o cheie — la prima solicitare cheia poate fi aleatoare; după emitere se folosește cheia primită:php artisan make:license-server CHEIE-ALEATOARE-SAU-EMISA
- Autoritatea verifică cheia și domeniul:
- Dacă licența este validă, răspunde cu un payload semnat și criptat, ce conține metadatele licenței.
- Dacă licența nu există sau necesită aprobare, răspunde cu un mesaj de pending/în așteptare.
- Dacă licența este invalidă, răspunde cu eroare și motiv.
- Clientul verifică semnătura autorității (folosind cheia publică) și salvează local payload-ul, criptat automat (fără setări suplimentare).
- Middleware-ul pachetului blochează accesul la aplicație până când există o licență validă și verificată local.
- Poți re-verifica oricând licența locală cu autoritatea (din UI sau CLI) pentru a actualiza statusul.
Acest flux asigură că doar licențele validate de autoritate pot debloca aplicația, iar orice modificare locală este detectată și blocată.
Flux
- Clientul (comandă/Interfață): Trimite cheie + domeniu către autoritate
- Autoritatea (hearth.master-data.ro): Răspunde cu status (valid/pending/invalid) + semnătură
- Clientul: Verifică semnătura, salvează local fișierul de licență criptat
- Middleware: Verifică la fiecare request dacă licența este validă
- Aplicația Laravel: Permite acces doar dacă licența este validă
Flux simplificat:
Client → Autoritate → Client → Middleware → Aplicație
- Prima cerere (domeniu):
make:license-server+ cheie provizorie (SSH) → răspuns semnat (pending/valid) → salvare locală dacă se returnează payload - Activare finală: aceeași comandă sau UI cu cheia emisă → enforcement → acces
Detalii suplimentare
- Verificare periodică: Poți re-verifica licența oricând (din UI sau CLI) pentru a actualiza statusul fără a reinstala.
- Pending/În aprobare: Dacă autoritatea răspunde cu pending, aplicația va afișa statusul "În aprobare" și va bloca funcționalitatea până la aprobare.
- Securitate: Orice modificare manuală a fișierului de licență va fi detectată și va bloca accesul.
- Push automat: Autoritatea poate trimite licențe noi/actualizate direct către endpoint-ul clientului.
- Fără configurare la client: Pachetul este blocat; nu există
config/license-client.phpși nu se pot schimba endpoint-uri sau comportamente. Singurul lucru setabil este cheia de licență, introdusă din UI sau CLI. - Debug: Mesajele de la autoritate sunt afișate clar în UI pentru transparență.
Securitate / Security
- Licența este salvată local, criptată în mod implicit; nu sunt necesare parole sau variabile suplimentare.
- Cheia publică a autorității este preluată automat de la:
https://hearth.master-data.ro/keys/pem.
Notă enforcement
- Middleware-ul de enforcement nu poate fi dezactivat. Fără licență validă, cererile HTML sunt redirecționate către pagina de activare (ruta
license-client.licenta.index, calea:Package::licenseActivationBasePath()); cererile care așteaptă JSON (Accept: application/json/expectsJson(), tipic API-uri, Inertia, unele SPA) primesc 403 cu corp și headere stabile (vezi mai jos). Cu licență activă,/licentaredirecționează la/. Alte rute permise implicit: health, JWKS, push-license etc. (veziPackage::whitelist()).
Contract stabil: HTTP 403 JSON (blocare licență)
Pentru frontend-uri SPA / Inertia / API, tratați uniform răspunsul 403:
Corp JSON (câmpurile message și license_code sunt mereu prezente):
{
"message": "…",
"license_code": "missing"
}
license_code: cod intern al stării (ex.missing,invalid,not_active,expired,domain_mismatch) — același cod ca înLicenseState::resolve()['code'].retry_after: opțional; număr întreg de secunde (ex. rate limiting viitor). Dacă lipsește din corp, nu presupuneți retry.
Headere HTTP (comune cu răspunsurile de tip „health” unde se expune starea):
| Header | Rol |
|---|---|
X-License-Code |
Același cod ca license_code din corp (comod pentru interceptor fără parsare JSON). |
X-License-Ok |
0 la blocare. |
Constantele din cod: Hearth\LicenseClient\Package::HEADER_LICENSE_CODE, Package::HEADER_LICENSE_OK. Helper corp: Package::licenseForbiddenJsonBody().
Dacă în viitor apare retry_after în corp, un client poate folosi și header-ul standard Retry-After când este trimis de server (alinieri viitoare); până atunci, bazați-vă pe corpul JSON de mai sus.
Testare (pachet)
În monorepo-ul pachetului, după composer install:
composer test
Se folosește Orchestra Testbench și PHPUnit. În phpunit.xml este setat HEARTH_SKIP_JWKS_BOOT=1 ca pachetul să nu depindă de JWKS live la boot în timpul testelor (util și în CI pentru suite rapide și deterministe), iar APP_KEY în format base64:… (32 octeți) pentru ca cifrarea Laravel din timpul request-urilor de test să fie validă. Pentru integrare reală contra autorității, rulați license-client:probe în mediul țintă.
Linkuri utile
© 2025-2026 master-data.ro