Skip to content

Commit 4dce9f2

Browse files
Merge remote-tracking branch 'origin/main' into modal
2 parents 32e71b1 + d4fec23 commit 4dce9f2

9 files changed

Lines changed: 367 additions & 106 deletions

File tree

main.py

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
get_user_from_request,
2121
require_unauthenticated_client,
2222
)
23-
from utils.core.auth import COOKIE_SECURE
23+
from utils.core.auth import refresh_token_is_persistent, set_auth_cookies
2424
from utils.core.htmx import (
2525
is_htmx_request,
2626
toast_response,
@@ -170,19 +170,11 @@ async def needs_new_tokens_handler(request: Request, exc: NeedsNewTokens):
170170
response = RedirectResponse(
171171
url=redirect_url, status_code=status.HTTP_307_TEMPORARY_REDIRECT
172172
)
173-
response.set_cookie(
174-
key="access_token",
175-
value=exc.access_token,
176-
httponly=True,
177-
secure=COOKIE_SECURE,
178-
samesite="strict",
179-
)
180-
response.set_cookie(
181-
key="refresh_token",
182-
value=exc.refresh_token,
183-
httponly=True,
184-
secure=COOKIE_SECURE,
185-
samesite="strict",
173+
set_auth_cookies(
174+
response,
175+
exc.access_token,
176+
exc.refresh_token,
177+
persistent=refresh_token_is_persistent(exc.refresh_token),
186178
)
187179
return response
188180

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "fastapi-jinja2-postgres-webapp"
3-
version = "0.1.23"
3+
version = "0.1.24"
44
description = "A template webapp with a pure-Python FastAPI backend, frontend templating with Jinja2, and a Postgres database to power user auth"
55
readme = "README.md"
66
package-mode = false

routers/core/account.py

Lines changed: 47 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@
2020
from utils.core.auth import (
2121
HTML_PASSWORD_PATTERN,
2222
COMPILED_PASSWORD_PATTERN,
23-
COOKIE_SECURE,
2423
MAX_EMAILS_PER_ACCOUNT,
2524
oauth2_scheme_cookie,
2625
get_password_hash,
2726
create_access_token,
2827
create_tracked_refresh_token,
2928
revoke_all_refresh_tokens,
3029
validate_token,
30+
set_auth_cookies,
31+
clear_auth_cookies,
3132
send_reset_email_task,
3233
send_email_verification,
3334
send_email_verified_notification,
@@ -127,8 +128,7 @@ def logout(
127128
Log out a user by revoking their refresh token and clearing cookies.
128129
"""
129130
response = RedirectResponse(url="/", status_code=303)
130-
response.delete_cookie("access_token")
131-
response.delete_cookie("refresh_token")
131+
clear_auth_cookies(response)
132132

133133
_, refresh_token_value = tokens
134134
if refresh_token_value:
@@ -382,7 +382,9 @@ async def register(
382382

383383
# Create access token using the committed account's email
384384
access_token = create_access_token(data={"sub": account.email, "fresh": True})
385-
refresh_token = create_tracked_refresh_token(account.id, account.email, session)
385+
refresh_token = create_tracked_refresh_token(
386+
account.id, account.email, session, persistent=False
387+
)
386388
session.commit()
387389

388390
# Set cookie — use HX-Redirect for HTMX, 303 for regular form submissions
@@ -391,19 +393,11 @@ async def register(
391393
response.headers["HX-Redirect"] = str(redirect_url)
392394
else:
393395
response = RedirectResponse(url=str(redirect_url), status_code=303)
394-
response.set_cookie(
395-
key="access_token",
396-
value=access_token,
397-
httponly=True,
398-
secure=COOKIE_SECURE,
399-
samesite="strict",
400-
)
401-
response.set_cookie(
402-
key="refresh_token",
403-
value=refresh_token,
404-
httponly=True,
405-
secure=COOKIE_SECURE,
406-
samesite="strict",
396+
set_auth_cookies(
397+
response,
398+
access_token,
399+
refresh_token,
400+
persistent=False,
407401
)
408402

409403
return response
@@ -417,6 +411,7 @@ async def login(
417411
account_and_session: Tuple[Account, Session] = Depends(
418412
get_account_from_credentials
419413
),
414+
remember: Optional[str] = Form(None),
420415
invitation_token: Optional[str] = Form(
421416
None,
422417
title="Invitation token",
@@ -507,8 +502,11 @@ async def login(
507502

508503
# Create access token
509504
assert account.id is not None
505+
persistent = remember == "on"
510506
access_token = create_access_token(data={"sub": account.email, "fresh": True})
511-
refresh_token = create_tracked_refresh_token(account.id, account.email, session)
507+
refresh_token = create_tracked_refresh_token(
508+
account.id, account.email, session, persistent=persistent
509+
)
512510
session.commit()
513511

514512
# Set cookie — use HX-Redirect for HTMX, 303 for regular form submissions
@@ -517,19 +515,11 @@ async def login(
517515
response.headers["HX-Redirect"] = str(redirect_url)
518516
else:
519517
response = RedirectResponse(url=str(redirect_url), status_code=303)
520-
response.set_cookie(
521-
key="access_token",
522-
value=access_token,
523-
httponly=True,
524-
secure=COOKIE_SECURE,
525-
samesite="strict",
526-
)
527-
response.set_cookie(
528-
key="refresh_token",
529-
value=refresh_token,
530-
httponly=True,
531-
secure=COOKIE_SECURE,
532-
samesite="strict",
518+
set_auth_cookies(
519+
response,
520+
access_token,
521+
refresh_token,
522+
persistent=persistent,
533523
)
534524

535525
return response
@@ -553,8 +543,7 @@ async def refresh_token(
553543
response = RedirectResponse(
554544
url=router.url_path_for("read_login"), status_code=303
555545
)
556-
response.delete_cookie("access_token")
557-
response.delete_cookie("refresh_token")
546+
clear_auth_cookies(response)
558547
return response
559548

560549
# Validate JTI server-side
@@ -563,8 +552,7 @@ async def refresh_token(
563552
response = RedirectResponse(
564553
url=router.url_path_for("read_login"), status_code=303
565554
)
566-
response.delete_cookie("access_token")
567-
response.delete_cookie("refresh_token")
555+
clear_auth_cookies(response)
568556
return response
569557

570558
user_email = decoded_token.get("sub")
@@ -591,32 +579,26 @@ async def refresh_token(
591579
response = RedirectResponse(
592580
url=router.url_path_for("read_login"), status_code=303
593581
)
594-
response.delete_cookie("access_token")
595-
response.delete_cookie("refresh_token")
582+
clear_auth_cookies(response)
596583
return response
597584

598585
# Revoke current token and issue new ones
599586
db_token.revoked = True
587+
persistent = bool(decoded_token.get("persistent", False))
600588
new_access_token = create_access_token(data={"sub": account.email, "fresh": False})
601-
new_refresh_token = create_tracked_refresh_token(account.id, account.email, session)
589+
new_refresh_token = create_tracked_refresh_token(
590+
account.id, account.email, session, persistent=persistent
591+
)
602592
session.commit()
603593

604594
response = RedirectResponse(
605595
url=dashboard_router.url_path_for("read_dashboard"), status_code=303
606596
)
607-
response.set_cookie(
608-
key="access_token",
609-
value=new_access_token,
610-
httponly=True,
611-
secure=COOKIE_SECURE,
612-
samesite="strict",
613-
)
614-
response.set_cookie(
615-
key="refresh_token",
616-
value=new_refresh_token,
617-
httponly=True,
618-
secure=COOKIE_SECURE,
619-
samesite="strict",
597+
set_auth_cookies(
598+
response,
599+
new_access_token,
600+
new_refresh_token,
601+
persistent=persistent,
620602
)
621603

622604
return response
@@ -696,7 +678,7 @@ async def reset_password(
696678
data={"sub": authorized_account.email, "fresh": True}
697679
)
698680
refresh_token = create_tracked_refresh_token(
699-
authorized_account.id, authorized_account.email, session
681+
authorized_account.id, authorized_account.email, session, persistent=False
700682
)
701683
session.commit()
702684

@@ -709,19 +691,11 @@ async def reset_password(
709691
else:
710692
response = RedirectResponse(url=redirect_url, status_code=303)
711693

712-
response.set_cookie(
713-
key="access_token",
714-
value=access_token,
715-
httponly=True,
716-
secure=COOKIE_SECURE,
717-
samesite="strict",
718-
)
719-
response.set_cookie(
720-
key="refresh_token",
721-
value=refresh_token,
722-
httponly=True,
723-
secure=COOKIE_SECURE,
724-
samesite="strict",
694+
set_auth_cookies(
695+
response,
696+
access_token,
697+
refresh_token,
698+
persistent=False,
725699
)
726700
set_flash_cookie(response, message)
727701
return response
@@ -961,7 +935,9 @@ async def promote_email(
961935

962936
# Issue new tokens with the new primary email
963937
access_token = create_access_token(data={"sub": account.email, "fresh": True})
964-
refresh_token = create_tracked_refresh_token(account.id, account.email, session)
938+
refresh_token = create_tracked_refresh_token(
939+
account.id, account.email, session, persistent=False
940+
)
965941
session.commit()
966942

967943
# Create recovery token and send notification to the old primary
@@ -979,18 +955,11 @@ async def promote_email(
979955
else:
980956
response = RedirectResponse(url=str(profile_path), status_code=303)
981957
set_flash_cookie(response, "Primary email address updated.")
982-
response.set_cookie(
983-
key="access_token",
984-
value=access_token,
985-
httponly=True,
986-
secure=COOKIE_SECURE,
987-
samesite="lax",
988-
)
989-
response.set_cookie(
990-
key="refresh_token",
991-
value=refresh_token,
992-
httponly=True,
993-
secure=COOKIE_SECURE,
958+
set_auth_cookies(
959+
response,
960+
access_token,
961+
refresh_token,
962+
persistent=False,
994963
samesite="lax",
995964
)
996965
return response

0 commit comments

Comments
 (0)