Gregniuki commited on
Commit
67d68eb
·
verified ·
1 Parent(s): 8a8daf2

Create appp.py

Browse files
Files changed (1) hide show
  1. appp.py +287 -0
appp.py ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Depends, HTTPException, Request, Form, status
2
+ from fastapi.responses import RedirectResponse, HTMLResponse, JSONResponse
3
+ from fastapi.templating import Jinja2Templates
4
+ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
5
+ from pydantic import BaseModel
6
+ from sqlalchemy.orm import Session
7
+ from database import get_db, get_user_by_email
8
+ from models import User
9
+ from passlib.context import CryptContext
10
+ from datetime import datetime, timedelta
11
+ import jwt
12
+ from emailx import send_verification_email, generate_verification_token
13
+ from fastapi.staticfiles import StaticFiles
14
+ from typing import Optional
15
+ import httpx
16
+ import os
17
+ from starlette.middleware.sessions import SessionMiddleware
18
+ from authlib.integrations.starlette_client import OAuth
19
+
20
+ # Environment variables
21
+ GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID')
22
+ GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET')
23
+ SECRET_KEY = os.getenv('SecretKey', 'default_secret')
24
+ ALGORITHM = "HS256"
25
+ ACCESS_TOKEN_EXPIRE_MINUTES = 30
26
+
27
+ # FastAPI and OAuth setup
28
+ app = FastAPI()
29
+ app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)
30
+ oauth = OAuth()
31
+
32
+ # Password context
33
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
34
+
35
+ # OAuth2 scheme
36
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
37
+
38
+ class TokenData(BaseModel):
39
+ token: str
40
+
41
+ class UserCreate(BaseModel):
42
+ username: str
43
+ email: str
44
+ password: str
45
+
46
+ # OAuth Configuration
47
+ oauth.register(
48
+ name='google',
49
+ client_id=os.environ['GOOGLE_CLIENT_ID'],
50
+ client_secret=os.environ['GOOGLE_CLIENT_SECRET'],
51
+ access_token_url='https://accounts.google.com/o/oauth2/token',
52
+ authorize_url='https://accounts.google.com/o/oauth2/auth',
53
+ authorize_params=None,
54
+ api_base_url='https://www.googleapis.com/oauth2/v1/',
55
+ client_kwargs={'scope': 'openid email profile'}
56
+ )
57
+
58
+ # Static and template configurations
59
+ app.mount("/static", StaticFiles(directory="static"), name="static")
60
+ templates = Jinja2Templates(directory="templates")
61
+
62
+ # OAuth routes
63
+ @app.get("/login/oauth")
64
+ async def login_oauth(request: Request):
65
+ redirect_uri = request.url_for('auth_callback')
66
+ return await oauth.google.authorize_redirect(request, redirect_uri)
67
+
68
+ @app.get("/auth/callback")
69
+ async def auth_callback(request: Request, db: Session = Depends(get_db)):
70
+ token = await oauth.google.authorize_access_token(request)
71
+ user_info = await oauth.google.parse_id_token(request, token)
72
+ request.session["user_info"] = user_info
73
+
74
+ db_user = db.query(User).filter(User.email == user_info['email']).first()
75
+ if not db_user:
76
+ db_user = User(email=user_info['email'], username=user_info['name'], is_verified=True)
77
+ db.add(db_user)
78
+ db.commit()
79
+ db.refresh(db_user)
80
+
81
+ access_token = create_access_token(data={"sub": db_user.email}, expires_delta=timedelta(minutes=30))
82
+ response = RedirectResponse(url="/protected")
83
+ response.set_cookie(key="access_token", value=f"Bearer {access_token}", httponly=True)
84
+ return response
85
+
86
+ @app.post("/login")
87
+ async def login(
88
+ request: Request,
89
+ form_data: OAuth2PasswordRequestForm = Depends(),
90
+ db: Session = Depends(get_db),
91
+ recaptcha_token: str = Form(...)
92
+ ):
93
+ if not await verify_recaptcha(recaptcha_token):
94
+ return templates.TemplateResponse("login.html", {"request": request, "error_message": "reCAPTCHA validation failed."})
95
+
96
+ user = authenticate_user(db, form_data.username, form_data.password)
97
+ if not user:
98
+ return templates.TemplateResponse("login.html", {"request": request, "error_message": "Invalid email or password"})
99
+
100
+ if not user.is_verified:
101
+ return templates.TemplateResponse("login.html", {"request": request, "error_message": "Please verify your email before accessing this resource."})
102
+
103
+ access_token = create_access_token(data={"sub": user.email}, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
104
+ url = app.url_path_for("get_protected")
105
+ response = RedirectResponse(url)
106
+ response.set_cookie(key="access_token", value=f"Bearer {access_token}", httponly=True, secure=True, samesite='Lax')
107
+ return response
108
+
109
+ @app.get("/login", response_class=HTMLResponse)
110
+ async def login_get(request: Request):
111
+ google_oauth_url = request.url_for("login_oauth")
112
+ return templates.TemplateResponse("login.html", {"request": request, "google_oauth_url": google_oauth_url})
113
+
114
+ @app.get("/register/google")
115
+ async def register_google(request: Request):
116
+ redirect_uri = request.url_for('auth_callback')
117
+ return await oauth.google.authorize_redirect(request, redirect_uri)
118
+
119
+ @app.get("/auth/callback")
120
+ async def auth_callback(request: Request, db: Session = Depends(get_db)):
121
+ token = await oauth.google.authorize_access_token(request)
122
+ user_info = await oauth.google.parse_id_token(request, token)
123
+
124
+ existing_user = db.query(User).filter(User.email == user_info['email']).first()
125
+ if existing_user:
126
+ request.session["user_info"] = {"username": existing_user.username, "email": existing_user.email}
127
+ return RedirectResponse(url="/login")
128
+ else:
129
+ new_user = User(email=user_info['email'], username=user_info.get('name'), is_verified=True)
130
+ db.add(new_user)
131
+ db.commit()
132
+ db.refresh(new_user)
133
+ request.session["user_info"] = {"username": new_user.username, "email": new_user.email}
134
+
135
+ return RedirectResponse(url="/registration_successful")
136
+
137
+ @app.get("/registration_successful", response_class=HTMLResponse)
138
+ async def registration_successful(request: Request):
139
+ return templates.TemplateResponse("registration_successful.html", {"request": request})
140
+
141
+ async def verify_recaptcha(recaptcha_token: str) -> bool:
142
+ recaptcha_secret = '6LeSJgwpAAAAAJrLrvlQYhRsOjf2wKXee_Jc4Z-k' # Replace with your reCAPTCHA secret key
143
+ recaptcha_url = 'https://www.google.com/recaptcha/api/siteverify'
144
+ recaptcha_data = {
145
+ 'secret': recaptcha_secret,
146
+ 'response': recaptcha_token
147
+ }
148
+
149
+ async with httpx.AsyncClient() as client:
150
+ recaptcha_response = await client.post(recaptcha_url, data=recaptcha_data)
151
+
152
+ recaptcha_result = recaptcha_response.json()
153
+ return recaptcha_result.get('success', False)
154
+
155
+ @app.get("/verify", response_class=HTMLResponse)
156
+ async def verify_email(request: Request, token: str, db: Session = Depends(get_db)):
157
+ user = get_user_by_verification_token(db, token)
158
+ if not user:
159
+ return templates.TemplateResponse("verification_failed.html", {"request": request, "error_message": "Invalid verification token"})
160
+
161
+ if user.is_verified:
162
+ return templates.TemplateResponse("verification_failed.html", {"request": request, "error_message": "Email already verified"})
163
+
164
+ user.is_verified = True
165
+ user.email_verification_token = None
166
+ db.commit()
167
+
168
+ access_token = create_access_token(data={"sub": user.email}, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
169
+ response = RedirectResponse(url="/protected")
170
+ response.set_cookie(key="access_token", value=f"Bearer {access_token}", httponly=True, secure=True, samesite='Lax')
171
+ return response
172
+
173
+ def is_username_available(db: Session, username: str) -> bool:
174
+ return db.query(User).filter(User.username == username).first() is None
175
+
176
+ @app.get("/register", response_class=HTMLResponse)
177
+ async def register_get(request: Request):
178
+ return templates.TemplateResponse("register.html", {"request": request, "google_oauth_url": request.url_for("login_oauth")})
179
+
180
+ @app.post("/register")
181
+ async def register_post(
182
+ request: Request,
183
+ username: str = Form(...),
184
+ email: str = Form(...),
185
+ password: str = Form(...),
186
+ confirm_password: str = Form(...),
187
+ recaptcha_token: str = Form(...),
188
+ db: Session = Depends(get_db)
189
+ ):
190
+ if not await verify_recaptcha(recaptcha_token):
191
+ return templates.TemplateResponse("register.html", {"request": request, "error_message": "reCAPTCHA validation failed."})
192
+
193
+ if password != confirm_password:
194
+ return templates.TemplateResponse("register.html", {"request": request, "error_message": "Passwords do not match."})
195
+
196
+ user_data = UserCreate(username=username, email=email, password=password)
197
+ if not is_username_available(db, user_data.username):
198
+ return templates.TemplateResponse("register.html", {"request": request, "error_message": "Username already taken"})
199
+
200
+ try:
201
+ registered_user = register_user(user_data, db)
202
+ return RedirectResponse(url="/registration_successful", status_code=status.HTTP_303_SEE_OTHER)
203
+ except HTTPException as e:
204
+ return templates.TemplateResponse("register.html", {"request": request, "error_message": e.detail})
205
+
206
+ @app.get("/", response_class=HTMLResponse)
207
+ async def landing(request: Request):
208
+ return templates.TemplateResponse("landing.html", {"request": request})
209
+
210
+ def verify_password(plain_password, hashed_password):
211
+ return pwd_context.verify(plain_password, hashed_password)
212
+
213
+ def get_password_hash(password):
214
+ return pwd_context.hash(password)
215
+
216
+ def authenticate_user(db: Session, email: str, password: str):
217
+ user = db.query(User).filter(User.email == email).first()
218
+ if not user or not verify_password(password, user.hashed_password):
219
+ return None
220
+ return user
221
+
222
+ def create_access_token(data: dict, expires_delta: timedelta = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)):
223
+ to_encode = data.copy()
224
+ expire = datetime.utcnow() + expires_delta
225
+ to_encode.update({"exp": expire})
226
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
227
+ return encoded_jwt
228
+
229
+ def verify_token(token: str):
230
+ try:
231
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
232
+ return payload.get("sub")
233
+ except jwt.ExpiredSignatureError:
234
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token has expired")
235
+ except jwt.InvalidTokenError:
236
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials")
237
+
238
+ async def get_current_user(request: Request, db: Session = Depends(get_db)):
239
+ token = request.cookies.get("access_token")
240
+ if not token:
241
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
242
+
243
+ try:
244
+ email = verify_token(token.split(" ")[1])
245
+ user = get_user_by_email(db, email)
246
+ if not user or not user.is_verified:
247
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found or not verified")
248
+ return user
249
+ except HTTPException:
250
+ raise
251
+ except Exception as e:
252
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(e))
253
+
254
+ @app.get("/protected", response_class=HTMLResponse)
255
+ async def get_protected(request: Request, current_user: User = Depends(get_current_user)):
256
+ return templates.TemplateResponse("protected.html", {"request": request, "user": current_user.username})
257
+
258
+ def register_user(user_data: UserCreate, db: Session):
259
+ if get_user_by_email(db, user_data.email):
260
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered")
261
+
262
+ hashed_password = get_password_hash(user_data.password)
263
+ verification_token = generate_verification_token(user_data.email)
264
+ reset_link = f"http://gregniuki-loginauth.hf.space/verify?token={verification_token}"
265
+ send_verification_email(user_data.email, reset_link)
266
+
267
+ new_user = User(
268
+ email=user_data.email,
269
+ username=user_data.username,
270
+ hashed_password=hashed_password,
271
+ email_verification_token=verification_token
272
+ )
273
+ db.add(new_user)
274
+ db.commit()
275
+ db.refresh(new_user)
276
+ return new_user
277
+
278
+ def get_user_by_verification_token(db: Session, verification_token: str):
279
+ return db.query(User).filter(User.email_verification_token == verification_token).first()
280
+
281
+ def reset_password(user: User, db: Session):
282
+ verification_token = generate_verification_token(user.email)
283
+ reset_link = f"http://gregniuki-loginauth.hf.space/reset-password?token={verification_token}"
284
+ send_verification_email(user.email, reset_link)
285
+
286
+ user.email_verification_token = verification_token
287
+ db.commit()