Mark-Lasfar commited on
Commit
35ab8fe
·
1 Parent(s): 46dd238

Update Model

Browse files
Files changed (1) hide show
  1. api/auth.py +72 -251
api/auth.py CHANGED
@@ -1,32 +1,23 @@
 
1
  from fastapi_users import FastAPIUsers
2
  from fastapi_users.authentication import CookieTransport, JWTStrategy, AuthenticationBackend
3
  from fastapi_users.db import SQLAlchemyUserDatabase
4
  from httpx_oauth.clients.google import GoogleOAuth2
5
  from httpx_oauth.clients.github import GitHubOAuth2
6
- from api.database import User, OAuthAccount, get_user_db, get_db
7
  from fastapi_users.manager import BaseUserManager, IntegerIDMixin
8
- from fastapi import Depends, Request, FastAPI, HTTPException, status, APIRouter
9
- from fastapi.responses import RedirectResponse
10
  from fastapi_users.models import UP
11
  from typing import Optional
12
  import os
13
  import logging
14
- from api.database import User, OAuthAccount
15
- from api.models import UserRead, UserCreate, UserUpdate
16
- from sqlalchemy import select
17
- from sqlalchemy.orm import Session
18
- from datetime import datetime
19
- import secrets
20
- import json
21
- import httpx
22
 
23
  # Setup logging
24
  logger = logging.getLogger(__name__)
25
 
26
- # Cookie transport for JWT
27
  cookie_transport = CookieTransport(cookie_max_age=3600)
28
 
29
- # JWT Secret
30
  SECRET = os.getenv("JWT_SECRET")
31
  if not SECRET or len(SECRET) < 32:
32
  logger.error("JWT_SECRET is not set or too short.")
@@ -49,268 +40,98 @@ GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET")
49
 
50
  # Log OAuth credentials status
51
  logger.info("GOOGLE_CLIENT_ID is set: %s", bool(GOOGLE_CLIENT_ID))
 
52
  logger.info("GITHUB_CLIENT_ID is set: %s", bool(GITHUB_CLIENT_ID))
 
53
 
54
  if not all([GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET]):
55
  logger.error("One or more OAuth environment variables are missing.")
56
- raise ValueError("All OAuth credentials are required.")
57
 
58
- # Create OAuth clients
59
- google_oauth_client = GoogleOAuth2(
60
- client_id=GOOGLE_CLIENT_ID,
61
- client_secret=GOOGLE_CLIENT_SECRET
62
- )
63
-
64
- github_oauth_client = GitHubOAuth2(
65
- client_id=GITHUB_CLIENT_ID,
66
- client_secret=GITHUB_CLIENT_SECRET
67
- )
68
 
69
  class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
70
  reset_password_token_secret = SECRET
71
  verification_token_secret = SECRET
72
 
73
- # Standard user manager methods (for JWT only)
74
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
  async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):
77
  yield UserManager(user_db)
78
 
