- Kompletna Go implementacija licencnog servera (19 Go fajlova) - Klijentski API: activate, deactivate, validate - Admin API: CRUD licence, stats, audit log - Admin dashboard: htmx + Go templates - RSA-2048 potpisivanje licencnih podataka - Rate limiting i API key autentifikacija - MySQL migracije i seed podaci (ESIR, ARV, LIGHT_TICKET) - Unit testovi: keygen, crypto, model, middleware (24 testa) - Dokumentacija: SPEC.md, ARCHITECTURE.md, SETUP.md, API.md, TESTING.md, README.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
745 lines
26 KiB
Markdown
745 lines
26 KiB
Markdown
# DAL License Server — CLAUDE.md
|
|
|
|
## Pregled
|
|
|
|
Univerzalni licencni server za **sve DAL proizvode**: 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:** License 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 DAL-a. 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
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ DAL LICENSE 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
|
|
|
|
```
|
|
dal-license-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 `dal-license-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=dal_license_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 `dal_license_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=dal_license_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
|
|
|
|
---
|
|
|
|
*Kreirano: mart 2026*
|
|
*Ažurirano: 04.03.2026 — Kompletna implementacija, 22/22 testova*
|
|
*Autor: Nenad Đukić / DAL d.o.o.*
|