Gestion des identifiants saisis comme objet d'identification ou directement dans le volet de configuration du nœud, avec la possibilité de fournir un mot de passe ou un jeton

Donc, je travaille sur un nœud personnalisé avec beaucoup d’options de configuration. Pas terriblement complexe, juste chargé. L’une des choses sur laquelle je me casse la tête est la gestion des identifiants – essentiellement quatre options différentes dans deux ensembles différents.

Le premier choix que les utilisateurs ont est de savoir si les identifiants sont « codés en dur » en tant qu’objet credential (basé sur une définition .credentials.ts) ou fournis dynamiquement dans les propriétés du nœud (définis dans le fichier .node.ts), pour les cas où un seul pipeline pourrait être utilisé dans un environnement multi-site (permettant aux identifiants d’être transmis dynamiquement à partir d’une source sécurisée ; le « d’où » dépasse ma sphère d’influence, donc cela semblait être l’approche la plus rationnelle).

Le deuxième choix est de savoir s’ils fournissent un jeton API directement ou s’ils fournissent un mot de passe utilisé pour récupérer un jeton. Si l’utilisateur fournit un jeton, il est simplement encodé et mis dans un en-tête Auth pour n’importe quelle requête – pas de problème. Mais s’il fournit un mot de passe, le nœud doit d’abord effectuer une requête « spéciale », avec un en-tête Auth personnalisé contenant une chaîne encodée en base64 construite à partir du nom d’utilisateur et du mot de passe, et le jeton est renvoyé par cela.