79
- # Custom OAuth Router - SYNC VERSION
80
- oauth_router = APIRouter(prefix="/auth", tags=["auth"])
81
-
82
- @oauth_router.get("/google/authorize")
83
- async def google_authorize(request: Request):
84
- state = secrets.token_urlsafe(32)
85
- request.session["oauth_state"] = state
86
- request.session["oauth_provider"] = "google"
87
-
88
- # SYNC call to get_authorization_url
89
- redirect_uri = "https://mgzon-mgzon-app.hf.space/auth/google/callback"
90
- url = google_oauth_client.get_authorization_url(
91
- redirect_uri, state=state, scope=["openid", "email", "profile"]
92
- )
93
- logger.info(f"Redirecting to Google OAuth: {url}")
94
- return RedirectResponse(str(url))
95
-
96
- @oauth_router.get("/google/callback")
97
- async def google_callback(request: Request, db: Session = Depends(get_db)):
98
- state = request.query_params.get("state")
99
- code = request.query_params.get("code")
100
-
101
- # Verify state
102
- if not state or state != request.session.get("oauth_state"):
103
- logger.error("OAuth state mismatch")
104
- return RedirectResponse("/login?error=Invalid state")
105
-
106
- provider = request.session.get("oauth_provider")
107
- if provider != "google":
108
- return RedirectResponse("/login?error=Invalid provider")
109
-
110
- try:
111
- # SYNC call to get_access_token
112
- redirect_uri = "https://mgzon-mgzon-app.hf.space/auth/google/callback"
113
- token = google_oauth_client.get_access_token(code, redirect_uri=redirect_uri)
114
-
115
- # SYNC call to get_id_email
116
- user_info = google_oauth_client.get_id_email(token)
117
-
118
- account_id = user_info["id"]
119
- account_email = user_info["email"]
120
- logger.info(f"Google OAuth success: account_id={account_id}, email={account_email}")
121
-
122
- # Find or create user - SYNC
123
- user = db.query(User).filter(User.email == account_email).first()
124
- if user is None:
125
- # Create new user
126
- user = User(
127
- email=account_email,
128
- hashed_password="dummy_hashed_password", # We'll use JWT only, no password login
129
- is_active=True,
130
- is_verified=True,
131
- )
132
- db.add(user)
133
- db.commit()
134
- db.refresh(user)
135
- logger.info(f"Created new user: {user.email}")
136
-
137
- # Find or create OAuth account - SYNC
138
- oauth_account = db.query(OAuthAccount).filter(
139
- OAuthAccount.oauth_name == "google",
140
- OAuthAccount.account_id == account_id
141
- ).first()
142
-
143
- if oauth_account is None:
144
- oauth_account = OAuthAccount(
145
- oauth_name="google",
146
- access_token=token["access_token"],
147
- account_id=account_id,
148
- account_email=account_email,
149
- user_id=user.id
150
- )
151
- db.add(oauth_account)
152
- db.commit()
153
- logger.info(f"Created OAuth account for user: {user.email}")
154
- else:
155
- # Update existing OAuth account
156
- oauth_account.access_token = token["access_token"]
157
- oauth_account.account_email = account_email
158
- db.commit()
159
- logger.info(f"Updated OAuth account for user: {user.email}")
160
-
161
- # Create JWT token using fastapi_users - ASYNC
162
- user_db = get_user_db(db)
163
- user_manager = UserManager(user_db)
164
- jwt_token = await user_manager.create_access_token(user)
165
-
166
- # Set cookie
167
- response = RedirectResponse("/chat")
168
- response.set_cookie(
169
- key="access_token",
170
- value=jwt_token,
171
- max_age=3600,
172
- httponly=True,
173
- secure=True,
174
- samesite="lax"
175
- )
176
-
177
- # Clear session
178
- request.session.clear()
179
-
180
- logger.info(f"OAuth login successful for user: {user.email}")
181
- return response
182
-
183
- except Exception as e:
184
- logger.error(f"Google OAuth callback failed: {e}")
185
- request.session.clear()
186
- return RedirectResponse(f"/login?error={str(e)}")
187
-
188
- @oauth_router.get("/github/authorize")
189
- async def github_authorize(request: Request):
190
- state = secrets.token_urlsafe(32)
191
- request.session["oauth_state"] = state
192
- request.session["oauth_provider"] = "github"
193
-
194
- # SYNC call to get_authorization_url
195
- redirect_uri = "https://mgzon-mgzon-app.hf.space/auth/github/callback"
196
- url = github_oauth_client.get_authorization_url(
197
- redirect_uri, state=state, scope=["user:email"]
198
- )
199
- logger.info(f"Redirecting to GitHub OAuth: {url}")
200
- return RedirectResponse(str(url))
201
 
