Build a minimal, secure license/ban server that controls extension access even if the extension ZIP is shared. The server will validate license tokens and enforce bans without ever receiving or storing GitHub access tokens.
Target capacity: at least 100 requests per minute (sustained), with headroom for bursts.
- No GitHub token storage.
- No analytics or heavy user tracking (optional later).
- No complex billing system (optional later).
- Client: Code Bridge extension
- Server: Express (Node.js)
- Database: MongoDB
- Auth: JWT for session tokens + API key for admin routes
- Deployment: single instance with horizontal scaling later if needed
- Validate license tokens on startup and periodically.
- Enforce bans by license key and/or deviceId.
- Control device limits per license.
- Provide admin endpoints to create/revoke licenses and ban/unban devices.
Represents a license key and its constraints.
Fields:
- _id (ObjectId)
- licenseKey (string, unique, opaque)
- status (string: active | revoked | expired)
- maxDevices (number, default 1)
- expiresAt (Date, optional)
- createdAt (Date)
- notes (string, optional)
Indexes:
- unique index on licenseKey
- index on status
- index on expiresAt
Represents a device that has activated a license.
Fields:
- _id (ObjectId)
- licenseId (ObjectId)
- deviceId (string)
- status (string: active)
- deactivatedAt (Date, optional)
- firstSeen (Date)
- lastSeen (Date)
- appVersion (string, optional)
- platform (string, optional)
Indexes:
- index on licenseId
- unique compound index on licenseId + deviceId
- index on lastSeen
Explicit bans independent of activation state.
Fields:
- _id (ObjectId)
- type (string: deviceId | licenseKey)
- value (string)
- reason (string)
- createdAt (Date)
Indexes:
- unique compound index on type + value
Notes:
- Bans are the single source of truth for blocking access. Activations do not store a banned state.
All requests are JSON over HTTPS.
Purpose: Exchange license key + deviceId for a short-lived token.
Input: { "licenseKey": "CB-XXXX-YYYY-ZZZZ", "deviceId": "uuid-v4", "appVersion": "1.3.0" }
Output (success): { "valid": true, "token": "jwt", "expiresAt": "2026-02-16T12:00:00.000Z", "nextCheckInSeconds": 21600 }
Output (failure): { "valid": false, "reason": "revoked" | "expired" | "banned" | "device_limit" | "not_found" }
Checks:
- licenseKey exists and status = active
- license not expired
- not banned (licenseKey or deviceId)
- idempotent activation: if licenseKey + deviceId already active, return existing token (do not increment device count)
- otherwise enforce device count <= maxDevices and create activation
Purpose: Validate token for ongoing access.
Input: { "token": "jwt", "deviceId": "uuid-v4" }
Output: { "valid": true, "reason": "ok", "nextCheckInSeconds": 21600 }
If invalid: { "valid": false, "reason": "revoked" | "expired" | "banned" | "token_invalid" }
Checks:
- JWT signature valid
- token not expired
- license still active
- device not banned (check bans collection)
- activation exists and is not deactivated
Purpose: Deactivate a device for a license.
Input: { "token": "jwt", "deviceId": "uuid-v4" }
Output: { "success": true }
Behavior:
- set activations.deactivatedAt to the current time for the matching deviceId
- treat deactivated activations as non-active in validation
Auth:
- Authorization: Bearer <API_KEY>
- Optional: X-API-Key: <API_KEY>
API key guidance:
- Use a 32+ char cryptographically random string (prefix like adm_ optional)
- Generate server-side, store hashed, rotate regularly, and revoke on compromise
- Support key expiry/TTL and a rotation process that allows overlap during rollout
Input: { "maxDevices": 2, "expiresAt": "2026-12-01T00:00:00Z", "notes": "beta access" }
Output: { "licenseKey": "CB-XXXX-YYYY-ZZZZ" }
Input: { "licenseKey": "CB-XXXX-YYYY-ZZZZ" }
Output: { "success": true }
Input: { "type": "deviceId", "value": "uuid-v4", "reason": "abuse" }
Output: { "success": true }
Input: { "type": "deviceId", "value": "uuid-v4" }
Output: { "success": true }
- Use JWT (HS256) with short expiration (e.g., 24 hours)
- Include licenseId and deviceId in the token payload
- Server validates token on /validate
- Extension stores token locally and revalidates periodically
JWT payload example: { "licenseId": "licenseId.toString()", "deviceId": "uuid-v4", "exp": 1234567890 }
- First run
- User enters license key
- Extension generates deviceId (uuid-v4) and stores in chrome.storage.local
- Call /activate
- Store token and nextCheckInSeconds
- Normal operation
- On startup, call /validate
- If valid, enable core features
- If invalid, disable tracking/upload and show message
- Periodic validation
- Recheck every nextCheckInSeconds (default 6-12 hours)
- Optional grace period if server is unreachable (e.g., 24-72 hours)
Target: 100 requests per minute sustained.
This is very small for Node + MongoDB. A single instance can handle 1,000+ rpm easily.
Recommended limits:
- /activate: rate limit 10/min per IP
- /validate: rate limit 60/min per IP (or higher)
- admin routes: strict rate limit and API key
- HTTPS only
- Do not log tokens in plain text
- Rate limit all endpoints
- Store only minimal device identifiers (generated UUID)
- Never store GitHub tokens
- Store JWT signing keys in environment variables or a secrets manager and rotate regularly
- Allow small clock skew on JWT expiration checks (60-120s leeway)
- Manage admin API keys securely: store hashed, rotate on a schedule, and revoke on compromise
- Always return structured JSON with reason codes
- Do not leak internal errors to clients
Example failure: { "valid": false, "reason": "revoked" }
- Set up Express server + MongoDB connection
- Define Mongoose models
- Implement /activate and /validate
- Implement admin endpoints (create/revoke/ban)
- Add rate limiting and API key middleware
- Add basic logging
- Deploy to Render/Fly/Railway
- Device list dashboard for admins
- License self-service portal
- Webhook or email on ban/revoke
- Multi-tenant license groups
- Usage analytics (counts only, no PII)
At 100 requests/minute:
- MongoDB and Express on a single small instance are sufficient.
- Consider a small cache for license lookups if traffic grows.
This design gives you real remote control with minimal complexity:
- Token validation + device bans
- No GitHub token risk
- Scales easily past 100 rpm
If you want, the next step is an implementation blueprint or a minimal Express codebase.