File size: 7,624 Bytes
dd39508 abb58cc 5deb24f abb58cc 0f53af1 dd39508 0f53af1 ada8d83 ddc6fbf 52bdd44 ada8d83 ec35549 0f53af1 f2e2632 c2e047f 5c94484 ec35549 dd39508 c8451a3 ba9c0a2 f2e2632 0f53af1 35909d0 f2e2632 0f53af1 ba9c0a2 0f53af1 ba9c0a2 f2e2632 ba9c0a2 f2e2632 c2e047f ddc6fbf f77fc7f 9203dcf ada8d83 52bdd44 ba9c0a2 52bdd44 ba9c0a2 52bdd44 c8451a3 c84f9ee 8c77862 583d268 52bdd44 4ccbedb 8c77862 ddc6fbf 31625fc ddc6fbf c2e047f ddc6fbf ada8d83 583d268 ddc6fbf 31625fc ddc6fbf c2e047f ddc6fbf ada8d83 0f53af1 ddc6fbf 0f53af1 35909d0 ada8d83 8c77862 ada8d83 ddc6fbf ada8d83 ec35549 |
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 |
# api/auth.py
# SPDX-FileCopyrightText: Hadad <hadad@linuxmail.org>
# SPDX-License-License: Apache-2.0
from fastapi_users import FastAPIUsers
from fastapi_users.authentication import CookieTransport, JWTStrategy, AuthenticationBackend
from fastapi_users.router.oauth import get_oauth_router
from httpx_oauth.clients.google import GoogleOAuth2
from httpx_oauth.clients.github import GitHubOAuth2
from fastapi_users.manager import BaseUserManager, IntegerIDMixin
from fastapi import Depends, Request, FastAPI
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from fastapi_users.models import UP
from typing import Optional, Dict
import os
import logging
import secrets
from api.database import User, OAuthAccount, CustomSQLAlchemyUserDatabase, get_user_db # استيراد من database.py
from api.models import UserRead, UserCreate, UserUpdate
# إعداد اللوقينج
logger = logging.getLogger(__name__)
cookie_transport = CookieTransport(cookie_max_age=3600)
SECRET = os.getenv("JWT_SECRET")
if not SECRET or len(SECRET) < 32:
logger.error("JWT_SECRET is not set or too short.")
raise ValueError("JWT_SECRET is required (at least 32 characters).")
def get_jwt_strategy() -> JWTStrategy:
return JWTStrategy(secret=SECRET, lifetime_seconds=3600)
auth_backend = AuthenticationBackend(
name="jwt",
transport=cookie_transport,
get_strategy=get_jwt_strategy,
)
# OAuth بيانات الاعتماد
GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
GITHUB_CLIENT_ID = os.getenv("GITHUB_CLIENT_ID")
GITHUB_CLIENT_SECRET = os.getenv("GITHUB_CLIENT_SECRET")
# تحقق من توافر بيانات الاعتماد
if not all([GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET]):
logger.error("One or more OAuth environment variables are missing.")
raise ValueError("All OAuth credentials are required.")
GOOGLE_REDIRECT_URL = os.getenv("GOOGLE_REDIRECT_URL", "https://mgzon-mgzon-app.hf.space/auth/google/callback")
GITHUB_REDIRECT_URL = os.getenv("GITHUB_REDIRECT_URL", "https://mgzon-mgzon-app.hf.space/auth/github/callback")
google_oauth_client = GoogleOAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET)
github_oauth_client = GitHubOAuth2(GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET)
# مدير المستخدمين
class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
reset_password_token_secret = SECRET
verification_token_secret = SECRET
async def get_by_oauth_account(self, oauth_name: str, account_id: str):
logger.info(f"Checking OAuth account: {oauth_name}/{account_id}")
statement = select(OAuthAccount).where(
OAuthAccount.oauth_name == oauth_name,
OAuthAccount.account_id == account_id
)
result = await self.user_db.session.execute(statement)
return result.scalar_one_or_none()
async def add_oauth_account(self, oauth_account: OAuthAccount):
logger.info(f"Adding OAuth account for user {oauth_account.user_id}")
self.user_db.session.add(oauth_account)
await self.user_db.session.commit()
await self.user_db.session.refresh(oauth_account)
async def oauth_callback(
self,
oauth_name: str,
access_token: str,
account_id: str,
account_email: str,
expires_at: Optional[int] = None,
refresh_token: Optional[str] = None,
request: Optional[Request] = None,
*,
associate_by_email: bool = False,
is_verified_by_default: bool = False,
) -> UP:
logger.info(f"OAuth callback for {oauth_name} account {account_id}")
oauth_account = OAuthAccount(
oauth_name=oauth_name,
access_token=access_token,
account_id=account_id,
account_email=account_email,
expires_at=expires_at,
refresh_token=refresh_token,
)
existing_oauth_account = await self.get_by_oauth_account(oauth_name, account_id)
if existing_oauth_account:
logger.info(f"Fetching user for OAuth account with user_id: {existing_oauth_account.user_id}")
statement = select(User).where(User.id == existing_oauth_account.user_id)
result = await self.user_db.session.execute(statement)
user = result.scalar_one_or_none()
if user:
logger.info(f"User found: {user.email}, proceeding with on_after_login")
await self.on_after_login(user, request)
return user
else:
logger.error(f"No user found for OAuth account with user_id: {existing_oauth_account.user_id}")
raise ValueError("User not found for existing OAuth account")
if associate_by_email:
logger.info(f"Associating OAuth account by email: {account_email}")
user = await self.user_db.get_by_email(account_email)
if user:
oauth_account.user_id = user.id
await self.add_oauth_account(oauth_account)
logger.info(f"User associated: {user.email}, proceeding with on_after_login")
await self.on_after_login(user, request)
return user
logger.info(f"Creating new user for email: {account_email}")
user_dict = {
"email": account_email,
"hashed_password": self.password_helper.hash(secrets.token_hex(32)),
"is_active": True,
"is_verified": is_verified_by_default,
}
user = await self.user_db.create(user_dict)
oauth_account.user_id = user.id
await self.add_oauth_account(oauth_account)
logger.info(f"New user created: {user.email}, proceeding with on_after_login")
await self.on_after_login(user, request)
return user
# استدعاء user manager من get_user_db
async def get_user_manager(user_db: CustomSQLAlchemyUserDatabase = Depends(get_user_db)):
yield UserManager(user_db)
# OAuth Routers مع معالجة مخصصة لـ GitHub
google_oauth_router = get_oauth_router(
google_oauth_client,
auth_backend,
get_user_manager,
state_secret=SECRET,
associate_by_email=True,
redirect_url=GOOGLE_REDIRECT_URL,
)
github_oauth_client._access_token_url = "https://github.com/login/oauth/access_token"
github_oauth_client._access_token_params = {"headers": {"Accept": "application/json"}}
github_oauth_router = get_oauth_router(
github_oauth_client,
auth_backend,
get_user_manager,
state_secret=SECRET,
associate_by_email=True,
redirect_url=GITHUB_REDIRECT_URL,
)
fastapi_users = FastAPIUsers[User, int](
get_user_db,
[auth_backend],
)
current_active_user = fastapi_users.current_user(active=True, optional=True)
# تضمين الراوترات داخل التطبيق
def get_auth_router(app: FastAPI):
app.include_router(google_oauth_router, prefix="/auth/google", tags=["auth"])
app.include_router(github_oauth_router, prefix="/auth/github", tags=["auth"])
app.include_router(fastapi_users.get_auth_router(auth_backend), prefix="/auth/jwt", tags=["auth"])
app.include_router(fastapi_users.get_register_router(UserRead, UserCreate), prefix="/auth", tags=["auth"])
app.include_router(fastapi_users.get_reset_password_router(), prefix="/auth", tags=["auth"])
app.include_router(fastapi_users.get_verify_router(UserRead), prefix="/auth", tags=["auth"])
app.include_router(fastapi_users.get_users_router(UserRead, UserUpdate), prefix="/users", tags=["users"])
|