fix(dev): support 127.0.0.1 dev OAuth end-to-end
Re-add the 127.0.0.1 callbacks to the dev host allowlist (now registered in the Google console alongside the localhost ones). Deriving the redirect_uri from the request host keeps the whole round-trip — login, Google callback, and the session/state cookie — on one origin; a cross-host fallback (127.0.0.1 → the fixed localhost callback) would silently lose the OAuth state, since a session cookie set on 127.0.0.1 is never sent to localhost. Clarify that in the comment and cover all four hosts in the unit test.
This commit is contained in:
+10
-5
@@ -249,11 +249,16 @@ def upsert_pending_invite(db: Session, email: str) -> Invite | None:
|
|||||||
# Local-dev origins allowed to drive the OAuth redirect_uri from the request Host. The Vite dev
|
# Local-dev origins allowed to drive the OAuth redirect_uri from the request Host. The Vite dev
|
||||||
# server (:5173) proxies /auth to the backend (:8080) preserving the Host header, and the session
|
# server (:5173) proxies /auth to the backend (:8080) preserving the Host header, and the session
|
||||||
# cookie is port-agnostic — so a flow started on :5173 can come back to :5173 instead of always
|
# cookie is port-agnostic — so a flow started on :5173 can come back to :5173 instead of always
|
||||||
# landing on the fixed :8080 callback. Only the localhost variants are listed: they must ALSO be
|
# landing on the fixed :8080 callback. Deriving the redirect_uri from the request host keeps the
|
||||||
# registered as redirect URIs in the Google console, and Google rejects an unregistered one with
|
# WHOLE round-trip on ONE origin (login, Google callback, and the session/state cookie all on the
|
||||||
# redirect_uri_mismatch. A 127.0.0.1 origin isn't registered, so it deliberately falls through to
|
# same host) — that consistency is load-bearing, because a session cookie set on 127.0.0.1 is NOT
|
||||||
# the fixed configured callback (which is registered) rather than producing a mismatch.
|
# sent to localhost and vice versa, so a cross-host fallback would silently lose the OAuth state.
|
||||||
_DEV_OAUTH_HOSTS = frozenset({"localhost:5173", "localhost:8080"})
|
# EVERY host listed here must ALSO be registered as a redirect URI in the Google console, or Google
|
||||||
|
# rejects it with redirect_uri_mismatch. An unlisted host falls back to the fixed configured
|
||||||
|
# callback (fine only when that lands on the same host the flow started on).
|
||||||
|
_DEV_OAUTH_HOSTS = frozenset(
|
||||||
|
{"localhost:5173", "localhost:8080", "127.0.0.1:5173", "127.0.0.1:8080"}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _oauth_redirect_uri(request: Request) -> str:
|
def _oauth_redirect_uri(request: Request) -> str:
|
||||||
|
|||||||
@@ -28,8 +28,12 @@ def _prod_settings():
|
|||||||
|
|
||||||
def test_dev_known_host_returns_to_that_origin(monkeypatch):
|
def test_dev_known_host_returns_to_that_origin(monkeypatch):
|
||||||
monkeypatch.setattr(auth, "settings", _dev_settings())
|
monkeypatch.setattr(auth, "settings", _dev_settings())
|
||||||
|
# Each allowlisted host keeps the whole round-trip on one origin (all four are registered in the
|
||||||
|
# Google console), so the redirect_uri matches the host the flow started on.
|
||||||
assert auth._oauth_redirect_uri(_req("localhost:5173")) == "http://localhost:5173/auth/callback"
|
assert auth._oauth_redirect_uri(_req("localhost:5173")) == "http://localhost:5173/auth/callback"
|
||||||
assert auth._oauth_redirect_uri(_req("localhost:8080")) == "http://localhost:8080/auth/callback"
|
assert auth._oauth_redirect_uri(_req("localhost:8080")) == "http://localhost:8080/auth/callback"
|
||||||
|
assert auth._oauth_redirect_uri(_req("127.0.0.1:5173")) == "http://127.0.0.1:5173/auth/callback"
|
||||||
|
assert auth._oauth_redirect_uri(_req("127.0.0.1:8080")) == "http://127.0.0.1:8080/auth/callback"
|
||||||
|
|
||||||
|
|
||||||
def test_dev_host_is_case_insensitive(monkeypatch):
|
def test_dev_host_is_case_insensitive(monkeypatch):
|
||||||
@@ -40,9 +44,6 @@ def test_dev_host_is_case_insensitive(monkeypatch):
|
|||||||
def test_dev_unknown_or_missing_host_falls_back_to_configured(monkeypatch):
|
def test_dev_unknown_or_missing_host_falls_back_to_configured(monkeypatch):
|
||||||
monkeypatch.setattr(auth, "settings", _dev_settings())
|
monkeypatch.setattr(auth, "settings", _dev_settings())
|
||||||
# An origin not on the allowlist (or a request with no Host) must not drive the redirect.
|
# An origin not on the allowlist (or a request with no Host) must not drive the redirect.
|
||||||
# 127.0.0.1 is deliberately NOT allowlisted (not registered in the Google console), so it falls
|
|
||||||
# back to the fixed configured callback rather than producing a redirect_uri_mismatch.
|
|
||||||
assert auth._oauth_redirect_uri(_req("127.0.0.1:5173")) == "http://localhost:8080/auth/callback"
|
|
||||||
assert auth._oauth_redirect_uri(_req("evil.example.com")) == "http://localhost:8080/auth/callback"
|
assert auth._oauth_redirect_uri(_req("evil.example.com")) == "http://localhost:8080/auth/callback"
|
||||||
assert auth._oauth_redirect_uri(_req(None)) == "http://localhost:8080/auth/callback"
|
assert auth._oauth_redirect_uri(_req(None)) == "http://localhost:8080/auth/callback"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user