202
- @oauth_router.get("/github/callback")
203
- async def github_callback(request: Request, db: Session = Depends(get_db)):
204
- state = request.query_params.get("state")
205
- code = request.query_params.get("code")
206
-
207
- # Verify state
208
- if not state or state != request.session.get("oauth_state"):
209
- logger.error("OAuth state mismatch")
210
- return RedirectResponse("/login?error=Invalid state")
211
-
212
- provider = request.session.get("oauth_provider")
213
- if provider != "github":
214
- return RedirectResponse("/login?error=Invalid provider")
215
-
216
- try:
217
- # SYNC call to get_access_token
218
- redirect_uri = "https://mgzon-mgzon-app.hf.space/auth/github/callback"
219
- token = github_oauth_client.get_access_token(code, redirect_uri=redirect_uri)
220
-
221
- # SYNC call to get_id_email
222
- user_info = github_oauth_client.get_id_email(token)
223
-
224
- account_id = user_info["id"]
225
- account_email = user_info["email"]
226
- logger.info(f"GitHub OAuth success: account_id={account_id}, email={account_email}")
227
-
228
- # Find or create user - SYNC
229
- user = db.query(User).filter(User.email == account_email).first()
230
- if user is None:
231
- # Create new user
232
- user = User(
233
- email=account_email,
234
- hashed_password="dummy_hashed_password", # We'll use JWT only, no password login
235
- is_active=True,
236
- is_verified=True,
237
- )
238
- db.add(user)
239
- db.commit()
240
- db.refresh(user)
241
- logger.info(f"Created new user: {user.email}")
242
-
243
- # Find or create OAuth account - SYNC
244
- oauth_account = db.query(OAuthAccount).filter(
245
- OAuthAccount.oauth_name == "github",
246
- OAuthAccount.account_id == account_id
247
- ).first()
248
-
249
- if oauth_account is None:
250
- oauth_account = OAuthAccount(
251
- oauth_name="github",
252
- access_token=token["access_token"],
253
- account_id=account_id,
254
- account_email=account_email,
255
- user_id=user.id
256
- )
257
- db.add(oauth_account)
258
- db.commit()
259
- logger.info(f"Created OAuth account for user: {user.email}")
260
- else:
261
- # Update existing OAuth account
262
- oauth_account.access_token = token["access_token"]
263
- oauth_account.account_email = account_email
264
- db.commit()
265
- logger.info(f"Updated OAuth account for user: {user.email}")
266
-
267
- # Create JWT token using fastapi_users - ASYNC
268
- user_db = get_user_db(db)
269
- user_manager = UserManager(user_db)
270
- jwt_token = await user_manager.create_access_token(user)
271
-
272
- # Set cookie
273
- response = RedirectResponse("/chat")
274
- response.set_cookie(
275
- key="access_token",
276
- value=jwt_token,
277
- max_age=3600,
278
- httponly=True,
279
- secure=True,
280
- samesite="lax"
281
- )
282
-
283
- # Clear session
284
- request.session.clear()
285
-
286
- logger.info(f"OAuth login successful for user: {user.email}")
287
- return response
288
-
289
- except Exception as e:
290
- logger.error(f"GitHub OAuth callback failed: {e}")
291
- request.session.clear()
292
- return RedirectResponse(f"/login?error={str(e)}")
293
 
294
- @oauth_router.get("/logout")
295
- async def logout(request: Request):
296
- request.session.clear()
297
- response = RedirectResponse("/login")
298
- response.delete_cookie("access_token")
299
- return response
 
 
300
 
301
- # Standard fastapi_users setup for JWT only (no OAuth)
302
  fastapi_users = FastAPIUsers[User, int](
303
- get_user_manager,
304
  [auth_backend],
305
  )
306
 
307
  current_active_user = fastapi_users.current_user(active=True, optional=True)
308
 
309
  def get_auth_router(app: FastAPI):
310
- # Add custom OAuth router
311
- app.include_router(oauth_router)
312
-
313
- # Add standard fastapi_users routes (without OAuth)
314
  app.include_router(fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"])
315
  app.include_router(fastapi_users.get_register_router(UserRead, UserCreate), prefix="/auth", tags=["auth"])
316
  app.include_router(fastapi_users.get_reset_password_router(), prefix="/auth", tags=["auth"])
 
1
+ # api/auth.py
2
  from fastapi_users import FastAPIUsers
3
  from fastapi_users.authentication import CookieTransport, JWTStrategy, AuthenticationBackend
4
  from fastapi_users.db import SQLAlchemyUserDatabase
5
  from httpx_oauth.clients.google import GoogleOAuth2
6
  from httpx_oauth.clients.github import GitHubOAuth2
7
+ from api.database import User, OAuthAccount, get_user_db
8
  from fastapi_users.manager import BaseUserManager, IntegerIDMixin
9
+ from fastapi import Depends, Request, FastAPI
10
+ from sqlalchemy.ext.asyncio import AsyncSession
11
  from fastapi_users.models import UP
12
  from typing import Optional
13
  import os
14
  import logging
 
 
 
 
 
 
 
 
15
 
16
  # Setup logging
17
  logger = logging.getLogger(__name__)
18
 
 
19
  cookie_transport = CookieTransport(cookie_max_age=3600)
20
 
 
21
  SECRET = os.getenv("JWT_SECRET")
22
  if not SECRET or len(SECRET) < 32:
23
  logger.error("JWT_SECRET is not set or too short.")
 
40
 
41
  # Log OAuth credentials status
42
  logger.info("GOOGLE_CLIENT_ID is set: %s", bool(GOOGLE_CLIENT_ID))
43
+ logger.info("GOOGLE_CLIENT_SECRET is set: %s", bool(GOOGLE_CLIENT_SECRET))
44
  logger.info("GITHUB_CLIENT_ID is set: %s", bool(GITHUB_CLIENT_ID))
45
+ logger.info("GITHUB_CLIENT_SECRET is set: %s", bool(GITHUB_CLIENT_SECRET))
46
 
47
  if not all([GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET]):
48
  logger.error("One or more OAuth environment variables are missing.")
49
+ raise ValueError("All OAuth credentials (GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET) are required.")
50
 
51
+ google_oauth_client = GoogleOAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET)
52
+ github_oauth_client = GitHubOAuth2(GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET)
 
 
 
 
 
 
 
 
53
 
