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:
|
||||
return "Invalid redirect_uri", 400
|
||||
|
||||
# Account's own device id — always available via factory hook
|
||||
account_did = g.device_id
|
||||
|
||||
# Not logged in
|
||||
if not g.get("user"):
|
||||
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 "?"
|
||||
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
|
||||
store_login_redirect_target()
|
||||
return redirect(url_for("auth.login_form", next=authorize_path))
|
||||
@@ -110,7 +116,10 @@ def register(url_prefix="/auth"):
|
||||
s.add(oauth_code)
|
||||
|
||||
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) ------------------------------
|
||||
|
||||
@@ -276,6 +285,20 @@ def register(url_prefix="/auth"):
|
||||
# Fresh account session ID for grant tracking
|
||||
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()
|
||||
return redirect(redirect_url, 303)
|
||||
|
||||
@@ -296,6 +319,15 @@ def register(url_prefix="/auth"):
|
||||
except SQLAlchemyError:
|
||||
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(ACCOUNT_SESSION_KEY, None)
|
||||
from shared.infrastructure.urls import blog_url
|
||||
@@ -318,6 +350,15 @@ def register(url_prefix="/auth"):
|
||||
except SQLAlchemyError:
|
||||
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(ACCOUNT_SESSION_KEY, None)
|
||||
from shared.infrastructure.urls import blog_url
|
||||
|
||||
Reference in New Issue
Block a user