- Go to Firebase Console
- Click Add project
- Name it (e.g.,
pdf-toolkit) - Disable Google Analytics (not needed)
- Click Create project
- In the Firebase Console, go to Build → Firestore Database
- Click Create database
- Choose Start in production mode (we'll set rules next)
- Select a region close to your users (e.g.,
us-east1oreurope-west1) - Click Enable
- In Firestore, go to the Rules tab
- Replace the default rules with the contents of
firestore.rulesfrom this project - Click Publish
These rules allow:
- Admin (Adnan@thothica.com): Full read/write/delete via Firebase Auth
- App clients: Read license docs (key hash is the secret) + one-time machine binding
- Everyone else: Denied
- Go to Build → Authentication
- Click Get started
- Go to the Sign-in method tab
- Click Google → toggle Enable → set a support email → click Save
- Go to Settings → Authorized domains and verify
localhostis listed (it should be by default)
- Go to Project Settings (gear icon) → General
- Scroll down to Your apps
- Click Add app → choose Web (</> icon)
- Name it (e.g.,
pdf-toolkit-client) - From the config snippet, note these values:
apiKey(e.g.,AIzaSy...)authDomain(e.g.,pdf-toolkit-abc123.firebaseapp.com)projectId(e.g.,pdf-toolkit-abc123)
FIREBASE_PROJECT_ID=pdf-toolkit-abc123
FIREBASE_API_KEY=AIzaSy...
FIREBASE_AUTH_DOMAIN=pdf-toolkit-abc123.firebaseapp.com
ADMIN_EMAIL=Adnan@thothica.com
Open admin/index.html and replace the placeholder values near the top of the <script> section:
const FIREBASE_CONFIG = {
apiKey: "AIzaSy...",
authDomain: "pdf-toolkit-abc123.firebaseapp.com",
projectId: "pdf-toolkit-abc123",
};The admin portal (admin/index.html) is now the primary way to manage licenses.
The CLI (admin_license.py) is optional, for scripting/automation.
If you want to use the CLI:
- Go to Project Settings → Service Accounts
- Click Generate new private key
- Save the downloaded JSON file as
service_account.jsonin the project directory - IMPORTANT: Never include this file in the EXE or share it with customers
- Install:
pip install firebase-admin
- Start a local server:
python -m http.server 8080 --directory admin - Open
http://localhost:8080in your browser - Sign in with your Google account (Adnan@thothica.com)
- Click + Generate Key to create a test license
- Run
python app.py - The license modal should appear
- Enter the key you generated in the admin portal
- The app should activate and load normally
- In the admin portal, click Revoke next to the test license
- Restart the app — it should show "This license has been revoked" on the next online check
- In the admin portal, click Unbind next to a bound license
- The key can now be activated on a different machine
To access the admin portal from anywhere (not just localhost):
- Install Firebase CLI:
npm install -g firebase-tools - Login:
firebase login - Initialize hosting:
firebase init hosting- Select your project
- Set public directory to
admin - Configure as single-page app: No
- Deploy:
firebase deploy --only hosting - Your admin portal will be live at
https://your-project-id.web.app
Make sure to add your hosting domain to Authentication → Settings → Authorized domains.
- The API key is safe to embed in both the app and admin portal. It only identifies the Firebase project; all access control is enforced by Firestore security rules.
- The service account key (
service_account.json) grants full admin access. Keep it on your machine only. Not needed if you only use the admin portal. - Admin access is restricted to
Adnan@thothica.comvia Firestore security rules. No other Google account can create, modify, or delete licenses. - License keys are stored as SHA-256 hashes in Firestore. Even if someone accesses the database, they can't recover the original keys.
- Machine binding is enforced both in Firestore rules (one-time write) and in the app's local validation logic.
Each license is stored at licenses/{sha256_hash_of_key}:
| Field | Type | Description |
|---|---|---|
| key_preview | string | Last 4 characters of the key (for admin display) |
| machine_id | string | SHA-256 of the bound machine's ID (empty = unbound) |
| expires_at | timestamp | When the license expires |
| revoked | boolean | Whether the license has been revoked |
| created_at | timestamp | When the license was generated |
| note | string | Admin notes (customer name, etc.) |