Hiren122 commited on
Commit
1e2860e
·
verified ·
1 Parent(s): 598d624

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +164 -0
app.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ============================================================
3
+ LICENSE SERVER — Deploy this on a SEPARATE HuggingFace Space
4
+
5
+ This server:
6
+ - Stores valid license keys (in licenses.json)
7
+ - Validates keys submitted by clients
8
+ - Lets you add/revoke keys via admin API
9
+
10
+ Set the env var ADMIN_SECRET in your HF Space secrets.
11
+ ============================================================
12
+ """
13
+
14
+ import os
15
+ import json
16
+ import uuid
17
+ import hashlib
18
+ from datetime import datetime, timezone
19
+ from pathlib import Path
20
+ from fastapi import FastAPI, HTTPException, Header, Depends
21
+ from fastapi.responses import JSONResponse
22
+ import uvicorn
23
+
24
+ # ============================================================
25
+ # CONFIG
26
+ # ============================================================
27
+ ADMIN_SECRET = os.environ.get("ADMIN_SECRET", "change-me-super-secret")
28
+ LICENSE_FILE = Path("licenses.json")
29
+ PORT = int(os.environ.get("PORT", 7860))
30
+
31
+ # ============================================================
32
+ # LICENSE STORAGE
33
+ # ============================================================
34
+
35
+ def load_licenses() -> dict:
36
+ """Load licenses from file."""
37
+ if LICENSE_FILE.exists():
38
+ try:
39
+ return json.loads(LICENSE_FILE.read_text())
40
+ except Exception:
41
+ return {}
42
+ return {}
43
+
44
+ def save_licenses(data: dict):
45
+ """Save licenses to file."""
46
+ LICENSE_FILE.write_text(json.dumps(data, indent=2))
47
+
48
+ def hash_key(key: str) -> str:
49
+ """Hash a license key for storage (never store raw keys)."""
50
+ return hashlib.sha256(key.encode()).hexdigest()
51
+
52
+ # ============================================================
53
+ # FASTAPI APP
54
+ # ============================================================
55
+ app = FastAPI(title="License Server", docs_url=None, redoc_url=None)
56
+
57
+ # ---- Admin auth dependency ----
58
+ def require_admin(x_admin_secret: str = Header(...)):
59
+ if x_admin_secret != ADMIN_SECRET:
60
+ raise HTTPException(status_code=403, detail="Invalid admin secret")
61
+
62
+ # ============================================================
63
+ # PUBLIC ENDPOINT — clients call this to validate their key
64
+ # ============================================================
65
+ @app.get("/validate")
66
+ async def validate_license(key: str):
67
+ """
68
+ Validate a license key.
69
+ Returns {"valid": true/false, "message": "..."}
70
+ """
71
+ licenses = load_licenses()
72
+ key_hash = hash_key(key)
73
+
74
+ if key_hash not in licenses:
75
+ return JSONResponse({"valid": False, "message": "Invalid license key."})
76
+
77
+ entry = licenses[key_hash]
78
+
79
+ # Check if revoked
80
+ if entry.get("revoked"):
81
+ return JSONResponse({"valid": False, "message": "License key has been revoked."})
82
+
83
+ # Check expiry
84
+ expires_at = entry.get("expires_at")
85
+ if expires_at:
86
+ expiry_dt = datetime.fromisoformat(expires_at)
87
+ if datetime.now(timezone.utc) > expiry_dt:
88
+ return JSONResponse({"valid": False, "message": "License key has expired."})
89
+
90
+ # Update last_seen
91
+ entry["last_seen"] = datetime.now(timezone.utc).isoformat()
92
+ licenses[key_hash] = entry
93
+ save_licenses(licenses)
94
+
95
+ return JSONResponse({
96
+ "valid": True,
97
+ "message": "License key is valid.",
98
+ "label": entry.get("label", ""),
99
+ "expires_at": expires_at
100
+ })
101
+
102
+ # ============================================================
103
+ # ADMIN ENDPOINTS — only you can call these
104
+ # ============================================================
105
+
106
+ @app.post("/admin/generate", dependencies=[Depends(require_admin)])
107
+ async def generate_key(label: str = "", expires_days: int = 0):
108
+ """
109
+ Generate a new license key.
110
+ - label: optional note (e.g. customer name)
111
+ - expires_days: 0 = never expires
112
+ """
113
+ key = str(uuid.uuid4()).replace("-", "").upper()
114
+ key_formatted = f"LMA-{key[:8]}-{key[8:16]}-{key[16:24]}"
115
+ key_hash = hash_key(key_formatted)
116
+
117
+ expires_at = None
118
+ if expires_days > 0:
119
+ from datetime import timedelta
120
+ expires_at = (datetime.now(timezone.utc) + timedelta(days=expires_days)).isoformat()
121
+
122
+ licenses = load_licenses()
123
+ licenses[key_hash] = {
124
+ "label": label,
125
+ "created_at": datetime.now(timezone.utc).isoformat(),
126
+ "expires_at": expires_at,
127
+ "revoked": False,
128
+ "last_seen": None
129
+ }
130
+ save_licenses(licenses)
131
+
132
+ return {"key": key_formatted, "label": label, "expires_at": expires_at}
133
+
134
+ @app.post("/admin/revoke", dependencies=[Depends(require_admin)])
135
+ async def revoke_key(key: str):
136
+ """Revoke a license key."""
137
+ licenses = load_licenses()
138
+ key_hash = hash_key(key)
139
+
140
+ if key_hash not in licenses:
141
+ raise HTTPException(status_code=404, detail="Key not found.")
142
+
143
+ licenses[key_hash]["revoked"] = True
144
+ save_licenses(licenses)
145
+ return {"message": f"Key revoked successfully."}
146
+
147
+ @app.get("/admin/list", dependencies=[Depends(require_admin)])
148
+ async def list_keys():
149
+ """List all license keys (hashed, with metadata)."""
150
+ licenses = load_licenses()
151
+ return {"total": len(licenses), "licenses": licenses}
152
+
153
+ @app.get("/health")
154
+ async def health():
155
+ return {"status": "ok"}
156
+
157
+ # ============================================================
158
+ if __name__ == "__main__":
159
+ print("=" * 50)
160
+ print("🔑 License Server Starting...")
161
+ print(f" Port: {PORT}")
162
+ print(f" Licenses file: {LICENSE_FILE.absolute()}")
163
+ print("=" * 50)
164
+ uvicorn.run(app, host="0.0.0.0", port=PORT)