fix(editor): Remove isOwner from IUser interface (#8888)

This commit is contained in:
Csaba Tuncsik
2024-03-18 11:39:15 +01:00
committed by GitHub
parent 024be62693
commit 6955e8991c
24 changed files with 74 additions and 65 deletions

View File

@@ -7,7 +7,8 @@ import type {
REGULAR_NODE_CREATOR_VIEW, REGULAR_NODE_CREATOR_VIEW,
AI_OTHERS_NODE_CREATOR_VIEW, AI_OTHERS_NODE_CREATOR_VIEW,
VIEWS, VIEWS,
} from './constants'; ROLE,
} from '@/constants';
import type { IMenuItem } from 'n8n-design-system'; import type { IMenuItem } from 'n8n-design-system';
import { import {
type GenericValue, type GenericValue,
@@ -688,9 +689,9 @@ export type IPersonalizationSurveyVersions =
| IPersonalizationSurveyAnswersV2 | IPersonalizationSurveyAnswersV2
| IPersonalizationSurveyAnswersV3; | IPersonalizationSurveyAnswersV3;
export type IRole = 'default' | 'global:owner' | 'global:member' | 'global:admin'; export type Roles = typeof ROLE;
export type IRole = Roles[keyof Roles];
export type InvitableRoleName = 'global:member' | 'global:admin'; export type InvitableRoleName = Roles['Member' | 'Admin'];
export interface IUserResponse { export interface IUserResponse {
id: string; id: string;
@@ -714,7 +715,6 @@ export interface IUser extends IUserResponse {
isDefaultUser: boolean; isDefaultUser: boolean;
isPendingUser: boolean; isPendingUser: boolean;
hasRecoveryCodesLeft: boolean; hasRecoveryCodesLeft: boolean;
isOwner: boolean;
inviteAcceptUrl?: string; inviteAcceptUrl?: string;
fullName?: string; fullName?: string;
createdAt?: string; createdAt?: string;

View File

@@ -1,5 +1,6 @@
import { parsePermissionsTable } from '@/permissions'; import { parsePermissionsTable } from '@/permissions';
import type { IUser } from '@/Interface'; import type { IUser } from '@/Interface';
import { ROLE } from '@/constants';
describe('parsePermissionsTable()', () => { describe('parsePermissionsTable()', () => {
const user: IUser = { const user: IUser = {
@@ -7,9 +8,11 @@ describe('parsePermissionsTable()', () => {
firstName: 'John', firstName: 'John',
lastName: 'Doe', lastName: 'Doe',
isDefaultUser: false, isDefaultUser: false,
isOwner: true,
isPending: false, isPending: false,
isPendingUser: false, isPendingUser: false,
mfaEnabled: false,
hasRecoveryCodesLeft: false,
role: ROLE.Owner,
}; };
it('should return permissions object using generic permissions table', () => { it('should return permissions object using generic permissions table', () => {

View File

@@ -16,9 +16,6 @@ export const userFactory = Factory.extend<IUser>({
isDefaultUser() { isDefaultUser() {
return false; return false;
}, },
isOwner() {
return false;
},
isPending() { isPending() {
return false; return false;
}, },
@@ -28,4 +25,10 @@ export const userFactory = Factory.extend<IUser>({
signInType(): SignInType { signInType(): SignInType {
return SignInType.EMAIL; return SignInType.EMAIL;
}, },
mfaEnabled() {
return false;
},
hasRecoveryCodesLeft() {
return false;
},
}); });

View File

@@ -2,8 +2,8 @@ import type {
CurrentUserResponse, CurrentUserResponse,
IPersonalizationLatestVersion, IPersonalizationLatestVersion,
IRestApiContext, IRestApiContext,
IRole,
IUserResponse, IUserResponse,
InvitableRoleName,
} from '@/Interface'; } from '@/Interface';
import type { IDataObject } from 'n8n-workflow'; import type { IDataObject } from 'n8n-workflow';
import { makeRestApiRequest } from '@/utils/apiUtils'; import { makeRestApiRequest } from '@/utils/apiUtils';
@@ -157,7 +157,7 @@ export async function submitPersonalizationSurvey(
export interface UpdateGlobalRolePayload { export interface UpdateGlobalRolePayload {
id: string; id: string;
newRoleName: Exclude<IRole, 'default' | 'global:owner'>; newRoleName: InvitableRoleName;
} }
export async function updateGlobalRole( export async function updateGlobalRole(

View File

@@ -1,5 +1,6 @@
import type { IOnboardingCallPrompt, IUser } from '@/Interface'; import type { IOnboardingCallPrompt, IUser } from '@/Interface';
import { get, post } from '@/utils/apiUtils'; import { get, post } from '@/utils/apiUtils';
import { isUserGlobalOwner } from '@/utils/userUtils';
const N8N_API_BASE_URL = 'https://api.n8n.io/api'; const N8N_API_BASE_URL = 'https://api.n8n.io/api';
const ONBOARDING_PROMPTS_ENDPOINT = '/prompts/onboarding'; const ONBOARDING_PROMPTS_ENDPOINT = '/prompts/onboarding';
@@ -12,7 +13,7 @@ export async function fetchNextOnboardingPrompt(
return await get(N8N_API_BASE_URL, ONBOARDING_PROMPTS_ENDPOINT, { return await get(N8N_API_BASE_URL, ONBOARDING_PROMPTS_ENDPOINT, {
instance_id: instanceId, instance_id: instanceId,
user_id: `${instanceId}#${currentUser.id}`, user_id: `${instanceId}#${currentUser.id}`,
is_owner: currentUser.isOwner ?? false, is_owner: isUserGlobalOwner(currentUser),
survey_results: currentUser.personalizationAnswers, survey_results: currentUser.personalizationAnswers,
}); });
} }

View File

@@ -66,8 +66,12 @@ import { mapStores } from 'pinia';
import { useToast } from '@/composables/useToast'; import { useToast } from '@/composables/useToast';
import Modal from './Modal.vue'; import Modal from './Modal.vue';
import type { IFormInputs, IInviteResponse, IUser } from '@/Interface'; import type { IFormInputs, IInviteResponse, IUser } from '@/Interface';
import { ROLE } from '@/utils/userUtils'; import {
import { EnterpriseEditionFeature, VALID_EMAIL_REGEX, INVITE_USER_MODAL_KEY } from '@/constants'; EnterpriseEditionFeature,
VALID_EMAIL_REGEX,
INVITE_USER_MODAL_KEY,
ROLE,
} from '@/constants';
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';

View File

@@ -4,6 +4,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { useCollaborationStore } from '@/stores/collaboration.store'; import { useCollaborationStore } from '@/stores/collaboration.store';
import { onBeforeUnmount, onMounted, computed, ref } from 'vue'; import { onBeforeUnmount, onMounted, computed, ref } from 'vue';
import { TIME } from '@/constants'; import { TIME } from '@/constants';
import { isUserGlobalOwner } from '@/utils/userUtils';
const collaborationStore = useCollaborationStore(); const collaborationStore = useCollaborationStore();
const usersStore = useUsersStore(); const usersStore = useUsersStore();
@@ -16,7 +17,7 @@ const activeUsersSorted = computed(() => {
const currentWorkflowUsers = (collaborationStore.getUsersForCurrentWorkflow ?? []).map( const currentWorkflowUsers = (collaborationStore.getUsersForCurrentWorkflow ?? []).map(
(userInfo) => userInfo.user, (userInfo) => userInfo.user,
); );
const owner = currentWorkflowUsers.find((user) => user.role === 'global:owner'); const owner = currentWorkflowUsers.find(isUserGlobalOwner);
return { return {
defaultGroup: owner defaultGroup: owner
? [owner, ...currentWorkflowUsers.filter((user) => user.id !== owner.id)] ? [owner, ...currentWorkflowUsers.filter((user) => user.id !== owner.id)]

View File

@@ -2,7 +2,7 @@ import { merge } from 'lodash-es';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils'; import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
import { STORES } from '@/constants'; import { ROLE, STORES } from '@/constants';
import { createTestingPinia } from '@pinia/testing'; import { createTestingPinia } from '@pinia/testing';
import BannerStack from '@/components/banners/BannerStack.vue'; import BannerStack from '@/components/banners/BannerStack.vue';
@@ -26,11 +26,11 @@ const initialState = {
users: { users: {
'aaa-bbb': { 'aaa-bbb': {
id: 'aaa-bbb', id: 'aaa-bbb',
role: 'global:owner', role: ROLE.Owner,
}, },
'bbb-bbb': { 'bbb-bbb': {
id: 'bbb-bbb', id: 'bbb-bbb',
role: 'global:member', role: ROLE.Member,
}, },
}, },
}, },

View File

@@ -1,6 +1,6 @@
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
import { SETTINGS_STORE_DEFAULT_STATE, waitAllPromises } from '@/__tests__/utils'; import { SETTINGS_STORE_DEFAULT_STATE, waitAllPromises } from '@/__tests__/utils';
import { STORES } from '@/constants'; import { ROLE, STORES } from '@/constants';
import { createTestingPinia } from '@pinia/testing'; import { createTestingPinia } from '@pinia/testing';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
import CollaborationPane from '@/components//MainHeader/CollaborationPane.vue'; import CollaborationPane from '@/components//MainHeader/CollaborationPane.vue';
@@ -13,10 +13,9 @@ const OWNER_USER = {
email: 'owner@user.com', email: 'owner@user.com',
firstName: 'Owner', firstName: 'Owner',
lastName: 'User', lastName: 'User',
role: 'global:owner', role: ROLE.Owner,
disabled: false, disabled: false,
isPending: false, isPending: false,
isOwner: true,
fullName: 'Owner User', fullName: 'Owner User',
}; };
@@ -26,10 +25,9 @@ const MEMBER_USER = {
email: 'member@user.com', email: 'member@user.com',
firstName: 'Member', firstName: 'Member',
lastName: 'User', lastName: 'User',
role: 'global:member', role: ROLE.Member,
disabled: false, disabled: false,
isPending: false, isPending: false,
isOwner: false,
fullName: 'Member User', fullName: 'Member User',
}; };
@@ -39,10 +37,9 @@ const MEMBER_USER_2 = {
email: 'member2@user.com', email: 'member2@user.com',
firstName: 'Another Member', firstName: 'Another Member',
lastName: 'User', lastName: 'User',
role: 'global:member', role: ROLE.Member,
disabled: false, disabled: false,
isPending: false, isPending: false,
isOwner: false,
fullName: 'Another Member User', fullName: 'Another Member User',
}; };

View File

@@ -1,7 +1,7 @@
import PersonalizationModal from '@/components/PersonalizationModal.vue'; import PersonalizationModal from '@/components/PersonalizationModal.vue';
import { createTestingPinia } from '@pinia/testing'; import { createTestingPinia } from '@pinia/testing';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { PERSONALIZATION_MODAL_KEY, STORES, VIEWS } from '@/constants'; import { PERSONALIZATION_MODAL_KEY, ROLE, STORES, VIEWS } from '@/constants';
import { retry } from '@/__tests__/utils'; import { retry } from '@/__tests__/utils';
import { createComponentRenderer } from '@/__tests__/render'; import { createComponentRenderer } from '@/__tests__/render';
import { fireEvent } from '@testing-library/vue'; import { fireEvent } from '@testing-library/vue';
@@ -31,7 +31,7 @@ const pinia = createTestingPinia({
isDefaultUser: false, isDefaultUser: false,
isPendingUser: false, isPendingUser: false,
hasRecoveryCodesLeft: true, hasRecoveryCodesLeft: true,
isOwner: true, role: ROLE.Owner,
mfaEnabled: false, mfaEnabled: false,
}, },
}, },

View File

@@ -2,6 +2,8 @@ import { render } from '@testing-library/vue';
import V1Banner from '../V1Banner.vue'; import V1Banner from '../V1Banner.vue';
import { createPinia, setActivePinia } from 'pinia'; import { createPinia, setActivePinia } from 'pinia';
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
import { ROLE } from '@/constants';
import type { IUser } from '@/Interface';
describe('V1 Banner', () => { describe('V1 Banner', () => {
let pinia: ReturnType<typeof createPinia>; let pinia: ReturnType<typeof createPinia>;
@@ -22,8 +24,8 @@ describe('V1 Banner', () => {
it('should render banner with dismiss call if user is owner', () => { it('should render banner with dismiss call if user is owner', () => {
vi.spyOn(usersStore, 'currentUser', 'get').mockReturnValue({ vi.spyOn(usersStore, 'currentUser', 'get').mockReturnValue({
role: 'global:owner', role: ROLE.Owner,
}); } as IUser);
const { container } = render(V1Banner); const { container } = render(V1Banner);
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();

View File

@@ -752,3 +752,10 @@ export const TEMPLATES_URLS = {
BASE_WEBSITE_URL: 'https://n8n.io/workflows', BASE_WEBSITE_URL: 'https://n8n.io/workflows',
UTM_QUERY: 'utm_source=n8n_app&utm_medium=template_library', UTM_QUERY: 'utm_source=n8n_app&utm_medium=template_library',
}; };
export const ROLE = {
Owner: 'global:owner',
Member: 'global:member',
Admin: 'global:admin',
Default: 'default', // default user with no email when setting up instance
} as const;

View File

@@ -8,6 +8,7 @@ import type { IUser, ICredentialsResponse, IWorkflowDb } from '@/Interface';
import { EnterpriseEditionFeature, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants'; import { EnterpriseEditionFeature, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import { hasPermission } from './rbac/permissions'; import { hasPermission } from './rbac/permissions';
import { isUserGlobalOwner } from './utils/userUtils';
/** /**
* Old permissions implementation * Old permissions implementation
@@ -43,7 +44,7 @@ export const parsePermissionsTable = (
table: IPermissionsTable, table: IPermissionsTable,
): IPermissions => { ): IPermissions => {
const genericTable: IPermissionsTable = [ const genericTable: IPermissionsTable = [
{ name: UserRole.InstanceOwner, test: () => !!user?.isOwner }, { name: UserRole.InstanceOwner, test: () => (user ? isUserGlobalOwner(user) : false) },
]; ];
return [...genericTable, ...table].reduce( return [...genericTable, ...table].reduce(

View File

@@ -1,6 +1,6 @@
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
import { hasRole } from '@/rbac/checks'; import { hasRole } from '@/rbac/checks';
import { ROLE } from '@/utils/userUtils'; import { ROLE } from '@/constants';
vi.mock('@/stores/users.store', () => ({ vi.mock('@/stores/users.store', () => ({
useUsersStore: vi.fn(), useUsersStore: vi.fn(),
@@ -12,7 +12,7 @@ describe('Checks', () => {
vi.mocked(useUsersStore).mockReturnValue({ vi.mocked(useUsersStore).mockReturnValue({
currentUser: { currentUser: {
isDefaultUser: false, isDefaultUser: false,
role: 'global:owner', role: ROLE.Owner,
}, },
} as ReturnType<typeof useUsersStore>); } as ReturnType<typeof useUsersStore>);

View File

@@ -1,6 +1,6 @@
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
import type { RBACPermissionCheck, RolePermissionOptions } from '@/types/rbac'; import type { RBACPermissionCheck, RolePermissionOptions } from '@/types/rbac';
import { ROLE } from '@/utils/userUtils'; import { ROLE } from '@/constants';
import type { IRole } from '@/Interface'; import type { IRole } from '@/Interface';
export const hasRole: RBACPermissionCheck<RolePermissionOptions> = (checkRoles) => { export const hasRole: RBACPermissionCheck<RolePermissionOptions> = (checkRoles) => {

View File

@@ -1,9 +1,8 @@
import { roleMiddleware } from '@/rbac/middleware/role'; import { roleMiddleware } from '@/rbac/middleware/role';
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
import { ROLE } from '@/utils/userUtils';
import type { IUser } from '@/Interface'; import type { IUser } from '@/Interface';
import type { RouteLocationNormalized } from 'vue-router'; import type { RouteLocationNormalized } from 'vue-router';
import { VIEWS } from '@/constants'; import { VIEWS, ROLE } from '@/constants';
vi.mock('@/stores/users.store', () => ({ vi.mock('@/stores/users.store', () => ({
useUsersStore: vi.fn(), useUsersStore: vi.fn(),
@@ -15,7 +14,7 @@ describe('Middleware', () => {
vi.mocked(useUsersStore).mockReturnValue({ vi.mocked(useUsersStore).mockReturnValue({
currentUser: { currentUser: {
isDefaultUser: false, isDefaultUser: false,
role: 'global:owner', role: ROLE.Owner,
} as IUser, } as IUser,
} as ReturnType<typeof useUsersStore>); } as ReturnType<typeof useUsersStore>);
@@ -54,7 +53,7 @@ describe('Middleware', () => {
vi.mocked(useUsersStore).mockReturnValue({ vi.mocked(useUsersStore).mockReturnValue({
currentUser: { currentUser: {
isDefaultUser: false, isDefaultUser: false,
role: 'global:owner', role: ROLE.Owner,
} as IUser, } as IUser,
} as ReturnType<typeof useUsersStore>); } as ReturnType<typeof useUsersStore>);

View File

@@ -14,6 +14,7 @@ import {
getNotTrialingUserResponse, getNotTrialingUserResponse,
} from './utils/cloudStoreUtils'; } from './utils/cloudStoreUtils';
import type { IRole } from '@/Interface'; import type { IRole } from '@/Interface';
import { ROLE } from '@/constants';
let uiStore: ReturnType<typeof useUIStore>; let uiStore: ReturnType<typeof useUIStore>;
let settingsStore: ReturnType<typeof useSettingsStore>; let settingsStore: ReturnType<typeof useSettingsStore>;
@@ -33,7 +34,7 @@ function setUser(role: IRole) {
} }
function setupOwnerAndCloudDeployment() { function setupOwnerAndCloudDeployment() {
setUser('global:owner'); setUser(ROLE.Owner);
settingsStore.setSettings( settingsStore.setSettings(
merge({}, SETTINGS_STORE_DEFAULT_STATE.settings, { merge({}, SETTINGS_STORE_DEFAULT_STATE.settings, {
n8nMetadata: { n8nMetadata: {
@@ -75,19 +76,19 @@ describe('UI store', () => {
[ [
'default', 'default',
'production', 'production',
'global:owner', ROLE.Owner,
'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source', 'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source',
], ],
[ [
'default', 'default',
'development', 'development',
'global:owner', ROLE.Owner,
'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source', 'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source',
], ],
[ [
'cloud', 'cloud',
'production', 'production',
'global:owner', ROLE.Owner,
`https://app.n8n.cloud/login?code=123&returnPath=${encodeURIComponent( `https://app.n8n.cloud/login?code=123&returnPath=${encodeURIComponent(
'/account/change-plan', '/account/change-plan',
)}&utm_campaign=utm-test-campaign&source=test_source`, )}&utm_campaign=utm-test-campaign&source=test_source`,
@@ -95,7 +96,7 @@ describe('UI store', () => {
[ [
'cloud', 'cloud',
'production', 'production',
'global:member', ROLE.Member,
'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source', 'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source',
], ],
])( ])(

View File

@@ -67,11 +67,9 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
const getUserCloudAccount = async () => { const getUserCloudAccount = async () => {
if (!hasCloudPlan.value) throw new Error('User does not have a cloud plan'); if (!hasCloudPlan.value) throw new Error('User does not have a cloud plan');
try { try {
if (hasPermission(['instanceOwner'])) { await usersStore.fetchUserCloudAccount();
await usersStore.fetchUserCloudAccount(); if (!usersStore.currentUserCloudInfo?.confirmed && !userIsTrialing.value) {
if (!usersStore.currentUserCloudInfo?.confirmed && !userIsTrialing.value) { useUIStore().pushBannerToStack('EMAIL_CONFIRMATION');
useUIStore().pushBannerToStack('EMAIL_CONFIRMATION');
}
} }
} catch (error) { } catch (error) {
throw new Error(error.message); throw new Error(error.message);

View File

@@ -343,7 +343,6 @@ export const useUIStore = defineStore(STORES.UI, {
let linkUrl = ''; let linkUrl = '';
const searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
const { isInstanceOwner } = useUsersStore();
if (deploymentType === 'cloud' && hasPermission(['instanceOwner'])) { if (deploymentType === 'cloud' && hasPermission(['instanceOwner'])) {
const adminPanelHost = new URL(window.location.href).host.split('.').slice(1).join('.'); const adminPanelHost = new URL(window.location.href).host.split('.').slice(1).join('.');

View File

@@ -18,7 +18,7 @@ import {
validateSignupToken, validateSignupToken,
updateGlobalRole, updateGlobalRole,
} from '@/api/users'; } from '@/api/users';
import { PERSONALIZATION_MODAL_KEY, STORES } from '@/constants'; import { PERSONALIZATION_MODAL_KEY, STORES, ROLE } from '@/constants';
import type { import type {
Cloud, Cloud,
ICredentialsResponse, ICredentialsResponse,
@@ -46,7 +46,7 @@ import type { Scope } from '@n8n/permissions';
import { inviteUsers, acceptInvitation } from '@/api/invitation'; import { inviteUsers, acceptInvitation } from '@/api/invitation';
const isPendingUser = (user: IUserResponse | null) => !!user?.isPending; const isPendingUser = (user: IUserResponse | null) => !!user?.isPending;
const isInstanceOwner = (user: IUserResponse | null) => user?.role === 'global:owner'; const isInstanceOwner = (user: IUserResponse | null) => user?.role === ROLE.Owner;
const isDefaultUser = (user: IUserResponse | null) => isInstanceOwner(user) && isPendingUser(user); const isDefaultUser = (user: IUserResponse | null) => isInstanceOwner(user) && isPendingUser(user);
export const useUsersStore = defineStore(STORES.USERS, { export const useUsersStore = defineStore(STORES.USERS, {
@@ -139,7 +139,6 @@ export const useUsersStore = defineStore(STORES.USERS, {
: undefined, : undefined,
isDefaultUser: isDefaultUser(updatedUser), isDefaultUser: isDefaultUser(updatedUser),
isPendingUser: isPendingUser(updatedUser), isPendingUser: isPendingUser(updatedUser),
isOwner: isInstanceOwner(updatedUser),
}; };
this.users = { this.users = {

View File

@@ -59,6 +59,7 @@ import {
BAMBOO_HR_NODE_TYPE, BAMBOO_HR_NODE_TYPE,
GOOGLE_SHEETS_NODE_TYPE, GOOGLE_SHEETS_NODE_TYPE,
CODE_NODE_TYPE, CODE_NODE_TYPE,
ROLE,
} from '@/constants'; } from '@/constants';
import type { import type {
IPersonalizationSurveyAnswersV1, IPersonalizationSurveyAnswersV1,
@@ -83,18 +84,13 @@ function isPersonalizationSurveyV2OrLater(
return 'version' in data; return 'version' in data;
} }
export const ROLE = {
Owner: 'global:owner',
Member: 'global:member',
Admin: 'global:admin',
Default: 'default', // default user with no email when setting up instance
} as const;
export const LOGIN_STATUS: { LoggedIn: ILogInStatus; LoggedOut: ILogInStatus } = { export const LOGIN_STATUS: { LoggedIn: ILogInStatus; LoggedOut: ILogInStatus } = {
LoggedIn: 'LoggedIn', // Can be owner or member or default user LoggedIn: 'LoggedIn', // Can be owner or member or default user
LoggedOut: 'LoggedOut', // Can only be logged out if UM has been setup LoggedOut: 'LoggedOut', // Can only be logged out if UM has been setup
}; };
export const isUserGlobalOwner = (user: IUser): boolean => user.role === ROLE.Owner;
export function getPersonalizedNodeTypes( export function getPersonalizedNodeTypes(
answers: answers:
| IPersonalizationSurveyAnswersV1 | IPersonalizationSurveyAnswersV1

View File

@@ -8,7 +8,6 @@ import { i18n as locale } from '@/plugins/i18n';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
import { N8N_PRICING_PAGE_URL } from '@/constants'; import { N8N_PRICING_PAGE_URL } from '@/constants';
import { useToast } from '@/composables/useToast'; import { useToast } from '@/composables/useToast';
import { ROLE } from '@/utils/userUtils';
import { hasPermission } from '@/rbac/permissions'; import { hasPermission } from '@/rbac/permissions';
const usageStore = useUsageStore(); const usageStore = useUsageStore();
@@ -29,9 +28,7 @@ const activationKey = ref('');
const activationKeyInput = ref<HTMLInputElement | null>(null); const activationKeyInput = ref<HTMLInputElement | null>(null);
const canUserActivateLicense = computed(() => const canUserActivateLicense = computed(() =>
hasPermission(['role'], { hasPermission(['rbac'], { rbac: { scope: 'license:manage' } }),
role: [ROLE.Owner],
}),
); );
const showActivationSuccess = () => { const showActivationSuccess = () => {

View File

@@ -87,7 +87,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { EnterpriseEditionFeature, INVITE_USER_MODAL_KEY, VIEWS } from '@/constants'; import { EnterpriseEditionFeature, INVITE_USER_MODAL_KEY, VIEWS, ROLE } from '@/constants';
import type { IUser, IUserListAction, InvitableRoleName } from '@/Interface'; import type { IUser, IUserListAction, InvitableRoleName } from '@/Interface';
import { useToast } from '@/composables/useToast'; import { useToast } from '@/composables/useToast';
@@ -97,7 +97,6 @@ import { useUsersStore } from '@/stores/users.store';
import { useUsageStore } from '@/stores/usage.store'; import { useUsageStore } from '@/stores/usage.store';
import { useSSOStore } from '@/stores/sso.store'; import { useSSOStore } from '@/stores/sso.store';
import { hasPermission } from '@/rbac/permissions'; import { hasPermission } from '@/rbac/permissions';
import { ROLE } from '@/utils/userUtils';
import { useClipboard } from '@/composables/useClipboard'; import { useClipboard } from '@/composables/useClipboard';
import type { UpdateGlobalRolePayload } from '@/api/users'; import type { UpdateGlobalRolePayload } from '@/api/users';
@@ -324,3 +323,4 @@ export default defineComponent({
left: calc(50% + 100px); left: calc(50% + 100px);
} }
</style> </style>
IRole,

View File

@@ -5,6 +5,7 @@ import { useSettingsStore } from '@/stores/settings.store';
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
import { createComponentRenderer } from '@/__tests__/render'; import { createComponentRenderer } from '@/__tests__/render';
import { setupServer } from '@/__tests__/server'; import { setupServer } from '@/__tests__/server';
import { ROLE } from '@/constants';
let pinia: ReturnType<typeof createPinia>; let pinia: ReturnType<typeof createPinia>;
let settingsStore: ReturnType<typeof useSettingsStore>; let settingsStore: ReturnType<typeof useSettingsStore>;
@@ -19,7 +20,7 @@ const currentUser = {
lastName: 'Doe', lastName: 'Doe',
email: 'joh.doe@example.com', email: 'joh.doe@example.com',
createdAt: Date().toString(), createdAt: Date().toString(),
isOwner: true, role: ROLE.Owner,
isDefaultUser: false, isDefaultUser: false,
isPendingUser: false, isPendingUser: false,
isPending: false, isPending: false,