import json
import os
import hashlib
import secrets
import time
import random
import urllib.request
import urllib.error
# ── PATHS ─────────────────────────────────────────────────────────────────────
USER_FILE = "/app/users.json"
OTP_FILE = "/app/otps.json"
# ── BREVO HTTP API CONFIG (set in HuggingFace Secrets) ───────────────────────
BREVO_API_KEY = os.environ.get("BREVO_API_KEY", "") # Brevo API key (starts with xkeysib-)
EMAIL_SENDER = os.environ.get("EMAIL_SENDER", "") # your verified sender email
SENDER_NAME = "FitPlan Pro"
# ── CORE HELPERS ──────────────────────────────────────────────────────────────
def hash_password(password):
return hashlib.sha256(password.encode()).hexdigest()
def generate_token():
return secrets.token_hex(32)
def generate_otp():
return str(random.randint(100000, 999999))
# ── FILE HELPERS ──────────────────────────────────────────────────────────────
def load_users():
if not os.path.exists(USER_FILE):
return {}
try:
with open(USER_FILE, "r") as f:
return json.load(f)
except:
return {}
def save_users(users):
with open(USER_FILE, "w") as f:
json.dump(users, f, indent=2)
def load_otps():
if not os.path.exists(OTP_FILE):
return {}
try:
with open(OTP_FILE, "r") as f:
return json.load(f)
except:
return {}
def save_otps(otps):
with open(OTP_FILE, "w") as f:
json.dump(otps, f, indent=2)
# ── SEND OTP via Brevo HTTP API ───────────────────────────────────────────────
def send_otp_email(to_email, otp, purpose="signup"):
if not BREVO_API_KEY:
return False, "BREVO_API_KEY not set in Space Secrets."
if not EMAIL_SENDER:
return False, "EMAIL_SENDER not set in Space Secrets."
html_body = """
🏋 FitPlan Pro
Personalized Fitness
|
|
Your email verification code is:
This code expires in 10 minutes.
If you did not request this, please ignore this email.
FitPlan Pro — Your Personalized Fitness Journey
|
|
"""
payload = json.dumps({
"sender": {
"name": SENDER_NAME,
"email": EMAIL_SENDER
},
"to": [{"email": to_email}],
"subject": "FitPlan Pro - Your Verification Code: " + otp,
"htmlContent": html_body
}).encode("utf-8")
req = urllib.request.Request(
"https://api.brevo.com/v3/smtp/email",
data=payload,
headers={
"accept": "application/json",
"api-key": BREVO_API_KEY,
"content-type": "application/json"
},
method="POST"
)
try:
with urllib.request.urlopen(req, timeout=15) as resp:
if resp.status in [200, 201, 202]:
return True, "OTP sent successfully!"
else:
return False, "Brevo API error: status " + str(resp.status)
except urllib.error.HTTPError as e:
body = e.read().decode("utf-8", errors="ignore")
return False, "Brevo error: " + str(e.code) + " - " + body
except Exception as e:
return False, "Failed to send OTP: " + str(e)
# ── STORE / VERIFY OTP ────────────────────────────────────────────────────────
def store_otp(email, otp, purpose="signup"):
otps = load_otps()
key = purpose + ":" + email.lower().strip()
otps[key] = {
"otp": otp,
"created_at": time.time(),
"expires_at": time.time() + 600
}
save_otps(otps)
def verify_otp(email, otp_input, purpose="signup"):
otps = load_otps()
key = purpose + ":" + email.lower().strip()
if key not in otps:
return False, "OTP not found. Please request a new one."
record = otps[key]
if time.time() > record["expires_at"]:
del otps[key]
save_otps(otps)
return False, "OTP expired. Please request a new one."
if record["otp"] != otp_input.strip():
return False, "Incorrect OTP. Please try again."
del otps[key]
save_otps(otps)
return True, "OTP verified!"
# ── INITIATE SIGNUP ───────────────────────────────────────────────────────────
def initiate_signup(username, email, password):
users = load_users()
if username.strip().lower() in [k.lower() for k in users]:
return False, "Username already taken. Please choose another."
for u in users.values():
if isinstance(u, dict) and u.get("email", "").lower() == email.lower().strip():
return False, "Email already registered. Please login."
if len(password) < 6:
return False, "Password must be at least 6 characters."
otp = generate_otp()
ok, msg = send_otp_email(email, otp, purpose="signup")
if not ok:
return False, msg
store_otp(email, otp, purpose="signup")
return True, "OTP sent to " + email
# ── COMPLETE SIGNUP ───────────────────────────────────────────────────────────
def complete_signup(username, email, password, otp_input):
ok, msg = verify_otp(email, otp_input, purpose="signup")
if not ok:
return False, None, msg
users = load_users()
token = generate_token()
users[username.strip()] = {
"email": email.lower().strip(),
"password": hash_password(password),
"token": token,
"verified": True,
"created_at": time.time()
}
save_users(users)
return True, token, "Email verified! Account created. Please sign in."
# ── LOGIN ─────────────────────────────────────────────────────────────────────
def login(username_or_email, password):
users = load_users()
matched_key = None
for k, v in users.items():
if k.lower() == username_or_email.strip().lower():
matched_key = k
break
if isinstance(v, dict) and v.get("email", "").lower() == username_or_email.strip().lower():
matched_key = k
break
if not matched_key:
return False, None, "Account not found. Please sign up first."
user = users[matched_key]
stored_pw = user["password"] if isinstance(user, dict) else user
if stored_pw != hash_password(password):
return False, None, "Incorrect password. Please try again."
new_token = generate_token()
users[matched_key]["token"] = new_token
users[matched_key]["last_login"] = time.time()
save_users(users)
return True, new_token, "Login successful!"
# ── VERIFY TOKEN ──────────────────────────────────────────────────────────────
def verify_token(username, token):
users = load_users()
for k, v in users.items():
if k.lower() == username.strip().lower():
if isinstance(v, dict):
return v.get("token") == token
return False
# ── LOGOUT ────────────────────────────────────────────────────────────────────
def logout(username):
users = load_users()
for k in users:
if k.lower() == username.strip().lower():
if isinstance(users[k], dict):
users[k]["token"] = None
users[k]["last_logout"] = time.time()
save_users(users)
return True
return False