mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 16:10:30 +02:00
feat(Slack Node): Allow users to configure OAuth2 scopes (#28728)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7722023abd
commit
aa0daf9fb6
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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(' ')}"}}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user