From 0ee050ab37ff7d2a263e491b6f023865d8fc863f Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Thu, 2 Apr 2026 17:43:25 +0200 Subject: [PATCH] wip use a transit token --- .../outlook-addin/src/taskpane/taskpane.js | 7 ++--- src/backend/core/addons/service.py | 26 +++++++++++++++---- src/backend/core/addons/views.py | 11 +++++--- src/backend/core/addons/viewsets.py | 4 +-- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/addins/outlook-addin/src/taskpane/taskpane.js b/src/addins/outlook-addin/src/taskpane/taskpane.js index b934741a..8fbf08eb 100644 --- a/src/addins/outlook-addin/src/taskpane/taskpane.js +++ b/src/addins/outlook-addin/src/taskpane/taskpane.js @@ -50,8 +50,8 @@ function startPolling(session_id, { onSuccess, onTimeout, onError }) { // ─── Transit Dialog ─────────────────────────────────────────────────────── -function openTransitDialog(session_id, { onCancel, onError }) { - const meetUrl = `${BASE_URL}/addons/transit/?session_id=${session_id}`; +function openTransitDialog(transit_token, { onCancel, onError }) { + const meetUrl = `${BASE_URL}/addons/transit/?transit_token=${transit_token}`; Office.context.ui.displayDialogAsync( meetUrl, @@ -92,6 +92,7 @@ function connect() { .then((res) => res.json()) .then((data) => { const session_id = data.session_id; + const transit_token = data.transit_token; setStatus("En attente d'authentification..."); const pollInterval = startPolling(session_id, { @@ -109,7 +110,7 @@ function connect() { }, }); - openTransitDialog(session_id, { + openTransitDialog(transit_token, { onCancel: () => clearInterval(pollInterval), onError: (err) => { clearInterval(pollInterval); diff --git a/src/backend/core/addons/service.py b/src/backend/core/addons/service.py index d2bcb77a..7adabea2 100644 --- a/src/backend/core/addons/service.py +++ b/src/backend/core/addons/service.py @@ -35,7 +35,7 @@ class TokenExchangeService: secret_key=settings.ADDONS_JWT_SECRET_KEY, algorithm=settings.ADDONS_JWT_ALG, issuer=settings.ADDONS_JWT_ISSUER, - audience=settings.ADDONS_JWT_AUDIENCE, + audience=settings.ADDONS_JWT_AUDIENCE, # todo - precise expiration_seconds=settings.ADDONS_JWT_EXPIRATION_SECONDS, token_type=settings.ADDONS_JWT_TOKEN_TYPE, ) @@ -48,7 +48,7 @@ class TokenExchangeService: """Wip.""" return f"{settings.ADDONS_SESSION_TOKEN_PREFIX}_{result_token}" - def init_session(self) -> tuple[str, str]: + def init_session(self) -> tuple[str, str, str]: """Create a new pending authentication session and return its ID.""" session_id = secrets.token_urlsafe(settings.ADDONS_SESSION_ID_LENGTH) @@ -77,10 +77,18 @@ class TokenExchangeService: timeout=settings.ADDONS_SESSION_TIMEOUT, ) - print('$$ init session_id') - print(session_id) + # Transit token → session_id, very short TTL, one-time use + transit_token = secrets.token_urlsafe(32) + cache.set( + f"addon_transit_{transit_token}", + session_id, + timeout=120 + ) - return session_id, result_token + print('$$ init transit_token') + print(transit_token) + + return session_id, result_token, transit_token # todo - wip def get_session(self, session_id: str) -> dict: @@ -174,3 +182,11 @@ class TokenExchangeService: def token_to_session(self, result_token): """wip.""" return None + + def consume_transit_token(self, transit_token: str) -> str | None: + """Resolve and immediately delete the transit token (one-time use).""" + key = f"addon_transit_{transit_token}" + session_id = cache.get(key) + if session_id: + cache.delete(key) # consumed — cannot be replayed + return session_id diff --git a/src/backend/core/addons/views.py b/src/backend/core/addons/views.py index 8417ef94..ea26f691 100644 --- a/src/backend/core/addons/views.py +++ b/src/backend/core/addons/views.py @@ -18,13 +18,18 @@ def render_error(request, message, status=400): def transit_page(request): """Initialize authentication flow for add-on session.""" - session_id = request.GET.get("session_id") + transit_token = request.GET.get("transit_token") + + if not transit_token: + return render_error(request, _("Transit token is required."), status=400) + + session_id = TokenExchangeService().consume_transit_token(transit_token) if not session_id: - return render_error(request, _("Session ID is required."), status=400) + return render_error(request, _("Invalid or expired transit token."), status=404) + # Validate the session is still pending data = TokenExchangeService().get_session(session_id) - if not data: return render_error(request, _("Session not found or expired."), status=404) diff --git a/src/backend/core/addons/viewsets.py b/src/backend/core/addons/viewsets.py index 467702a9..4da24b75 100644 --- a/src/backend/core/addons/viewsets.py +++ b/src/backend/core/addons/viewsets.py @@ -30,9 +30,9 @@ class AuthSessionViewSet(viewsets.ViewSet): Returns session_id in the body (client forwards it to the 3rd-party view). Sets result_token as an HttpOnly cookie (the only poll credential). """ - session_id, result_token = TokenExchangeService().init_session() + session_id, result_token, transit_token = TokenExchangeService().init_session() response = drf_response.Response( - {"session_id": session_id}, status=drf_status.HTTP_201_CREATED + {"session_id": session_id, "transit_token": transit_token}, status=drf_status.HTTP_201_CREATED ) response.set_cookie( key=settings.ADDONS_RESULT_TOKEN_COOKIE_NAME,