mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 11:22:15 +00:00
fix(editor): Remove isOwner from IUser interface (#8888)
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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>);
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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>);
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
],
|
],
|
||||||
])(
|
])(
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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('.');
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 = () => {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user