mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
refactor: Use POST /users to re-invite users (no-changelog) (#7714)
This commit is contained in:
parent
3460eb5eeb
commit
4020c14d59
|
@ -590,78 +590,4 @@ export class UsersController {
|
||||||
await this.externalHooks.run('user.deleted', [await this.userService.toPublic(userToDelete)]);
|
await this.externalHooks.run('user.deleted', [await this.userService.toPublic(userToDelete)]);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resend email invite to user.
|
|
||||||
*/
|
|
||||||
@Post('/:id/reinvite')
|
|
||||||
async reinviteUser(req: UserRequest.Reinvite) {
|
|
||||||
const { id: idToReinvite } = req.params;
|
|
||||||
const isWithinUsersLimit = Container.get(License).isWithinUsersLimit();
|
|
||||||
|
|
||||||
if (!isWithinUsersLimit) {
|
|
||||||
this.logger.debug(
|
|
||||||
'Request to send email invite(s) to user(s) failed because the user limit quota has been reached',
|
|
||||||
);
|
|
||||||
throw new UnauthorizedError(RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.mailer.isEmailSetUp) {
|
|
||||||
this.logger.error('Request to reinvite a user failed because email sending was not set up');
|
|
||||||
throw new InternalServerError('Email sending must be set up in order to invite other users');
|
|
||||||
}
|
|
||||||
|
|
||||||
const reinvitee = await this.userService.findOneBy({ id: idToReinvite });
|
|
||||||
if (!reinvitee) {
|
|
||||||
this.logger.debug(
|
|
||||||
'Request to reinvite a user failed because the ID of the reinvitee was not found in database',
|
|
||||||
);
|
|
||||||
throw new NotFoundError('Could not find user');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reinvitee.password) {
|
|
||||||
this.logger.debug(
|
|
||||||
'Request to reinvite a user failed because the invite had already been accepted',
|
|
||||||
{ userId: reinvitee.id },
|
|
||||||
);
|
|
||||||
throw new BadRequestError('User has already accepted the invite');
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseUrl = getInstanceBaseUrl();
|
|
||||||
const inviteAcceptUrl = `${baseUrl}/signup?inviterId=${req.user.id}&inviteeId=${reinvitee.id}`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await this.mailer.invite({
|
|
||||||
email: reinvitee.email,
|
|
||||||
inviteAcceptUrl,
|
|
||||||
domain: baseUrl,
|
|
||||||
});
|
|
||||||
if (result.emailSent) {
|
|
||||||
void this.internalHooks.onUserReinvite({
|
|
||||||
user: req.user,
|
|
||||||
target_user_id: reinvitee.id,
|
|
||||||
public_api: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
void this.internalHooks.onUserTransactionalEmail({
|
|
||||||
user_id: reinvitee.id,
|
|
||||||
message_type: 'Resend invite',
|
|
||||||
public_api: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
void this.internalHooks.onEmailFailed({
|
|
||||||
user: reinvitee,
|
|
||||||
message_type: 'Resend invite',
|
|
||||||
public_api: false,
|
|
||||||
});
|
|
||||||
this.logger.error('Failed to send email', {
|
|
||||||
email: reinvitee.email,
|
|
||||||
inviteAcceptUrl,
|
|
||||||
domain: baseUrl,
|
|
||||||
});
|
|
||||||
throw new InternalServerError(`Failed to send email to ${reinvitee.email}`);
|
|
||||||
}
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ describe('Auth Middleware', () => {
|
||||||
const ROUTES_REQUIRING_AUTHORIZATION: Readonly<Array<[string, string]>> = [
|
const ROUTES_REQUIRING_AUTHORIZATION: Readonly<Array<[string, string]>> = [
|
||||||
['POST', '/users'],
|
['POST', '/users'],
|
||||||
['DELETE', '/users/123'],
|
['DELETE', '/users/123'],
|
||||||
['POST', '/users/123/reinvite'],
|
|
||||||
['POST', '/owner/setup'],
|
['POST', '/owner/setup'],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -655,26 +655,3 @@ describe('POST /users', () => {
|
||||||
assertInviteUserErrorResponse(invitationResponse);
|
assertInviteUserErrorResponse(invitationResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /users/:id/reinvite', () => {
|
|
||||||
test('should send reinvite, but fail if user already accepted invite', async () => {
|
|
||||||
mailer.invite.mockImplementation(async () => ({ emailSent: true }));
|
|
||||||
|
|
||||||
const email = randomEmail();
|
|
||||||
const payload = [{ email }];
|
|
||||||
const response = await authOwnerAgent.post('/users').send(payload);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const { data } = response.body;
|
|
||||||
const invitedUserId = data[0].user.id;
|
|
||||||
const reinviteResponse = await authOwnerAgent.post(`/users/${invitedUserId}/reinvite`);
|
|
||||||
|
|
||||||
expect(reinviteResponse.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const member = await createMember();
|
|
||||||
const reinviteMemberResponse = await authOwnerAgent.post(`/users/${member.id}/reinvite`);
|
|
||||||
|
|
||||||
expect(reinviteMemberResponse.statusCode).toBe(400);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {
|
||||||
login,
|
login,
|
||||||
loginCurrentUser,
|
loginCurrentUser,
|
||||||
logout,
|
logout,
|
||||||
reinvite,
|
|
||||||
sendForgotPasswordEmail,
|
sendForgotPasswordEmail,
|
||||||
setupOwner,
|
setupOwner,
|
||||||
signup,
|
signup,
|
||||||
|
@ -328,9 +327,14 @@ export const useUsersStore = defineStore(STORES.USERS, {
|
||||||
this.addUsers(users.map(({ user }) => ({ isPending: true, ...user })));
|
this.addUsers(users.map(({ user }) => ({ isPending: true, ...user })));
|
||||||
return users;
|
return users;
|
||||||
},
|
},
|
||||||
async reinviteUser(params: { id: string }): Promise<void> {
|
async reinviteUser(params: { email: string }): Promise<void> {
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
await reinvite(rootStore.getRestApiContext, params);
|
const invitationResponse = await inviteUsers(rootStore.getRestApiContext, [
|
||||||
|
{ email: params.email },
|
||||||
|
]);
|
||||||
|
if (!invitationResponse[0].user.emailSent) {
|
||||||
|
throw Error(invitationResponse[0].error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async getUserInviteLink(params: { id: string }): Promise<{ link: string }> {
|
async getUserInviteLink(params: { id: string }): Promise<{ link: string }> {
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
|
|
|
@ -144,15 +144,14 @@ 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) {
|
if (user?.email) {
|
||||||
try {
|
try {
|
||||||
await this.usersStore.reinviteUser({ id: user.id });
|
await this.usersStore.reinviteUser({ email: user.email });
|
||||||
|
|
||||||
this.showToast({
|
this.showToast({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: this.$locale.baseText('settings.users.inviteResent'),
|
title: this.$locale.baseText('settings.users.inviteResent'),
|
||||||
message: this.$locale.baseText('settings.users.emailSentTo', {
|
message: this.$locale.baseText('settings.users.emailSentTo', {
|
||||||
interpolate: { email: user.email || '' },
|
interpolate: { email: user.email ?? '' },
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
Loading…
Reference in a new issue