File size: 6,993 Bytes
d778c1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7904bd0
 
d778c1b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import os
import json
import requests
import firebase_admin
from firebase_admin import credentials, auth as firebase_auth

FIREBASE_API_KEY = os.environ.get("FIREBASE_API_KEY", "")
FIREBASE_AUTH_REST = (
    "https://identitytoolkit.googleapis.com/v1/accounts"
)

# ── Firebase Admin Init ──────────────────────────────────────
def init_firebase():
    if firebase_admin._apps:
        return
    sa_json = os.environ.get("FIREBASE_SERVICE_ACCOUNT_JSON")
    if not sa_json:
        print("⚠️ No Firebase service account - auth disabled")
        return
    try:
        cred = credentials.Certificate(json.loads(sa_json))
        firebase_admin.initialize_app(cred)
        print("βœ… Firebase Admin initialized")
    except Exception as e:
        print(f"⚠️ Firebase init error: {e}")

init_firebase()

# ── Email / Password ─────────────────────────────────────────
def email_signup(name: str, email: str, password: str) -> dict:
    r = requests.post(
        f"{FIREBASE_AUTH_REST}:signUp?key={FIREBASE_API_KEY}",
        json={
            "email": email,
            "password": password,
            "returnSecureToken": True
        },
        timeout=10
    )
    data = r.json()
    if "idToken" not in data:
        msg = data.get("error", {}).get("message", "Signup failed")
        raise ValueError(msg)
    # Set display name
    requests.post(
        f"{FIREBASE_AUTH_REST}:update?key={FIREBASE_API_KEY}",
        json={
            "idToken": data["idToken"],
            "displayName": name
        },
        timeout=10
    )
    return {
        "uid": data["localId"],
        "email": email,
        "name": name,
        "avatar": "",
        "token": data["idToken"]
    }

def email_login(email: str, password: str) -> dict:
    r = requests.post(
        f"{FIREBASE_AUTH_REST}:signInWithPassword"
        f"?key={FIREBASE_API_KEY}",
        json={
            "email": email,
            "password": password,
            "returnSecureToken": True
        },
        timeout=10
    )
    data = r.json()
    if "idToken" not in data:
        msg = data.get("error", {}).get("message", "Login failed")
        raise ValueError(msg)
    return {
        "uid": data["localId"],
        "email": email,
        "name": data.get("displayName", email.split("@")[0]),
        "avatar": data.get("photoUrl", ""),
        "token": data["idToken"]
    }

def forgot_password(email: str):
    r = requests.post(
        f"{FIREBASE_AUTH_REST}:sendOobCode"
        f"?key={FIREBASE_API_KEY}",
        json={"requestType": "PASSWORD_RESET", "email": email},
        timeout=10
    )
    data = r.json()
    if "email" not in data:
        raise ValueError("Could not send reset email")

# ── Google OAuth ─────────────────────────────────────────────
def get_google_oauth_url(app_url: str) -> str:
    client_id = os.environ.get("GOOGLE_CLIENT_ID", "")
    return (
        "https://accounts.google.com/o/oauth2/v2/auth"
        f"?client_id={client_id}"
        f"&redirect_uri={app_url}"
        "&response_type=code"
        "&scope=openid%20email%20profile"
        "&state=google"
        "&prompt=select_account"
    )

def google_callback(code: str, app_url: str) -> dict:
    client_id = os.environ.get("GOOGLE_CLIENT_ID", "")
    client_secret = os.environ.get("GOOGLE_CLIENT_SECRET", "")
    # Exchange code for tokens
    token_r = requests.post(
        "https://oauth2.googleapis.com/token",
        data={
            "code": code,
            "client_id": client_id,
            "client_secret": client_secret,
            "redirect_uri": app_url,
            "grant_type": "authorization_code"
        },
        timeout=10
    )
    tokens = token_r.json()
    google_id_token = tokens.get("id_token")
    if not google_id_token:
        raise ValueError("Google token exchange failed")
    # Sign in to Firebase with Google token
    fb_r = requests.post(
        f"https://identitytoolkit.googleapis.com/v1/accounts"
        f":signInWithIdp?key={FIREBASE_API_KEY}",
        json={
            "postBody": (
                f"id_token={google_id_token}"
                "&providerId=google.com"
            ),
            "requestUri": app_url,
            "returnIdpCredential": True,
            "returnSecureToken": True
        },
        timeout=10
    )
    fb_data = fb_r.json()
    if "idToken" not in fb_data:
        err_msg = fb_data.get("error", {}).get("message", "Unknown Firebase error")
        raise ValueError(f"Firebase Google sign-in failed: {err_msg}")
    return {
        "uid": fb_data["localId"],
        "email": fb_data.get("email", ""),
        "name": fb_data.get("displayName", "Student"),
        "avatar": fb_data.get("photoUrl", ""),
        "token": fb_data["idToken"]
    }

# ── GitHub OAuth ─────────────────────────────────────────────
def get_github_oauth_url(app_url: str) -> str:
    client_id = os.environ.get("GITHUB_CLIENT_ID", "")
    return (
        "https://github.com/login/oauth/authorize"
        f"?client_id={client_id}"
        f"&redirect_uri={app_url}"
        "&scope=user:email"
        "&state=github"
    )

def github_callback(code: str, app_url: str) -> dict:
    client_id = os.environ.get("GITHUB_CLIENT_ID", "")
    client_secret = os.environ.get("GITHUB_CLIENT_SECRET", "")
    # Exchange code for access token
    token_r = requests.post(
        "https://github.com/login/oauth/access_token",
        headers={"Accept": "application/json"},
        data={
            "client_id": client_id,
            "client_secret": client_secret,
            "code": code,
            "redirect_uri": app_url
        },
        timeout=10
    )
    token_data = token_r.json()
    access_token = token_data.get("access_token")
    if not access_token:
        raise ValueError("GitHub token exchange failed")
    # Sign in to Firebase with GitHub token
    fb_r = requests.post(
        f"https://identitytoolkit.googleapis.com/v1/accounts"
        f":signInWithIdp?key={FIREBASE_API_KEY}",
        json={
            "postBody": (
                f"access_token={access_token}"
                "&providerId=github.com"
            ),
            "requestUri": app_url,
            "returnIdpCredential": True,
            "returnSecureToken": True
        },
        timeout=10
    )
    fb_data = fb_r.json()
    if "idToken" not in fb_data:
        raise ValueError("Firebase GitHub sign-in failed")
    return {
        "uid": fb_data["localId"],
        "email": fb_data.get("email", ""),
        "name": fb_data.get("displayName", "Student"),
        "avatar": fb_data.get("photoUrl", ""),
        "token": fb_data["idToken"]
    }