Upload de pièces jointes depuis un widget personnalisé bloqué par CORS

Bonjour,

Je travaille sur un widget personnalisé pour le suivi d’élèves à besoins éducatifs particuliers (EBEP) dans les écoles. Le widget est hébergé sur La Forge des communs numériques et intégré dans Grist (grist.numerique.gouv.fr) via la fonctionnalité de widget personnalisé.

Je voudrais permettre à l’utilisateur d’ajouter des pièces jointes (compte-rendus, documents de suivi…) directement depuis l’interface du widget, sans avoir à passer par la vue table.

J’utilise grist.docApi.getAccessToken({ readOnly: false }) pour récupérer un token et le baseUrl de l’API REST du document, puis je tente un fetch() en POST :

const { token, baseUrl } = await grist.docApi.getAccessToken({ readOnly: false }); const formData = new FormData(); formData.append("upload", file, file.name); const resp = await fetch(${baseUrl}/attachments?auth=${encodeURIComponent(token)}, { method: "POST", body: formData, });

  • La lecture des métadonnées des pièces jointes existantes (GET /attachments/:id) fonctionne.
  • Le téléchargement via window.open(url, "_blank", "noopener,noreferrer") fonctionne (pas de CORS car c’est une navigation, pas un fetch).

Le [fetch()] en POST est bloqué par le navigateur (erreur CORS). L’origine du widget (domaine de La Forge) n’est pas autorisée à faire des requêtes cross-origin vers le serveur Grist, même avec un token valide dans les paramètres de l’URL et des permissions Full document access accordées au widget.

Extraits des messages d’erreur de la console :
"POST https://grist.numerique.gouv.fr/o/docs/api/docs//attachments?auth=

Raison : l’en-tête CORS « Access-Control-Allow-Origin » est manquant.
Code d’état : 401.

TypeError: NetworkError when attempting to fetch resource."

  • Y a-t-il une méthode dans l’API plugin Grist côté widget (grist.docApi.*) qui permette d’uploader un fichier sans passer par un fetch cross-origin (c’est-à-dire en passant par le canal postMessage que Grist utilise déjà pour les autres appels API) ?
  • Est-ce que la configuration CORS de l’instance http://grist.numerique.gouv.fr peut être ajustée pour autoriser les origines déclarées dans les widgets personnalisés ?
  • Existe-t-il un contournement pour ce cas d’usage ?

Merci d’avance pour vos pistes !

Bonjour c’est déjà possible avec le code suivant Import et sauvegarde d'un fichier via un Custom Widget Builder / upload - charger PJ depuis custom widget - #8 par audezu

Cependant il y a un souci a priori obscur et complexe provoquant la ré-apparition de cette erreur CORS… @ohemelaar est-ce que tu pourras regarder stp, je viens de tester et le souci est revenu sur l’instance DINUM (ANCT ok)

doc test DINUM : https://grist.numerique.gouv.fr/o/docs/doc/6RmTTpquReku
doc test ANCT : Ex - afficher PJ / charger PJ / télécharger PJ - Grist

Merci beaucoup ! J’avais cherché dans le forum, mais pas assez bien :sweat_smile:

J’ai testé, mais ça ne semble effectivement pas fonctionner actuellement. Voici le diagnostic fait par Claude (Sonnet 4.6) d’après les logs de la console :

L’en-tête X-Requested-With: XMLHttpRequest est un en-tête non standard, ce qui déclenche automatiquement une requête de pré-vérification CORS (OPTIONS) avant le vrai POST. Sur l’instance ANCT, cette requête OPTIONS est bien traitée par Grist. Sur l’instance DINUM, elle est bloquée avant d’atteindre Grist par un proxy ou WAF intermédiaire, avec un code 403 — d’où l’échec.

OPTIONS .../attachments?auth=<token> → CORS Preflight Did Not Succeed — Code d'état : 403

Le problème n’est donc pas dans Grist lui-même, mais dans la configuration réseau de l’instance grist.numerique.gouv.fr. Il faudrait que l’infrastructure autorise les requêtes OPTIONS cross-origin provenant des origines déclarées dans les widgets personnalisés (ou que les en-têtes CORS correspondants soient renvoyés par le proxy).

Bonjour @audezu, bonjour @ohemelaar,

Auriez-vous une visibilité sur une éventuelle résolution de ce blocage CORS pour l’upload de pièces jointes depuis un widget personnalisé ?
Même à moyen terme, cela nous aiderait à anticiper.

Merci d’avance pour votre retour !

ping @hexaltation @Raywan

1 « J'aime »

Je crois que le correctif est juste de passer l’entête X-Requested-With comme indiqué dans cet issue :

Coucou c’est déjà fait (en tout cas dans mon cw de test) et ça fonctionnait momentanément, mais il y a un bug côté serveur qui fait que ca tombe régulièrement. Ça avait été résolu par @ohemelaar mais il avait prévenu que ça risquait de retomber, ce qui est arrivé, je n’ai pas les détails mais apparemment c’est compliqué à fix précisément et trouver la source

Je n’ai pas retesté ça re-fonctionne peut être (les liens des docs pour tester sont dans mon post précédent si besoin)

Bonjour,

je rencontrais exactement ce problème et j’ai développé une extension Chrome qui le résout de manière stable côté client, sans dépendre d’un fix serveur.

L’approche est différente : plutôt que de modifier les headers de la requête cross-origin, l’extension injecte un pont dans la page Grist elle-même. Le widget envoie le fichier via postMessage , et le pont fait l’upload depuis la même origine que Grist.
le CORS ne se pose donc plus.

Elle règle aussi le problème localStorage bloqué dans les iframes cross-origin.

Points importants

  • :white_check_mark: Multi-instances : fonctionne avec n’importe quel instance Grist
  • :white_check_mark: Multi-widgets : autant d’origines que vous voulez
  • :white_check_mark: Aucune URL en dur : l’extension n’agit que sur ce que vous configurez
  • :white_check_mark: Aucune donnée envoyée à un serveur tiers : tout est local
  • :white_check_mark: Code entièrement lisible dans le package

Grist Widget Bridge : disponible sur le Chrome Web Store (https://chromewebstore.google.com/detail/grist-widget-bridge/noidpkhmggkoefhfhenenalafekjbglm) se configure en 2 clics depuis l’icône de l’extension.

Bonjour, l’upload de PJ fonctionne désormais, @ohemelaar a fait un fix :slight_smile:

1 « J'aime »

OUIIII !!! J’ai vu ça ! Merci beaucoup :grin: :+1: