File size: 20,717 Bytes
763951b 1deefa3 325f578 5ed4a12 325f578 de6e2ba 325f578 f5ffa3e fc80837 9bbbf05 9731a64 afd16c0 e78b3ed b702b7c 10b597c afd16c0 eec8df9 9bbbf05 e78b3ed 9bbbf05 10b597c eec8df9 e78b3ed 4b8c1e6 e78b3ed d89b508 fd7be58 d89b508 fd7be58 d89b508 e78b3ed 36ba74d eb1aa37 36ba74d fdaacc6 e78b3ed fdaacc6 e78b3ed 9bbbf05 46a06f8 325f578 10b597c 0da1f27 af8268b c856d60 1fb8df5 c856d60 af8268b bf46f0e eb1aa37 bf46f0e 974d955 bf46f0e 625461f 10511db 325f578 60597c3 44372ef 60597c3 b98508f 60597c3 b98508f 775f8ab 60597c3 b98508f 60597c3 b98508f 44372ef c5c4c92 775f8ab 8268a41 7c0b4d6 8268a41 c5c4c92 fab519b 8dcf0b4 7c0b4d6 c5c4c92 8dcf0b4 8268a41 09274a9 0e0e35e bda9eb4 7c0b4d6 bda9eb4 cb6494b b7bbd14 8d61206 17abc14 b7bbd14 8d61206 b7bbd14 8d61206 09274a9 8268a41 09274a9 e3afd2e cb6494b a3ea3d6 4da28a8 dcdcf2f 35db52b 4da28a8 c856d60 9bbbf05 603e0c6 35db52b 9bbbf05 35db52b f18e366 35db52b c856d60 3d61992 653d617 c856d60 653d617 c856d60 2a3fa0d dcdcf2f 4da28a8 377e020 8e04748 473df3a 44372ef de07443 096ed42 e007f86 dc39372 8338c06 e007f86 dc39372 e007f86 5ed4a12 e007f86 5ed4a12 c209774 1deefa3 fecefe5 1deefa3 493f2fc 1deefa3 493f2fc ca1fdb8 493f2fc ca1fdb8 1deefa3 3dc66e5 ca1fdb8 ac6bf4c f0f499b 0158681 14781cf 9768922 14781cf be9fa10 0158681 4c1de62 0158681 a3e50a2 0158681 1deefa3 ca1fdb8 fecefe5 ca1fdb8 |
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 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 |
#main.py
from fastapi import FastAPI, Form, Depends, HTTPException, status
from fastapi.requests import Request
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
from auth import verify_token, oauth2_scheme, auth_views, register, UserCreate, TokenData, authenticate_user, get_user_by_verification_token, resetpassword
from database import get_db, get_user_by_email
from datetime import timedelta
from typing import Optional
import httpx
#import auth
#import tts
import os
#import asyncio
import authlib
from starlette.middleware.sessions import SessionMiddleware
my_secret_key = os.environ['my_secret_key']
SECRET_KEY = os.environ['SecretKey']
from fastapi.staticfiles import StaticFiles
from authlib.integrations.starlette_client import OAuth
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)
oauth = OAuth()
# Configure OAuth registry
oauth.register(
name='google',
client_id=os.environ['GOOGLE_CLIENT_ID'],
client_secret=os.environ['GOOGLE_CLIENT_SECRET'],
access_token_url='https://accounts.google.com/o/oauth2/token',
authorize_url='https://accounts.google.com/o/oauth2/auth',
authorize_params=None,
api_base_url='https://www.googleapis.com/oauth2/v1/',
client_kwargs={'scope': 'openid email profile'}
)
@app.get("/login/oauth")
async def login_oauth(request: Request):
# Redirect to OAuth provider (e.g., Google)
redirect_uri = request.url_for('auth_callback')
return await oauth.google.authorize_redirect(request, redirect_uri)
@app.get("/auth/callback")
async def auth_callback(request: Request, db: Session = Depends(get_db)):
# Exchange code for token
token = await oauth.google.authorize_access_token(request)
# Use token to get user info
user_info = await oauth.google.parse_id_token(request, token)
# Store user_info in session
request.session["user_info"] = user_info
# Check if this user is already in your database, if not, create a new user record
db_user = db.query(User).filter(User.email == user_info['email']).first()
if not db_user:
db_user = User(email=user_info['email'], username=user_info['name'], is_verified=True)
db.add(db_user)
db.commit()
db.refresh(db_user)
# Create an access token for the user
access_token = auth_views.create_access_token(
data={"sub": db_user.email},
expires_delta=timedelta(minutes=auth_views.ACCESS_TOKEN_EXPIRE_MINUTES)
)
# Redirect the user to the protected route
url = app.url_path_for("get_protected")
response = RedirectResponse(url)
response.set_cookie(key="access_token", value=f"Bearer {access_token}", httponly=True)
return response
# Other routes and logic for your application...
app.mount("/static", StaticFiles(directory="static"), name="static")
#router = APIRouter()
templates = Jinja2Templates(directory="templates")
#from google.cloud import recaptchaenterprise_v1
#from google.cloud.recaptchaenterprise_v1 import Assessment
"""
def create_assessment(
project_id: str, recaptcha_key: str, token: str, recaptcha_action: str
) -> Assessment:
client = recaptchaenterprise_v1.RecaptchaEnterpriseServiceClient()
# Set the properties of the event to be tracked.
event = recaptchaenterprise_v1.Event()
event.site_key = recaptcha_key
event.token = token
assessment = recaptchaenterprise_v1.Assessment()
assessment.event = event
project_name = f"projects/{project_id}"
# Build the assessment request.
request = recaptchaenterprise_v1.CreateAssessmentRequest()
request.assessment = assessment
request.parent = project_name
response = client.create_assessment(request)
# Check if the token is valid.
if not response.token_properties.valid:
print(
"The CreateAssessment call failed because the token was "
+ "invalid for the following reasons: "
+ str(response.token_properties.invalid_reason)
)
return
# Check if the expected action was executed.
if response.token_properties.action != recaptcha_action:
print(
"The action attribute in your reCAPTCHA tag does"
+ "not match the action you are expecting to score"
)
return
else:
# Get the risk score and the reason(s).
# For more information on interpreting the assessment, see:
# https://cloud.google.com/recaptcha-enterprise/docs/interpret-assessment
for reason in response.risk_analysis.reasons:
print(reason)
print(
"The reCAPTCHA score for this token is: "
+ str(response.risk_analysis.score)
)
# Get the assessment name (ID). Use this to annotate the assessment.
assessment_name = client.parse_assessment_path(response.name).get("assessment")
print(f"Assessment name: {assessment_name}")
return response
"""
@app.post("/verify-google-token")
async def verify_google_token(token_data: TokenData, db: Session = Depends(get_db)):
# Verify the token with Google
response = requests.get(f'https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={token_data.token}')
if response.status_code != 200:
raise HTTPException(status_code=400, detail="Invalid Google token")
google_user_info = response.json()
email = google_user_info.get('email')
# Check if user exists in database and verify them
db_user = db.query(User).filter(User.email == email).first()
if not db_user:
# Create a new user if doesn't exist
db_user = User(email=email, is_verified=True, username=google_user_info.get('name'))
db.add(db_user)
db.commit()
db.refresh(db_user)
elif not db_user.is_verified:
# Verify the user if not already verified
db_user.is_verified = True
db.commit()
# Create an access token for the user
access_token = auth_views.create_access_token(
data={"sub": db_user.email},
expires_delta=timedelta(minutes=auth_views.ACCESS_TOKEN_EXPIRE_MINUTES)
)
# Redirect the user to the protected route
# response = RedirectResponse(url="/protected")
# response.set_cookie(key="access_token", value=f"Bearer {access_token}", httponly=True)
url = app.url_path_for("get_protected") # Ensure you have a name="get_protected" in your app.get("/protected") decorator
response = RedirectResponse(f"{url}?token={access_token}", status_code=status.HTTP_303_SEE_OTHER)
response.set_cookie(key="access_token", value=f"Bearer {access_token}", httponly=True)
return response
# Dependency for verifying the user's token
def get_current_user(token: str = Depends(verify_token)):
if not token:
raise HTTPException(status_code=401, detail="Token not valid")
return token
# Route for the landing page
@app.get("/", response_class=HTMLResponse)
async def landing(request: Request):
return templates.TemplateResponse("landing.html", {"request": request})
# Your other routes and app configuration go here
from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_400_BAD_REQUEST
from jwt import ExpiredSignatureError, InvalidTokenError # Ensure you've imported these
@app.get("/login", response_class=HTMLResponse)
async def login(request: Request, db: Session = Depends(get_db)):
access_token = request.cookies.get("access_token")
if access_token:
try:
user_email = verify_token(access_token.split("Bearer ")[1])
if user_email:
# Retrieve the user from the database
db_user = db.query(User).filter(User.email == user_email).first()
if not db_user:
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="User not found")
# Check if user is verified
if not db_user.is_verified:
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="User is not verified")
# Create a new access token for the user
new_access_token = auth_views.create_access_token(
data={"sub": db_user.email},
expires_delta=timedelta(minutes=auth_views.ACCESS_TOKEN_EXPIRE_MINUTES)
)
# Redirect the user to the protected route
url = app.url_path_for("get_protected")
response = RedirectResponse(url)
response.set_cookie(key="access_token", value=f"Bearer {new_access_token}", httponly=True)
return response
except ExpiredSignatureError:
# Token has expired. You could redirect to the login page or inform the user
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Token expired")
except InvalidTokenError:
# Token is invalid, inform the user or redirect
raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail="Invalid token")
except Exception as e:
# General exception, log this exception for debugging
# Respond with a generic error message
raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail="An error occurred")
# If not authenticated, show the login page
return templates.TemplateResponse("login.html", {"request": request})
@app.post("/login")
async def login_post(
request: Request,
email: str = Form(...),
password: str = Form(...),
recaptcha_token: str = Form(...),
db: Session = Depends(get_db)
):
# Perform reCAPTCHA verification first
recaptcha_secret = '6LeSJgwpAAAAAJrLrvlQYhRsOjf2wKXee_Jc4Z-k' # Replace with your reCAPTCHA secret key
recaptcha_url = 'https://www.google.com/recaptcha/api/siteverify'
recaptcha_data = {
'secret': recaptcha_secret,
'response': recaptcha_token
}
async with httpx.AsyncClient() as client:
recaptcha_response = await client.post(recaptcha_url, data=recaptcha_data)
recaptcha_result = recaptcha_response.json()
print(recaptcha_result) # or use proper logging
if not recaptcha_result.get('success', False):
raise HTTPException(status_code=400, detail="reCAPTCHA validation failed.")
if not email or not password:
raise HTTPException(status_code=400, detail="Invalid email or password")
user = authenticate_user(db, email, password)
if user and user.is_verified: # Check if user is verified
access_token = auth_views.create_access_token(
data={"sub": user.email},
expires_delta=timedelta(minutes=auth_views.ACCESS_TOKEN_EXPIRE_MINUTES)
)
# Redirect the user to the protected route with the token in the URL
url = app.url_path_for("get_protected") # Ensure you have a name="get_protected" in your app.get("/protected") decorator
#return RedirectResponse(url=f"/protected?token={access_token}", status_code=status.HTTP_303_SEE_OTHER)
#return RedirectResponse(f"{url}?token={access_token}")
response = RedirectResponse(f"{url}?token={access_token}", status_code=status.HTTP_303_SEE_OTHER)
response.set_cookie(key="access_token", value=f"Bearer {access_token}", httponly=True)
# response.set_cookie(key="access_token", value=access_token, httponly=True)
return response
elif user and not user.is_verified: # User is not verified
raise HTTPException(
status_code=400,
detail="You must verify your email before accessing this resource."
)
else:
# If authentication fails, return to the login page with an error message
return templates.TemplateResponse(
"login.html",
{"request": request, "error_message": "Invalid email or password"}
)
@app.get("/register", response_class=HTMLResponse)
async def register_get(request: Request):
return templates.TemplateResponse("register.html", {"request": request})
@app.post("/register", response_class=HTMLResponse)
async def register_post(
request: Request,
username: str = Form(...),
email: str = Form(...),
password: str = Form(...),
confirm_password: str = Form(...),
recaptcha_token: str = Form(...), # Add this line to accept the reCAPTCHA token
db: Session = Depends(get_db)
):
# Perform reCAPTCHA verification first
recaptcha_secret = '6LeSJgwpAAAAAJrLrvlQYhRsOjf2wKXee_Jc4Z-k' # Replace with your reCAPTCHA secret key
recaptcha_url = 'https://www.google.com/recaptcha/api/siteverify'
recaptcha_data = {
'secret': recaptcha_secret,
'response': recaptcha_token
}
async with httpx.AsyncClient() as client:
recaptcha_response = await client.post(recaptcha_url, data=recaptcha_data)
recaptcha_result = recaptcha_response.json()
print(recaptcha_result) # or use proper logging
if not recaptcha_result.get('success', False):
raise HTTPException(status_code=400, detail="reCAPTCHA validation failed.")
# Call the create_assessment function to validate the token
# assessment = await create_assessment( project_id, recaptcha_key, token, recaptcha_action )
# loop = asyncio.get_running_loop()
# assessment = await loop.run_in_executor(
# None, create_assessment, project_id, recaptcha_key, token, recaptcha_action
# )
# Check the assessment result
# if not assessment or assessment.risk_analysis.score < 0.5: # Use an appropriate risk score threshold
# return templates.TemplateResponse("register.html", {
# "request": request,
# "error_message": "Captcha validation failed."
# })
if password != confirm_password:
# Return to the registration page with an error
return templates.TemplateResponse("register.html", {
"request": request,
"error_message": "Passwords do not match."
})
try:
user = UserCreate(username=username, email=email, password=password, confirm_password=confirm_password)
register(user, db) # If this function raises an HTTPException, it should be handled
except HTTPException as e:
# Return to the registration page with the error detail
return templates.TemplateResponse("register.html", {
"request": request,
"error_message": e.detail
})
# Redirect to the successful registration page
response = RedirectResponse("/registration_successful", status_code=status.HTTP_302_FOUND)
return response
@app.get("/registration_successful", response_class=HTMLResponse)
async def registration_successful(request: Request, db: Session = Depends(get_db)):
# Assuming the OAuth process has been completed and user info is stored in the session or a similar mechanism
user_info = request.session.get("user_info") # Replace with your method of retrieving user info
if not user_info:
raise HTTPException(status_code=401, detail="User not authenticated")
email = user_info["email"]
db_user = db.query(User).filter(User.email == email).first()
if not db_user:
raise HTTPException(status_code=404, detail="User not found")
# Create an access token for the user
access_token = auth_views.create_access_token(
data={"sub": db_user.email},
expires_delta=timedelta(minutes=auth_views.ACCESS_TOKEN_EXPIRE_MINUTES)
)
# Redirect the user to the protected route
response = RedirectResponse(url="/protected")
response.set_cookie(key="access_token", value=f"Bearer {access_token}", httponly=True)
return response
@app.get("/verify", response_class=HTMLResponse)
async def verify_email(token: str, db: Session = Depends(get_db)):
user = get_user_by_verification_token(db, token)
if not user:
raise HTTPException(status_code=400, detail="Invalid verification token")
if user.is_verified:
raise HTTPException(status_code=400, detail="Email already verified")
user.is_verified = True
user.email_verification_token = None # Clear the verification token
db.commit()
# Create access token for the user after successful verification
access_token = auth_views.create_access_token(data={"sub": user.email}, expires_delta=timedelta(minutes=auth_views.ACCESS_TOKEN_EXPIRE_MINUTES))
# Redirect to the protected route with the token as a query parameter (or as required by your front-end/client)
return RedirectResponse(url=f"/protected?token={access_token}")
#from jwt import decode, PyJWTError # make sure jwt is imported
@app.get("/protected", response_class=HTMLResponse)
async def get_protected(
request: Request,
db: Session = Depends(get_db),
token: Optional[str] = None # token is Optional because it may come from the cookie
):
# Try to get the token from the query parameter first, then fall back to the cookie
token = token or request.cookies.get("access_token")
if not token:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
# Here verify_token is used directly in the endpoint
# If the token is invalid, verify_token will raise an HTTPException and the following lines will not be executed
user_email = verify_token(token) # Assuming that verify_token returns the user's email if the token is valid
# Get the user from the database
db_user = get_user_by_email(db, user_email)
if db_user is None or not db_user.is_verified:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found or not verified in the database")
# Render a template response
return templates.TemplateResponse("protected.html", {"request": request, "user": db_user.username})
@app.get("/reset-password")
async def reset_password_get(request: Request, token: str):
return templates.TemplateResponse("reset-password.html", {"request": request, "token": token})
@app.post("/password-reset-request")
async def password_reset_request(email: str = Form(...), db: Session = Depends(get_db)):
user = get_user_by_email(db, email)
if user:
resetpassword(user,db)
# Always return the same message to avoid revealing whether an email is registered
return {"message": "Password reset link sent if the email is registered with us."}
@app.get("/password-reset-request", response_class=HTMLResponse)
async def password_reset_form(request: Request):
return templates.TemplateResponse("password_reset_request.html", {"request": request})
from fastapi import Form
@app.post("/reset-password")
async def reset_password(token: str = Form(...), new_password: str = Form(...), db: Session = Depends(get_db)):
user = get_user_by_verification_token(db, token)
if not user:
raise HTTPException(status_code=400, detail="Invalid or expired token")
# Hash the new password
hashed_password = auth_views.pwd_context.hash(new_password)
# Update the user's password
user.hashed_password = hashed_password
user.email_verification_token = None # Clear the token
db.commit()
return {"message": "Password successfully reset."}
#@app.get("/protected", response_class=HTMLResponse)
#async def get_protected(request: Request, token: Optional[str] = None, db: Session = Depends(get_db)):
# Try to get the token from the query parameter first, then fall back to the cookie
# token = token or request.cookies.get("access_token")
# if not token:
# raise HTTPException(status_code=401, detail="Not authenticated")
# try:
# payload = decode(token, auth_views.SECRET_KEY, algorithms=[auth_views.ALGORITHM])
# user_email = payload.get("sub")
# if user_email is None:
# raise HTTPException(status_code=401, detail="Not authenticated")
# except PyJWTError:
# raise HTTPException(status_code=401, detail="Could not validate credentials")
# db_user = get_user_by_email(db, user_email)
# if db_user is None or not db_user.is_verified:
# raise HTTPException(status_code=401, detail="User not found or not verified in the database")
# return templates.TemplateResponse("protected.html", {"request": request, "user": db_user.username})
#async def get_protected(
# request: Request,
# token: str = Query(None), # Accept token from query parameters
# db: Session = Depends(get_db)
#):
# Now pass both the request and token to the protected_route function
# return await protected_route(request, token, db) |