n8n:node-add-oauth

Par n8n-io · n8n

Ajouter la prise en charge des identifiants OAuth2 à un nœud n8n existant — crée le fichier de credential, met à jour le nœud, ajoute les tests et maintient la constante CLI synchronisée. À utiliser quand l'utilisateur saisit /node-add-oauth.

npx skills add https://github.com/n8n-io/n8n --skill n8n:node-add-oauth

Vue d'ensemble

Ajouter le support OAuth2 (Authorization Code / 3LO) à un nœud n8n existant. Fonctionne pour tout service tiers qui supporte OAuth2 standard.

Avant de commencer, lisez les fichiers de credentials OAuth2 existants et les tests comparables sous packages/nodes-base/credentials/ pour comprendre les conventions utilisées dans cette base de code (par exemple DiscordOAuth2Api.credentials.ts, MicrosoftTeamsOAuth2Api.credentials.ts).


Étape 0 — Parser les arguments

Extraire :

  • NODE_NAME : le nom du service (par exemple GitHub, Notion). Essayez de l'inférer à partir de l'argument ; en cas d'ambiguïté, demandez à l'utilisateur.
  • CUSTOM_SCOPES : si le credential doit supporter les scopes définis par l'utilisateur. Si l'argument ne le précise pas, demandez à l'utilisateur avant de continuer :

    "Les utilisateurs doivent-ils pouvoir personnaliser les scopes OAuth2 pour ce credential, ou les scopes doivent-ils être fixes ?"


Étape 1 — Explorer le nœud

Lisez les fichiers suivants (ajustez les conventions de chemin pour le service spécifique) :

  1. Répertoire du nœud : packages/nodes-base/nodes/{NODE_NAME}/
    • Trouvez *.node.ts (nœud principal) et tout *Trigger.node.ts
    • Trouvez GenericFunctions.ts (peut avoir un nom différent)
    • Vérifiez si un sous-répertoire auth / version existe
  2. Credentials existants : packages/nodes-base/credentials/ — cherchez les fichiers existants {NODE_NAME}*Api.credentials.ts pour comprendre la convention de nommage et toute méthode d'authentification déjà utilisée.
  3. package.json à packages/nodes-base/package.json — trouvez où les credentials existants pour ce nœud sont enregistrés (grep pour le nom du nœud).

Étape 2 — Rechercher les endpoints OAuth2

Consultez la documentation OAuth2 du service :

  • URL d'autorisation
  • URL du token d'accès
  • Paramètres de requête d'authentification requis (par exemple prompt=consent, access_type=offline)
  • Scopes par défaut nécessaires pour les opérations existantes du nœud
  • Si l'API nécessite une recherche de cloudId / workspace ID après l'échange de token (les APIs gateway de style Atlassian le font ; la plupart des services non)

Si vous ne pouvez pas déterminer les endpoints avec certitude, demandez à l'utilisateur de les fournir.


Étape 3 — Créer le fichier credential

Fichier : packages/nodes-base/credentials/{NODE_NAME}OAuth2Api.credentials.ts

import type { ICredentialType, INodeProperties } from 'n8n-workflow';

const defaultScopes = [/* minimum scopes for existing node operations */];

export class {NODE_NAME}OAuth2Api implements ICredentialType {
    name = '{camelCase}OAuth2Api';
    extends = ['oAuth2Api'];
    displayName = '{Display Name} OAuth2 API';
    documentationUrl = '{doc-slug}'; // matches docs.n8n.io/integrations/...

