Loginauth / main.py
Gregniuki's picture
Update main.py
e1dedf3
raw
history blame
13.6 kB
#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, 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
my_secret_key = os.environ['my_secret_key']
from fastapi.staticfiles import StaticFiles
app = FastAPI()
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:
"""Create an assessment to analyse the risk of a UI action.
Args:
project_id: Your Google Cloud project ID.
recaptcha_key: The reCAPTCHA key associated with the site/app
token: The generated token obtained from the client.
recaptcha_action: Action name corresponding to the token.
"""
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
# 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
@app.get("/login", response_class=HTMLResponse)
async def login(request: Request):
return templates.TemplateResponse("login.html", {"request": request})
@app.post("/login")
async def login_post(
request: Request,
email: str = Form(...),
password: str = Form(...),
db: Session = Depends(get_db)
):
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 = '6LdMjQcpAAAAAF8wutZt2Jcwvur-qtxsnoiGoHdG' # 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):
# Render the successful registration page
return templates.TemplateResponse("registration_successful.html", {"request": request})
@app.get("/verify/{verification_token}", response_class=HTMLResponse)
async def verify_email(verification_token: str, db: Session = Depends(get_db)):
user = get_user_by_verification_token(db, verification_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", response_class=HTMLResponse)
async def reset_password_get(request: Request, token: str):
# You can add additional validation for the token if necessary
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})
@app.post("/reset-password")
async def reset_password(token: str, new_password: str, 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)