54
  class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
55
  reset_password_token_secret = SECRET
56
  verification_token_secret = SECRET
57
 
58
+ async def oauth_callback(
59
+ self,
60
+ oauth_name: str,
61
+ access_token: str,
62
+ account_id: str,
63
+ account_email: str,
64
+ expires_at: Optional[int] = None,
65
+ refresh_token: Optional[str] = None,
66
+ request: Optional[Request] = None,
67
+ *,
68
+ associate_by_email: bool = False,
69
+ is_verified_by_default: bool = False,
70
+ ) -> UP:
71
+ oauth_account_dict = {
72
+ "oauth_name": oauth_name,
73
+ "access_token": access_token,
74
+ "account_id": account_id,
75
+ "account_email": account_email,
76
+ "expires_at": expires_at,
77
+ "refresh_token": refresh_token,
78
+ }
79
+ oauth_account = OAuthAccount(**oauth_account_dict)
80
+ existing_oauth_account = await self.user_db.get_by_oauth_account(oauth_name, account_id)
81
+ if existing_oauth_account is not None:
82
+ return await self.on_after_login(existing_oauth_account.user, request)
83
+
84
+ if associate_by_email:
85
+ user = await self.user_db.get_by_email(account_email)
86
+ if user is not None:
87
+ oauth_account.user_id = user.id
88
+ await self.user_db.add_oauth_account(oauth_account)
89
+ return await self.on_after_login(user, request)
90
+
91
+ user_dict = {
92
+ "email": account_email,
93
+ "hashed_password": self.password_helper.hash("dummy_password"),
94
+ "is_active": True,
95
+ "is_verified": is_verified_by_default,
96
+ }
97
+ user = await self.user_db.create(user_dict)
98
+ oauth_account.user_id = user.id
99
+ await self.user_db.add_oauth_account(oauth_account)
100
+ return await self.on_after_login(user, request)
101
 
102
  async def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):
103
  yield UserManager(user_db)
104
 
105
+ from fastapi_users.router.oauth import get_oauth_router
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
+ google_oauth_router = get_oauth_router(
108
+ google_oauth_client,
109
+ auth_backend,
110
+ get_user_manager,
111
+ state_secret=SECRET, # أضف هذا السطر
112
+ associate_by_email=True,
113
+ redirect_url="https://mgzon-mgzon-app.hf.space/auth/google/callback",
114
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
+ github_oauth_router = get_oauth_router(
117
+ github_oauth_client,
118
+ auth_backend,
119
+ get_user_manager,
120
+ state_secret=SECRET, # أضف هذا السطر
121
+ associate_by_email=True,
122
+ redirect_url="https://mgzon-mgzon-app.hf.space/auth/github/callback",
123
+ )
124
 
 
125
  fastapi_users = FastAPIUsers[User, int](
126
+ get_user_db,
127
  [auth_backend],
128
  )
129
 
130
  current_active_user = fastapi_users.current_user(active=True, optional=True)
131
 
132
  def get_auth_router(app: FastAPI):
133
+ app.include_router(google_oauth_router, prefix="/auth/google", tags=["auth"])
134
+ app.include_router(github_oauth_router, prefix="/auth/github", tags=["auth"])
 
 
135
  app.include_router(fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"])
136
  app.include_router(fastapi_users.get_register_router(UserRead, UserCreate), prefix="/auth", tags=["auth"])
137
  app.include_router(fastapi_users.get_reset_password_router(), prefix="/auth", tags=["auth"])