    properties: INodeProperties[] = [
        // Include service-specific fields the node needs to construct API calls
        // (e.g. domain, workspace URL) — add BEFORE the hidden fields below.

        { displayName: 'Grant Type',        name: 'grantType',      type: 'hidden', default: 'authorizationCode' },
        { displayName: 'Authorization URL', name: 'authUrl',        type: 'hidden', default: '{AUTH_URL}', required: true },
        { displayName: 'Access Token URL',  name: 'accessTokenUrl', type: 'hidden', default: '{TOKEN_URL}', required: true },
        // Only include authQueryParameters if the service requires extra query params:
        { displayName: 'Auth URI Query Parameters', name: 'authQueryParameters', type: 'hidden', default: '{QUERY_PARAMS}' },
        { displayName: 'Authentication',    name: 'authentication', type: 'hidden', default: 'header' },

        // ── Custom scopes block (ONLY when CUSTOM_SCOPES = yes) ──────────────
        {
            displayName: 'Custom Scopes',
            name: 'customScopes',
            type: 'boolean',
            default: false,
            description: 'Define custom scopes',
        },
        {
            displayName:
                'The default scopes needed for the node to work are already set. If you change these the node may not function correctly.',
            name: 'customScopesNotice',
            type: 'notice',
            default: '',
            displayOptions: { show: { customScopes: [true] } },
        },
        {
            displayName: 'Enabled Scopes',
            name: 'enabledScopes',
            type: 'string',
            displayOptions: { show: { customScopes: [true] } },
            default: defaultScopes.join(' '),
            description: 'Scopes that should be enabled',
        },
        // ── End custom scopes block ───────────────────────────────────────────

        {
            displayName: 'Scope',
            name: 'scope',
            type: 'hidden',
            // Custom scopes: expression toggles between user value and defaults.
            // Fixed scopes: use the literal defaultScopes string instead.
            default:
                '={{$self["customScopes"] ? $self["enabledScopes"] : "' + defaultScopes.join(' ') + '"}}',
        },
    ];
}

Règles :

  • Pas de bloc authenticate — la machinerie oAuth2Api gère l'injection automatique du token Bearer.
  • Pas de bloc test — la danse OAuth valide le credential.
  • defaultScopes au niveau du module est la source unique de vérité : elle remplit à la fois la valeur par défaut enabledScopes et le fallback d'expression scope. Mettez-la à jour en un seul endroit.
  • Si le service a besoin d'un domaine / URL de workspace pour la construction des appels API, ajoutez-le comme champ string visible avant les champs cachés.

Étape 4 — Enregistrer le credential dans package.json

Fichier : packages/nodes-base/package.json

Trouvez le tableau n8n.credentials et insérez la nouvelle entrée près d'autres credentials pour ce service (ordre alphabétique au sein du bloc du service) :

"dist/credentials/{NODE_NAME}OAuth2Api.credentials.js",

Étape 5 — Mettre à jour GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE (custom scopes uniquement)

Faites cette étape uniquement quand CUSTOM_SCOPES = yes.

Fichier : packages/cli/src/constants.ts

Ajoutez '{camelCase}OAuth2Api' au tableau GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE. Sans cela, n8n supprime le scope personnalisé de l'utilisateur lors de la reconnexion OAuth2.

export const GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE = [
    'oAuth2Api',
    'googleOAuth2Api',
    'microsoftOAuth2Api',
    'highLevelOAuth2Api',
    'mcpOAuth2Api',
    '{camelCase}OAuth2Api', // ← add this
];

Étape 6 — Mettre à jour GenericFunctions.ts

6a — Services standards (le token fonctionne directement contre l'URL d'instance)

