feat: connecteurs Google + Microsoft 365 (OAuth2 PKCE) — phase 6/8 (v7.56.0)
FlowDeck CI / lint (push) Successful in 2m14s
FlowDeck CI / test (push) Failing after 16m4s
FlowDeck CI / docker (push) Skipped

- app/services/oauth_connectors.py : flow OAuth2 complet PKCE (S256) pour
  2 fournisseurs décrits par 1 dict — Google (Drive/Gmail/Calendar en lecture
  seule) et Microsoft 365 (Graph Files.Read / Mail.Read / Calendars.Read) ;
  begin() = URL d'autorisation + state + code_verifier, complete() = échange du
  code, access_token() = refresh automatique (60 s de marge, refresh_token
  conservé si absent de la réponse), api_get() = path absolu refusé + validation
  SSRF + borne 20 000 car.
- Tokens chiffrés Fernet en réutilisant calendar_sync._encrypt_tokens (zéro
  dépendance) dans la table connector_tokens (migration 33, PK (kind, user_id)).
- 4 routes /api/agent/connectors/oauth/{kind}/… : status, authorize (cookies
  d'état HttpOnly 10 min, retour same-origin validé), callback (GET safe, state
  comparé en temps constant, tokens stockés puis cookies purgés, redirection
  ?oauth=connected / ?oauth_error=), disconnect. OpenAPI 523 chemins.
- Config + .env.example : GOOGLE_CLIENT_ID/SECRET, MS_CLIENT_ID/SECRET (vidés =
  « non configuré »), redirect URI dérivé d'APP_BASE_URL.
- Catalogue : google/ms365 en natifs avec badge connecté/non connecté ;
  connector_fetch et Tester passent par l'API du fournisseur avec le token de
  l'utilisateur (user_id transmis par l'outil LLM).
- Menu + : « Se connecter » / « Déconnecter » sur la fiche, toast au retour du
  flux (URL nettoyée par history.replaceState). État dans la fiche du menu
  plutôt qu'une page dédiée.
- Tests : tests/test_v756_oauth_connectors.py (13), 0 appel réseau réel
  (_post_form / _api_get monkeypatchés) — state forgé refusé sans échange,
  tokens chiffrés en base, refresh, URL absolue refusée, 401/404, câblage menu ;
  test_v755 adapté (5 natifs). Suite complète 1319 verts (-n auto), ruff 0,
  eslint 0 erreur (19 warnings préexistants hors fichiers touchés).
This commit is contained in:
2026-10-07 08:29:22 -04:00
parent 18c72d77fe
commit 1d7750bda0
16 changed files with 983 additions and 33 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
# V74 — Menu + de l'assistant : hub de contexte (design)
> **Statut** : design — **phases 1 à 5 livrées (v7.51.0 → v7.55.0, 2026-10-06)** ;
> phases 6-8 restent à livrer. La **mémoire** a été simplifiée par rapport au
> **Statut** : design — **phases 1 à 6 livrées (v7.51.0 → v7.56.0, 2026-10-06)** ;
> phases 7-8 restent à livrer. La **mémoire** a été simplifiée par rapport au
> modèle ici : une seule ligne résumé **par conversation** (pas de lignes
> `workspace_id NULL` — rien ne les écrivait), extraction déterministe sans LLM. Adaptations faites en cours de route : le parcours
> utilise `GET /api/nav/menu?parent_id=` (un niveau par appel, contrat
+195 -2
View File
@@ -2,7 +2,7 @@
"openapi": "3.1.0",
"info": {
"title": "FlowDeck",
"version": "7.55.0"
"version": "7.56.0"
},
"paths": {
"/auth/register": {
@@ -16246,7 +16246,7 @@
"agent"
],
"summary": "List Connectors Route",
"description": "Catalogue : 3 natifs (statut dérivé de la config) + personnels.",
"description": "Catalogue : natifs (statut dérivé de la config) + personnels.",
"operationId": "list_connectors_route_api_agent_connectors_get",
"responses": {
"200": {
@@ -16429,6 +16429,199 @@
}
}
},
"/api/agent/connectors/oauth/{kind}/status": {
"get": {
"tags": [
"agent"
],
"summary": "Connector Oauth Status",
"description": "État du connecteur OAuth pour l'utilisateur courant.",
"operationId": "connector_oauth_status_api_agent_connectors_oauth__kind__status_get",
"parameters": [
{
"name": "kind",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Kind"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/agent/connectors/oauth/{kind}/authorize": {
"post": {
"tags": [
"agent"
],
"summary": "Connector Oauth Authorize",
"description": "URL d'autorisation (PKCE) + cookies d'état éphémères (10 min).",
"operationId": "connector_oauth_authorize_api_agent_connectors_oauth__kind__authorize_post",
"parameters": [
{
"name": "kind",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Kind"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/agent/connectors/oauth/{kind}/callback": {
"get": {
"tags": [
"agent"
],
"summary": "Connector Oauth Callback",
"description": "Reçoit le code du fournisseur, échange, stocke, renvoie sur la page.",
"operationId": "connector_oauth_callback_api_agent_connectors_oauth__kind__callback_get",
"parameters": [
{
"name": "kind",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Kind"
}
},
{
"name": "code",
"in": "query",
"required": false,
"schema": {
"type": "string",
"default": "",
"title": "Code"
}
},
{
"name": "state",
"in": "query",
"required": false,
"schema": {
"type": "string",
"default": "",
"title": "State"
}
},
{
"name": "error",
"in": "query",
"required": false,
"schema": {
"type": "string",
"default": "",
"title": "Error"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/agent/connectors/oauth/{kind}/disconnect": {
"post": {
"tags": [
"agent"
],
"summary": "Connector Oauth Disconnect",
"operationId": "connector_oauth_disconnect_api_agent_connectors_oauth__kind__disconnect_post",
"parameters": [
{
"name": "kind",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Kind"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/api/agent/mentions": {
"get": {
"tags": [