- 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>
26 KiB
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:
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_operatorsprilikom kreiranja licence - Nema eksplicitnog
editionpolja — 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) | |
| 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_keyna (license_key) — UNIQUEidx_licenses_productna (product_id)idx_licenses_customerna (customer_name)idx_licenses_expiresna (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_licensena (license_id)idx_activations_fingerprintna (machine_fingerprint)idx_activations_activena (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_licensena (license_id)idx_audit_actionna (action)idx_audit_createdna (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
// 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
// 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
// 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)
# 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.pemza potpisivanje licenci pri aktivaciji - Klijenti (ARV, ESIR, LT): imaju
public.pemugrađ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_dayskolona) - 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:
- Kreiraj novi
dal-license-serverprojekat - Kopiraj korisnu logiku iz
esir-license-server(keygen, verify flow, helpers) - Proširi model (product, features, limits, RSA)
- Zameni port 8090 (isti port, drop-in replacement)
- 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)
# 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-Keyheader-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.pemNIKAD u git-u (u .gitignore)private.pempermisije: 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_dbkreirana, userlicense - ✅ 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)
- Health endpoint
- Products list
- Create license (sva 3 proizvoda)
- List licenses
- License detail
- Activate license
- Already activated (drugi hardver)
- Deactivate license
- Re-activate (novi hardver)
- Validate license
- Revoke license
- Validate revoked (odbijeno)
- Invalid license key
- API key auth (401 bez ključa)
- Stats endpoint
- Audit log
- Dashboard login
- Dashboard index
- Licenses page
- New license form
- License detail page
- 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.