mcpField
N
node-add-oauth

node-add-oauth

github.com/n8n-io开发208.9k59 次安装今天更新
代码仓库

关于 node-add-oauth

用于为现有 n8n 节点添加标准 OAuth2 授权码支持的工作流技能,涵盖端点调研、凭据配置、请求路由、测试与验证。

主要功能

  1. 01检查节点与凭据惯例
  2. 02调研 OAuth2 端点和作用域
  3. 03创建并注册凭据定义
  4. 04更新节点与认证路由
  5. 05添加凭据和路由测试
  6. 06运行类型、Lint 与测试检查

适用场景

  1. 01为 n8n 集成添加 OAuth2 认证
  2. 02支持凭据的自定义作用域
  3. 03实现基于网关的 OAuth 路由
  4. 04通过测试验证 OAuth 凭据行为

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/node-add-oauth确认已加载。

详细介绍

概述

为现有的 n8n 节点添加 OAuth2(授权码 / 3LO)支持。适用于任何支持标准 OAuth2 的第三方服务。

开始之前,请阅读 packages/nodes-base/credentials/ 下现有的类似 OAuth2 凭据文件及其测试,了解此代码库中使用的约定 (例如 DiscordOAuth2Api.credentials.ts、MicrosoftTeamsOAuth2Api.credentials.ts)。


步骤 0 — 解析参数

提取:

  • NODE_NAME:服务名称(例如 GitHub、Notion)。尝试根据参数推断; 如果不明确,请询问用户。
  • CUSTOM_SCOPES:凭据是否应支持用户自定义作用域。如果参数未明确说明这一点,请在继续之前询问用户:

    "用户是否应能自定义此凭据的 OAuth2 作用域,还是应固定作用域?"


步骤 1 — 探索节点

阅读以下内容(根据具体服务调整路径约定):

  1. 节点目录:packages/nodes-base/nodes/{NODE_NAME}/
    • 查找 *.node.ts(主节点)及任何 *Trigger.node.ts
    • 查找 GenericFunctions.ts(名称可能有所不同)
    • 检查是否存在 auth / version 子目录
  2. 现有凭据:packages/nodes-base/credentials/ — 查找现有的 {NODE_NAME}*Api.credentials.ts 文件,了解命名约定以及已使用的认证方式。
  3. packages/nodes-base/package.json 中的 package.json — 查找此节点的现有凭据注册位置(搜索节点名称)。

步骤 2 — 调研 OAuth2 端点

查阅服务的 OAuth2 文档:

  • 授权 URL
  • 访问令牌 URL
  • 必需的认证查询参数(例如 prompt=consent、access_type=offline)
  • 节点现有操作所需的默认作用域
  • API 是否需要在令牌交换后查询 cloudId / workspace ID (Atlassian 风格的网关 API 需要;大多数服务不需要)

如果无法有把握地确定端点,请让用户提供。


步骤 3 — 创建凭据文件

文件: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(' ') + '"}}',
		},
	];
}

规则:

  • 不要添加 authenticate 块 — oAuth2Api 机制会自动注入 Bearer 令牌。
  • 不要添加 test 块 — OAuth 流程会验证凭据。
  • 模块级的 defaultScopes 是唯一事实来源:它会同时填充 enabledScopes 默认值和 scope 表达式回退值。只需在一个位置更新。
  • 如果服务需要域名 / workspace URL 来构造 API 调用,请在隐藏字段之前添加一个可见的 string 字段。

步骤 4 — 在 package.json 中注册凭据

文件:packages/nodes-base/package.json

找到 n8n.credentials 数组,并在此服务的其他凭据附近插入新条目(在该服务的区块内按字母顺序排列):

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

步骤 5 — 更新 GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE(仅限自定义作用域)

仅当 CUSTOM_SCOPES = yes 时执行此步骤。

文件:packages/cli/src/constants.ts

将 '{camelCase}OAuth2Api' 添加到 GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE 数组。否则,n8n 会在 OAuth2 重新连接时删除用户的自定义作用域。

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

步骤 6 — 更新 GenericFunctions.ts

6a — 标准服务(令牌可直接用于实例 URL)

在现有的 else 回退分支之前添加一个 else if 分支:

} else if ({versionParam} === '{camelCase}OAuth2') {
	domain = (await this.getCredentials('{camelCase}OAuth2Api')).{domainField} as string;
	credentialType = '{camelCase}OAuth2Api';
} else {
6b — 需要查询 workspace / cloud ID 的网关服务

当 OAuth 令牌的作用域针对网关 URL 而非直接实例 URL 时(Atlassian 的 api.atlassian.com 是典型示例),请在主请求函数之前添加模块级缓存和查询辅助函数:

// 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;
}

然后在主请求函数中:

} 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 {

这样,现有的 uri: \${domain}/rest${endpoint}`` 构造会自动生成正确的网关 URL。

如果尚未从 n8n-workflow 导入,请添加 NodeOperationError。

步骤 7 — 更新节点文件

主节点(*.node.ts)

凭据数组 — 添加新凭据类型的条目:

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

版本/身份验证选项 — 添加到 {versionParam}(或同等内容)的选项列表中:

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

保持 default 不变——不得影响现有工作流。

触发器节点(如果存在,则为 *Trigger.node.ts)

进行相同的两项更改。保留该触发器节点凭据数组中其他凭据条目所使用的 displayName 标签模式。


步骤 8 — 编写凭据测试

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

使用 @n8n/client-oauth2 中的 ClientOAuth2 和 nock 模拟 HTTP 请求。遵循 MicrosoftTeamsOAuth2Api.credentials.test.ts 中的结构。

必需的测试用例:

  1. 元数据 — 名称、extends 数组、enabledScopes 默认值、身份验证 URL、令牌 URL、 authQueryParameters 默认值(如适用)。
  2. 授权 URI 中的默认作用域 — 调用 oauthClient.code.getUri(),断言每个默认作用域都存在。
  3. 使用默认作用域获取令牌 — 使用 nock 模拟令牌端点,调用 oauthClient.code.getToken(...),断言 token.data.scope 包含每个作用域。
  4. 授权 URI 中的自定义作用域(当 CUSTOM_SCOPES = no 时跳过)。
  5. 使用自定义作用域获取令牌(当 CUSTOM_SCOPES = no 时跳过)。
  6. 最少/不同的作用域集合(当 CUSTOM_SCOPES = no 时跳过)— 断言集合中不存在的作用域在 URI 和令牌响应中均不存在。

必需的生命周期钩子:

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

步骤 9 — 更新 GenericFunctions.test.ts

在凭据路由的 describe 块中:

  1. 如果添加了站点 ID 缓存(_cloudIdCache),导入它,并在 afterEach 中调用 _cloudIdCache.clear()(或等效方法)。
  2. 添加/更新 OAuth2 路由测试用例:
    • 简单路由:断言 getCredentials 使用正确的凭据名称调用,并且 requestWithAuthentication 使用正确的名称和 URI 调用。
    • 网关查找:模拟 requestWithAuthentication,使其第一次调用时返回可访问资源负载,第二次调用时返回 {}。断言第一次调用指向资源端点,第二次调用使用包含站点 ID 的网关基础 URL。

步骤 10 — 验证

# 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

完成前修复所有类型错误。绝不要跳过 pnpm typecheck。

评论

支持 Markdown