diff --git a/cypress/e2e/18-user-management.cy.ts b/cypress/e2e/18-user-management.cy.ts index dcf32fd678..8c4e89f22c 100644 --- a/cypress/e2e/18-user-management.cy.ts +++ b/cypress/e2e/18-user-management.cy.ts @@ -63,13 +63,23 @@ describe('User Management', { disableAutoLogin: true }, () => { personalSettingsPage.actions.changeTheme('Dark'); cy.get('body').should('have.attr', 'data-theme', 'dark'); settingsSidebar.actions.back(); - mainSidebar.getters.logo().should('have.attr', 'src', '/n8n-dev-logo-dark-mode.svg'); + mainSidebar.getters + .logo() + .should('have.attr', 'src') + .then((src) => { + expect(src).to.include('/n8n-dev-logo-dark-mode.svg'); + }); cy.visit(personalSettingsPage.url); personalSettingsPage.actions.changeTheme('Light'); cy.get('body').should('have.attr', 'data-theme', 'light'); settingsSidebar.actions.back(); - mainSidebar.getters.logo().should('have.attr', 'src', '/n8n-dev-logo.svg'); + mainSidebar.getters + .logo() + .should('have.attr', 'src') + .then((src) => { + expect(src).to.include('/n8n-dev-logo.svg'); + }); }); it('should delete user and their data', () => { diff --git a/packages/@n8n/permissions/src/types.ts b/packages/@n8n/permissions/src/types.ts index 7d14f0ad94..ebd5015cb4 100644 --- a/packages/@n8n/permissions/src/types.ts +++ b/packages/@n8n/permissions/src/types.ts @@ -1,19 +1,21 @@ export type DefaultOperations = 'create' | 'read' | 'update' | 'delete' | 'list'; export type Resource = - | 'workflow' - | 'tag' - | 'user' + | 'auditLogs' + | 'communityPackage' | 'credential' - | 'variable' - | 'sourceControl' | 'externalSecretsProvider' | 'externalSecret' | 'eventBusEvent' | 'eventBusDestination' - | 'orchestration' - | 'communityPackage' | 'ldap' - | 'saml'; + | 'logStreaming' + | 'orchestration' + | 'sourceControl' + | 'saml' + | 'tag' + | 'user' + | 'variable' + | 'workflow'; export type ResourceScope< R extends Resource, @@ -22,45 +24,49 @@ export type ResourceScope< export type WildcardScope = `${Resource}:*` | '*'; -export type WorkflowScope = ResourceScope<'workflow', DefaultOperations | 'share'>; -export type TagScope = ResourceScope<'tag'>; -export type UserScope = ResourceScope<'user', DefaultOperations | 'resetPassword'>; +export type AuditLogsScope = ResourceScope<'auditLogs', 'manage'>; +export type CommunityPackageScope = ResourceScope< + 'communityPackage', + 'install' | 'uninstall' | 'update' | 'list' | 'manage' +>; export type CredentialScope = ResourceScope<'credential', DefaultOperations | 'share'>; -export type VariableScope = ResourceScope<'variable'>; -export type SourceControlScope = ResourceScope<'sourceControl', 'pull' | 'push' | 'manage'>; +export type ExternalSecretScope = ResourceScope<'externalSecret', 'list'>; export type ExternalSecretProviderScope = ResourceScope< 'externalSecretsProvider', DefaultOperations | 'sync' >; -export type ExternalSecretScope = ResourceScope<'externalSecret', 'list'>; -export type EventBusEventScope = ResourceScope<'eventBusEvent', DefaultOperations | 'query'>; export type EventBusDestinationScope = ResourceScope< 'eventBusDestination', DefaultOperations | 'test' >; -export type OrchestrationScope = ResourceScope<'orchestration', 'read' | 'list'>; -export type CommunityPackageScope = ResourceScope< - 'communityPackage', - 'install' | 'uninstall' | 'update' | 'list' ->; +export type EventBusEventScope = ResourceScope<'eventBusEvent', DefaultOperations | 'query'>; export type LdapScope = ResourceScope<'ldap', 'manage' | 'sync'>; +export type LogStreamingScope = ResourceScope<'logStreaming', 'manage'>; +export type OrchestrationScope = ResourceScope<'orchestration', 'read' | 'list'>; export type SamlScope = ResourceScope<'saml', 'manage'>; +export type SourceControlScope = ResourceScope<'sourceControl', 'pull' | 'push' | 'manage'>; +export type TagScope = ResourceScope<'tag'>; +export type UserScope = ResourceScope<'user', DefaultOperations | 'resetPassword'>; +export type VariableScope = ResourceScope<'variable'>; +export type WorkflowScope = ResourceScope<'workflow', DefaultOperations | 'share'>; export type Scope = - | WorkflowScope - | TagScope - | UserScope + | AuditLogsScope + | CommunityPackageScope | CredentialScope - | VariableScope - | SourceControlScope | ExternalSecretProviderScope | ExternalSecretScope | EventBusEventScope | EventBusDestinationScope - | OrchestrationScope - | CommunityPackageScope | LdapScope - | SamlScope; + | LogStreamingScope + | OrchestrationScope + | SamlScope + | SourceControlScope + | TagScope + | UserScope + | VariableScope + | WorkflowScope; export type ScopeLevel = 'global' | 'project' | 'resource'; export type GetScopeLevel = Record; diff --git a/packages/cli/src/permissions/roles.ts b/packages/cli/src/permissions/roles.ts index 4d80481848..b8f19349cb 100644 --- a/packages/cli/src/permissions/roles.ts +++ b/packages/cli/src/permissions/roles.ts @@ -1,53 +1,17 @@ import type { Scope } from '@n8n/permissions'; export const ownerPermissions: Scope[] = [ - 'workflow:create', - 'workflow:read', - 'workflow:update', - 'workflow:delete', - 'workflow:list', - 'workflow:share', - 'tag:create', - 'tag:read', - 'tag:update', - 'tag:delete', - 'tag:list', - 'user:create', - 'user:read', - 'user:update', - 'user:delete', - 'user:list', - 'user:resetPassword', + 'auditLogs:manage', 'credential:create', 'credential:read', 'credential:update', 'credential:delete', 'credential:list', 'credential:share', - 'variable:create', - 'variable:read', - 'variable:update', - 'variable:delete', - 'variable:list', - 'sourceControl:pull', - 'sourceControl:push', - 'sourceControl:manage', - 'externalSecretsProvider:create', - 'externalSecretsProvider:read', - 'externalSecretsProvider:update', - 'externalSecretsProvider:delete', - 'externalSecretsProvider:list', - 'externalSecretsProvider:sync', - 'externalSecret:list', - 'orchestration:read', - 'orchestration:list', 'communityPackage:install', 'communityPackage:uninstall', 'communityPackage:update', 'communityPackage:list', - 'ldap:manage', - 'ldap:sync', - 'saml:manage', 'eventBusEvent:create', 'eventBusEvent:read', 'eventBusEvent:update', @@ -61,18 +25,56 @@ export const ownerPermissions: Scope[] = [ 'eventBusDestination:delete', 'eventBusDestination:list', 'eventBusDestination:test', -]; -export const adminPermissions: Scope[] = ownerPermissions.concat(); -export const memberPermissions: Scope[] = [ - 'user:list', - 'variable:list', - 'variable:read', + 'externalSecretsProvider:create', + 'externalSecretsProvider:read', + 'externalSecretsProvider:update', + 'externalSecretsProvider:delete', + 'externalSecretsProvider:list', + 'externalSecretsProvider:sync', + 'externalSecret:list', + 'ldap:manage', + 'ldap:sync', + 'logStreaming:manage', + 'orchestration:read', + 'orchestration:list', + 'saml:manage', + 'sourceControl:pull', + 'sourceControl:push', + 'sourceControl:manage', 'tag:create', 'tag:read', 'tag:update', + 'tag:delete', 'tag:list', + 'user:create', + 'user:read', + 'user:update', + 'user:delete', + 'user:list', + 'user:resetPassword', + 'variable:create', + 'variable:read', + 'variable:update', + 'variable:delete', + 'variable:list', + 'workflow:create', + 'workflow:read', + 'workflow:update', + 'workflow:delete', + 'workflow:list', + 'workflow:share', +]; +export const adminPermissions: Scope[] = ownerPermissions.concat(); +export const memberPermissions: Scope[] = [ 'eventBusEvent:list', 'eventBusEvent:read', 'eventBusDestination:list', 'eventBusDestination:test', + 'tag:create', + 'tag:read', + 'tag:update', + 'tag:list', + 'user:list', + 'variable:list', + 'variable:read', ]; diff --git a/packages/editor-ui/src/components/MainSidebar.vue b/packages/editor-ui/src/components/MainSidebar.vue index 9da524a48e..1ba1996a8c 100644 --- a/packages/editor-ui/src/components/MainSidebar.vue +++ b/packages/editor-ui/src/components/MainSidebar.vue @@ -120,7 +120,6 @@ import { useWorkflowsStore } from '@/stores/workflows.store'; import { isNavigationFailure } from 'vue-router'; import ExecutionsUsage from '@/components/ExecutionsUsage.vue'; import MainSidebarSourceControl from '@/components/MainSidebarSourceControl.vue'; -import { ROLE } from '@/utils/userUtils'; import { hasPermission } from '@/rbac/permissions'; export default defineComponent({ @@ -177,9 +176,7 @@ export default defineComponent({ return accessibleRoute !== null; }, showUserArea(): boolean { - return hasPermission(['role'], { - role: [ROLE.Member, ROLE.Owner], - }); + return hasPermission(['authenticated']); }, workflowExecution(): IExecutionResponse | null { return this.workflowsStore.getWorkflowExecution; diff --git a/packages/editor-ui/src/rbac/__tests__/permissions.test.ts b/packages/editor-ui/src/rbac/__tests__/permissions.test.ts index 7a7ea3fa12..204ace46fa 100644 --- a/packages/editor-ui/src/rbac/__tests__/permissions.test.ts +++ b/packages/editor-ui/src/rbac/__tests__/permissions.test.ts @@ -5,6 +5,7 @@ vi.mock('@/rbac/checks', () => ({ hasRole: vi.fn(), hasScope: vi.fn(), isGuest: vi.fn(), + isDefaultUser: vi.fn(), isAuthenticated: vi.fn(), isEnterpriseFeatureEnabled: vi.fn(), isValid: vi.fn(), @@ -15,13 +16,22 @@ describe('hasPermission()', () => { vi.mocked(checks.hasRole).mockReturnValue(true); vi.mocked(checks.hasScope).mockReturnValue(true); vi.mocked(checks.isGuest).mockReturnValue(true); + vi.mocked(checks.isDefaultUser).mockReturnValue(true); vi.mocked(checks.isAuthenticated).mockReturnValue(true); vi.mocked(checks.isEnterpriseFeatureEnabled).mockReturnValue(true); vi.mocked(checks.isValid).mockReturnValue(true); - expect(hasPermission(['authenticated', 'custom', 'enterprise', 'guest', 'rbac', 'role'])).toBe( - true, - ); + expect( + hasPermission([ + 'authenticated', + 'custom', + 'enterprise', + 'guest', + 'rbac', + 'role', + 'defaultUser', + ]), + ).toBe(true); }); it('should return false if any permission is invalid', () => { diff --git a/packages/editor-ui/src/rbac/checks/__tests__/hasScope.test.ts b/packages/editor-ui/src/rbac/checks/__tests__/hasScope.test.ts index 80feaaaf96..bf33c75e56 100644 --- a/packages/editor-ui/src/rbac/checks/__tests__/hasScope.test.ts +++ b/packages/editor-ui/src/rbac/checks/__tests__/hasScope.test.ts @@ -1,6 +1,6 @@ import { useRBACStore } from '@/stores/rbac.store'; import { hasScope } from '@/rbac/checks/hasScope'; -import type { HasScopeOptions } from '@n8n/permissions'; +import type { ScopeOptions } from '@n8n/permissions'; vi.mock('@/stores/rbac.store', () => ({ useRBACStore: vi.fn(), @@ -19,7 +19,7 @@ describe('Checks', () => { } as unknown as ReturnType); const scope = 'workflow:read'; - const options: HasScopeOptions = { mode: 'allOf' }; + const options: ScopeOptions = { mode: 'allOf' }; const projectId = 'proj123'; const resourceType = 'workflow'; const resourceId = 'res123'; diff --git a/packages/editor-ui/src/rbac/checks/__tests__/isDefaultUser.test.ts b/packages/editor-ui/src/rbac/checks/__tests__/isDefaultUser.test.ts new file mode 100644 index 0000000000..815c021768 --- /dev/null +++ b/packages/editor-ui/src/rbac/checks/__tests__/isDefaultUser.test.ts @@ -0,0 +1,27 @@ +import { useUsersStore } from '@/stores/users.store'; +import { isDefaultUser } from '@/rbac/checks/isDefaultUser'; + +vi.mock('@/stores/users.store', () => ({ + useUsersStore: vi.fn(), +})); + +describe('Checks', () => { + describe('isDefaultUser()', () => { + it('should return false if user not logged in', () => { + vi.mocked(useUsersStore).mockReturnValue({ currentUser: null } as ReturnType< + typeof useUsersStore + >); + + expect(isDefaultUser()).toBe(false); + }); + + it('should return true if user is default user', () => { + const mockUser = { id: 'user123', name: 'Test User', isDefaultUser: true }; + vi.mocked(useUsersStore).mockReturnValue({ currentUser: mockUser } as unknown as ReturnType< + typeof useUsersStore + >); + + expect(isDefaultUser()).toBe(mockUser.isDefaultUser); + }); + }); +}); diff --git a/packages/editor-ui/src/rbac/checks/index.ts b/packages/editor-ui/src/rbac/checks/index.ts index cb8acc3533..17088ed8b1 100644 --- a/packages/editor-ui/src/rbac/checks/index.ts +++ b/packages/editor-ui/src/rbac/checks/index.ts @@ -1,6 +1,7 @@ export * from './hasRole'; export * from './hasScope'; export * from './isAuthenticated'; +export * from './isDefaultUser'; export * from './isEnterpriseFeatureEnabled'; export * from './isGuest'; export * from './isValid'; diff --git a/packages/editor-ui/src/rbac/checks/isDefaultUser.ts b/packages/editor-ui/src/rbac/checks/isDefaultUser.ts new file mode 100644 index 0000000000..f0f812df69 --- /dev/null +++ b/packages/editor-ui/src/rbac/checks/isDefaultUser.ts @@ -0,0 +1,12 @@ +import { useUsersStore } from '@/stores/users.store'; +import type { DefaultUserMiddlewareOptions, RBACPermissionCheck } from '@/types/rbac'; + +export const isDefaultUser: RBACPermissionCheck = () => { + const usersStore = useUsersStore(); + const currentUser = usersStore.currentUser; + + if (currentUser) { + return currentUser.isDefaultUser; + } + return false; +}; diff --git a/packages/editor-ui/src/rbac/middleware.ts b/packages/editor-ui/src/rbac/middleware.ts index e8c0ed5ae3..e6805b3813 100644 --- a/packages/editor-ui/src/rbac/middleware.ts +++ b/packages/editor-ui/src/rbac/middleware.ts @@ -5,6 +5,7 @@ import { guestMiddleware } from '@/rbac/middleware/guest'; import { rbacMiddleware } from '@/rbac/middleware/rbac'; import { roleMiddleware } from '@/rbac/middleware/role'; import { customMiddleware } from '@/rbac/middleware/custom'; +import { defaultUserMiddleware } from '@/rbac/middleware/defaultUser'; type Middleware = { [key in RouterMiddlewareType]: RouterMiddleware; @@ -13,6 +14,7 @@ type Middleware = { export const middleware: Middleware = { authenticated: authenticatedMiddleware, custom: customMiddleware, + defaultUser: defaultUserMiddleware, enterprise: enterpriseMiddleware, guest: guestMiddleware, rbac: rbacMiddleware, diff --git a/packages/editor-ui/src/rbac/middleware/__tests__/defaultUser.test.ts b/packages/editor-ui/src/rbac/middleware/__tests__/defaultUser.test.ts new file mode 100644 index 0000000000..d550dd9fe0 --- /dev/null +++ b/packages/editor-ui/src/rbac/middleware/__tests__/defaultUser.test.ts @@ -0,0 +1,54 @@ +import { useUsersStore } from '@/stores/users.store'; +import { VIEWS } from '@/constants'; +import { defaultUserMiddleware } from '@/rbac/middleware/defaultUser'; +import type { RouteLocationNormalized } from 'vue-router'; + +vi.mock('@/stores/users.store', () => ({ + useUsersStore: vi.fn(), +})); + +describe('Middleware', () => { + describe('defaultUser', () => { + it('should redirect to homepage if user not logged in', async () => { + vi.mocked(useUsersStore).mockReturnValue({ + currentUser: null, + } as ReturnType); + + const nextMock = vi.fn(); + const toMock = { query: {} } as RouteLocationNormalized; + const fromMock = {} as RouteLocationNormalized; + + await defaultUserMiddleware(toMock, fromMock, nextMock, {}); + + expect(nextMock).toHaveBeenCalledWith({ name: VIEWS.HOMEPAGE }); + }); + + it('should redirect to homepage if user is not default user', async () => { + vi.mocked(useUsersStore).mockReturnValue({ + currentUser: { id: '123', isDefaultUser: false }, + } as ReturnType); + + const nextMock = vi.fn(); + const toMock = { query: {} } as RouteLocationNormalized; + const fromMock = {} as RouteLocationNormalized; + + await defaultUserMiddleware(toMock, fromMock, nextMock, {}); + + expect(nextMock).toHaveBeenCalledWith({ name: VIEWS.HOMEPAGE }); + }); + + it('should allow navigation if a current user is present', async () => { + vi.mocked(useUsersStore).mockReturnValue({ + currentUser: { id: '123', isDefaultUser: true }, + } as ReturnType); + + const nextMock = vi.fn(); + const toMock = { query: {} } as RouteLocationNormalized; + const fromMock = {} as RouteLocationNormalized; + + await defaultUserMiddleware(toMock, fromMock, nextMock, {}); + + expect(nextMock).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/editor-ui/src/rbac/middleware/defaultUser.ts b/packages/editor-ui/src/rbac/middleware/defaultUser.ts new file mode 100644 index 0000000000..bab12869f9 --- /dev/null +++ b/packages/editor-ui/src/rbac/middleware/defaultUser.ts @@ -0,0 +1,15 @@ +import type { RouterMiddleware } from '@/types/router'; +import { VIEWS } from '@/constants'; +import type { DefaultUserMiddlewareOptions } from '@/types/rbac'; +import { isDefaultUser } from '@/rbac/checks'; + +export const defaultUserMiddleware: RouterMiddleware = async ( + to, + from, + next, +) => { + const valid = isDefaultUser(); + if (!valid) { + return next({ name: VIEWS.HOMEPAGE }); + } +}; diff --git a/packages/editor-ui/src/rbac/permissions.ts b/packages/editor-ui/src/rbac/permissions.ts index 13e9c2be00..f132ce8049 100644 --- a/packages/editor-ui/src/rbac/permissions.ts +++ b/packages/editor-ui/src/rbac/permissions.ts @@ -2,6 +2,7 @@ import { hasRole, hasScope, isAuthenticated, + isDefaultUser, isEnterpriseFeatureEnabled, isGuest, isValid, @@ -15,6 +16,7 @@ type Permissions = { export const permissions: Permissions = { authenticated: isAuthenticated, custom: isValid, + defaultUser: isDefaultUser, enterprise: isEnterpriseFeatureEnabled, guest: isGuest, rbac: hasScope, diff --git a/packages/editor-ui/src/router.ts b/packages/editor-ui/src/router.ts index 034a064afd..781217b9a9 100644 --- a/packages/editor-ui/src/router.ts +++ b/packages/editor-ui/src/router.ts @@ -9,7 +9,6 @@ import type { } from 'vue-router'; import { createRouter, createWebHistory } from 'vue-router'; import { runExternalHook } from '@/utils/externalHooks'; -import { ROLE } from '@/utils/userUtils'; import { useSettingsStore } from '@/stores/settings.store'; import { useTemplatesStore } from '@/stores/templates.store'; import { useUIStore } from '@/stores/ui.store'; @@ -406,10 +405,7 @@ export const routes = [ default: SetupView, }, meta: { - middleware: ['role'], - middlewareOptions: { - role: [ROLE.Default], - }, + middleware: ['defaultUser'], telemetry: { pageCategory: 'auth', }, @@ -480,10 +476,7 @@ export const routes = [ settingsView: SettingsPersonalView, }, meta: { - middleware: ['authenticated', 'role'], - middlewareOptions: { - role: [ROLE.Owner, ROLE.Member], - }, + middleware: ['authenticated'], telemetry: { pageCategory: 'settings', getProperties(route: RouteLocation) { @@ -548,9 +541,11 @@ export const routes = [ settingsView: SettingsSourceControl, }, meta: { - middleware: ['authenticated', 'role'], + middleware: ['authenticated', 'rbac'], middlewareOptions: { - role: [ROLE.Owner], + rbac: { + scope: 'sourceControl:manage', + }, }, telemetry: { pageCategory: 'settings', @@ -569,9 +564,11 @@ export const routes = [ settingsView: SettingsExternalSecrets, }, meta: { - middleware: ['authenticated', 'role'], + middleware: ['authenticated', 'rbac'], middlewareOptions: { - role: [ROLE.Owner], + rbac: { + scope: ['externalSecretsProvider:list', 'externalSecretsProvider:update'], + }, }, telemetry: { pageCategory: 'settings', @@ -590,13 +587,15 @@ export const routes = [ settingsView: SettingsSso, }, meta: { - middleware: ['authenticated', 'role', 'custom'], + middleware: ['authenticated', 'rbac', 'custom'], middlewareOptions: { custom: () => { const settingsStore = useSettingsStore(); return !settingsStore.isDesktopDeployment; }, - role: [ROLE.Owner], + rbac: { + scope: 'saml:manage', + }, }, telemetry: { pageCategory: 'settings', @@ -615,9 +614,11 @@ export const routes = [ settingsView: SettingsLogStreamingView, }, meta: { - middleware: ['authenticated', 'role'], + middleware: ['authenticated', 'rbac'], middlewareOptions: { - role: [ROLE.Owner], + rbac: { + scope: 'logStreaming:manage', + }, }, telemetry: { pageCategory: 'settings', @@ -641,9 +642,11 @@ export const routes = [ settingsView: SettingsCommunityNodesView, }, meta: { - middleware: ['authenticated', 'role', 'custom'], + middleware: ['authenticated', 'rbac', 'custom'], middlewareOptions: { - role: [ROLE.Owner], + rbac: { + scope: ['communityPackage:list', 'communityPackage:update'], + }, custom: () => { const settingsStore = useSettingsStore(); return settingsStore.isCommunityNodesFeatureEnabled; @@ -679,9 +682,11 @@ export const routes = [ settingsView: SettingsLdapView, }, meta: { - middleware: ['authenticated', 'role'], + middleware: ['authenticated', 'rbac'], middlewareOptions: { - role: [ROLE.Owner], + rbac: { + scope: 'ldap:manage', + }, }, }, }, @@ -692,12 +697,14 @@ export const routes = [ settingsView: SettingsAuditLogs, }, meta: { - middleware: ['authenticated', 'role', 'custom'], + middleware: ['authenticated', 'rbac', 'custom'], middlewareOptions: { custom: () => { return !!useStorage('audit-logs').value; }, - role: [ROLE.Owner], + rbac: { + scope: 'auditLogs:manage', + }, }, telemetry: { pageCategory: 'settings', diff --git a/packages/editor-ui/src/stores/rbac.store.ts b/packages/editor-ui/src/stores/rbac.store.ts index 3667b519ac..fbcb6b80b6 100644 --- a/packages/editor-ui/src/stores/rbac.store.ts +++ b/packages/editor-ui/src/stores/rbac.store.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia'; import { hasScope as genericHasScope } from '@n8n/permissions'; -import type { HasScopeOptions, Scope, Resource } from '@n8n/permissions'; +import type { ScopeOptions, Scope, Resource } from '@n8n/permissions'; import { ref } from 'vue'; import { STORES } from '@/constants'; import type { IRole } from '@/Interface'; @@ -80,7 +80,7 @@ export const useRBACStore = defineStore(STORES.RBAC, () => { resourceId?: string; projectId?: string; }, - options?: HasScopeOptions, + options?: ScopeOptions, ): boolean { return genericHasScope( scope, diff --git a/packages/editor-ui/src/types/rbac.ts b/packages/editor-ui/src/types/rbac.ts index b6603b2e1b..169a7a0a73 100644 --- a/packages/editor-ui/src/types/rbac.ts +++ b/packages/editor-ui/src/types/rbac.ts @@ -1,9 +1,10 @@ import type { EnterpriseEditionFeature } from '@/constants'; -import type { Resource, HasScopeOptions, Scope } from '@n8n/permissions'; +import type { Resource, ScopeOptions, Scope } from '@n8n/permissions'; import type { IRole } from '@/Interface'; export type AuthenticatedPermissionOptions = {}; export type CustomPermissionOptions = RBACPermissionCheck; +export type DefaultUserMiddlewareOptions = {}; export type EnterprisePermissionOptions = { feature?: EnterpriseEditionFeature | EnterpriseEditionFeature[]; mode?: 'oneOf' | 'allOf'; @@ -14,14 +15,22 @@ export type RBACPermissionOptions = { projectId?: string; resourceType?: Resource; resourceId?: string; - options?: HasScopeOptions; + options?: ScopeOptions; }; export type RolePermissionOptions = IRole[]; -export type PermissionType = 'authenticated' | 'custom' | 'enterprise' | 'guest' | 'rbac' | 'role'; +export type PermissionType = + | 'authenticated' + | 'custom' + | 'defaultUser' + | 'enterprise' + | 'guest' + | 'rbac' + | 'role'; export type PermissionTypeOptions = { authenticated: AuthenticatedPermissionOptions; custom: CustomPermissionOptions; + defaultUser: DefaultUserMiddlewareOptions; enterprise: EnterprisePermissionOptions; guest: GuestPermissionOptions; rbac: RBACPermissionOptions; diff --git a/packages/editor-ui/src/types/router.ts b/packages/editor-ui/src/types/router.ts index 6ddf027e55..43268767e8 100644 --- a/packages/editor-ui/src/types/router.ts +++ b/packages/editor-ui/src/types/router.ts @@ -13,6 +13,7 @@ import type { RBACPermissionOptions, RolePermissionOptions, PermissionType, + DefaultUserMiddlewareOptions, } from '@/types/rbac'; export type RouterMiddlewareType = PermissionType; @@ -24,6 +25,7 @@ export type CustomMiddlewareOptions = CustomPermissionOptions<{ export type MiddlewareOptions = { authenticated: AuthenticatedPermissionOptions; custom: CustomMiddlewareOptions; + defaultUser: DefaultUserMiddlewareOptions; enterprise: EnterprisePermissionOptions; guest: GuestPermissionOptions; rbac: RBACPermissionOptions; diff --git a/packages/editor-ui/src/views/SettingsUsersView.vue b/packages/editor-ui/src/views/SettingsUsersView.vue index e408b88bf7..f1520d238a 100644 --- a/packages/editor-ui/src/views/SettingsUsersView.vue +++ b/packages/editor-ui/src/views/SettingsUsersView.vue @@ -92,7 +92,7 @@ export default defineComponent({ return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing); }, showUMSetupWarning() { - return hasPermission(['role'], { role: [ROLE.Default] }); + return hasPermission(['defaultUser']); }, usersListActions(): IUserListAction[] { return [