fix: Fix user reinvites on FE and BE (#8261)

This commit is contained in:
Iván Ovejero 2024-01-09 13:52:34 +01:00 committed by GitHub
parent b1c1372bc2
commit 0dabe5c74e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 50 additions and 13 deletions

View file

@ -270,7 +270,7 @@ export class UserService {
const usersInvited = await this.sendEmails(
owner,
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) };

View file

@ -346,6 +346,29 @@ describe('POST /invitations', () => {
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 () => {
license.isAdvancedPermissionsLicensed.mockReturnValue(false);
mailer.invite.mockResolvedValue({ emailSent: false });

View file

@ -687,6 +687,8 @@ export type IPersonalizationSurveyVersions =
export type IRole = 'default' | 'owner' | 'member' | 'admin';
export type InvitableRoleName = 'member' | 'admin';
export interface IUserResponse {
id: string;
firstName?: string;

View file

@ -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 { makeRestApiRequest } from '@/utils/apiUtils';
@ -12,7 +17,7 @@ type AcceptInvitationParams = {
export async function inviteUsers(
context: IRestApiContext,
params: Array<{ email: string; role: IRole }>,
params: Array<{ email: string; role: InvitableRoleName }>,
) {
return makeRestApiRequest<IInviteResponse[]>(context, 'POST', '/invitations', params);
}

View file

@ -29,6 +29,7 @@ import type {
IUserResponse,
IUsersState,
CurrentUserResponse,
InvitableRoleName,
} from '@/Interface';
import { getCredentialPermissions } from '@/permissions';
import { getPersonalizedNodeTypes, ROLE } from '@/utils/userUtils';
@ -302,7 +303,9 @@ export const useUsersStore = defineStore(STORES.USERS, {
const users = await getUsers(rootStore.getRestApiContext);
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 users = await inviteUsers(rootStore.getRestApiContext, params);
this.addUsers(
@ -314,11 +317,9 @@ export const useUsersStore = defineStore(STORES.USERS, {
);
return users;
},
async reinviteUser(params: { email: string }): Promise<void> {
async reinviteUser({ email, role }: { email: string; role: InvitableRoleName }): Promise<void> {
const rootStore = useRootStore();
const invitationResponse = await inviteUsers(rootStore.getRestApiContext, [
{ email: params.email },
]);
const invitationResponse = await inviteUsers(rootStore.getRestApiContext, [{ email, role }]);
if (!invitationResponse[0].user.emailSent) {
throw Error(invitationResponse[0].error);
}

View file

@ -63,7 +63,7 @@ export async function request(config: {
baseURL: string;
endpoint: string;
headers?: IDataObject;
data?: IDataObject;
data?: IDataObject | IDataObject[];
withCredentials?: boolean;
}) {
const { method, baseURL, endpoint, headers, data } = config;
@ -119,7 +119,7 @@ export async function makeRestApiRequest<T>(
context: IRestApiContext,
method: Method,
endpoint: string,
data?: IDataObject,
data?: IDataObject | IDataObject[],
) {
const response = await request({
method,

View file

@ -89,7 +89,7 @@ import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
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 { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store';
@ -207,9 +207,15 @@ export default defineComponent({
},
async onReinvite(userId: string) {
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 {
await this.usersStore.reinviteUser({ email: user.email });
await this.usersStore.reinviteUser({
email: user.email,
role: user.globalRole.name as InvitableRoleName,
});
this.showToast({
type: 'success',
title: this.$locale.baseText('settings.users.inviteResent'),