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:

""" + otp + """

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