feat: Fusion ACC-3.2 — pont SSO draco_session (/auth/draco), liens sub → compte (migration 51) (#21)
FlowDeck CI / lint (push) Failing after 2m8s
FlowDeck CI / test (push) Failing after 20m43s
FlowDeck CI / docker (push) Skipped

This commit is contained in:
Spark
2026-10-20 23:56:24 -04:00
parent 6a75b189ec
commit 1ce9f691a6
7 changed files with 433 additions and 3 deletions
+64 -1
View File
@@ -102,7 +102,8 @@ body{background:#191919;color:#fff;font-family:-apple-system,BlinkMacSystemFont,
<div class="oauth-section" id="oauth-section">
<p style="font-size:13px;color:rgba(255,255,255,.4);margin-bottom:8px;">Or continue with</p>
<button class="oauth-btn" onclick="window.location='/auth/login?provider=gitea'" style="margin-bottom:8px">🔗 <span id="gitea-btn-label">Login</span> with Gitea</button>
<button class="oauth-btn" onclick="window.location='/auth/login?provider=github'">🐙 <span id="github-btn-label">Login</span> with GitHub</button>
<button class="oauth-btn" onclick="window.location='/auth/login?provider=github'" style="margin-bottom:8px">🐙 <span id="github-btn-label">Login</span> with GitHub</button>
<button class="oauth-btn" onclick="window.location='https://og.dracodev.net/sso/authorize?return_to=https%3A%2F%2Fflowdeck.dracodev.net%2Fauth%2Fdraco'">🧭 Login with ObsiGate (SSO)</button>
</div>
<!-- SSO / SAML + OIDC (v6.7.0) — buttons injected by loadSsoProviders() -->
<div class="oauth-section" id="sso-section" style="display:none">
@@ -451,6 +452,63 @@ async def callback(
return response
# ── Fusion ACC-3.2 — pont SSO draco_session ──────────────────────────────
@router.get("/draco")
def draco_bridge(request: Request, next: str = "/workspaces"):
"""Convertit le cookie SSO ``draco_session`` (ObsiGate) en session.
Point de retour de ``GET /sso/authorize`` de l'émettrice : si le
cookie est valide et résolu vers un compte local, une session
Flowdeck native est créée ; sinon, retour à la connexion locale.
"""
from app.services import draco
user = draco.user_for_draco(request)
if user is None:
return RedirectResponse(url="/auth/login?provider=local", status_code=302)
session = SessionManager.create_session(user, request)
_log_login(user["id"], request)
cible = next if next.startswith("/") and not next.startswith("//") else "/workspaces"
response = RedirectResponse(url=cible, status_code=302)
response.set_cookie("flowdeck_session", session, httponly=True,
max_age=86400 * 7, samesite="lax", path="/")
return response
@router.get("/draco-link")
def draco_link_get(request: Request):
"""Sub draco associé au compte courant (ou null)."""
from app.services import draco
user = _session_user_or_401(request)
return {"sub": draco.current_link(user["id"])}
@router.post("/draco-link")
def draco_link_post(request: Request):
"""(Ré)associe le sub du cookie draco de la requête au compte."""
from fastapi import HTTPException
from app.services import draco
user = _session_user_or_401(request)
sub = draco.relink(request, user)
if sub is None:
raise HTTPException(400, "Cookie draco_session absent, invalide ou déjà lié ailleurs")
return {"sub": sub}
@router.delete("/draco-link")
def draco_link_delete(request: Request):
"""Dissocie le compte (pierre tombale anti re-création automatique)."""
from app.services import draco
user = _session_user_or_401(request)
draco.unlink(user["id"])
return {"ok": True}
@router.get("/logout")
def logout(request: Request):
"""Clear session and redirect to login page.
@@ -479,6 +537,11 @@ def current_user(request: Request):
user = gcu(request)
if not user:
return {"authenticated": False}
# Fusion ACC-3.2 : si un cookie draco valide accompagne la session
# native, l'association sub ↔ compte se crée à cette occasion.
from app.services import draco
draco.maybe_link(request, user)
return {"authenticated": True, "user": public_user(user)}