shieldauth-key 0.1.0__tar.gz
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.
- shieldauth_key-0.1.0/PKG-INFO +37 -0
- shieldauth_key-0.1.0/README.md +26 -0
- shieldauth_key-0.1.0/pyproject.toml +20 -0
- shieldauth_key-0.1.0/setup.cfg +4 -0
- shieldauth_key-0.1.0/shieldauth/__init__.py +4 -0
- shieldauth_key-0.1.0/shieldauth/_hwid.py +8 -0
- shieldauth_key-0.1.0/shieldauth/_prompt.py +37 -0
- shieldauth_key-0.1.0/shieldauth/_store.py +42 -0
- shieldauth_key-0.1.0/shieldauth/_verify.py +123 -0
- shieldauth_key-0.1.0/shieldauth_key.egg-info/PKG-INFO +37 -0
- shieldauth_key-0.1.0/shieldauth_key.egg-info/SOURCES.txt +12 -0
- shieldauth_key-0.1.0/shieldauth_key.egg-info/dependency_links.txt +1 -0
- shieldauth_key-0.1.0/shieldauth_key.egg-info/requires.txt +1 -0
- shieldauth_key-0.1.0/shieldauth_key.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: shieldauth-key
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: License key protection for Python apps — one line of code
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://shieldauth.pages.dev
|
|
7
|
+
Project-URL: Source, https://github.com/shieldauth/shieldauth-python
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: requests>=2.28
|
|
11
|
+
|
|
12
|
+
# shieldauth
|
|
13
|
+
|
|
14
|
+
License key protection for Python apps — one line of code.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install shieldauth
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import shieldauth
|
|
26
|
+
shieldauth.verify("sk_live_xxxx")
|
|
27
|
+
|
|
28
|
+
# Your app code here — only runs if license is valid
|
|
29
|
+
print("Welcome!")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
On first run, the user is prompted for their license key. It's saved locally so they're never asked again.
|
|
33
|
+
|
|
34
|
+
## Links
|
|
35
|
+
|
|
36
|
+
- Dashboard: https://shieldauth.pages.dev
|
|
37
|
+
- Docs: https://shieldauth.pages.dev/docs
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# shieldauth
|
|
2
|
+
|
|
3
|
+
License key protection for Python apps — one line of code.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install shieldauth
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import shieldauth
|
|
15
|
+
shieldauth.verify("sk_live_xxxx")
|
|
16
|
+
|
|
17
|
+
# Your app code here — only runs if license is valid
|
|
18
|
+
print("Welcome!")
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
On first run, the user is prompted for their license key. It's saved locally so they're never asked again.
|
|
22
|
+
|
|
23
|
+
## Links
|
|
24
|
+
|
|
25
|
+
- Dashboard: https://shieldauth.pages.dev
|
|
26
|
+
- Docs: https://shieldauth.pages.dev/docs
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "shieldauth-key"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "License key protection for Python apps — one line of code"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
dependencies = ["requests>=2.28"]
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://shieldauth.pages.dev"
|
|
16
|
+
Source = "https://github.com/shieldauth/shieldauth-python"
|
|
17
|
+
|
|
18
|
+
[tool.setuptools.packages.find]
|
|
19
|
+
where = ["."]
|
|
20
|
+
include = ["shieldauth*"]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
def ask_for_key(app_name: str) -> str:
|
|
2
|
+
"""Prompt the user for a license key.
|
|
3
|
+
Uses a tkinter GUI dialog if available (works inside compiled EXEs),
|
|
4
|
+
falls back to terminal input.
|
|
5
|
+
"""
|
|
6
|
+
try:
|
|
7
|
+
import tkinter as tk
|
|
8
|
+
from tkinter import simpledialog
|
|
9
|
+
|
|
10
|
+
root = tk.Tk()
|
|
11
|
+
root.withdraw()
|
|
12
|
+
root.lift()
|
|
13
|
+
root.attributes("-topmost", True)
|
|
14
|
+
|
|
15
|
+
key = simpledialog.askstring(
|
|
16
|
+
title=f"{app_name} — License",
|
|
17
|
+
prompt=f"Enter your {app_name} license key:",
|
|
18
|
+
parent=root,
|
|
19
|
+
)
|
|
20
|
+
root.destroy()
|
|
21
|
+
|
|
22
|
+
if key is None:
|
|
23
|
+
# User closed the dialog
|
|
24
|
+
print("License key required. Exiting.")
|
|
25
|
+
raise SystemExit(1)
|
|
26
|
+
|
|
27
|
+
return key.strip()
|
|
28
|
+
|
|
29
|
+
except ImportError:
|
|
30
|
+
# tkinter not available — use terminal
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
key = input(f"Enter your {app_name} license key: ").strip()
|
|
34
|
+
if not key:
|
|
35
|
+
print("License key required. Exiting.")
|
|
36
|
+
raise SystemExit(1)
|
|
37
|
+
return key
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _path() -> Path:
|
|
8
|
+
return Path.home() / ".shieldauth" / "keys.json"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load(app_secret: str) -> Optional[dict]:
|
|
12
|
+
p = _path()
|
|
13
|
+
if not p.exists():
|
|
14
|
+
return None
|
|
15
|
+
try:
|
|
16
|
+
data = json.loads(p.read_text())
|
|
17
|
+
return data.get(app_secret)
|
|
18
|
+
except Exception:
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def save(app_secret: str, entry: dict) -> None:
|
|
23
|
+
p = _path()
|
|
24
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
try:
|
|
26
|
+
data = json.loads(p.read_text()) if p.exists() else {}
|
|
27
|
+
except Exception:
|
|
28
|
+
data = {}
|
|
29
|
+
data[app_secret] = entry
|
|
30
|
+
p.write_text(json.dumps(data, indent=2))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def clear(app_secret: str) -> None:
|
|
34
|
+
p = _path()
|
|
35
|
+
if not p.exists():
|
|
36
|
+
return
|
|
37
|
+
try:
|
|
38
|
+
data = json.loads(p.read_text())
|
|
39
|
+
data.pop(app_secret, None)
|
|
40
|
+
p.write_text(json.dumps(data, indent=2))
|
|
41
|
+
except Exception:
|
|
42
|
+
pass
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from datetime import datetime, timezone, timedelta
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from . import _hwid, _store, _prompt
|
|
7
|
+
|
|
8
|
+
API_BASE = "https://shieldauth.pages.dev/api/v1"
|
|
9
|
+
OFFLINE_GRACE_DAYS = 7
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _post(endpoint: str, payload: dict, timeout: int = 8) -> Optional[dict]:
|
|
13
|
+
try:
|
|
14
|
+
r = requests.post(f"{API_BASE}/{endpoint}", json=payload, timeout=timeout)
|
|
15
|
+
return r.json()
|
|
16
|
+
except Exception:
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def verify(app_secret: str) -> None:
|
|
21
|
+
hwid = _hwid.get()
|
|
22
|
+
stored = _store.load(app_secret)
|
|
23
|
+
|
|
24
|
+
# ── Case 1: No saved key — need to prompt ────────────────────────────────
|
|
25
|
+
if not stored:
|
|
26
|
+
# Fetch app_name first (key=None)
|
|
27
|
+
res = _post("validate", {"app_secret": app_secret, "hwid": hwid})
|
|
28
|
+
if res is None:
|
|
29
|
+
print("Unable to reach ShieldAuth servers and no cached key found. Exiting.")
|
|
30
|
+
raise SystemExit(1)
|
|
31
|
+
|
|
32
|
+
app_name = res.get("app_name", "ShieldAuth")
|
|
33
|
+
|
|
34
|
+
if "error" in res and res["error"] == "invalid_app_secret":
|
|
35
|
+
# Developer misconfigured — show a generic error
|
|
36
|
+
print("Invalid application configuration. Exiting.")
|
|
37
|
+
raise SystemExit(1)
|
|
38
|
+
|
|
39
|
+
key = _prompt.ask_for_key(app_name)
|
|
40
|
+
_activate(app_secret, key, hwid, app_name)
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# ── Case 2: Saved key — validate silently ────────────────────────────────
|
|
44
|
+
key = stored["key"]
|
|
45
|
+
app_name = stored.get("app_name", "ShieldAuth")
|
|
46
|
+
|
|
47
|
+
res = _post("validate", {"app_secret": app_secret, "key": key, "hwid": hwid})
|
|
48
|
+
|
|
49
|
+
if res is None:
|
|
50
|
+
# Network error — allow offline grace period
|
|
51
|
+
last_seen_str = stored.get("last_seen")
|
|
52
|
+
if last_seen_str:
|
|
53
|
+
last_seen = datetime.fromisoformat(last_seen_str)
|
|
54
|
+
if datetime.now(timezone.utc) - last_seen < timedelta(days=OFFLINE_GRACE_DAYS):
|
|
55
|
+
return # within grace period, allow through
|
|
56
|
+
print("Unable to verify license. Check your internet connection. Exiting.")
|
|
57
|
+
raise SystemExit(1)
|
|
58
|
+
|
|
59
|
+
if res.get("valid"):
|
|
60
|
+
# Update last_seen in local store
|
|
61
|
+
_store.save(app_secret, {
|
|
62
|
+
**stored,
|
|
63
|
+
"last_seen": datetime.now(timezone.utc).isoformat(),
|
|
64
|
+
})
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
error = res.get("error", "")
|
|
68
|
+
|
|
69
|
+
if error == "hwid_mismatch":
|
|
70
|
+
print(
|
|
71
|
+
f"\n[{app_name}] This license key is registered to a different device.\n"
|
|
72
|
+
"To transfer it, visit: https://shieldauth.pages.dev/reset\n"
|
|
73
|
+
)
|
|
74
|
+
raise SystemExit(1)
|
|
75
|
+
|
|
76
|
+
if error in ("paused", "banned"):
|
|
77
|
+
print(f"\n[{app_name}] Your license has been {error}. Contact support.")
|
|
78
|
+
raise SystemExit(1)
|
|
79
|
+
|
|
80
|
+
if error == "expired":
|
|
81
|
+
print(f"\n[{app_name}] Your license has expired. Please renew.")
|
|
82
|
+
raise SystemExit(1)
|
|
83
|
+
|
|
84
|
+
# Key no longer valid — clear cache and re-prompt
|
|
85
|
+
_store.clear(app_secret)
|
|
86
|
+
print(f"\n[{app_name}] License key is no longer valid.")
|
|
87
|
+
key = _prompt.ask_for_key(app_name)
|
|
88
|
+
_activate(app_secret, key, hwid, app_name)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _activate(app_secret: str, key: str, hwid: str, app_name: str) -> None:
|
|
92
|
+
res = _post("validate", {"app_secret": app_secret, "key": key, "hwid": hwid})
|
|
93
|
+
|
|
94
|
+
if res is None:
|
|
95
|
+
print("Unable to reach ShieldAuth servers. Exiting.")
|
|
96
|
+
raise SystemExit(1)
|
|
97
|
+
|
|
98
|
+
if res.get("valid"):
|
|
99
|
+
_store.save(app_secret, {
|
|
100
|
+
"key": key,
|
|
101
|
+
"app_name": res.get("app_name", app_name),
|
|
102
|
+
"last_seen": datetime.now(timezone.utc).isoformat(),
|
|
103
|
+
})
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
error = res.get("error", "")
|
|
107
|
+
app_name = res.get("app_name", app_name)
|
|
108
|
+
|
|
109
|
+
if error == "hwid_mismatch":
|
|
110
|
+
print(
|
|
111
|
+
f"\n[{app_name}] This key is already activated on another device.\n"
|
|
112
|
+
"Visit https://shieldauth.pages.dev/reset to transfer it.\n"
|
|
113
|
+
)
|
|
114
|
+
elif error == "key_not_found":
|
|
115
|
+
print(f"\n[{app_name}] Invalid license key. Please check and try again.")
|
|
116
|
+
elif error in ("paused", "banned"):
|
|
117
|
+
print(f"\n[{app_name}] This license key has been {error}.")
|
|
118
|
+
elif error == "expired":
|
|
119
|
+
print(f"\n[{app_name}] This license key has expired.")
|
|
120
|
+
else:
|
|
121
|
+
print(f"\n[{app_name}] License verification failed: {error}")
|
|
122
|
+
|
|
123
|
+
raise SystemExit(1)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: shieldauth-key
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: License key protection for Python apps — one line of code
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://shieldauth.pages.dev
|
|
7
|
+
Project-URL: Source, https://github.com/shieldauth/shieldauth-python
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: requests>=2.28
|
|
11
|
+
|
|
12
|
+
# shieldauth
|
|
13
|
+
|
|
14
|
+
License key protection for Python apps — one line of code.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install shieldauth
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import shieldauth
|
|
26
|
+
shieldauth.verify("sk_live_xxxx")
|
|
27
|
+
|
|
28
|
+
# Your app code here — only runs if license is valid
|
|
29
|
+
print("Welcome!")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
On first run, the user is prompted for their license key. It's saved locally so they're never asked again.
|
|
33
|
+
|
|
34
|
+
## Links
|
|
35
|
+
|
|
36
|
+
- Dashboard: https://shieldauth.pages.dev
|
|
37
|
+
- Docs: https://shieldauth.pages.dev/docs
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
shieldauth/__init__.py
|
|
4
|
+
shieldauth/_hwid.py
|
|
5
|
+
shieldauth/_prompt.py
|
|
6
|
+
shieldauth/_store.py
|
|
7
|
+
shieldauth/_verify.py
|
|
8
|
+
shieldauth_key.egg-info/PKG-INFO
|
|
9
|
+
shieldauth_key.egg-info/SOURCES.txt
|
|
10
|
+
shieldauth_key.egg-info/dependency_links.txt
|
|
11
|
+
shieldauth_key.egg-info/requires.txt
|
|
12
|
+
shieldauth_key.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.28
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
shieldauth
|