Multi-platform license management platform for software publishers. Activate, validate, monitor, and secure licenses across desktop, mobile, and web applications.
- Overview
- Core Capabilities
- Architecture
- Supported Platforms
- Repository Structure
- Getting Started
- Local Development
- License Validation Flow
- API Overview
- Security Model
- SDK Ecosystem
- Deployment
- Observability
- Roadmap
- Contributing
- License
Glassefc is a license management platform that provides cryptographic license validation, device binding, and multi-platform SDK support. The platform is designed for software publishers who need to enforce licensing across desktop, mobile, and web applications without compromising offline usability or security.
The system uses Ed25519 digital signatures for offline-capable license validation, device fingerprinting for hardware binding, and refresh token rotation for secure session management. All SDKs share a common validation core, ensuring consistent behavior across platforms.
| Capability | Description |
|---|---|
| Creation | Generate licenses with configurable plan-tier limits, expiry, and device caps |
| Activation | Bind a license to a device using hardware fingerprint |
| Validation | Cryptographically verify license state offline and online |
| Blocking | Toggle license block state to instantly revoke access |
| Extension | Extend license expiry without regenerating keys |
| Device Replacement | Deauthorize old hardware and authorize replacement |
| Device Revocation | Remove device bindings from a license |
| Capability | Description |
|---|---|
| Ed25519 Signatures | All validation responses signed with Ed25519 keypairs |
| Replay Protection | Nonce + timestamp validation via Redis |
| Key Rotation | Versioned Ed25519 keypairs with rotation support |
| Device Fingerprinting | Hardware-bound device identity verification |
| Audit Logging | All admin actions logged with actor, IP, and metadata |
| Security Events | Suspicious activity detection and recording |
| Rate Limiting | Per-endpoint limits with brute force protection |
| Offline Validation | Signed token cache with configurable grace period |
| Refresh Token Rotation | Rotating refresh token lifecycle with automatic renewal |
| Capability | Description |
|---|---|
| Multi-Platform SDKs | Native SDKs for iOS, Android, Flutter, Web, Next.js, Vue, React Native, Electron, Tauri |
| API-First Design | RESTful API with consistent patterns across all endpoints |
| Admin Dashboard | Web-based dashboard for license and device management |
| Docker Support | Containerized deployment for API, dashboard, and services |
| CI/CD Pipelines | GitHub Actions workflows for testing and publishing |
| Prometheus Metrics | Built-in observability with Prometheus-compatible metrics |
flowchart LR
subgraph Client["Client Side"]
direction TB
SDK["SDK Layer"]
SIG["Signature Verification"]
LOCK["Lock Enforcement"]
CACHE["Cache + Offline Store"]
end
subgraph Gateway["Gateway Layer"]
direction TB
GW["API Gateway"]
RL["Rate Limiter"]
RSV["Request Sig Validator"]
RP["Replay Protection (Nonce)"]
end
subgraph Core["Core Services"]
direction TB
LE["License Engine"]
DM["Device Manager"]
ACT["Activation Service"]
SUB["Subscription Service"]
end
subgraph Security["Security Layer"]
direction TB
ES["Ed25519 Signer"]
KM["Key Manager"]
TG["Token Generator"]
AUD["Audit Logger"]
end
subgraph Data["Data Layer"]
direction TB
PGPRIMARY["PostgreSQL Primary"] --> PGREPLICA["Read Replica Pool"]
RCACHE["Redis Cache"]
RNONCE["Redis Nonce Store"]
end
subgraph Admin["Admin Side"]
DASH["Dashboard UI"]
ADMINAPI["Admin API"]
BILL["Billing Service"]
ANALYTICS["Analytics Service"]
end
subgraph Obs["Observability"]
PROM["Prometheus"]
GRAF["Grafana"]
LOKI["Loki"]
ALERT["Alertmanager"]
end
Client -- "License Check" --> Gateway
Gateway -- "Validated Request" --> Core
Core -- "Token Signing" --> Security
Security -- "Data Lookup" --> Data
Security -. "Signed License Token" .-> Client
Admin -. "Admin Operations" .-> Core
Client -. "Metrics" .-> Obs
Gateway -. "Metrics" .-> Obs
Core -. "Metrics" .-> Obs
Security -. "Metrics" .-> Obs
Data -. "Metrics" .-> Obs
The platform is composed of five logical layers that form a complete request lifecycle:
| Layer | Role |
|---|---|
| Client Side | Platform-specific SDKs handle device fingerprinting, Ed25519 signature verification, local token caching with offline validation, and lock enforcement when a license is invalid, expired, or blocked. |
| Gateway Layer | API Gateway receives signed HTTPS requests, applies rate limiting per endpoint, validates incoming request signatures, and enforces replay protection via nonce checks against Redis. Only validated requests reach core services. |
| Core Services | License Engine processes validation logic, Device Manager tracks hardware bindings, Activation Service registers new devices, and Subscription Service enforces plan-tier limits. |
| Security Layer | Ed25519 Signer cryptographically signs all validation responses, Key Manager handles key generation and rotation, Token Generator creates signed license tokens, and Audit Logger records all administrative actions. |
| Data Layer | PostgreSQL Primary handles all write operations (activation, device binding, license revocation). Read Replica Pool serves validation and analytics queries. Redis provides both cache storage and nonce-based replay protection. |
A dark-themed SVG architecture diagram is available at docs/architecture/glassefc-architecture.svg suitable for presentations and documentation. Editable versions are also available for Excalidraw and Draw.io.
| Platform | SDK Package | Language | Distribution |
|---|---|---|---|
| iOS | sdk-swift | Swift | Swift Package Manager |
| Android | sdk-android | Kotlin | Maven Central |
| Flutter | sdk-flutter | Dart | pub.dev |
| Web (Vanilla) | sdk-js | TypeScript | npm |
| Next.js | sdk-nextjs | TypeScript | npm |
| Vue.js | sdk-vue | TypeScript | npm |
| React Native | sdk-react-native | TypeScript | npm |
| Electron | sdk-electron | TypeScript | npm |
| Tauri | sdk-tauri | Rust + TypeScript | crates.io + npm |
glassefc-saas/
├── apps/
│ ├── api/ # Fastify API server
│ │ ├── src/
│ │ │ ├── routes/ # Endpoint handlers
│ │ │ ├── plugins/ # Fastify plugins
│ │ │ └── lib/ # Shared utilities
│ │ ├── test/
│ │ └── package.json
│ └── dashboard/ # Next.js 14 admin panel
│ ├── src/
│ │ ├── app/ # App router pages
│ │ ├── components/ # React components
│ │ └── lib/ # API client, queries
│ ├── public/
│ └── package.json
├── packages/
│ ├── sdk-core/ # Shared validation logic (TypeScript)
│ ├── sdk-js/ # Web SDK
│ ├── sdk-nextjs/ # Next.js SDK (context + hooks)
│ ├── sdk-vue/ # Vue SDK (plugin + composable)
│ ├── sdk-react-native/ # React Native SDK (hooks + lock overlay)
│ ├── sdk-electron/ # Electron SDK (main/preload/renderer)
│ ├── sdk-tauri/ # Tauri SDK (Rust core + TS bindings)
│ ├── sdk-swift/ # iOS Swift package
│ ├── sdk-android/ # Android Kotlin library
│ └── sdk-flutter/ # Flutter Dart package
├── services/
│ ├── license-engine/ # License validation, signing, activation
│ └── security/ # Key management, encryption, audit
├── prisma/
│ ├── schema.prisma # Database schema
│ └── migrations/ # Migration history
├── infra/
│ ├── docker/ # Dockerfiles for each service
│ ├── nginx/ # Reverse proxy configs
│ └── postgres/ # Init scripts
├── .github/
│ └── workflows/ # CI/CD pipelines
├── turbo.json # Turborepo configuration
├── pnpm-workspace.yaml # PNPM workspace definition
└── package.json # Root package.json
- Node.js 20+
- PNPM 9+
- Docker (for local PostgreSQL and Redis)
- PostgreSQL 16+
- Redis 7+
# Clone the repository
git clone https://github.com/your-org/glassefc-saas.git
cd glassefc-saas
# Install dependencies
pnpm install
# Copy environment variables
cp .env.example .env
# Start database and cache services
docker compose up -d postgres redis
# Run database migrations
pnpm db:migrate
# Start development servers
pnpm devThe API server starts at http://localhost:3001 and the dashboard at http://localhost:3000.
Environment variables for the API server:
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | postgresql://localhost:5432/glassefc |
REDIS_URL |
Redis connection string | redis://localhost:6379 |
JWT_SECRET |
JWT signing secret for admin auth | -- |
API_PORT |
API server port | 3001 |
CORS_ORIGIN |
Allowed CORS origin for dashboard | http://localhost:3000 |
ENCRYPTION_KEY |
Key for encrypting private keys at rest | -- |
# Start all services in development mode
pnpm dev
# Start only the API server
pnpm dev --filter=api
# Start only the dashboard
pnpm dev --filter=dashboard
# Build all packages
pnpm build
# Run tests across all packages
pnpm test
# Lint all packages
pnpm lint
# Generate Prisma client
pnpm db:generate
# Create a migration
pnpm db:migrate:dev
# Open Prisma Studio
pnpm db:studio- Create a new package under
packages/sdk-<platform>using the existing SDKs as reference. - Implement the SDK interface defined in
sdk-core:checkLicense(licenseKey, deviceFingerprint)-- validates license onlinevalidateCachedToken()-- validates locally cached signed tokenactivateLicense(licenseKey, deviceFingerprint)-- binds device to license- Event callbacks for license state changes and lock overlay triggers
- Import and re-export the core validation functions from
sdk-core. - Register the new package in
pnpm-workspace.yaml. - Add GitHub Actions workflows for automated testing on PR.
Client App SDK API Server Database / Redis
│ │ │ │
│ Initialization │ │ │
│───────────────────────▶│ │ │
│ │ Check cached token │ │
│ ├───────────────────────────x │
│ │ ◀── No valid cache ──────┤ │
│ │ │ │
│ checkLicense(key,fp) │ │ │
│───────────────────────▶│ POST /license/check │ │
│ │──────────────────────────▶│ │
│ │ │ Validate license │
│ │ ├──────────────────────▶│
│ │ │ ◀── license data ────┤
│ │ │ │
│ │ │ Check device match │
│ │ ├──────────────────────▶│
│ │ │ ◀── device data ─────┤
│ │ │ │
│ │ │ Check replay nonce │
│ │ ├────── (Redis) ───────▶│
│ │ │ ◀── OK/expired ──────┤
│ │ │ │
│ │ │ Sign response with │
│ │ │ Ed25519 │
│ │ │ │
│ │ ◀── Signed token ────────┤ │
│ │ │ │
│ │ Verify Ed25519 sig │ │
│ │ Cache token locally │ │
│ │ Return license state │ │
│ ◀── LicenseResult ────┤ │ │
│ │ │ │
The validation flow uses a cache-first strategy:
- On initialization, the SDK checks for a locally cached signed token.
- If a valid token exists and has not expired, it is verified using the cached public key and returned immediately.
- If no valid cache exists, the SDK sends
POST /license/checkwith the license key and device fingerprint. - The API validates the license exists, is not blocked, and has not expired.
- The API verifies the device fingerprint matches the bound device (if activated).
- The API checks the replay nonce in Redis to prevent token reuse.
- A signed token is returned containing the license state, device ID, and expiry.
- The SDK verifies the Ed25519 signature using the public key fetched from
/.well-known/public-keys/:appId. - The SDK caches the validated token and fires the appropriate callback.
When the device is offline, the SDK validates the cached signed token without contacting the API:
SDK Offline Path
│
├── Load cached token from secure storage
├── Verify Ed25519 signature with cached public key
├── Check token expiry (default grace: 72 hours)
├── Check license state (active, blocked, expired)
└── Return validation result or fire lock callback
The signed token contains all data needed for offline validation, eliminating the need for network access during the grace period.
| Method | Path | Description |
|---|---|---|
| POST | /license/check |
Validate license and device. Returns Ed25519-signed token |
| POST | /license/activate |
Register a device to a license using device fingerprint |
| POST | /license/refresh/request |
Request a refresh token for device rotation |
| POST | /license/refresh |
Rotate refresh token, return new signed license |
| Method | Path | Description |
|---|---|---|
| POST | /admin/app/create |
Register a new application. Auto-generates Ed25519 keypair |
| GET | /admin/apps |
List applications owned by the authenticated user |
| POST | /admin/license/create |
Create a license with plan-tier device limits and expiry |
| POST | /admin/license/block/:id |
Toggle license block state |
| POST | /admin/license/extend |
Extend license expiry date |
| GET | /admin/devices |
List devices with pagination, search, and filter |
| POST | /admin/device/revoke |
Revoke a device binding (soft delete) |
| POST | /admin/device/replace |
Replace an old device binding with a new device |
| GET | /admin/audit-logs |
Paginated audit log of all admin actions |
| GET | /admin/security-events |
Paginated security events |
| Method | Path | Description |
|---|---|---|
| GET | /.well-known/public-key/:keyId |
Retrieve a specific public key by key ID |
| GET | /.well-known/public-keys/:appId |
List all active public keys for an application |
import { Glassefc } from '@glassefc/sdk-js'
const glassefc = new Glassefc({
appId: 'app_abc123',
publicKey: '-----BEGIN PUBLIC KEY-----...',
})
const result = await glassefc.checkLicense({
licenseKey: 'LIC-XXXX-YYYY-ZZZZ',
deviceFingerprint: await glassefc.getDeviceFingerprint(),
})
if (result.valid) {
// License is valid -- proceed with application
} else {
// License is invalid, expired, or blocked
glassefc.showLockOverlay(result.reason)
}All license validation responses are signed with Ed25519 keypairs. Each application registered in the system is issued a unique keypair upon creation. The private key is encrypted at rest using AES-256-GCM and is only decrypted in memory during signing operations.
Each signed token includes a nonce and timestamp. The nonce is stored in Redis with a TTL matching the token validity period. If the same nonce is presented again, the request is rejected. This prevents token replay attacks even if a signed token is intercepted.
Keys can be rotated through the admin dashboard or API. When a key is rotated:
- A new keypair is generated and marked as the active signing key.
- The previous key remains available for verification until all tokens signed with it expire.
- Old keys are automatically removed from
/.well-known/public-keysafter all tokens signed with them have expired.
Each device is identified by a fingerprint derived from hardware characteristics. The fingerprint is generated by the SDK on the client device and sent during license activation and check. Once a license is activated on a device, validation checks enforce that the fingerprint matches the one stored during activation.
All administrative actions are logged with:
- Actor ID and role
- IP address and user agent
- Action type and affected resources
- Before/after state for mutations
- ISO 8601 timestamp
Rate limits are applied per endpoint using a sliding window algorithm backed by Redis:
| Endpoint Group | Limit | Window |
|---|---|---|
| License check | 60 requests | 1 minute |
| License activation | 10 requests | 1 minute |
| Admin endpoints | 120 requests | 1 minute |
| Public key endpoints | 300 requests | 1 minute |
All SDKs share a common validation core (sdk-core) that implements the following contract:
interface LicenseSDK {
initialize(config: SDKConfig): void
checkLicense(input: CheckInput): Promise<CheckResult>
activateLicense(input: ActivateInput): Promise<ActivationResult>
validateLocalToken(): CheckResult | null
onLicenseValid(callback: (result: CheckResult) => void): void
onLicenseInvalid(callback: (reason: string) => void): void
onLockRequired(callback: (reason: string) => void): void
getDeviceFingerprint(): Promise<string>
getCachedLicenseState(): CachedState | null
clearCache(): void
}| SDK | Language | Key Adaptation |
|---|---|---|
| sdk-js | TypeScript | Web crypto API for signature verification |
| sdk-nextjs | TypeScript | React context provider + hooks, SSR-compatible initialization |
| sdk-vue | TypeScript | Vue plugin + composable useLicense |
| sdk-react-native | TypeScript | Native device fingerprinting via RN modules, full-screen lock overlay component |
| sdk-electron | TypeScript | Main/preload/renderer architecture, machine ID for fingerprinting |
| sdk-tauri | Rust + TS | Rust core for signature verification, Tauri command bindings |
| sdk-swift | Swift | CryptoKit for Ed25519 verification, device fingerprint via IOKit |
| sdk-android | Kotlin | Android Keystore for secure storage, HardwareAttestation for fingerprinting |
| sdk-flutter | Dart | Method channels for native device fingerprinting, Dart crypto for signature verification |
version: '3.8'
services:
api:
build:
context: .
dockerfile: infra/docker/api.Dockerfile
environment:
- DATABASE_URL=postgresql://postgres:password@postgres:5432/glassefc
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET}
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
depends_on:
- postgres
- redis
ports:
- '3001:3001'
restart: unless-stopped
dashboard:
build:
context: .
dockerfile: infra/dashboard.Dockerfile
environment:
- NEXT_PUBLIC_API_URL=https://api.glassefc.example.com
ports:
- '3000:3000'
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: glassefc
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
restart: unless-stopped
volumes:
pgdata:
redis-data:| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string |
REDIS_URL |
Yes | Redis connection string |
JWT_SECRET |
Yes | Secret for JWT token signing (min 32 chars) |
ENCRYPTION_KEY |
Yes | AES-256 key for private key encryption (32 bytes, hex-encoded) |
CORS_ORIGIN |
Yes | Dashboard URL for CORS configuration |
NODE_ENV |
Yes | production |
LOG_LEVEL |
No | Logging level (info, warn, error). Default: info |
RATE_LIMIT_MAX |
No | Global rate limit. Default: 100 |
# Run migrations in production
pnpm db:migrate:deploy
# Generate Prisma client (included in Docker build)
pnpm db:generateserver {
listen 443 ssl;
server_name api.glassefc.example.com;
location / {
proxy_pass http://api:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /.well-known/ {
proxy_pass http://api:3001;
proxy_cache_valid 200 5m;
add_header Cache-Control "public, max-age=300";
}
}
server {
listen 443 ssl;
server_name dashboard.glassefc.example.com;
location / {
proxy_pass http://dashboard:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}curl https://api.glassefc.example.com/health{
"status": "ok",
"uptime": 482301,
"version": "1.2.0",
"database": "connected",
"redis": "connected"
}Metrics are exposed at /metrics in Prometheus text format:
| Metric | Type | Description |
|---|---|---|
license_checks_total |
Counter | Total license check requests |
license_activations_total |
Counter | Total license activations |
license_validation_duration_ms |
Histogram | License validation latency |
active_licenses |
Gauge | Currently active licenses |
blocked_licenses |
Gauge | Currently blocked licenses |
device_count |
Gauge | Total registered devices |
api_request_duration_ms |
Histogram | API request latency by endpoint |
rate_limit_exceeded_total |
Counter | Rate-limited requests |
Structured JSON logging is used across all services:
{
"level": "info",
"timestamp": "2026-06-17T10:30:00.000Z",
"service": "license-engine",
"action": "license_check",
"licenseId": "lic_abc123",
"appId": "app_def456",
"deviceId": "dev_789012",
"result": "valid",
"durationMs": 12
}- Concurrent license enforcement (per-seat limits across devices)
- Floating licenses with lease-based allocation
- License templates and bulk creation
- Webhook events for license state changes
- Billing integration (Stripe, Paddle, Lemon Squeezy)
- On-premise deployment guides (Kubernetes, Nomad)
- License analytics dashboard with usage reporting
- Feature flags gated by license tier
- gRPC API for low-latency validation
- Plugin system for custom validation rules
This repository uses PNPM workspaces and Turborepo for monorepo management.
# Setup
pnpm install
pnpm build
# Run tests before committing
pnpm test
# Lint
pnpm lint
# Format
pnpm formatAll SDKs share a common validation interface defined in packages/sdk-core. Platform-specific implementations should import from sdk-core rather than duplicating validation logic.
Pull requests should include tests covering the changed behavior. Each package has its own test suite run via Turborepo's test pipeline.