File size: 20,925 Bytes
763951b
 
1deefa3
325f578
5ed4a12
325f578
 
de6e2ba
325f578
f5ffa3e
fc80837
9bbbf05
9731a64
 
afd16c0
e78b3ed
a06439c
 
b702b7c
10b597c
afd16c0
1b47f41
9bbbf05
e78b3ed
9bbbf05
10b597c
e78b3ed
d69f776
1b47f41
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
 
 
 
 
 
 
 
 
f2c4246
 
 
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
489
490
491
#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 authlib.integrations.starlette_client import OAuth
from starlette.middleware.sessions import SessionMiddleware

my_secret_key = os.environ['my_secret_key']
SECRET_KEY = os.getenv('SecretKey', 'default_secret')
from fastapi.staticfiles import StaticFiles
from authlib.integrations.starlette_client import OAuth

app = FastAPI()

oauth = OAuth()
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)
# 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)
        registered_user = register(user, db)
        # If registration is successful, store user info in the session
        request.session["user_info"] = {"username": registered_user.username, "email": registered_user.email}
    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)