Account authorize passes account_did, login/logout signal via Redis
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 48s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 48s
- OAuth authorize: pass account_did (g.device_id) in both success
and error redirects so client apps can track the device
- Magic link login: set did_auth:{device_id} in Redis so client
apps detect login even when their prompt=none cache says "no"
- Logout + SSO-logout: clear did_auth:{device_id} from Redis
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -67,12 +67,18 @@ def register(url_prefix="/auth"):
|
|||||||
if redirect_uri != expected_redirect:
|
if redirect_uri != expected_redirect:
|
||||||
return "Invalid redirect_uri", 400
|
return "Invalid redirect_uri", 400
|
||||||
|
|
||||||
|
# Account's own device id — always available via factory hook
|
||||||
|
account_did = g.device_id
|
||||||
|
|
||||||
# Not logged in
|
# Not logged in
|
||||||
if not g.get("user"):
|
if not g.get("user"):
|
||||||
if prompt == "none":
|
if prompt == "none":
|
||||||
# Silent check — no interactive login, return error
|
# Silent check — pass account_did so client can watch for future logins
|
||||||
sep = "&" if "?" in redirect_uri else "?"
|
sep = "&" if "?" in redirect_uri else "?"
|
||||||
return redirect(f"{redirect_uri}{sep}error=login_required&state={state}")
|
return redirect(
|
||||||
|
f"{redirect_uri}{sep}error=login_required"
|
||||||
|
f"&state={state}&account_did={account_did}"
|
||||||
|
)
|
||||||
authorize_path = request.full_path
|
authorize_path = request.full_path
|
||||||
store_login_redirect_target()
|
store_login_redirect_target()
|
||||||
return redirect(url_for("auth.login_form", next=authorize_path))
|
return redirect(url_for("auth.login_form", next=authorize_path))
|
||||||
@@ -110,7 +116,10 @@ def register(url_prefix="/auth"):
|
|||||||
s.add(oauth_code)
|
s.add(oauth_code)
|
||||||
|
|
||||||
sep = "&" if "?" in redirect_uri else "?"
|
sep = "&" if "?" in redirect_uri else "?"
|
||||||
return redirect(f"{redirect_uri}{sep}code={code}&state={state}")
|
return redirect(
|
||||||
|
f"{redirect_uri}{sep}code={code}&state={state}"
|
||||||
|
f"&account_did={account_did}"
|
||||||
|
)
|
||||||
|
|
||||||
# --- Grant verification (internal endpoint) ------------------------------
|
# --- Grant verification (internal endpoint) ------------------------------
|
||||||
|
|
||||||
@@ -276,6 +285,20 @@ def register(url_prefix="/auth"):
|
|||||||
# Fresh account session ID for grant tracking
|
# Fresh account session ID for grant tracking
|
||||||
qsession[ACCOUNT_SESSION_KEY] = secrets.token_urlsafe(32)
|
qsession[ACCOUNT_SESSION_KEY] = secrets.token_urlsafe(32)
|
||||||
|
|
||||||
|
# Signal login for this device so client apps can detect it
|
||||||
|
try:
|
||||||
|
from shared.browser.app.redis_cacher import get_redis
|
||||||
|
import time as _time
|
||||||
|
_redis = get_redis()
|
||||||
|
if _redis:
|
||||||
|
await _redis.set(
|
||||||
|
f"did_auth:{g.device_id}",
|
||||||
|
str(_time.time()).encode(),
|
||||||
|
ex=30 * 24 * 3600,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
current_app.logger.exception("[auth] failed to set did_auth in Redis")
|
||||||
|
|
||||||
redirect_url = pop_login_redirect_target()
|
redirect_url = pop_login_redirect_target()
|
||||||
return redirect(redirect_url, 303)
|
return redirect(redirect_url, 303)
|
||||||
|
|
||||||
@@ -296,6 +319,15 @@ def register(url_prefix="/auth"):
|
|||||||
except SQLAlchemyError:
|
except SQLAlchemyError:
|
||||||
current_app.logger.exception("[auth] failed to revoke grants")
|
current_app.logger.exception("[auth] failed to revoke grants")
|
||||||
|
|
||||||
|
# Clear login signal for this device
|
||||||
|
try:
|
||||||
|
from shared.browser.app.redis_cacher import get_redis
|
||||||
|
_redis = get_redis()
|
||||||
|
if _redis:
|
||||||
|
await _redis.delete(f"did_auth:{g.device_id}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
qsession.pop(SESSION_USER_KEY, None)
|
qsession.pop(SESSION_USER_KEY, None)
|
||||||
qsession.pop(ACCOUNT_SESSION_KEY, None)
|
qsession.pop(ACCOUNT_SESSION_KEY, None)
|
||||||
from shared.infrastructure.urls import blog_url
|
from shared.infrastructure.urls import blog_url
|
||||||
@@ -318,6 +350,15 @@ def register(url_prefix="/auth"):
|
|||||||
except SQLAlchemyError:
|
except SQLAlchemyError:
|
||||||
current_app.logger.exception("[auth] failed to revoke grants")
|
current_app.logger.exception("[auth] failed to revoke grants")
|
||||||
|
|
||||||
|
# Clear login signal for this device
|
||||||
|
try:
|
||||||
|
from shared.browser.app.redis_cacher import get_redis
|
||||||
|
_redis = get_redis()
|
||||||
|
if _redis:
|
||||||
|
await _redis.delete(f"did_auth:{g.device_id}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
qsession.pop(SESSION_USER_KEY, None)
|
qsession.pop(SESSION_USER_KEY, None)
|
||||||
qsession.pop(ACCOUNT_SESSION_KEY, None)
|
qsession.pop(ACCOUNT_SESSION_KEY, None)
|
||||||
from shared.infrastructure.urls import blog_url
|
from shared.infrastructure.urls import blog_url
|
||||||
|
|||||||
Reference in New Issue
Block a user