diff --git a/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts b/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts index 9c923fcb4d6ae..6315269e23274 100644 --- a/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts @@ -1,7 +1,7 @@ import type { ICredentialType, INodeProperties } from 'n8n-workflow'; //https://api.slack.com/authentication/oauth-v2 -const userScopes = [ +export const userScopes = [ 'channels:read', 'channels:write', 'channels:history', @@ -54,18 +54,49 @@ export class SlackOAuth2Api implements ICredentialType { type: 'hidden', default: 'https://slack.com/api/oauth.v2.access', }, - //https://api.slack.com/scopes { displayName: 'Scope', name: 'scope', type: 'hidden', - default: 'chat:write', + default: '', + }, + { + 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: 'User Scope', + name: 'userScope', + type: 'string', + displayOptions: { + show: { + customScopes: [true], + }, + }, + default: userScopes.join(' '), + description: 'Space-separated user-level scopes for your Slack app', + }, + //https://api.slack.com/scopes { displayName: 'Auth URI Query Parameters', name: 'authQueryParameters', type: 'hidden', - default: `user_scope=${userScopes.join(' ')}`, + default: `={{$self["customScopes"] ? "user_scope=" + $self["userScope"] : "user_scope=${userScopes.join(' ')}"}}`, }, { displayName: 'Authentication', diff --git a/packages/nodes-base/credentials/test/SlackOAuth2Api.credentials.test.ts b/packages/nodes-base/credentials/test/SlackOAuth2Api.credentials.test.ts new file mode 100644 index 0000000000000..0f9c8055de8dc --- /dev/null +++ b/packages/nodes-base/credentials/test/SlackOAuth2Api.credentials.test.ts @@ -0,0 +1,42 @@ +import { SlackOAuth2Api, userScopes } from '../SlackOAuth2Api.credentials'; + +describe('SlackOAuth2Api Credential', () => { + const credential = new SlackOAuth2Api(); + + it('should have correct credential metadata', () => { + expect(credential.name).toBe('slackOAuth2Api'); + expect(credential.extends).toEqual(['oAuth2Api']); + + const authUrlProperty = credential.properties.find((p) => p.name === 'authUrl'); + expect(authUrlProperty?.default).toBe('https://slack.com/oauth/v2/authorize'); + + const accessTokenUrlProperty = credential.properties.find((p) => p.name === 'accessTokenUrl'); + expect(accessTokenUrlProperty?.default).toBe('https://slack.com/api/oauth.v2.access'); + }); + + it('should not have a hardcoded bot scope field', () => { + const scopeProperty = credential.properties.find( + (p) => p.name === 'scope' && p.default === 'chat:write', + ); + expect(scopeProperty).toBeUndefined(); + }); + + it('should have custom scopes toggle defaulting to false', () => { + const customScopesProperty = credential.properties.find((p) => p.name === 'customScopes'); + expect(customScopesProperty?.default).toBe(false); + }); + + it('should have userScope defaulting to the full default scope list', () => { + const userScopeProperty = credential.properties.find((p) => p.name === 'userScope'); + expect(userScopeProperty?.default).toBe(userScopes.join(' ')); + }); + + it('should use userScope in authQueryParameters when customScopes is true, otherwise use defaults', () => { + const authQueryParamsProperty = credential.properties.find( + (p) => p.name === 'authQueryParameters', + ); + expect(authQueryParamsProperty?.default).toBe( + `={{$self["customScopes"] ? "user_scope=" + $self["userScope"] : "user_scope=${userScopes.join(' ')}"}}`, + ); + }); +}); diff --git a/packages/testing/playwright/tests/e2e/ai/assistant-credential-help.spec.ts b/packages/testing/playwright/tests/e2e/ai/assistant-credential-help.spec.ts index f064d3b57f548..f1d28c0b5a7e8 100644 --- a/packages/testing/playwright/tests/e2e/ai/assistant-credential-help.spec.ts +++ b/packages/testing/playwright/tests/e2e/ai/assistant-credential-help.spec.ts @@ -107,12 +107,12 @@ test.describe( // Default is managed OAuth (click to connect) — no assistant button await expect(n8n.canvas.credentialModal.oauthConnectButton).toHaveCount(1); - await expect(n8n.canvas.credentialModal.getCredentialInputs()).toHaveCount(2); + await expect(n8n.canvas.credentialModal.getCredentialInputs()).toHaveCount(3); await expect(n8n.aiAssistant.getCredentialEditAssistantButton()).toHaveCount(0); // Switch to custom OAuth via dropdown — assistant button should appear await n8n.canvas.credentialModal.selectAuthTypeFromDropdown('Custom OAuth2'); - await expect(n8n.canvas.credentialModal.getCredentialInputs()).toHaveCount(4); + await expect(n8n.canvas.credentialModal.getCredentialInputs()).toHaveCount(5); await expect(n8n.aiAssistant.getCredentialEditAssistantButton()).toHaveCount(1); });