A comprehensive authentication and licensing system with support for license keys, HWID binding, multi-level products, resellers, and a full admin panel.
- License Management: Create, manage, and revoke license keys with expiration dates
- HWID Binding: Secure hardware ID binding with configurable limits per license
- Multi-Level Products: Support for different product tiers and pricing
- Reseller System: Built-in reseller management with balance and pricing controls
- Admin Panel: Full-featured web interface for managing all aspects of the system
- Two-Factor Authentication: TOTP-based 2FA for admin accounts
- Security: Hashed license storage, secure dashboard cookies, rate limiting, durable replay protection, and audit logging
- C++ SDK: C++17 Windows SDK with strict TLS validation, timeouts, and response limits
- Audit Logging: Comprehensive logging of all actions
- Python 3.12 or later
- pip package manager
- Clone the repository:
git clone <repository-url>
cd server- Install dependencies:
# Windows
install_deps.bat
# Linux/Mac
pip install -r requirements.txt- Configure environment variables:
cp .env.example .env
# Edit .env with your configuration- Run the server:
# Windows
start_server.bat
# Linux/Mac
python main.py- Access the admin panel:
http://localhost:8080/panel/
This project is designed to be self-hosted. The sections below cover the common things operators usually need after the first launch.
Before starting the server for the first time:
- Copy
.env.exampleto.env. - Set
HOST,PORT, andDB_PATHif you want custom values. - Set
ADMIN_USERNAMEandADMIN_PASSWORDif you want a known initial admin login. - Generate different random values for
LICENSE_LOOKUP_KEYandLICENSE_ENCRYPTION_KEY. Back both up separately; losing them makes existing keys unusable. - If you do not set
ADMIN_PASSWORD, the server generates one on first startup and prints it once to the terminal. - Use HTTPS directly or place the service behind a trusted reverse proxy. Set
COOKIE_SECURE=truewhen the public URL is HTTPS. - Keep
ALLOW_LEGACY_PROTOCOL=falsefor new deployments. Enable it only temporarily when migrating pre-v2 clients.
If you just want to test the project locally:
python main.pyOn Ubuntu 22.04 or newer, clone the repository and run the included installer from the repository root:
chmod +x deploy/install_ubuntu.sh
sudo ./deploy/install_ubuntu.shThe installer adds Docker and Compose when missing, creates a locked-down
.env, generates a cryptographically random license-key pepper and admin
password, starts the service, and waits for the health check. The database and
logs remain in the persistent data directory.
The application listens on port 8080. Place Nginx, Caddy, or another HTTPS
reverse proxy in front of it before public use; an Nginx starting point is
provided at deploy/nginx.conf.example. Set CORS_ORIGINS to the exact public
HTTPS origin and keep COOKIE_SECURE=true in production.
The optional bot in discord_bot/ provides private slash commands for
licenses, sessions, builds, news, variables, logs, and HWID bans. Create its
app-bound, separately revocable key from the owner-only Discord bot panel,
invite the bot from that same page, then use the key with /setup. The key is
shown once and encrypted at rest by the bot. See
discord_bot/README.md for installation instructions.
Then open:
http://localhost:8080/panel/
The repository includes Docker support if you want a reproducible deployment with persistent data.
From the project root:
cp .env.example .env
# Set ADMIN_PASSWORD, LICENSE_LOOKUP_KEY, and LICENSE_ENCRYPTION_KEY first.
docker compose up -d --buildCompose refuses to start without those three secrets. Its default origin is
http://localhost:8080; set CORS_ORIGINS to the exact public dashboard
origin before deployment.
If your Docker installation still uses the older command, this also works:
docker-compose up -d --buildThis starts the server on port 8080 and stores the SQLite database and logs in ./data on the host machine.
To view logs:
docker compose logs -fTo stop the container:
docker compose downIf you prefer to run the image manually:
docker build -t enauth .
docker run -d \
--name enauth-server \
-p 8080:8080 \
-e HOST=0.0.0.0 \
-e PORT=8080 \
-e DB_PATH=/app/data/enauth.db \
-v "$(pwd)/data:/app/data" \
enauthIf you are on Windows PowerShell, the volume path syntax may need to be adjusted for your shell environment.
- Keep the
./datafolder or mounted volume if you want to preserve the SQLite database and logs. - Set
ADMIN_PASSWORDbefore first startup if you want a known initial admin password. - Check
docker compose logs -fif the container exits or the panel does not load. - If you expose the container to the internet, prefer a reverse proxy in front of it for TLS and header handling.
For production, it is usually better to run the app behind a reverse proxy.
An example Nginx configuration is included in deploy/nginx.conf.example.
Recommended proxy responsibilities:
- Terminate TLS.
- Forward requests to the FastAPI server.
- Preserve the original client IP.
- Set trusted proxy headers only from your own proxy layer.
Important note:
- The app reads
X-Forwarded-Forfor client IP detection, so only trust that header if your proxy is the only thing allowed to reach the app directly.
See the changelog section below for release notes and upgrade highlights.
You can run the server with SSL directly by setting:
SSL_CERTSSL_KEY
If both files exist, the server will start with HTTPS enabled. For most deployments, using a reverse proxy with TLS is still the easier long-term setup.
Your main data lives in the SQLite database file defined by DB_PATH.
- Stop the server.
- Copy the database file somewhere safe.
- Store a copy of your
.envfile separately. - If you store uploaded files or other assets outside the database, back those up too.
Example:
cp enauth.db enauth.db.backup
cp .env .env.backup- Stop the server.
- Replace the database file with the backup copy.
- Restore your
.envfile if needed. - Start the server again.
When updating to a newer release:
- Read the release notes or changelog first.
- Back up
enauth.db. - Update dependencies.
- Start the server and check the logs for migration or startup warnings.
The database now uses versioned migrations, so schema changes should be applied automatically on startup. Keeping a backup before upgrading is still recommended.
On first launch, the server creates a default admin account if no admin users exist.
- Default username:
adminunless overridden byADMIN_USERNAME - Default password:
ADMIN_PASSWORDif set, otherwise a generated password is logged on startup
If you lose the admin password, use the password reset flow or replace the database during recovery.
Use the admin panel to create a new application, then note its app_id and secret_key. The client SDK needs both values.
Licenses are tied to an application. After creating a license, you can:
- bind HWIDs
- set expiration dates
- assign products
- ban or unban the key
Use the admin panel to publish news items for a specific application. Clients can fetch them through the /api/client/news endpoint.
Upload application files from the admin panel if you want clients to download signed or managed assets.
Create reseller accounts from the admin panel, assign product access and pricing access, then let them sell keys from their own panel.
Check the following:
- Python version matches the requirements.
- Dependencies are installed.
DB_PATHpoints to a writable location.- SSL certificate paths are valid if SSL is enabled.
- The port is not already in use.
Try these checks:
- Confirm you are using the correct username and password.
- Check whether 2FA is enabled on the account.
- Make sure the session token is being sent as a
Bearertoken if you are using the API directly. - Review the server logs for authentication errors.
That usually means one of the following:
- The request timestamp is too old or too far in the future.
- The client is reusing a previous nonce or request payload.
- The server and client clocks are out of sync.
Check that:
- The client is using the correct app secret.
- The
app_idmatches the application that owns the secret. - The request body was not modified before sending.
That means the HWID is present in the banned list for the application. Remove it from the admin panel if you want to allow it again.
This can happen if the key is already tied to a session or if the previous session was invalidated. Check the active sessions list in the admin panel.
SQLite can become locked if multiple processes are writing to the same file or if the process was interrupted. Make sure:
- Only one server instance is writing to the same database file.
- The database file is on a stable disk.
- You are not restoring a backup while the server is still running.
Password reset tokens expire after a limited time. If you generated one in debug mode, check the logs or the response body depending on your configuration.
Check:
HOSTis set to0.0.0.0if you want external access.- Firewall rules allow inbound traffic on the chosen port.
- Your reverse proxy is forwarding requests correctly.
- The server uses SQLite by default, so it is simple to deploy but not ideal for heavy concurrent writes.
- The maintenance loop clears expired sessions and stale runtime state automatically.
- Logs are written to
server.logwith rotation enabled. - The app exposes
/healthfor basic health monitoring.
- Open Applications and use Pause All when an entire application is unavailable.
- Open Product levels and use Pause when only one product/level is unavailable.
- Pausing immediately closes only the affected client sessions and blocks new affected logins.
- On resume, EnAuth automatically restores the exact paused duration. Enter optional extra
compensation days when prompted (for example, enter
2after a four-day outage to grant six days total). - Lifetime entitlements remain lifetime. Other product levels keep running and their expiry dates are not changed during a product-level outage.
- The dashboard's Operational Monitoring panel checks database integrity, recent login failures, paused services, uptime, and important production configuration settings.
- Each application has a public status page at
/panel/status.html?app=YOUR_APP_IDwith current product health and sanitized incident history. - Product status can use any short owner-defined label and a six-digit hex color from Product levels → Set Status. Common examples are Operational, Degraded, Maintenance, Updating, Delayed, or Offline.
- Use Licenses → Products to add, remove, extend, pause, or resume one product entitlement without changing the other products on that license.
- Files are private in the customer portal unless the owner explicitly enables Allow customers to download this file. Downloads may be restricted to licenses that own one selected product. Existing files remain private after upgrading.
- The customer portal displays owned products, separate expiration dates, service status, compensation received, and the configured HWID-reset cooldown.
- Resellers sign in from the main login page using Reseller Sign In.
- New license keys are stored as both a one-way lookup hash and AES-GCM authenticated ciphertext, allowing authorized admins, the issuing reseller, and the owning customer portal to copy them later without plaintext database storage. Keys created before this upgrade remain masked because their original values cannot be recovered retroactively.
Environment variables (see .env.example):
HOST: Server host (default: 0.0.0.0)PORT: Server port (default: 8080)DEBUG: Enable debug mode (default: false)DB_PATH: Database file path (default: enauth.db)ADMIN_USERNAME: Default admin usernameADMIN_PASSWORD: Default admin password (auto-generated if not set)CORS_ORIGINS: Comma-separated list of allowed originsSSL_CERT: Path to SSL certificate fileSSL_KEY: Path to SSL key file
TIMESTAMP_TOLERANCE: Request timestamp tolerance in seconds (default: 60)SESSION_DURATION: Client session duration in seconds (default: 86400)SESSION_TOKEN_SECONDS: Protocol-v2 bearer-token lifetime before rotation (default: 300)DOWNLOAD_TICKET_SECONDS: One-use SDK download-ticket lifetime (default: 60)MAX_BUILD_BYTES: Maximum uploaded build/loader size, capped at 100 MiBMAX_REQUEST_BYTES: Multipart request limit for the three upload endpoints (default and ceiling: 105 MiB)MAX_JSON_BYTES: Limit for all other request bodies (default: 2 MiB); actual streamed bytes are counted even without Content-LengthAUTO_BACKUP_HOURS: Verified scheduled-backup interval;0disables itBACKUP_ENCRYPTION_KEY: Encrypts signing-key copies stored beside scheduled database backups
Loader releases are managed from Loader releases in the panel. Publish each
new version under the same logical filename (for example loader.exe); EnAuth
archives the previous release automatically. SDK applications can call
AutoUpdateLoader("loader.exe", "1.2.0") after login and validation. A true
result means the signed and SHA-256-verified update was staged and the process
should exit immediately so the updater can replace and restart it.
MAX_LOGIN_STRIKES: Maximum failed login attempts before lockout (default: 5)NONCE_CACHE_SIZE: Maximum nonces to remember for replay protection (default: 10000)NONCE_TTL: Nonce time-to-live in seconds (default: 120)
RATE_LIMIT_INIT: Init endpoint (default: 20/minute)RATE_LIMIT_NEWS: News endpoint (default: 15/minute)RATE_LIMIT_LOGIN: Login endpoint (default: 8/minute)RATE_LIMIT_HEARTBEAT: Heartbeat endpoint (default: 60/minute)RATE_LIMIT_LOGOUT: Logout endpoint (default: 20/minute)RATE_LIMIT_VALIDATE: Validate endpoint (default: 30/minute)RATE_LIMIT_DOWNLOAD: Download endpoint (default: 10/minute)
When running in debug mode (DEBUG=true), API documentation is available at:
- Swagger UI:
http://localhost:8080/docs - ReDoc:
http://localhost:8080/redoc
See sdk/README.md for detailed C++ SDK integration instructions.
Basic usage:
#include "enauth.h"
enauth::Client client(
"https://your-server.com",
"your-app-id",
"1.0.0",
"YOUR_RESPONSE_SIGNING_PUBLIC_KEY"
);
auto result = client.Init();
if (result.success) {
auto login = client.Login("YOUR-LICENSE-KEY");
if (login.success) {
// Application authenticated
}
}The system uses SQLite with the following main tables:
applications: Application definitionslicenses: License keys and their propertieshwids: Hardware ID bindingssessions: Active client sessionsadmin_users: Admin accountsproducts: Product tiersresellers: Reseller accountslogs: Audit log
- TLS is mandatory for non-local SDK connections; certificate errors are never ignored
- License keys are returned only when created and stored as server-peppered hashes
- Dashboard sessions use
HttpOnly,SameSite=Strictcookies rather than browser storage - Replay nonces are stored in SQLite so protection survives restarts and multiple workers
- Protocol 2 SDK builds contain no application secret; TLS protects client requests
- Every server response is asymmetrically signed and bound to the exact request context
- A client-side license check can be patched; keep high-value authorization decisions server-side
- Nonce-based replay protection
- Rate limiting on all endpoints
- HWID validation requires SHA-256/512 hashes
- Brute force protection with strike counting
This project is provided as-is for authentication and licensing purposes.
For issues and questions, please refer to the project documentation or contact the maintainers.
- Added application-wide and per-product outage pause/resume controls.
- Added automatic downtime restoration plus optional compensation for affected entitlements.
- Added operational monitoring and production configuration checks to the dashboard.
- Added product-aware client sessions and SDK pause status values.
Release focus: self-hosting readiness, security hardening, and upgrade safety.
- Versioned SQLite migrations with startup migration discovery
- Persistent temporary 2FA sessions stored in SQLite
- CI workflow for automated test runs on GitHub Actions
- Security and migration test coverage for admin and client flows
- Expanded self-hosting documentation in the README
- Tightened owner-only admin permission checks
- Added rate limiting to sensitive auth endpoints
- Removed plaintext password reset token logging
- Improved logger configuration for file path and rotation settings
- Back up
enauth.dbbefore upgrading. - If you run behind a reverse proxy, only trust forwarded headers from your own proxy layer.
- Better self-hosting docs
- Safer admin and client auth paths
- Versioned schema migrations
- CI and tests for the important flows
- Initial public release of the EnAuth server