|
1 | | -# AuthForge Python SDK |
2 | | - |
3 | | -Official Python SDK for [AuthForge](https://authforge.cc) — credit-based license key authentication with HMAC-verified heartbeats. |
4 | | - |
5 | | -**Zero dependencies.** Standard library only. Works on Python 3.9+. |
6 | | - |
7 | | -## Quick Start |
8 | | - |
9 | | -Copy `authforge.py` into your project, then: |
10 | | - |
11 | | -```python |
12 | | -from authforge import AuthForgeClient |
13 | | - |
14 | | -client = AuthForgeClient( |
15 | | - app_id="YOUR_APP_ID", # from your AuthForge dashboard |
16 | | - app_secret="YOUR_APP_SECRET", # from your AuthForge dashboard |
17 | | - heartbeat_mode="SERVER", # "SERVER" or "LOCAL" |
18 | | -) |
19 | | - |
20 | | -license_key = input("Enter license key: ") |
21 | | - |
22 | | -if client.login(license_key): |
23 | | - print("Authenticated!") |
24 | | - # Your app logic here — heartbeats run automatically in the background |
25 | | -else: |
26 | | - print("Invalid license key.") |
27 | | - exit(1) |
28 | | -``` |
29 | | - |
30 | | -## Configuration |
31 | | - |
32 | | -| Parameter | Type | Default | Description | |
33 | | -|---|---|---|---| |
34 | | -| `app_id` | str | required | Your application ID from the AuthForge dashboard | |
35 | | -| `app_secret` | str | required | Your application secret from the AuthForge dashboard | |
36 | | -| `heartbeat_mode` | str | required | `"SERVER"` or `"LOCAL"` (see below) | |
37 | | -| `heartbeat_interval` | int | `900` | Seconds between heartbeat checks (default 15 min) | |
38 | | -| `api_base_url` | str | `https://auth.authforge.cc` | API endpoint | |
39 | | -| `on_failure` | callable | `None` | Callback `(reason: str, exc: Exception | None)` on auth failure | |
40 | | -| `request_timeout` | int | `15` | HTTP request timeout in seconds | |
41 | | - |
42 | | -## Methods |
43 | | - |
44 | | -| Method | Returns | Description | |
45 | | -|---|---|---| |
46 | | -| `login(license_key)` | `bool` | Validates key and stores signed session (`sessionToken`, `expiresIn`, `appVariables`, `licenseVariables`) | |
47 | | -| `logout()` | `None` | Stops heartbeat and clears all session/auth state | |
48 | | -| `is_authenticated()` | `bool` | True when an active authenticated session exists | |
49 | | -| `get_session_data()` | `dict \| None` | Full decoded payload map | |
50 | | -| `get_app_variables()` | `dict \| None` | App-scoped variables map | |
51 | | -| `get_license_variables()` | `dict \| None` | License-scoped variables map | |
52 | | - |
53 | | -## Heartbeat Modes |
54 | | - |
55 | | -**SERVER** — The SDK calls `/auth/heartbeat` every `heartbeat_interval` seconds with a fresh nonce, verifies signature + nonce, and triggers failure on invalid session state. |
56 | | - |
57 | | -**LOCAL** — No network calls. The SDK re-verifies stored signature state and checks expiry timestamp locally. If expired, it triggers failure with `session_expired`. |
58 | | - |
59 | | -## Failure Handling |
60 | | - |
61 | | -If authentication fails (login rejected, heartbeat fails, signature mismatch, etc.), the SDK calls your `on_failure` callback if one is provided. If no callback is set, **the SDK calls `os._exit(1)` to terminate the process.** This is intentional — it prevents your app from running without a valid license. |
62 | | - |
63 | | -Recognized server errors: |
64 | | -`invalid_app`, `invalid_key`, `expired`, `revoked`, `hwid_mismatch`, `no_credits`, `blocked`, `rate_limited`, `replay_detected`, `app_disabled`, `session_expired`, `bad_request`, `checksum_required`, `checksum_mismatch` |
65 | | - |
66 | | -Request retries are automatic inside the internal HTTP layer: |
67 | | -- `rate_limited`: retry after 2s, then 5s (max 3 attempts total) |
68 | | -- network failure: retry once after 2s |
69 | | -- every retry regenerates a fresh nonce |
70 | | - |
71 | | -```python |
72 | | -def handle_auth_failure(reason, exception): |
73 | | - print(f"Auth failed: {reason}") |
74 | | - if exception: |
75 | | - print(f"Details: {exception}") |
76 | | - # Clean up and exit gracefully |
77 | | - sys.exit(1) |
78 | | - |
79 | | -client = AuthForgeClient( |
80 | | - app_id="YOUR_APP_ID", |
81 | | - app_secret="YOUR_APP_SECRET", |
82 | | - heartbeat_mode="SERVER", |
83 | | - on_failure=handle_auth_failure, |
84 | | -) |
85 | | -``` |
86 | | - |
87 | | -## How It Works |
88 | | - |
89 | | -1. **Login** — Collects a hardware fingerprint (MAC, CPU, disk serial), generates a random nonce, and sends everything to the AuthForge API. The server validates the license key, binds the HWID, deducts a credit, and returns a signed payload. The SDK verifies the HMAC-SHA256 signature and nonce to prevent replay attacks. |
90 | | - |
91 | | -2. **Heartbeat** — A background daemon thread checks in at the configured interval. In SERVER mode, it sends a fresh nonce and verifies the response. In LOCAL mode, it re-verifies the stored signature and checks expiry without network calls. |
92 | | - |
93 | | -3. **Crypto** — The `/validate` response is signed with a key derived from `SHA256(appSecret + nonce)`. That response carries a per-session `sigKey` (32-byte random hex) embedded in the signed session token. Every `/heartbeat` response is then signed with a key derived from `SHA256(sigKey + nonce)`. This keeps `appSecret` out of the heartbeat path while still rotating the signing key on every nonce, making replay and MITM attacks impractical. |
94 | | - |
95 | | -## Hardware ID |
96 | | - |
97 | | -The SDK generates a deterministic hardware fingerprint by hashing: |
98 | | -- MAC address |
99 | | -- CPU identifier |
100 | | -- Disk serial number |
101 | | - |
102 | | -Each component falls back gracefully if it can't be read (e.g. permissions issues). The HWID is sent with every auth request so the server can enforce per-device license limits. |
103 | | - |
104 | | -## Test Vectors |
105 | | - |
106 | | -The `generate_vectors.py` script and `test_vectors.json` file are provided for cross-SDK verification. If you're porting this SDK to another language, your implementation must produce identical `derivedKeyHex` and `signatureHex` values for the same inputs. |
107 | | - |
108 | | -## Requirements |
109 | | - |
110 | | -- Python 3.9+ |
111 | | -- No external packages |
112 | | - |
113 | | -## License |
114 | | - |
| 1 | +# AuthForge Python SDK |
| 2 | + |
| 3 | +Official Python SDK for [AuthForge](https://authforge.cc) — credit-based license key authentication with Ed25519-verified responses. |
| 4 | + |
| 5 | +Uses `cryptography` for Ed25519 verification. Works on Python 3.9+. |
| 6 | + |
| 7 | +## Quick Start |
| 8 | + |
| 9 | +Copy `authforge.py` into your project, then: |
| 10 | + |
| 11 | +```python |
| 12 | +from authforge import AuthForgeClient |
| 13 | + |
| 14 | +client = AuthForgeClient( |
| 15 | + app_id="YOUR_APP_ID", # from your AuthForge dashboard |
| 16 | + app_secret="YOUR_APP_SECRET", # from your AuthForge dashboard |
| 17 | + public_key="YOUR_PUBLIC_KEY", # from your AuthForge dashboard |
| 18 | + heartbeat_mode="SERVER", # "SERVER" or "LOCAL" |
| 19 | +) |
| 20 | + |
| 21 | +license_key = input("Enter license key: ") |
| 22 | + |
| 23 | +if client.login(license_key): |
| 24 | + print("Authenticated!") |
| 25 | + # Your app logic here — heartbeats run automatically in the background |
| 26 | +else: |
| 27 | + print("Invalid license key.") |
| 28 | + exit(1) |
| 29 | +``` |
| 30 | + |
| 31 | +## Configuration |
| 32 | + |
| 33 | +| Parameter | Type | Default | Description | |
| 34 | +|---|---|---|---| |
| 35 | +| `app_id` | str | required | Your application ID from the AuthForge dashboard | |
| 36 | +| `app_secret` | str | required | Your application secret from the AuthForge dashboard | |
| 37 | +| `public_key` | str | required | App Ed25519 public key (base64) from dashboard | |
| 38 | +| `heartbeat_mode` | str | required | `"SERVER"` or `"LOCAL"` (see below) | |
| 39 | +| `heartbeat_interval` | int | `900` | Seconds between heartbeat checks (default 15 min) | |
| 40 | +| `api_base_url` | str | `https://auth.authforge.cc` | API endpoint | |
| 41 | +| `on_failure` | callable | `None` | Callback `(reason: str, exc: Exception | None)` on auth failure | |
| 42 | +| `request_timeout` | int | `15` | HTTP request timeout in seconds | |
| 43 | + |
| 44 | +## Methods |
| 45 | + |
| 46 | +| Method | Returns | Description | |
| 47 | +|---|---|---| |
| 48 | +| `login(license_key)` | `bool` | Validates key and stores signed session (`sessionToken`, `expiresIn`, `appVariables`, `licenseVariables`) | |
| 49 | +| `logout()` | `None` | Stops heartbeat and clears all session/auth state | |
| 50 | +| `is_authenticated()` | `bool` | True when an active authenticated session exists | |
| 51 | +| `get_session_data()` | `dict \| None` | Full decoded payload map | |
| 52 | +| `get_app_variables()` | `dict \| None` | App-scoped variables map | |
| 53 | +| `get_license_variables()` | `dict \| None` | License-scoped variables map | |
| 54 | + |
| 55 | +## Heartbeat Modes |
| 56 | + |
| 57 | +**SERVER** — The SDK calls `/auth/heartbeat` every `heartbeat_interval` seconds with a fresh nonce, verifies signature + nonce, and triggers failure on invalid session state. |
| 58 | + |
| 59 | +**LOCAL** — No network calls. The SDK re-verifies stored signature state and checks expiry timestamp locally. If expired, it triggers failure with `session_expired`. |
| 60 | + |
| 61 | +## Failure Handling |
| 62 | + |
| 63 | +If authentication fails (login rejected, heartbeat fails, signature mismatch, etc.), the SDK calls your `on_failure` callback if one is provided. If no callback is set, **the SDK calls `os._exit(1)` to terminate the process.** This is intentional — it prevents your app from running without a valid license. |
| 64 | + |
| 65 | +Recognized server errors: |
| 66 | +`invalid_app`, `invalid_key`, `expired`, `revoked`, `hwid_mismatch`, `no_credits`, `blocked`, `rate_limited`, `replay_detected`, `app_disabled`, `session_expired`, `bad_request` |
| 67 | + |
| 68 | +Request retries are automatic inside the internal HTTP layer: |
| 69 | +- `rate_limited`: retry after 2s, then 5s (max 3 attempts total) |
| 70 | +- network failure: retry once after 2s |
| 71 | +- every retry regenerates a fresh nonce |
| 72 | + |
| 73 | +```python |
| 74 | +def handle_auth_failure(reason, exception): |
| 75 | + print(f"Auth failed: {reason}") |
| 76 | + if exception: |
| 77 | + print(f"Details: {exception}") |
| 78 | + # Clean up and exit gracefully |
| 79 | + sys.exit(1) |
| 80 | + |
| 81 | +client = AuthForgeClient( |
| 82 | + app_id="YOUR_APP_ID", |
| 83 | + app_secret="YOUR_APP_SECRET", |
| 84 | + public_key="YOUR_PUBLIC_KEY", |
| 85 | + heartbeat_mode="SERVER", |
| 86 | + on_failure=handle_auth_failure, |
| 87 | +) |
| 88 | +``` |
| 89 | + |
| 90 | +## How It Works |
| 91 | + |
| 92 | +1. **Login** — Collects a hardware fingerprint (MAC, CPU, disk serial), generates a random nonce, and sends everything to the AuthForge API. The server validates the license key, binds the HWID, deducts a credit, and returns a signed payload. The SDK verifies the Ed25519 signature and nonce to prevent replay attacks. |
| 93 | + |
| 94 | +2. **Heartbeat** — A background daemon thread checks in at the configured interval. In SERVER mode, it sends a fresh nonce and verifies the response. In LOCAL mode, it re-verifies the stored signature and checks expiry without network calls. |
| 95 | + |
| 96 | +3. **Crypto** — Both `/validate` and `/heartbeat` responses are signed by AuthForge with your app's Ed25519 private key. The SDK verifies every signed `payload` using your configured `public_key` and rejects tampered responses. |
| 97 | + |
| 98 | +## Hardware ID |
| 99 | + |
| 100 | +The SDK generates a deterministic hardware fingerprint by hashing: |
| 101 | +- MAC address |
| 102 | +- CPU identifier |
| 103 | +- Disk serial number |
| 104 | + |
| 105 | +Each component falls back gracefully if it can't be read (e.g. permissions issues). The HWID is sent with every auth request so the server can enforce per-device license limits. |
| 106 | + |
| 107 | +## Test Vectors |
| 108 | + |
| 109 | +The shared `test_vectors.json` file validates cross-language Ed25519 verification behavior. |
| 110 | + |
| 111 | +## Requirements |
| 112 | + |
| 113 | +- Python 3.9+ |
| 114 | +- Dependency: `cryptography` |
| 115 | + |
| 116 | +## License |
| 117 | + |
115 | 118 | MIT |
0 commit comments