mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 20:54:07 -08:00
fix: Fix user reinvites on FE and BE (#8261)
This commit is contained in:
parent
b1c1372bc2
commit
0dabe5c74e
|
@ -270,7 +270,7 @@ export class UserService {
|
||||||
const usersInvited = await this.sendEmails(
|
const usersInvited = await this.sendEmails(
|
||||||
owner,
|
owner,
|
||||||
Object.fromEntries(createdUsers),
|
Object.fromEntries(createdUsers),
|
||||||
toCreateUsers[0].role, // same role for all invited users
|
attributes[0].role, // same role for all invited users
|
||||||
);
|
);
|
||||||
|
|
||||||
return { usersInvited, usersCreated: toCreateUsers.map(({ email }) => email) };
|
return { usersInvited, usersCreated: toCreateUsers.map(({ email }) => email) };
|
||||||
|
|
|
@ -346,6 +346,29 @@ describe('POST /invitations', () => {
|
||||||
assertInvitedUsersOnDb(storedUser);
|
assertInvitedUsersOnDb(storedUser);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should reinvite member', async () => {
|
||||||
|
mailer.invite.mockResolvedValue({ emailSent: false });
|
||||||
|
|
||||||
|
await ownerAgent.post('/invitations').send([{ email: randomEmail(), role: 'member' }]);
|
||||||
|
|
||||||
|
await ownerAgent
|
||||||
|
.post('/invitations')
|
||||||
|
.send([{ email: randomEmail(), role: 'member' }])
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should reinvite admin if licensed', async () => {
|
||||||
|
license.isAdvancedPermissionsLicensed.mockReturnValue(true);
|
||||||
|
mailer.invite.mockResolvedValue({ emailSent: false });
|
||||||
|
|
||||||
|
await ownerAgent.post('/invitations').send([{ email: randomEmail(), role: 'admin' }]);
|
||||||
|
|
||||||
|
await ownerAgent
|
||||||
|
.post('/invitations')
|
||||||
|
.send([{ email: randomEmail(), role: 'admin' }])
|
||||||
|
.expect(200);
|
||||||
|
});
|
||||||
|
|
||||||
test('should fail to create admin shell if not licensed', async () => {
|
test('should fail to create admin shell if not licensed', async () => {
|
||||||
license.isAdvancedPermissionsLicensed.mockReturnValue(false);
|
license.isAdvancedPermissionsLicensed.mockReturnValue(false);
|
||||||
mailer.invite.mockResolvedValue({ emailSent: false });
|
mailer.invite.mockResolvedValue({ emailSent: false });
|
||||||
|
|
|
@ -687,6 +687,8 @@ export type IPersonalizationSurveyVersions =
|
||||||
|
|
||||||
export type IRole = 'default' | 'owner' | 'member' | 'admin';
|
export type IRole = 'default' | 'owner' | 'member' | 'admin';
|
||||||
|
|
||||||
|
export type InvitableRoleName = 'member' | 'admin';
|
||||||
|
|
||||||
export interface IUserResponse {
|
export interface IUserResponse {
|
||||||
id: string;
|
id: string;
|
||||||
firstName?: string;
|
firstName?: string;
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import type { CurrentUserResponse, IInviteResponse, IRestApiContext, IRole } from '@/Interface';
|
import type {
|
||||||
|
CurrentUserResponse,
|
||||||
|
IInviteResponse,
|
||||||
|
IRestApiContext,
|
||||||
|
InvitableRoleName,
|
||||||
|
} from '@/Interface';
|
||||||
import type { IDataObject } from 'n8n-workflow';
|
import type { IDataObject } from 'n8n-workflow';
|
||||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||||
|
|
||||||
|
@ -12,7 +17,7 @@ type AcceptInvitationParams = {
|
||||||
|
|
||||||
export async function inviteUsers(
|
export async function inviteUsers(
|
||||||
context: IRestApiContext,
|
context: IRestApiContext,
|
||||||
params: Array<{ email: string; role: IRole }>,
|
params: Array<{ email: string; role: InvitableRoleName }>,
|
||||||
) {
|
) {
|
||||||
return makeRestApiRequest<IInviteResponse[]>(context, 'POST', '/invitations', params);
|
return makeRestApiRequest<IInviteResponse[]>(context, 'POST', '/invitations', params);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import type {
|
||||||
IUserResponse,
|
IUserResponse,
|
||||||
IUsersState,
|
IUsersState,
|
||||||
CurrentUserResponse,
|
CurrentUserResponse,
|
||||||
|
InvitableRoleName,
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
import { getCredentialPermissions } from '@/permissions';
|
import { getCredentialPermissions } from '@/permissions';
|
||||||
import { getPersonalizedNodeTypes, ROLE } from '@/utils/userUtils';
|
import { getPersonalizedNodeTypes, ROLE } from '@/utils/userUtils';
|
||||||
|
@ -302,7 +303,9 @@ export const useUsersStore = defineStore(STORES.USERS, {
|
||||||
const users = await getUsers(rootStore.getRestApiContext);
|
const users = await getUsers(rootStore.getRestApiContext);
|
||||||
this.addUsers(users);
|
this.addUsers(users);
|
||||||
},
|
},
|
||||||
async inviteUsers(params: Array<{ email: string; role: IRole }>): Promise<IInviteResponse[]> {
|
async inviteUsers(
|
||||||
|
params: Array<{ email: string; role: InvitableRoleName }>,
|
||||||
|
): Promise<IInviteResponse[]> {
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
const users = await inviteUsers(rootStore.getRestApiContext, params);
|
const users = await inviteUsers(rootStore.getRestApiContext, params);
|
||||||
this.addUsers(
|
this.addUsers(
|
||||||
|
@ -314,11 +317,9 @@ export const useUsersStore = defineStore(STORES.USERS, {
|
||||||
);
|
);
|
||||||
return users;
|
return users;
|
||||||
},
|
},
|
||||||
async reinviteUser(params: { email: string }): Promise<void> {
|
async reinviteUser({ email, role }: { email: string; role: InvitableRoleName }): Promise<void> {
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
const invitationResponse = await inviteUsers(rootStore.getRestApiContext, [
|
const invitationResponse = await inviteUsers(rootStore.getRestApiContext, [{ email, role }]);
|
||||||
{ email: params.email },
|
|
||||||
]);
|
|
||||||
if (!invitationResponse[0].user.emailSent) {
|
if (!invitationResponse[0].user.emailSent) {
|
||||||
throw Error(invitationResponse[0].error);
|
throw Error(invitationResponse[0].error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ export async function request(config: {
|
||||||
baseURL: string;
|
baseURL: string;
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
headers?: IDataObject;
|
headers?: IDataObject;
|
||||||
data?: IDataObject;
|
data?: IDataObject | IDataObject[];
|
||||||
withCredentials?: boolean;
|
withCredentials?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { method, baseURL, endpoint, headers, data } = config;
|
const { method, baseURL, endpoint, headers, data } = config;
|
||||||
|
@ -119,7 +119,7 @@ export async function makeRestApiRequest<T>(
|
||||||
context: IRestApiContext,
|
context: IRestApiContext,
|
||||||
method: Method,
|
method: Method,
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
data?: IDataObject,
|
data?: IDataObject | IDataObject[],
|
||||||
) {
|
) {
|
||||||
const response = await request({
|
const response = await request({
|
||||||
method,
|
method,
|
||||||
|
|
|
@ -89,7 +89,7 @@ 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 } from '@/constants';
|
||||||
|
|
||||||
import type { IUser, IUserListAction } from '@/Interface';
|
import type { IUser, IUserListAction, InvitableRoleName } from '@/Interface';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
@ -207,9 +207,15 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
async onReinvite(userId: string) {
|
async onReinvite(userId: string) {
|
||||||
const user = this.usersStore.getUserById(userId);
|
const user = this.usersStore.getUserById(userId);
|
||||||
if (user?.email) {
|
if (user?.email && user?.globalRole) {
|
||||||
|
if (!['admin', 'member'].includes(user.globalRole.name)) {
|
||||||
|
throw new Error('Invalid role name on reinvite');
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this.usersStore.reinviteUser({ email: user.email });
|
await this.usersStore.reinviteUser({
|
||||||
|
email: user.email,
|
||||||
|
role: user.globalRole.name as InvitableRoleName,
|
||||||
|
});
|
||||||
this.showToast({
|
this.showToast({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: this.$locale.baseText('settings.users.inviteResent'),
|
title: this.$locale.baseText('settings.users.inviteResent'),
|
||||||
|
|
Loading…
Reference in a new issue