|
8 | 8 | from sqlmodel import Session, select
|
9 | 9 | from utils.models import User, UserPassword
|
10 | 10 | from utils.auth import (
|
| 11 | + AuthenticationError, |
11 | 12 | get_session,
|
12 | 13 | get_user_from_reset_token,
|
13 | 14 | create_password_validator,
|
|
18 | 19 | create_access_token,
|
19 | 20 | create_refresh_token,
|
20 | 21 | validate_token,
|
21 |
| - send_reset_email |
| 22 | + send_reset_email, |
| 23 | + send_email_update_confirmation, |
| 24 | + get_user_from_email_update_token, |
| 25 | + get_authenticated_user |
22 | 26 | )
|
23 | 27 |
|
24 | 28 | logger = getLogger("uvicorn.error")
|
@@ -102,6 +106,17 @@ async def as_form(
|
102 | 106 | new_password=new_password, confirm_new_password=confirm_new_password)
|
103 | 107 |
|
104 | 108 |
|
| 109 | +class UpdateEmail(BaseModel): |
| 110 | + new_email: EmailStr |
| 111 | + |
| 112 | + @classmethod |
| 113 | + async def as_form( |
| 114 | + cls, |
| 115 | + new_email: EmailStr = Form(...) |
| 116 | + ): |
| 117 | + return cls(new_email=new_email) |
| 118 | + |
| 119 | + |
105 | 120 | # --- DB Request and Response Models ---
|
106 | 121 |
|
107 | 122 |
|
@@ -289,3 +304,72 @@ def logout():
|
289 | 304 | response.delete_cookie("access_token")
|
290 | 305 | response.delete_cookie("refresh_token")
|
291 | 306 | return response
|
| 307 | + |
| 308 | + |
| 309 | +@router.post("/update_email") |
| 310 | +async def request_email_update( |
| 311 | + update: UpdateEmail = Depends(UpdateEmail.as_form), |
| 312 | + user: User = Depends(get_authenticated_user), |
| 313 | + session: Session = Depends(get_session) |
| 314 | +): |
| 315 | + # Check if the new email is already registered |
| 316 | + existing_user = session.exec( |
| 317 | + select(User).where(User.email == update.new_email) |
| 318 | + ).first() |
| 319 | + |
| 320 | + if existing_user: |
| 321 | + raise HTTPException( |
| 322 | + status_code=400, |
| 323 | + detail="This email is already registered" |
| 324 | + ) |
| 325 | + |
| 326 | + if not user or not user.id: |
| 327 | + raise HTTPException( |
| 328 | + status_code=400, |
| 329 | + detail="User not found" |
| 330 | + ) |
| 331 | + |
| 332 | + # Send confirmation email |
| 333 | + send_email_update_confirmation( |
| 334 | + current_email=user.email, |
| 335 | + new_email=update.new_email, |
| 336 | + user_id=user.id, |
| 337 | + session=session |
| 338 | + ) |
| 339 | + |
| 340 | + return RedirectResponse( |
| 341 | + url="/settings?email_update_requested=true", |
| 342 | + status_code=303 |
| 343 | + ) |
| 344 | + |
| 345 | + |
| 346 | +@router.get("/confirm_email_update") |
| 347 | +async def confirm_email_update( |
| 348 | + user_id: int, |
| 349 | + token: str, |
| 350 | + new_email: str, |
| 351 | + session: Session = Depends(get_session) |
| 352 | +): |
| 353 | + user, update_token = get_user_from_email_update_token( |
| 354 | + user_id, token, session |
| 355 | + ) |
| 356 | + |
| 357 | + if not user or not update_token: |
| 358 | + raise AuthenticationError() |
| 359 | + |
| 360 | + # Get the new email from the most recent unconfirmed token |
| 361 | + if update_token.is_expired(): |
| 362 | + raise HTTPException( |
| 363 | + status_code=400, |
| 364 | + detail="Token has expired" |
| 365 | + ) |
| 366 | + |
| 367 | + # Update email and mark token as used |
| 368 | + user.email = new_email |
| 369 | + update_token.used = True |
| 370 | + session.commit() |
| 371 | + |
| 372 | + return RedirectResponse( |
| 373 | + url="/settings?email_updated=true", |
| 374 | + status_code=303 |
| 375 | + ) |
0 commit comments