refactor: Use POST /users to re-invite users (no-changelog) (#7714)

This commit is contained in:
Ricardo Espinoza 2023-11-15 06:40:57 -05:00 committed by GitHub
parent 3460eb5eeb
commit 4020c14d59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 10 additions and 105 deletions

View file

@ -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 };
}
} }

View file

@ -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'],
]; ];

View file

@ -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);
});
});

View file

@ -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();

View file

@ -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) {