Vlasnik izlazi iz DAL price za ovaj projekat - postaje samostalan proizvod. Preimenovanje je mehanicko (import putanje, imena fajlova/baze/UI naslova), NIJEDNA linija poslovne logike u internal/handler ili internal/service nije dirana - go build, go vet i go test ./... prolaze cisto pod novim imenom. - go.mod: module dal-license-server -> licence-server; 28 import linija u 13 .go fajlova usaglaseno - internal/config/config.go (+test): default DB_NAME licence_db - HTML sabloni (7 stranica) + 7 Playwright e2e spec fajlova: "DAL License Server" -> "Licence Server" u title/nav-brand, testovi usaglaseni u istom prolazu (nista ne moze da se razdvoji) - package.json/package-lock.json/.gitignore/.claude/project.json: ime projekta/binarnog fajla/work_dir - Usput: package.json je u repository.url nosio OTVORENU Gitea lozinku u cistom tekstu - uklonjena (URL sad bez kredencijala). Lozinka je i dalje u staroj git istoriji - preporuka: rotirati je posebno. - CLAUDE.md/README/API/TESTING/docs/SPEC/ARCHITECTURE/SETUP: naslovi, ASCII dijagrami, git clone URL, mysqldump primer, systemd predlozak u SETUP.md zamenjen stvarnim (obrazac terminia.service - journal log + graceful shutdown, ne stari minimalni predlozak) - Debrendiranje: "Univerzalni licencni server za sve DAL proizvode" -> "za vise proizvoda i klijenata"; potpis "Nenad Djukic / DAL d.o.o." -> "Nenad Djukic". NE dirano: nazivi ESIR/ARV/LIGHT_TICKET (tudji proizvodi, ne DAL brend), "DAL" kao stvaran naziv org-a u RBAC modelu (docs/loggerservice/README.md) - to je podatak, ne branding ovog servera. - docs/asp-terminia/, docs/loggerservice/: prozni pomeni imena servera Baza (dal_license_db -> licence_db) i deploy na 151 idu u posebnom koraku, posle preimenovanja Gitea repoa - vidi CLAUDE.md istorijsku belesku na dnu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
796 lines
28 KiB
Markdown
796 lines
28 KiB
Markdown
# Licence Server — CLAUDE.md
|
||
|
||
## Pregled
|
||
|
||
Univerzalni licencni server za **više proizvoda i klijenata**: ESIR, ARV, Light-Ticket, i buduće aplikacije.
|
||
|
||
Zamenjuje stari `esir-license-server` (koji niko ne koristi u produkciji). Arhitektura prema ARV licencnoj dokumentaciji (`/root/projects/arv/TASKS/Licenciranje-Starter.md`), ali univerzalna — podržava N proizvoda.
|
||
|
||
**Princip:** Licence Server je jedini koji ima RSA private key. Klijentske aplikacije imaju samo public key. Server potpisuje licence, klijenti verifikuju potpis. Niko osim servera ne može da kreira validnu licencu.
|
||
|
||
**KRITIČNO — Testiranje:** Ova aplikacija je osnova budućeg poslovanja. Mora biti **rigorozno testirana** svim poznatim tipovima testova:
|
||
- **Unit testovi** — svaki servis, model, helper, middleware
|
||
- **Integration testovi** — kompletni API flow-ovi (activate → validate → deactivate → re-activate)
|
||
- **Security testovi** — SQL injection, brute force, API key bypass, tampered signatures, invalid inputs
|
||
- **Edge case testovi** — expired licence, grace period granice, perpetual licence, race conditions
|
||
- **Regression testovi** — svaki bug fix mora imati test koji potvrdjuje ispravku
|
||
- **Load testovi** — rate limiting pod pritiskom, konkurentni zahtevi
|
||
- Nikad ne smanjivati pokrivenost testovima. Svaka nova funkcionalnost MORA imati test pre merge-a.
|
||
|
||
---
|
||
|
||
## Kako radi
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ LICENCE SERVER │
|
||
│ port: 8090 │
|
||
│ │
|
||
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
|
||
│ │ Admin API │ │ Klijent │ │ Admin Dashboard │ │
|
||
│ │ (CRUD) │ │ API │ │ (htmx) │ │
|
||
│ └──────────┘ └──────────┘ └──────────────────┘ │
|
||
│ │ │ │ │
|
||
│ └──────────────┼───────────────┘ │
|
||
│ │ │
|
||
│ ┌────────┴────────┐ │
|
||
│ │ MySQL baza │ │
|
||
│ │ license_db │ │
|
||
│ └─────────────────┘ │
|
||
│ │
|
||
│ RSA-2048 Private Key (samo ovde, nikad ne izlazi) │
|
||
└─────────────────────────────────────────────────────┘
|
||
▲ ▲ ▲
|
||
│ HTTPS │ HTTPS │ HTTPS
|
||
│ │ │
|
||
┌────┴────┐ ┌────┴────┐ ┌────┴────────┐
|
||
│ ESIR │ │ ARV │ │ Light-Ticket │
|
||
│ klijent │ │ klijent │ │ klijent │
|
||
└─────────┘ └─────────┘ └──────────────┘
|
||
Svaki ima RSA public key ugrađen u binary
|
||
Svaki čuva license.enc lokalno
|
||
```
|
||
|
||
---
|
||
|
||
## Tok — od kupovine do rada
|
||
|
||
### 1. Admin kreira licencu
|
||
```
|
||
Admin → Dashboard → Nova licenca
|
||
→ Bira proizvod (ESIR / ARV / LIGHT_TICKET)
|
||
→ Unosi: firma, email, tip (MONTHLY/ANNUAL/PERPETUAL), limiti
|
||
→ Server generiše ključ: {PREFIX}-XXXX-XXXX-XXXX-XXXX
|
||
→ Ključ se šalje klijentu (email ili ručno)
|
||
```
|
||
|
||
### 2. Klijent aktivira
|
||
```
|
||
Klijent instalira aplikaciju
|
||
→ Unese licencni ključ u Settings
|
||
→ Aplikacija šalje serveru: ključ + machine_fingerprint + app_version + OS
|
||
→ Server proverava: ključ validan? nije aktiviran drugde? nije istekao?
|
||
→ Server potpisuje licencne podatke RSA private key-em
|
||
→ Vraća: licencni JSON + RSA potpis
|
||
→ Klijent kreira license.enc (AES-256-GCM + RSA potpis)
|
||
→ Aplikacija radi OFFLINE dok licenca važi
|
||
```
|
||
|
||
### 3. Svakodnevni rad (offline)
|
||
```
|
||
Aplikacija se pokrene
|
||
→ Čita license.enc sa diska
|
||
→ Dekriptuje (AES sa machine fingerprint)
|
||
→ Proverava RSA potpis (public key ugrađen u binary)
|
||
→ Proverava fingerprint (isti računar?)
|
||
→ Proverava rok (nije istekao?)
|
||
→ SVE OK → normalan rad
|
||
→ Internet NIJE potreban za svakodnevni rad
|
||
```
|
||
|
||
### 4. Opciona online provera
|
||
```
|
||
Jednom dnevno (ako ima internet):
|
||
→ Aplikacija šalje: ključ + fingerprint
|
||
→ Server proverava: nije revocirana?
|
||
→ Ako je revocirana na serveru → klijent invalidira lokalnu licencu
|
||
→ Ako nema interneta → preskače, radi sa lokalnim fajlom
|
||
```
|
||
|
||
---
|
||
|
||
## Tech stack
|
||
|
||
| Komponenta | Tehnologija |
|
||
|------------|-------------|
|
||
| Backend | Go + net/http (Go 1.22+ routing) |
|
||
| Admin UI | htmx + Go html/template |
|
||
| Baza | MySQL 8.0 |
|
||
| Kripto | RSA-2048 (potpis), AES-256-GCM (enkripcija na klijentima) |
|
||
| Auth | API key za admin API, session za dashboard |
|
||
|
||
---
|
||
|
||
## Go struktura projekta
|
||
|
||
```
|
||
licence-server/
|
||
├── cmd/
|
||
│ └── server/
|
||
│ └── main.go
|
||
├── internal/
|
||
│ ├── config/
|
||
│ │ └── config.go # .env konfiguracija
|
||
│ ├── model/
|
||
│ │ ├── license.go # License, Product, LicenseType
|
||
│ │ ├── activation.go # Activation model
|
||
│ │ ├── audit.go # Audit log model
|
||
│ │ └── request.go # Request/Response structs
|
||
│ ├── repository/
|
||
│ │ ├── license_repo.go # License CRUD
|
||
│ │ ├── activation_repo.go # Activation CRUD
|
||
│ │ └── audit_repo.go # Audit log
|
||
│ ├── service/
|
||
│ │ ├── license_service.go # Poslovna logika
|
||
│ │ ├── activation_service.go # Aktivacija/deaktivacija
|
||
│ │ ├── crypto_service.go # RSA potpisivanje
|
||
│ │ └── keygen.go # Generisanje ključeva
|
||
│ ├── handler/
|
||
│ │ ├── client_handler.go # API za klijentske app-e (activate, deactivate, validate)
|
||
│ │ ├── admin_handler.go # Admin CRUD API
|
||
│ │ ├── dashboard_handler.go # Admin dashboard (htmx)
|
||
│ │ └── helpers.go # JSON/error helpers
|
||
│ ├── middleware/
|
||
│ │ ├── auth.go # API key + session auth
|
||
│ │ └── ratelimit.go # Rate limiting
|
||
│ └── router/
|
||
│ └── router.go
|
||
├── templates/
|
||
│ ├── layout/
|
||
│ │ └── base.html
|
||
│ ├── pages/
|
||
│ │ ├── login.html
|
||
│ │ ├── dashboard.html # Statistike po proizvodu
|
||
│ │ ├── licenses.html # Lista licenci + CRUD
|
||
│ │ ├── license-detail.html # Detalji licence + aktivacije
|
||
│ │ └── audit.html # Audit log pregled
|
||
│ └── partials/
|
||
│ ├── license-row.html
|
||
│ └── stats.html
|
||
├── crypto/
|
||
│ ├── private.pem # RSA private key (NIKAD u git-u!)
|
||
│ └── public.pem # RSA public key (deli se sa klijentima)
|
||
├── migrations/
|
||
│ ├── 001_create_tables.sql
|
||
│ └── 002_seed_products.sql
|
||
├── .env.example
|
||
├── .gitignore
|
||
├── go.mod
|
||
├── README.md
|
||
├── TESTING.md
|
||
└── CLAUDE.md
|
||
```
|
||
|
||
---
|
||
|
||
## Baza podataka (MySQL)
|
||
|
||
### Tabele
|
||
|
||
#### `products`
|
||
| Kolona | Tip | Opis |
|
||
|--------|-----|------|
|
||
| id | BIGINT PK AUTO_INCREMENT | |
|
||
| code | VARCHAR(20) UNIQUE | ESIR, ARV, LIGHT_TICKET |
|
||
| name | VARCHAR(100) | Puno ime proizvoda |
|
||
| key_prefix | VARCHAR(10) | ESIR-, ARV-, LT- |
|
||
| default_limits | JSON | Default limiti za taj proizvod |
|
||
| available_features | JSON | Sve moguće features za taj proizvod |
|
||
| active | BOOLEAN DEFAULT TRUE | |
|
||
| created_at | TIMESTAMP | |
|
||
|
||
**Seed podaci:**
|
||
```sql
|
||
INSERT INTO products (code, name, key_prefix, default_limits, available_features) VALUES
|
||
('ESIR', 'ESIR Fiskalizacija', 'ESIR-',
|
||
'{"max_installations": 1}',
|
||
'["FISCALIZATION", "REPORTS"]'),
|
||
('ARV', 'ARV Evidencija RV', 'ARV-',
|
||
'{"max_employees": 50, "max_readers": 4}',
|
||
'["TIME_ATTENDANCE", "BASIC_REPORTS", "EMPLOYEE_MANAGEMENT", "SHIFTS", "HR_MODULE", "ACCESS_CONTROL"]'),
|
||
('LIGHT_TICKET', 'Light-Ticket', 'LT-',
|
||
'{"max_operators": 3}',
|
||
'["TICKET_VALIDATION", "REPORTS", "EXCEL_EXPORT", "LIVE_FEED"]');
|
||
```
|
||
|
||
**Light-Ticket paketi licenci (odlučeno 03.03.2026):**
|
||
- Admin ručno upisuje `max_operators` prilikom kreiranja licence
|
||
- Nema eksplicitnog `edition` polja — paketi su samo konvencija:
|
||
|
||
| Paket | max_operators | Tip licence | Features |
|
||
|-------|--------------|-------------|----------|
|
||
| Starter | 3 | MONTHLY ili PERPETUAL | Sve |
|
||
| Pro | 10 | MONTHLY ili PERPETUAL | Sve |
|
||
| Enterprise | 0 (neograničeno) | MONTHLY ili PERPETUAL | Sve |
|
||
|
||
- Default u seed-u je `max_operators: 3` (Starter)
|
||
- Sve features su iste za sve pakete — razlikuje se SAMO `max_operators`
|
||
- Tipovi licence za LT: MONTHLY (mesečna) i PERPETUAL (trajna). TRIAL i ANNUAL nisu predviđeni za V1.
|
||
|
||
#### `licenses`
|
||
| Kolona | Tip | Opis |
|
||
|--------|-----|------|
|
||
| id | BIGINT PK AUTO_INCREMENT | |
|
||
| product_id | BIGINT FK(products.id) | Koji proizvod |
|
||
| license_key | VARCHAR(25) UNIQUE | {PREFIX}-XXXX-XXXX-XXXX-XXXX |
|
||
| license_type | VARCHAR(20) NOT NULL | TRIAL / MONTHLY / ANNUAL / PERPETUAL |
|
||
| customer_name | VARCHAR(255) NOT NULL | Naziv firme |
|
||
| customer_pib | VARCHAR(20) | PIB (opciono) |
|
||
| customer_email | VARCHAR(255) | Email |
|
||
| limits | JSON NOT NULL | {"max_employees": 50, "max_readers": 4} |
|
||
| features | JSON NOT NULL | ["TIME_ATTENDANCE", "BASIC_REPORTS"] |
|
||
| issued_at | TIMESTAMP DEFAULT NOW() | |
|
||
| expires_at | TIMESTAMP NULL | NULL = neograničena (PERPETUAL) |
|
||
| grace_days | INT DEFAULT 30 | Koliko dana grace period |
|
||
| active | BOOLEAN DEFAULT TRUE | |
|
||
| revoked | BOOLEAN DEFAULT FALSE | |
|
||
| revoked_at | TIMESTAMP NULL | |
|
||
| revoked_reason | TEXT | |
|
||
| notes | TEXT | Interne beleške |
|
||
| created_at | TIMESTAMP | |
|
||
| updated_at | TIMESTAMP | |
|
||
|
||
**Indeksi:**
|
||
- `idx_licenses_key` na (license_key) — UNIQUE
|
||
- `idx_licenses_product` na (product_id)
|
||
- `idx_licenses_customer` na (customer_name)
|
||
- `idx_licenses_expires` na (expires_at)
|
||
|
||
#### `activations`
|
||
| Kolona | Tip | Opis |
|
||
|--------|-----|------|
|
||
| id | BIGINT PK AUTO_INCREMENT | |
|
||
| license_id | BIGINT FK(licenses.id) | |
|
||
| machine_fingerprint | VARCHAR(100) NOT NULL | sha256:... |
|
||
| hostname | VARCHAR(100) | Ime računara |
|
||
| os_info | VARCHAR(50) | windows / linux |
|
||
| app_version | VARCHAR(20) | Verzija aplikacije |
|
||
| ip_address | VARCHAR(45) | IP pri aktivaciji |
|
||
| activated_at | TIMESTAMP DEFAULT NOW() | |
|
||
| deactivated_at | TIMESTAMP NULL | NULL = aktivna |
|
||
| is_active | BOOLEAN DEFAULT TRUE | |
|
||
| last_seen_at | TIMESTAMP | Poslednja online provera |
|
||
|
||
**Indeksi:**
|
||
- `idx_activations_license` na (license_id)
|
||
- `idx_activations_fingerprint` na (machine_fingerprint)
|
||
- `idx_activations_active` na (license_id, is_active)
|
||
|
||
#### `audit_log`
|
||
| Kolona | Tip | Opis |
|
||
|--------|-----|------|
|
||
| id | BIGINT PK AUTO_INCREMENT | |
|
||
| license_id | BIGINT FK(licenses.id) NULL | |
|
||
| action | VARCHAR(30) NOT NULL | ACTIVATE, DEACTIVATE, VALIDATE, REVOKE, FORCE_RELEASE, CREATE, UPDATE |
|
||
| ip_address | VARCHAR(45) | |
|
||
| details | JSON | Dodatni podaci (fingerprint, hostname, error, itd.) |
|
||
| created_at | TIMESTAMP DEFAULT NOW() | |
|
||
|
||
**Indeksi:**
|
||
- `idx_audit_license` na (license_id)
|
||
- `idx_audit_action` na (action)
|
||
- `idx_audit_created` na (created_at)
|
||
|
||
---
|
||
|
||
## API endpointi
|
||
|
||
### Klijentski API (za ESIR, ARV, Light-Ticket aplikacije)
|
||
|
||
| Metoda | Putanja | Auth | Opis |
|
||
|--------|---------|------|------|
|
||
| POST | `/api/v1/activate` | - | Aktivacija licence |
|
||
| POST | `/api/v1/deactivate` | - | Deaktivacija (transfer) |
|
||
| POST | `/api/v1/validate` | - | Opciona online provera |
|
||
| GET | `/api/v1/check-update` | - | Provera za update aplikacije |
|
||
|
||
#### POST `/api/v1/activate`
|
||
```json
|
||
// Request (od klijentske aplikacije)
|
||
{
|
||
"license_key": "LT-K7M2-9P4N-R3W8-J6T1",
|
||
"machine_fingerprint": "sha256:a1b2c3d4e5f6...",
|
||
"app_version": "1.0.0",
|
||
"os": "windows",
|
||
"hostname": "FIRMA-PC"
|
||
}
|
||
|
||
// Response 200
|
||
{
|
||
"license": {
|
||
"license_key": "LT-K7M2-9P4N-R3W8-J6T1",
|
||
"product": "LIGHT_TICKET",
|
||
"license_type": "MONTHLY",
|
||
"issued_at": "2026-03-01T00:00:00Z",
|
||
"expires_at": "2026-04-01T00:00:00Z",
|
||
"activated_at": "2026-03-03T10:00:00Z",
|
||
"machine_fingerprint": "sha256:a1b2c3d4e5f6...",
|
||
"grace_days": 30,
|
||
"limits": {
|
||
"max_operators": 3
|
||
},
|
||
"features": ["TICKET_VALIDATION", "REPORTS", "EXCEL_EXPORT", "LIVE_FEED"],
|
||
"customer": {
|
||
"name": "Firma DOO",
|
||
"email": "admin@firma.rs"
|
||
}
|
||
},
|
||
"signature": "RSA-SHA256:base64encodedSignature..."
|
||
}
|
||
|
||
// Response 400
|
||
{
|
||
"error": {
|
||
"code": "ALREADY_ACTIVATED",
|
||
"message": "Licenca je već aktivirana na drugom računaru",
|
||
"details": {
|
||
"activated_on": "DRUGA-PC",
|
||
"activated_at": "2026-02-15T08:00:00Z"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Error kodovi:** INVALID_KEY, ALREADY_ACTIVATED, KEY_EXPIRED, KEY_REVOKED, PRODUCT_MISMATCH
|
||
|
||
#### POST `/api/v1/deactivate`
|
||
```json
|
||
// Request
|
||
{
|
||
"license_key": "LT-K7M2-9P4N-R3W8-J6T1",
|
||
"machine_fingerprint": "sha256:a1b2c3d4e5f6..."
|
||
}
|
||
|
||
// Response 200
|
||
{
|
||
"message": "Licenca uspešno deaktivirana",
|
||
"can_reactivate": true
|
||
}
|
||
```
|
||
|
||
#### POST `/api/v1/validate`
|
||
```json
|
||
// Request
|
||
{
|
||
"license_key": "LT-K7M2-9P4N-R3W8-J6T1",
|
||
"machine_fingerprint": "sha256:a1b2c3d4e5f6..."
|
||
}
|
||
|
||
// Response 200
|
||
{
|
||
"valid": true,
|
||
"expires_at": "2026-04-01T00:00:00Z",
|
||
"revoked": false
|
||
}
|
||
```
|
||
|
||
### Admin API
|
||
|
||
| Metoda | Putanja | Auth | Opis |
|
||
|--------|---------|------|------|
|
||
| GET | `/api/v1/admin/products` | API key | Lista proizvoda |
|
||
| GET | `/api/v1/admin/licenses` | API key | Lista licenci (filter po proizvodu) |
|
||
| POST | `/api/v1/admin/licenses` | API key | Kreiraj licencu |
|
||
| GET | `/api/v1/admin/licenses/{id}` | API key | Detalji licence |
|
||
| PUT | `/api/v1/admin/licenses/{id}` | API key | Izmeni licencu |
|
||
| POST | `/api/v1/admin/licenses/{id}/revoke` | API key | Opozovi licencu |
|
||
| POST | `/api/v1/admin/licenses/{id}/release` | API key | Force release (računar crkao) |
|
||
| GET | `/api/v1/admin/licenses/{id}/activations` | API key | Aktivacije za licencu |
|
||
| GET | `/api/v1/admin/audit` | API key | Audit log |
|
||
| GET | `/api/v1/admin/stats` | API key | Statistike |
|
||
|
||
### Admin Dashboard (htmx)
|
||
|
||
| Metoda | Putanja | Auth | Opis |
|
||
|--------|---------|------|------|
|
||
| GET | `/login` | - | Login |
|
||
| POST | `/login` | - | Login submit |
|
||
| GET | `/dashboard` | Session | Početna — statistike po proizvodu |
|
||
| GET | `/licenses` | Session | Tabela licenci + filteri |
|
||
| GET | `/licenses/new` | Session | Forma za novu licencu |
|
||
| POST | `/licenses` | Session | Kreiraj licencu |
|
||
| GET | `/licenses/{id}` | Session | Detalji + aktivacije |
|
||
| POST | `/licenses/{id}/revoke` | Session | Opozovi |
|
||
| POST | `/licenses/{id}/release` | Session | Force release |
|
||
| GET | `/audit` | Session | Audit log |
|
||
|
||
---
|
||
|
||
## RSA ključevi
|
||
|
||
### Generisanje (jednom, čuva se zauvek)
|
||
```bash
|
||
# Generiši private key (ČUVAJ TAJNO)
|
||
openssl genrsa -out crypto/private.pem 2048
|
||
|
||
# Izvuci public key (deli se sa klijentima)
|
||
openssl rsa -in crypto/private.pem -pubout -out crypto/public.pem
|
||
```
|
||
|
||
### Korišćenje
|
||
- **License Server:** koristi `private.pem` za potpisivanje licenci pri aktivaciji
|
||
- **Klijenti (ARV, ESIR, LT):** imaju `public.pem` ugrađen u Go binary (`embed`) za verifikaciju potpisa
|
||
- **Isti par ključeva** za sve proizvode — jedan server, jedan ključ
|
||
|
||
### Šta se potpisuje
|
||
```
|
||
Server prima activate request
|
||
→ Kreira licencni JSON (bez signature polja)
|
||
→ Potpisuje JSON sa RSA-SHA256 (private key)
|
||
→ Dodaje potpis u response
|
||
→ Klijent prima JSON + potpis
|
||
→ Klijent verifikuje potpis (public key)
|
||
→ Klijent enkriptuje JSON sa AES-256-GCM (ključ = machine_fingerprint + app_secret)
|
||
→ Klijent sačuva kao license.enc
|
||
```
|
||
|
||
---
|
||
|
||
## Licencni ključ — format
|
||
|
||
```
|
||
{PREFIX}-XXXX-XXXX-XXXX-XXXX
|
||
|
||
Primeri:
|
||
ESIR-K7M2-9P4N-R3W8-J6T1
|
||
ARV-A3B5-C8D2-E7F4-G9H6
|
||
LT-M4N8-P2Q6-R5S3-T7U9
|
||
```
|
||
|
||
- Prefix po proizvodu: čita se iz `products.key_prefix`
|
||
- 4 grupe po 4 alfanumerička karaktera
|
||
- Karakteri: A-H, J-N, P-Y, 2-9 (bez O/0/I/1 konfuzije)
|
||
- Generisanje: crypto/rand
|
||
|
||
---
|
||
|
||
## Tipovi licenci
|
||
|
||
| Tip | Trajanje | Obnova | Primer |
|
||
|-----|----------|--------|--------|
|
||
| TRIAL | 30 dana | Nema | Besplatno testiranje |
|
||
| MONTHLY | 30 dana | Automatski ili ručno | Light-Ticket |
|
||
| ANNUAL | 365 dana | Ručno | ARV, ESIR |
|
||
| PERPETUAL | Zauvek | Nema (expires_at = NULL) | Kupljena zauvek |
|
||
|
||
### Grace period
|
||
- Default: 30 dana posle isteka
|
||
- Konfigurisano per licenca (`grace_days` kolona)
|
||
- Tokom grace perioda: pun rad + upozorenje na klijentskoj app
|
||
- Posle grace-a: read-only režim (GET dozvoljen, POST/PUT/DELETE blokiran)
|
||
|
||
---
|
||
|
||
## Dashboard — šta prikazuje
|
||
|
||
### Početna strana
|
||
- **Po proizvodu:** broj aktivnih / isteklih / u grace-u / trial
|
||
- **Ukupno:** sve licence, aktivne aktivacije
|
||
- **Alarm:** licence koje ističu u narednih 7 dana
|
||
- **Poslednja aktivnost:** zadnjih 10 akcija iz audit loga
|
||
|
||
### Lista licenci
|
||
- **Filter:** proizvod, status (active/expired/revoked/trial), pretraga po firmi
|
||
- **Kolone:** ključ, firma, proizvod, tip, ističe, aktivacija, status
|
||
- **Sortiranje:** po datumu isteka (najhitnije prvo)
|
||
|
||
### Detalji licence
|
||
- Sve informacije o licenci
|
||
- Lista aktivacija (hostname, OS, verzija, IP, poslednji put viđen)
|
||
- Audit log za tu licencu
|
||
- Akcije: produži, opozovi, force release
|
||
|
||
---
|
||
|
||
## Migracija sa ESIR License Server-a
|
||
|
||
Pošto stari server niko ne koristi u produkciji, migracija je jednostavna:
|
||
|
||
1. Kreiraj novi `licence-server` projekat
|
||
2. Kopiraj korisnu logiku iz `esir-license-server` (keygen, verify flow, helpers)
|
||
3. Proširi model (product, features, limits, RSA)
|
||
4. Zameni port 8090 (isti port, drop-in replacement)
|
||
5. Stari `esir-license-server` → arhiviraj
|
||
|
||
---
|
||
|
||
## Implementacioni taskovi
|
||
|
||
### Faza 0: Priprema
|
||
- **T0-01:** Go projekat (go mod init, struktura foldera)
|
||
- **T0-02:** .env.example, .gitignore, README.md
|
||
- **T0-03:** Git repo na Gitea
|
||
- **T0-04:** Generisanje RSA ključeva (private.pem, public.pem)
|
||
|
||
### Faza 1: Baza
|
||
- **T1-01:** Config modul (.env)
|
||
- **T1-02:** MySQL konekcija
|
||
- **T1-03:** Migracija 001_create_tables.sql (products, licenses, activations, audit_log)
|
||
- **T1-04:** Migracija 002_seed_products.sql (ESIR, ARV, LIGHT_TICKET)
|
||
- **T1-05:** Repository sloj (license_repo, activation_repo, audit_repo)
|
||
|
||
### Faza 2: Core servis
|
||
- **T2-01:** Keygen — generisanje ključa sa prefix-om po proizvodu
|
||
- **T2-02:** Crypto service — RSA potpisivanje licencnog JSON-a
|
||
- **T2-03:** License service — CRUD, validacija, revoke
|
||
- **T2-04:** Activation service — activate, deactivate, force release, validate
|
||
- **T2-05:** Audit logging — svaka akcija se loguje
|
||
|
||
### Faza 3: Klijentski API
|
||
- **T3-01:** POST /api/v1/activate
|
||
- **T3-02:** POST /api/v1/deactivate
|
||
- **T3-03:** POST /api/v1/validate
|
||
- **T3-04:** Rate limiting na klijentske endpointe
|
||
|
||
### Faza 4: Admin API
|
||
- **T4-01:** Auth middleware (API key)
|
||
- **T4-02:** CRUD endpointi za licence
|
||
- **T4-03:** Revoke + Force release endpointi
|
||
- **T4-04:** Aktivacije i audit endpointi
|
||
- **T4-05:** Statistike endpoint
|
||
|
||
### Faza 5: Admin Dashboard (htmx)
|
||
- **T5-01:** Login stranica + session auth
|
||
- **T5-02:** Dashboard — statistike po proizvodu
|
||
- **T5-03:** Lista licenci — tabela, filteri, pretraga
|
||
- **T5-04:** Nova licenca — forma sa izborom proizvoda
|
||
- **T5-05:** Detalji licence — info, aktivacije, audit, akcije
|
||
- **T5-06:** Audit log stranica
|
||
|
||
### Faza 6: Testovi
|
||
- **T6-01:** Unit — keygen (format, prefix, uniqueness)
|
||
- **T6-02:** Unit — crypto (RSA sign/verify, tampered data)
|
||
- **T6-03:** Unit — license service (create, expire, grace, revoke)
|
||
- **T6-04:** Unit — activation service (activate, deactivate, already_activated, force_release)
|
||
- **T6-05:** Integration — activate flow (full roundtrip)
|
||
- **T6-06:** Integration — deactivate flow
|
||
- **T6-07:** Integration — expired license
|
||
- **T6-08:** Integration — revoked license
|
||
- **T6-09:** Security — SQL injection na svim inputima
|
||
- **T6-10:** Security — rate limiting (brute force key)
|
||
- **T6-11:** Security — API key validation
|
||
- **T6-12:** TESTING.md
|
||
|
||
### Faza 7: DevOps
|
||
- **T7-01:** Gitea CI workflow
|
||
- **T7-02:** Systemd service fajl
|
||
- **T7-03:** Backup skripta za bazu
|
||
|
||
---
|
||
|
||
## Konfiguracija (.env)
|
||
|
||
```env
|
||
# Server
|
||
APP_PORT=8090
|
||
APP_ENV=development
|
||
|
||
# MySQL
|
||
DB_HOST=localhost
|
||
DB_PORT=3306
|
||
DB_NAME=licence_db
|
||
DB_USER=license
|
||
DB_PASS=OBAVEZNO-PROMENITI
|
||
|
||
# Auth
|
||
ADMIN_API_KEY=OBAVEZNO-GENERISATI
|
||
SESSION_SECRET=OBAVEZNO-PROMENITI
|
||
|
||
# RSA
|
||
RSA_PRIVATE_KEY_PATH=./crypto/private.pem
|
||
|
||
# Rate limiting
|
||
RATE_LIMIT_ACTIVATE=10/min
|
||
RATE_LIMIT_VALIDATE=60/min
|
||
|
||
# Logging
|
||
LOG_LEVEL=info
|
||
LOG_FILE=./log/server.log
|
||
```
|
||
|
||
---
|
||
|
||
## Konvencije
|
||
|
||
- Go net/http (bez framework-a, isto kao ESIR)
|
||
- Go 1.22+ method routing (`POST /api/v1/activate`)
|
||
- database/sql + raw SQL (bez ORM-a)
|
||
- RSA-2048 za potpis, SHA-256 za fingerprint
|
||
- API key u `X-API-Key` header-u za admin
|
||
- Sve akcije se loguju u audit_log
|
||
- Error wrapping: `fmt.Errorf("activate: %w", err)`
|
||
- Licencni ključ se NIKAD ne loguje ceo — samo prefix + poslednja 4 karaktera
|
||
|
||
---
|
||
|
||
## Bezbednost
|
||
|
||
- `private.pem` NIKAD u git-u (u .gitignore)
|
||
- `private.pem` permisije: 600 (samo owner čita)
|
||
- Admin API key min 32 karaktera
|
||
- Rate limiting na activate/validate (zaštita od brute force)
|
||
- Audit log za svaku akciju
|
||
- HTTPS u produkciji (TLS termination na reverse proxy)
|
||
- Licencni ključ u logovima maskiran: `LT-K7M2-****-****-J6T1`
|
||
|
||
---
|
||
|
||
## Status implementacije (mart 2026)
|
||
|
||
**Kompletno implementiran** — server je funkcionalan, testiran (22/22 testova prošlo).
|
||
|
||
### Implementirano
|
||
|
||
- ✅ Go projekat inicijalizovan (go.mod, go.sum)
|
||
- ✅ MySQL baza `licence_db` kreirana, user `license`
|
||
- ✅ Migracije (001_create_tables.sql, 002_seed_products.sql)
|
||
- ✅ RSA-2048 ključevi generisani (crypto/private.pem, crypto/public.pem)
|
||
- ✅ Seed podaci: 3 proizvoda (ESIR, ARV, LIGHT_TICKET)
|
||
- ✅ Client API: activate, deactivate, validate
|
||
- ✅ Admin API: CRUD licence, stats, audit log
|
||
- ✅ Dashboard: login, pregled licenci, kreiranje, detalji, audit log
|
||
- ✅ Rate limiting (in-memory sliding window)
|
||
- ✅ API key autentifikacija (X-API-Key header)
|
||
- ✅ RSA-SHA256 potpisivanje licencnih podataka
|
||
- ✅ Audit log za sve akcije
|
||
- ✅ Specifikacija kompletna (ovaj CLAUDE.md)
|
||
|
||
### Struktura fajlova
|
||
|
||
```
|
||
cmd/server/main.go — Entry point, MySQL konekcija, migracije, wire-up
|
||
internal/config/config.go — .env loading, DSN builder (multiStatements=true)
|
||
internal/model/
|
||
license.go — Product, License, LicenseWithActivation modeli
|
||
activation.go — Activation model
|
||
audit.go — AuditEntry model
|
||
request.go — Request/Response strukture (API)
|
||
internal/repository/
|
||
license_repo.go — License CRUD, product queries, stats
|
||
activation_repo.go — Activation CRUD, deactivate, force release
|
||
audit_repo.go — Audit log insert/list
|
||
internal/service/
|
||
license_service.go — License CRUD biznis logika, expiry kalkulacija
|
||
activation_service.go — Activate, Deactivate, Validate, ForceRelease
|
||
crypto_service.go — RSA-2048 potpisivanje (SHA-256, PKCS1v15)
|
||
keygen.go — Generisanje ključeva: {PREFIX}-XXXX-XXXX-XXXX-XXXX
|
||
internal/handler/
|
||
client_handler.go — POST /api/v1/activate, deactivate, validate
|
||
admin_handler.go — Admin CRUD API endpointi
|
||
dashboard_handler.go — Dashboard stranice, in-memory sesije
|
||
helpers.go — writeJSON, writeError, clientIP
|
||
internal/middleware/
|
||
auth.go — API key auth (X-API-Key header)
|
||
ratelimit.go — In-memory sliding window rate limiter
|
||
internal/router/router.go — Sve rute (client, admin, dashboard)
|
||
migrations/
|
||
001_create_tables.sql — products, licenses, activations, audit_log
|
||
002_seed_products.sql — ESIR, ARV, LIGHT_TICKET
|
||
templates/
|
||
layout/base.html — Glavni layout sa navbar-om
|
||
pages/login.html — Login stranica
|
||
pages/dashboard.html — Dashboard sa statistikama
|
||
pages/licenses.html — Lista licenci sa filterima
|
||
pages/license-new.html — Forma za novu licencu
|
||
pages/license-detail.html — Detalji licence, aktivacije, audit
|
||
pages/audit.html — Globalni audit log
|
||
static/css/style.css — Kompletni CSS
|
||
static/js/htmx.min.js — htmx biblioteka
|
||
crypto/private.pem — RSA-2048 privatni ključ (chmod 600)
|
||
crypto/public.pem — RSA javni ključ
|
||
```
|
||
|
||
### Konfiguracija (.env)
|
||
|
||
```
|
||
APP_PORT=8090
|
||
DB_HOST=localhost / DB_PORT=3306
|
||
DB_NAME=licence_db
|
||
DB_USER=license / DB_PASS=license_pass_2026
|
||
ADMIN_API_KEY=dal-admin-key-2026-supersecret-change-me
|
||
ADMIN_PASSWORD=DalAdmin2026!
|
||
RSA_PRIVATE_KEY_PATH=./crypto/private.pem
|
||
```
|
||
|
||
### DSN napomena
|
||
|
||
DSN sadrži `multiStatements=true` — neophodno za izvršavanje migracija sa više SQL naredbi u jednom `db.Exec()` pozivu.
|
||
|
||
### Testirano (22/22)
|
||
|
||
1. Health endpoint
|
||
2. Products list
|
||
3. Create license (sva 3 proizvoda)
|
||
4. List licenses
|
||
5. License detail
|
||
6. Activate license
|
||
7. Already activated (drugi hardver)
|
||
8. Deactivate license
|
||
9. Re-activate (novi hardver)
|
||
10. Validate license
|
||
11. Revoke license
|
||
12. Validate revoked (odbijeno)
|
||
13. Invalid license key
|
||
14. API key auth (401 bez ključa)
|
||
15. Stats endpoint
|
||
16. Audit log
|
||
17. Dashboard login
|
||
18. Dashboard index
|
||
19. Licenses page
|
||
20. New license form
|
||
21. License detail page
|
||
22. Audit page
|
||
|
||
### Sledeći koraci
|
||
|
||
- Faza 8 (Light-Ticket): Integracija sa license serverom
|
||
- ARV: Integracija sa license serverom
|
||
- ESIR: Integracija sa license serverom
|
||
- **ASP-Terminia (4. proizvod):** vidi `docs/asp-terminia/` — radni paket Z1–Z6
|
||
|
||
---
|
||
|
||
## Radni paket: ASP-Terminia (ESP32 terminal)
|
||
|
||
Četvrti proizvod na ovom serveru — **ASP-Terminia** (Autonomni Sistem Prolaza),
|
||
ESP32-S3 terminal za kontrolu prolaza (bazeni/spa). Licenca je vezana za **MAC**
|
||
čipa (eFuse) preko `machine_fingerprint`, a ne za računar.
|
||
|
||
**Klijentska strana je već gotova** — firmware modul `licenca.h/.cpp` (od FW
|
||
0.10.0) radi aktivaciju, offline verifikaciju RSA potpisa i UI. Čeka samo da
|
||
proizvod i ključ postoje ovde na serveru.
|
||
|
||
Kompletan paket (postojeće stanje, tačan API ugovor koji firmware očekuje,
|
||
zadaci Z1–Z6, pravila rada na produkciji, enforcement pitanja za vlasnika):
|
||
|
||
**[`docs/asp-terminia/README.md`](docs/asp-terminia/README.md)** — čitati prvo.
|
||
|
||
Razlika u odnosu na ESIR/ARV/LIGHT_TICKET koja se mora poštovati:
|
||
- fingerprint je MAC uređaja, ne hash računara;
|
||
- uređaj radi **offline** — mrežna greška NIKAD ne sme da ga obori;
|
||
- postojeći proizvodi su ŽIVI u produkciji (~140 licenci) — ništa se ne lomi.
|
||
|
||
---
|
||
|
||
## Radni paket: LoggerService (kontrolna ravan za flote terminala)
|
||
|
||
**Proširenje ovog servera**, ne novi projekat — RBAC stablo organizacija
|
||
(vendor → kupac → objekat, proizvoljna dubina), uređaji + heartbeat, servisni
|
||
grantovi, provisioning i katalog firmvera za ASP liniju terminala. Jedan Go
|
||
backend + baza, **dva lica** po hostu: `terminia.net` (vendor pogled) i
|
||
`loggerservice.com` (serviserski brend — kasnije samostalna aplikacija za
|
||
servisne firme, uređaji su joj samo modul).
|
||
|
||
RBAC model je **ZAKLJUČEN** sa vlasnikom (2026-07-24) — nije predlog za
|
||
raspravu, implementacione odluke (kolone/indeksi/Go slojevi) su slobodne.
|
||
|
||
Kompletan paket (vizija/MVP, RBAC šema i invarijante, heartbeat/discovery
|
||
formati koje uređaj već šalje, provisioning i katalog `.bin`-ova, zadaci
|
||
S1–S7, pravila rada na produkciji):
|
||
|
||
**[`docs/loggerservice/README.md`](docs/loggerservice/README.md)** — čitati prvo,
|
||
pa **[`docs/loggerservice/00-vizija-i-mvp.md`](docs/loggerservice/00-vizija-i-mvp.md)**
|
||
(merodavan nad ostalim fajlovima u paketu gde se razlikuju).
|
||
|
||
**Koordinacija sa `docs/asp-terminia/`:** isti backend, ista baza — zadaci S1–S2
|
||
(migracije RBAC šeme) idu POSLE Z1 (ASP proizvod na licenci) da se migracije ne
|
||
sudare; `devices.license_id` referencira licencu iz tog paketa.
|
||
|
||
---
|
||
|
||
*Kreirano: mart 2026*
|
||
*Ažurirano: 04.03.2026 — Kompletna implementacija, 22/22 testova*
|
||
*Autor: Nenad Đukić*
|
||
*Preimenovano 2026-07-25: dal-license-server → licence-server (samostalan
|
||
proizvod, izlazi iz DAL priče); baza `dal_license_db` → `licence_db`.*
|