authguard-sdk 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ """AuthGuard client SDK for Python.
2
+
3
+ Example:
4
+ from authguard_sdk import AuthGuard, AuthGuardError
5
+
6
+ app = AuthGuard(app_id="...", version="1.0", public_key="...")
7
+ app.init()
8
+ app.license("XXXX-XXXX-XXXX-XXXX")
9
+ """
10
+
11
+ from .client import AuthGuard, AuthGuardError, get_hwid
12
+
13
+ __all__ = ["AuthGuard", "AuthGuardError", "get_hwid"]
14
+ __version__ = "1.0.0"
@@ -0,0 +1,103 @@
1
+ """AuthGuard client SDK for Python."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import platform
8
+ import secrets
9
+ import subprocess
10
+ import time
11
+ import uuid
12
+ from typing import Any
13
+
14
+ import requests
15
+ from cryptography.exceptions import InvalidSignature
16
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
17
+
18
+
19
+ class AuthGuardError(Exception):
20
+ def __init__(self, message: str, response: dict[str, Any] | None = None) -> None:
21
+ super().__init__(message)
22
+ self.response: dict[str, Any] = response or {}
23
+
24
+
25
+ def get_hwid() -> str:
26
+ raw = ""
27
+ try:
28
+ if platform.system() == "Windows":
29
+ import winreg
30
+
31
+ with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Cryptography", 0, winreg.KEY_READ | winreg.KEY_WOW64_64KEY) as k:
32
+ raw = winreg.QueryValueEx(k, "MachineGuid")[0]
33
+ elif platform.system() == "Darwin":
34
+ out = subprocess.check_output(["ioreg", "-rd1", "-c", "IOPlatformExpertDevice"], text=True)
35
+ raw = next(line.split('"')[-2] for line in out.splitlines() if "IOPlatformUUID" in line)
36
+ else:
37
+ with open("/etc/machine-id") as f:
38
+ raw = f.read().strip()
39
+ except Exception:
40
+ raw = str(uuid.getnode())
41
+ return hashlib.sha256(raw.encode()).hexdigest()
42
+
43
+
44
+ class AuthGuard:
45
+ # Some hosts' firewalls block the default python-requests user agent, so send our own.
46
+ HEADERS = {"User-Agent": "AuthGuard-SDK/1.0"}
47
+
48
+ def __init__(self, app_id: str, version: str, public_key: str, url: str = "https://authguard.dk/api/client") -> None:
49
+ self.app_id = app_id
50
+ self.version = version
51
+ self.url = url
52
+ self.hwid = get_hwid()
53
+ self.session_id: str | None = None
54
+ self.info: dict[str, Any] = {}
55
+ self._key = Ed25519PublicKey.from_public_bytes(bytes.fromhex(public_key))
56
+
57
+ def _request(self, type_: str, **fields: Any) -> dict[str, Any]:
58
+ nonce = secrets.token_hex(16)
59
+ body = {"type": type_, "app_id": self.app_id, "nonce": nonce, **fields}
60
+ if self.session_id and type_ != "init":
61
+ body["sessionid"] = self.session_id
62
+ try:
63
+ r = requests.post(self.url, json=body, headers=self.HEADERS, timeout=15)
64
+ outer = r.json()
65
+ payload, signature = outer["payload"], outer["signature"]
66
+ except (requests.RequestException, ValueError, KeyError, TypeError) as e:
67
+ raise AuthGuardError(f"Could not reach the license server ({e.__class__.__name__})")
68
+ try:
69
+ self._key.verify(bytes.fromhex(signature), payload.encode())
70
+ except (InvalidSignature, ValueError):
71
+ raise AuthGuardError("Invalid server signature")
72
+ data = json.loads(payload)
73
+ if data.get("nonce") != nonce or abs(time.time() - data.get("timestamp", 0)) > 300:
74
+ raise AuthGuardError("Invalid server response")
75
+ if not data.get("success"):
76
+ raise AuthGuardError(data.get("message", "Request failed"), data)
77
+ return data
78
+
79
+ def init(self) -> None:
80
+ data = self._request("init", version=self.version, hwid=self.hwid)
81
+ self.session_id = data["sessionid"]
82
+
83
+ def license(self, key: str) -> dict[str, Any]:
84
+ self.info = self._request("license", key=key.strip(), hwid=self.hwid)["info"]
85
+ return self.info
86
+
87
+ def register(self, username: str, password: str, key: str) -> dict[str, Any]:
88
+ self.info = self._request("register", username=username, password=password, key=key.strip(), hwid=self.hwid)["info"]
89
+ return self.info
90
+
91
+ def login(self, username: str, password: str) -> dict[str, Any]:
92
+ self.info = self._request("login", username=username, password=password, hwid=self.hwid)["info"]
93
+ return self.info
94
+
95
+ def var(self, name: str) -> str:
96
+ return self._request("var", name=name)["value"]
97
+
98
+ def check(self) -> bool:
99
+ try:
100
+ self._request("check")
101
+ return True
102
+ except AuthGuardError:
103
+ return False
authguard_sdk/py.typed ADDED
File without changes
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.5
2
+ Name: authguard-sdk
3
+ Version: 1.0.0
4
+ Summary: Client SDK for AuthGuard, a software licensing platform with Ed25519-signed responses
5
+ Project-URL: Homepage, https://authguard.dk
6
+ Project-URL: Documentation, https://authguard.dk/docs.php
7
+ Author: AuthGuard
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: authguard,ed25519,keyauth,license-key,licensing
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Topic :: Security :: Cryptography
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.9
19
+ Requires-Dist: cryptography<47,>=43
20
+ Requires-Dist: requests<3,>=2.32
21
+ Description-Content-Type: text/markdown
22
+
23
+ # AuthGuard Python SDK
24
+
25
+ Client SDK for [AuthGuard](https://authguard.dk), a software licensing platform.
26
+ It talks to a single signed endpoint and verifies every reply with Ed25519, so a
27
+ tampered or replayed response is rejected before your code sees it.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install authguard-sdk
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ Copy the app ID and public key from **Dashboard -> your app -> Settings**.
38
+
39
+ ```python
40
+ import sys
41
+
42
+ from authguard_sdk import AuthGuard, AuthGuardError
43
+
44
+ app = AuthGuard(
45
+ app_id="YOUR_APP_ID",
46
+ version="1.0",
47
+ public_key="YOUR_PUBLIC_KEY",
48
+ )
49
+
50
+ try:
51
+ app.init()
52
+ app.license("XXXX-XXXX-XXXX-XXXX")
53
+ except AuthGuardError as e:
54
+ if e.response.get("update_required"):
55
+ print("Update required. Download:", e.response.get("download_url"))
56
+ else:
57
+ print("Error:", e)
58
+ sys.exit(1)
59
+
60
+ print("Access granted. Expires:", app.info.get("expires_at") or "never")
61
+ ```
62
+
63
+ ## API
64
+
65
+ `AuthGuard(app_id, version, public_key, url="https://authguard.dk/api/client")`
66
+
67
+ | Method | Purpose |
68
+ | --- | --- |
69
+ | `init()` | Open a session. Must be called first. Raises if the app is disabled or the version is out of date. |
70
+ | `license(key)` | Authenticate with a licence key. Returns the `info` dict. |
71
+ | `login(username, password)` | Authenticate an app user. Returns the `info` dict. |
72
+ | `register(username, password, key)` | Create an app user from a licence key. Returns the `info` dict. |
73
+ | `var(name)` | Read a server-side variable defined in the dashboard. |
74
+ | `check()` | Re-validate the session later on. Returns `True` or `False`. |
75
+
76
+ After a successful `license`, `login` or `register`, `app.info` holds the
77
+ subscription details (`expires_at`, `username`, `hwid`, ...).
78
+
79
+ Every failure raises `AuthGuardError`. Its `.response` attribute carries the
80
+ server's decoded payload when there was one, which is how you detect
81
+ `update_required` and read `download_url`.
82
+
83
+ ## Security notes
84
+
85
+ - Responses are Ed25519-signed over the exact payload bytes; the signature is
86
+ verified before the payload is parsed.
87
+ - Each request carries a random nonce that must be echoed back, and replies more
88
+ than five minutes from the local clock are rejected.
89
+ - The app ID and public key are identifiers, not secrets. It is fine for them to
90
+ sit in your source.
91
+ - The SDK sends a `User-Agent` of `AuthGuard-SDK/1.0`. Do not change it: some
92
+ hosting firewalls block the default `python-requests` user agent.
93
+
94
+ Full documentation: <https://authguard.dk/docs.php>
@@ -0,0 +1,7 @@
1
+ authguard_sdk/__init__.py,sha256=zqWO1N__QQRib5VPANTPWyHAP3emMQJ00xei8qSw1sI,361
2
+ authguard_sdk/client.py,sha256=sr0trDhuGZMLTAdsSSs4AltjdkFshfDkygU3HBSoeao,4072
3
+ authguard_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ authguard_sdk-1.0.0.dist-info/METADATA,sha256=Nsn9pzNd6isyEywPskJEBRbVx8njGk0aojLVImBRLPc,3323
5
+ authguard_sdk-1.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
6
+ authguard_sdk-1.0.0.dist-info/licenses/LICENSE,sha256=PYm9h5OLshu43fGAJA9lQ-Vjx8sIcoTADdWTpHbsMIg,1066
7
+ authguard_sdk-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AuthGuard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.