Après avoir progressé dans l’implémentation de ceci, j’ai réalisé que je devais gérer une grande partie de cela manuellement, donc le fichier .credentials.ts collecte simplement des données (si l’utilisateur emprunte cette voie) – tout comme les entrées du nœud. J’ai une fonction apiRequest() définie et exportée à partir d’un fichier séparé qui gère les requêtes API elles-mêmes, et une fonction getApiToken() dans ce même fichier (non exportée – elle n’est utilisée que par apiRequest() (pour le moment) qui gère spécifiquement la requête pour le jeton.

Les utilisateurs peuvent choisir l’une ou l’autre pour l’une ou l’autre. Quoi qu’il en soit, les entrées sont les mêmes : nom d’utilisateur, URL du site et mot de passe ou jeton API. J’avais déjà tout fait fonctionner sans l’option d’utiliser un mot de passe, mais j’ai entrepris cette quête aujourd’hui, et je l’ai fait fonctionner avec l’option identifiants dynamiques, mais cela semble avoir rompu la capacité à extraire les identifiants de la configuration basée sur .credentials.ts.

J’ai réduit le problème à un bloc de code dans le fichier .node.ts, qui n’est qu’une série d’assignations conditionnelles.

D’abord, il déclare une variable – credentials – et essaie d’utiliser getCredentials() pour les extraire de la configuration .credentials.ts. S’ils ne sont pas présents, il y a un catch d’erreur qui met simplement credentials par défaut à undefined. Immédiatement après, une constante est déclarée – dynamicConnection – qui utilise getNodeParameter() pour obtenir les entrées à partir de l’entrée des propriétés du nœud (chaque propriété est type-safe, spécifiée comme une string) – donc s’il n’y a rien dedans, ils sont juste null. Ensuite, un objet connection est créé où chacune des quatre propriétés est assignée en fonction des vérifications conditionnelles des propriétés des objets credentials et dynamicConnection. Pour chacune, nous vérifions si l’objet credentials.<prop> contient une chaîne ; si c’est le cas, elle est assignée, et sinon nous faisons la même chose pour dynamicConnection. Si aucune ne contient une valeur de chaîne, elle prend par défaut la valeur undefined. Finalement, une condition if() vérifie que nous avons (a) un nom d’utilisateur, (b) une URL d’instance, et (c) soit un mot de passe, soit un jeton ; si l’une de ces conditions n’est pas remplie, une erreur est levée. Cette condition if() se lit comme « si PAS nom d’utilisateur OU PAS URL du site OU (PAS jeton API ET PAS mot de passe) : lever une erreur ».

Cela fonctionne bien pour l’entrée dynamique d’identifiants, mais si j’essaie de faire cela avec des identifiants enregistrés, ils renvoient l’erreur du bloc if(), comme s’il manquait quelque chose, même si je sais que tous les champs sont là. De manière critique – cela fonctionnait bien avant d’ajouter la propriété password à connection, donc je ne pense vraiment pas que c’est un problème d’assignation ou je m’attendrais à ce qu’il ait échoué avant d’implémenter le champ password.

J’ai l’impression que je manque quelque chose d’absolument bête, mais j’espère que quelqu’un ici a plus de perspicacité que moi sur ma propre compréhension. Ou que vous pouvez au moins confirmer que je ne suis pas fou ? S’il vous plaît et merci !

@robby.emslie tu n’es pas fou, voici mon hypothèse : ajouter l’option password a ajouté un appel getNodeParameter (ou un auth-selector displayOptions qui masque un champ), et getNodeParameter lance « Could not get parameter » quand il ne peut pas résoudre la prop, ce qui est le cas sur le chemin saved-cred. si ce throw aboutit dans le même try/catch que tu utilises pour annuler les credentials, tout l’objet saved-cred se fait effacer et ton if() se déclenche comme missing. donne à chaque getNodeParameter une valeur par défaut (this.getNodeParameter(‘password’, i, ‘’)) pour qu’il ne puisse pas lancer, et garde le try limité à getCredentials seulement.

aujoute, pour l’échange password to token sur lequel tu rolls à la main getApiToken(), les creds n8n ont un hook preAuthentication intégré qui exécute une pre-request, récupère le token et le met en cache, puis authenticate l’injecte. MetabaseApi et Auth0ManagementApi creds font exactement username/password to token, ça vaut la peine de copier ce pattern pour la route saved-cred.

1 « J'aime »

@robby.emslie cela ressemble à un décalage entre la manière dont les champs sont assignés et celle dont ils sont validés.

Le moyen le plus rapide d’identifier le responsable — enregistrez les objets résolus juste avant le if(), sur le chemin des identifiants sauvegardés :

console.log(JSON.stringify({ credentials, dynamicConnection, connection }, null, 2));

Deux choses sur lesquelles je parierais :

  1. Chaîne vide vs undefined. Via getCredentials(), un champ inutilisé (comme password sur la route token) revient souvent sous la forme “” plutôt que undefined. “” passe un contrôle d’assignation “typeof === ‘string’” mais est falsy dans le if() — donc le validateur lève “missing” même si le champ existe techniquement. Mettez les deux d’accord, par exemple :
    const val = (typeof x === ‘string’ && x.trim() !== ‘’) ? x : undefined;

  2. La précédence/le regroupement de ce if() final. !user || !url || !token && !password s’évalue comme !user || !url || (!token && !password) — ce qui est ce que vous voulez. Mais si le code réel est ... || !token || !password, cela lève une exception chaque fois que password est vide même lorsqu’un token valide est présent. Ce seul échange OR/AND correspond exactement à votre symptôme « ça fonctionnait avant que j’ajoute la propriété password » — avant, il n’y avait pas de terme password du tout dans la condition.

Si vous collez le bloc .node.ts (les quatre assignations + le if), je peux l’identifier précisément.

1 « J'aime »

Les deux réponses ci-dessus couvrent le bug probable. J’ajouterais une garde-fou après : faire en sorte que les identifiants sauvegardés et l’entrée en ligne se résolvent tous les deux dans le même objet de connexion normalisé, avec les chaînes vides supprimées, le mode d’authentification enregistré et aucun secret brut consigné. Ensuite, validez uniquement cet objet. Cela facilite le débogage sans divulguer les tokens.

3 « J'aime »

Quelque chose a vraiment dérapé ici – haha ! Je vais essentiellement devoir tout reconstruire, cet après-midi, je crois. J’ai merdé quelque part. Mais je suis curieux au sujet du hook intégré – je vais jeter un coup d’œil à Metabase et Auth0Management pour un modèle. J’ai essayé d’utiliser ça, à l’origine, et je pense que j’étais presque arrivé à le faire fonctionner, mais ensuite quand j’ai intégré l’option de credentialing dynamique, j’ai cassé quelque chose.

C’est vraiment super utile, cependant. Merci, beaucoup. Je pense que le problème se produit quelque part avec getCredentials(), mais j’ai l’impression qu’à ce stade, ça va être plus de travail de savoir ce qui ne va pas que de rebaser mon repo à partir de ma dernière configuration fonctionnelle.

Merci, mon ami !

Je voulais revenir sur cette question et vous remercier. La suggestion de regarder Metabase et Auth0Management a été une vraie bouée de sauvetage.

Je pense que ça résout le problème que j’avais, mais je vais en fait supprimer l’authentification par mot de passe de dynamicCredentials, je crois. J’ai l’impression que c’est une faille de sécurité qui ne demande qu’à arriver. Ça marche déjà, mais je vais la commenter et si quelqu’un du côté récepteur veut l’activer, ce sera de sa responsabilité. :slightly_smiling_face: lol

De toute façon, merci encore, @achamm !

1 « J'aime »

@robby.emslie Ravi d’avoir pu vous aider ! N’hésitez pas à marquer l’une des réponses comme solution, et bonne chance !