Ajoutez une branche else if avant le fallback else existant :

} else if ({versionParam} === '{camelCase}OAuth2') {
    domain = (await this.getCredentials('{camelCase}OAuth2Api')).{domainField} as string;
    credentialType = '{camelCase}OAuth2Api';
} else {

6b — Services gateway nécessitant une recherche de workspace/cloud ID

Quand le token OAuth est scopé pour une URL gateway plutôt que l'URL d'instance directe (l'exemple canonique est api.atlassian.com d'Atlassian), ajoutez un cache au niveau du module et une fonction d'aide de lookup avant la fonction de requête principale :

// Module-level cache: normalised domain → site/cloud ID
export const _cloudIdCache = new Map<string, string>();

async function getSiteId(
    this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
    credentialType: string,
    domain: string,
): Promise<string> {
    const normalizedDomain = domain.replace(/\/$/, '');
    if (_cloudIdCache.has(normalizedDomain)) return _cloudIdCache.get(normalizedDomain)!;

    const resources = (await this.helpers.requestWithAuthentication.call(this, credentialType, {
        uri: '{ACCESSIBLE_RESOURCES_ENDPOINT}',
        json: true,
    })) as Array<{ id: string; url: string }>;

    const site = resources.find((r) => r.url === normalizedDomain);
    if (!site) {
        throw new NodeOperationError(
            this.getNode(),
            `No accessible site found for domain: ${domain}. Make sure the domain matches your site URL exactly.`,
        );
    }

    _cloudIdCache.set(normalizedDomain, site.id);
    return site.id;
}

Ensuite dans la fonction de requête principale :

} else if ({versionParam} === '{camelCase}OAuth2') {
    const rawDomain = (await this.getCredentials('{camelCase}OAuth2Api')).domain as string;
    credentialType = '{camelCase}OAuth2Api';
    const siteId = await getSiteId.call(this, credentialType, rawDomain);
    domain = `{GATEWAY_BASE_URL}/${siteId}`;
} else {

La construction existante uri: \${domain}/rest${endpoint}`` produit alors automatiquement l'URL gateway correcte.

Ajoutez NodeOperationError à l'import n8n-workflow s'il n'est pas déjà présent.


Étape 7 — Mettre à jour le(s) fichier(s) du nœud

Nœud principal (*.node.ts)

Tableau credentials — ajoutez une entrée pour le nouveau type de credential :

{
    name: '{camelCase}OAuth2Api',
    required: true,
    displayOptions: { show: { {versionParam}: ['{camelCase}OAuth2'] } },
},

Options de version/authentification — ajoutez à la liste d'options {versionParam} (ou équivalent) :

{ name: '{Display Name} (OAuth2)', value: '{camelCase}OAuth2' },

Gardez default inchangé — les workflows existants ne doivent pas être affectés.

Nœud trigger (*Trigger.node.ts, si présent)

Mêmes deux changements. Préservez tout motif d'étiquette displayName déjà utilisé par d'autres entrées de credentials dans le tableau credentials de ce nœud trigger.


Étape 8 — Écrire les tests du credential

Fichier : packages/nodes-base/credentials/test/{NODE_NAME}OAuth2Api.credentials.test.ts

Utilisez ClientOAuth2 de @n8n/client-oauth2 et nock pour le mocking HTTP. Suivez la structure dans MicrosoftTeamsOAuth2Api.credentials.test.ts.

Cas de test requis :

  1. Métadonnées — nom, tableau extends, défaut enabledScopes, URL d'auth, URL de token, défaut authQueryParameters (si applicable).
  2. Scopes par défaut dans l'URI d'autorisation — appelez oauthClient.code.getUri(), affirmez que chaque scope par défaut est présent.
  3. Récupération du token avec scopes par défaut — mockez l'endpoint du token avec nock, appelez oauthClient.code.getToken(...), affirmez que token.data.scope contient chaque scope.
  4. Scopes personnalisés dans l'URI d'autorisation _(omettez quand CUSTOMSCOPES = no).
  5. Récupération du token avec scopes personnalisés _(omettez quand CUSTOMSCOPES = no).
  6. Ensemble de scopes minimal / différent _(omettez quand CUSTOMSCOPES = no) — affirmez que les scopes non présents dans l'ensemble sont absents à la fois de l'URI et de la réponse du token.

Hooks de cycle de vie requis :

beforeAll(() => { nock.disableNetConnect(); });
afterAll(() => { nock.restore(); });
afterEach(() => { nock.cleanAll(); });

Étape 9 — Mettre à jour GenericFunctions.test.ts

Dans le bloc describe du routage des credentials :

  1. Si un cache de site ID (_cloudIdCache) a été ajouté, importez-le et appelez _cloudIdCache.clear() (ou équivalent) dans afterEach.
  2. Ajoutez/mettez à jour le cas de test du routage OAuth2 :
    • Routage simple : affirmez que getCredentials a été appelé avec le nom de credential correct et requestWithAuthentication a été appelé avec le nom correct et l'URI.
    • Lookup gateway : mockez requestWithAuthentication pour retourner la charge utile accessible-resources au premier appel et {} au deuxième. Affirmez que le premier appel cible l'endpoint des ressources et le deuxième appel utilise l'URL gateway base avec le site ID.

Étape 10 — Vérifier

# From packages/nodes-base/
pnpm test credentials/test/{NODE_NAME}OAuth2Api.credentials.test.ts
pnpm test nodes/{NODE_NAME}/__test__/GenericFunctions.test.ts
pnpm typecheck
pnpm lint

# Only when constants.ts was changed:
pushd ../cli && pnpm typecheck && popd

Corrigez les erreurs de type avant de terminer. Ne sautez jamais pnpm typecheck.

Skills similaires