|
| 1 | +# AuthForge SDK — AI Agent Reference |
| 2 | + |
| 3 | +> This file is optimized for AI coding agents (Cursor, Copilot, Claude Code, etc.). |
| 4 | +> It contains everything needed to correctly integrate AuthForge licensing into a project. |
| 5 | +
|
| 6 | +## What AuthForge does |
| 7 | + |
| 8 | +AuthForge is a license key validation service. Your app sends a license key + hardware ID to the AuthForge API, gets back a cryptographically signed response, and runs background heartbeats to maintain the session. If the license is revoked or expired, the heartbeat fails and you handle it (typically exit the app). |
| 9 | + |
| 10 | +## Installation |
| 11 | + |
| 12 | +Copy `authforge.py` into your project (single file, stdlib only). Requires Python 3.9+. |
| 13 | + |
| 14 | +## Minimal working integration |
| 15 | + |
| 16 | +```python |
| 17 | +import sys |
| 18 | +from typing import Optional |
| 19 | + |
| 20 | +from authforge import AuthForgeClient |
| 21 | + |
| 22 | + |
| 23 | +def on_failure(reason: str, exc: Optional[Exception]) -> None: |
| 24 | + print(f"AuthForge: {reason}", file=sys.stderr) |
| 25 | + if exc is not None: |
| 26 | + print(exc, file=sys.stderr) |
| 27 | + sys.exit(1) |
| 28 | + |
| 29 | + |
| 30 | +def main() -> None: |
| 31 | + client = AuthForgeClient( |
| 32 | + app_id="YOUR_APP_ID", |
| 33 | + app_secret="YOUR_APP_SECRET", |
| 34 | + heartbeat_mode="SERVER", |
| 35 | + on_failure=on_failure, |
| 36 | + ) |
| 37 | + license_key = input("Enter license key: ").strip() |
| 38 | + if not client.login(license_key): |
| 39 | + print("Login failed.", file=sys.stderr) |
| 40 | + sys.exit(1) |
| 41 | + # --- Your application code starts here --- |
| 42 | + print("Running with a valid license.") |
| 43 | + # --- Your application code ends here --- |
| 44 | + client.logout() |
| 45 | + |
| 46 | + |
| 47 | +if __name__ == "__main__": |
| 48 | + main() |
| 49 | +``` |
| 50 | + |
| 51 | +## Constructor parameters |
| 52 | + |
| 53 | +| Parameter | Type | Required | Default | Description | |
| 54 | +|-----------|------|----------|---------|-------------| |
| 55 | +| `app_id` | `str` | yes | — | Application ID | |
| 56 | +| `app_secret` | `str` | yes | — | Application secret | |
| 57 | +| `heartbeat_mode` | `str` | yes | — | `"SERVER"` or `"LOCAL"` (case-insensitive) | |
| 58 | +| `heartbeat_interval` | `int` | no | `900` | Seconds between heartbeats | |
| 59 | +| `api_base_url` | `str` | no | `https://auth.authforge.cc` | API base URL | |
| 60 | +| `on_failure` | `Callable[[str, Optional[Exception]], None] \| None` | no | `None` | Called on login/heartbeat/network failure; if omitted, process exits via `os._exit(1)` | |
| 61 | +| `request_timeout` | `int` | no | `15` | HTTP timeout (seconds) | |
| 62 | + |
| 63 | +## Methods |
| 64 | + |
| 65 | +| Method | Returns | Description | |
| 66 | +|--------|---------|-------------| |
| 67 | +| `login(license_key: str)` | `bool` | Validates license, verifies signatures, starts heartbeat thread | |
| 68 | +| `logout()` | `None` | Stops heartbeat and clears session state | |
| 69 | +| `is_authenticated()` | `bool` | Whether a session token is present and marked authenticated | |
| 70 | +| `get_session_data()` | `dict \| None` | Decoded signed payload map | |
| 71 | +| `get_app_variables()` | `dict \| None` | App-scoped variables | |
| 72 | +| `get_license_variables()` | `dict \| None` | License-scoped variables | |
| 73 | + |
| 74 | +## Error codes the server can return |
| 75 | + |
| 76 | +invalid_app, invalid_key, expired, revoked, hwid_mismatch, no_credits, blocked, rate_limited, replay_detected, checksum_required, checksum_mismatch, session_expired, app_disabled |
| 77 | + |
| 78 | +## Common patterns |
| 79 | + |
| 80 | +### Reading license variables (feature gating) |
| 81 | + |
| 82 | +```python |
| 83 | +vars_map = client.get_license_variables() or {} |
| 84 | +tier = vars_map.get("tier") |
| 85 | +``` |
| 86 | + |
| 87 | +### Graceful shutdown |
| 88 | + |
| 89 | +```python |
| 90 | +client.logout() |
| 91 | +``` |
| 92 | + |
| 93 | +### Custom error handling |
| 94 | + |
| 95 | +Server error codes appear as `ValueError` in the `exc` passed to `on_failure` from failed validation (e.g. `invalid_key`). Reasons are `login_failed`, `heartbeat_failed`, or `network_error`. |
| 96 | + |
| 97 | +```python |
| 98 | +import sys |
| 99 | +from typing import Optional |
| 100 | + |
| 101 | +def on_failure(reason: str, exc: Optional[Exception]) -> None: |
| 102 | + if isinstance(exc, ValueError) and exc.args: |
| 103 | + code = str(exc.args[0]) |
| 104 | + if code in {"invalid_key", "expired", "revoked"}: |
| 105 | + print(f"License issue: {code}", file=sys.stderr) |
| 106 | + sys.exit(1) |
| 107 | +``` |
| 108 | + |
| 109 | +## Do NOT |
| 110 | + |
| 111 | +- Do not hardcode the app secret as a plain string literal in source — use environment variables or encrypted config |
| 112 | +- Do not skip the `on_failure` callback — without it, heartbeat failures terminate the process via `os._exit(1)` without your cleanup |
| 113 | +- Do not call `login()` on every app action — call it once at startup; heartbeats handle the rest |
| 114 | +- Do not use `heartbeat_mode="LOCAL"` unless the app has no internet after initial auth |
0 commit comments