File size: 10,287 Bytes
b37a9c7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
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 = """
<!DOCTYPE html>
<html>
<body style="margin:0;padding:0;background:#f0f0f0;font-family:Arial,sans-serif;">
  <table width="100%" cellpadding="0" cellspacing="0" style="padding:30px 0;">
    <tr><td align="center">
      <table width="460" cellpadding="0" cellspacing="0"
             style="background:white;border-radius:14px;overflow:hidden;
                    box-shadow:0 4px 20px rgba(0,0,0,0.1);">
        <tr>
          <td style="background:linear-gradient(135deg,#cc0000,#880000);
                     padding:26px 32px;text-align:center;">
            <h1 style="color:white;font-size:22px;margin:0;">&#127947; FitPlan Pro</h1>
            <p style="color:rgba(255,255,255,0.7);margin:6px 0 0;font-size:11px;
                      letter-spacing:2px;text-transform:uppercase;">Personalized Fitness</p>
          </td>
        </tr>
        <tr>
          <td style="padding:30px 34px;">
            <p style="color:#333;font-size:15px;margin:0 0 20px;">
              Your email verification code is:
            </p>
            <table width="100%" cellpadding="0" cellspacing="0">
              <tr>
                <td style="background:#fff5f5;border:2px dashed #cc0000;
                           border-radius:12px;padding:24px;text-align:center;">
                  <span style="font-size:40px;font-weight:900;
                               letter-spacing:14px;color:#cc0000;
                               font-family:Arial,sans-serif;">""" + otp + """</span>
                </td>
              </tr>
            </table>
            <p style="color:#999;font-size:12px;margin:18px 0 0;">
              This code expires in <strong>10 minutes</strong>.<br>
              If you did not request this, please ignore this email.
            </p>
            <hr style="border:none;border-top:1px solid #eee;margin:22px 0;">
            <p style="color:#ccc;font-size:11px;text-align:center;margin:0;">
              FitPlan Pro &mdash; Your Personalized Fitness Journey
            </p>
          </td>
        </tr>
      </table>
    </td></tr>
  </table>
</body>
</html>
"""

    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