fix: Welcome emails should not be sent when inviting a user (#2132)
* chore: Bump nodemailer * fix: Welcome email sent to invites * test: Add regression test for emails from accountProvisioner
This commit is contained in:
@ -56,7 +56,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12 <=15"
|
"node": ">= 12 <=16"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -3,7 +3,7 @@ import subMinutes from "date-fns/sub_minutes";
|
|||||||
import Router from "koa-router";
|
import Router from "koa-router";
|
||||||
import { find } from "lodash";
|
import { find } from "lodash";
|
||||||
import { AuthorizationError } from "../../errors";
|
import { AuthorizationError } from "../../errors";
|
||||||
import mailer from "../../mailer";
|
import mailer, { sendEmail } from "../../mailer";
|
||||||
import methodOverride from "../../middlewares/methodOverride";
|
import methodOverride from "../../middlewares/methodOverride";
|
||||||
import validation from "../../middlewares/validation";
|
import validation from "../../middlewares/validation";
|
||||||
import { User, Team } from "../../models";
|
import { User, Team } from "../../models";
|
||||||
@ -97,6 +97,9 @@ router.get("email.callback", async (ctx) => {
|
|||||||
if (user.isSuspended) {
|
if (user.isSuspended) {
|
||||||
return ctx.redirect("/?notice=suspended");
|
return ctx.redirect("/?notice=suspended");
|
||||||
}
|
}
|
||||||
|
if (user.isInvited) {
|
||||||
|
sendEmail("welcome", user.email, { teamUrl: user.team.url });
|
||||||
|
}
|
||||||
|
|
||||||
await user.update({ lastActiveAt: new Date() });
|
await user.update({ lastActiveAt: new Date() });
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
EmailAuthenticationRequiredError,
|
EmailAuthenticationRequiredError,
|
||||||
AuthenticationProviderDisabledError,
|
AuthenticationProviderDisabledError,
|
||||||
} from "../errors";
|
} from "../errors";
|
||||||
|
import { sendEmail } from "../mailer";
|
||||||
import { Team, User } from "../models";
|
import { Team, User } from "../models";
|
||||||
import teamCreator from "./teamCreator";
|
import teamCreator from "./teamCreator";
|
||||||
import userCreator from "./userCreator";
|
import userCreator from "./userCreator";
|
||||||
@ -85,6 +86,10 @@ export default async function accountProvisioner({
|
|||||||
|
|
||||||
const { isNewUser, user } = result;
|
const { isNewUser, user } = result;
|
||||||
|
|
||||||
|
if (isNewUser) {
|
||||||
|
sendEmail("welcome", user.email, { teamUrl: team.url });
|
||||||
|
}
|
||||||
|
|
||||||
if (isNewTeam) {
|
if (isNewTeam) {
|
||||||
await team.provisionFirstCollection(user.id);
|
await team.provisionFirstCollection(user.id);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
import { sendEmail } from "../mailer";
|
||||||
import { Collection, UserAuthentication } from "../models";
|
import { Collection, UserAuthentication } from "../models";
|
||||||
import { buildUser, buildTeam } from "../test/factories";
|
import { buildUser, buildTeam } from "../test/factories";
|
||||||
import { flushdb } from "../test/support";
|
import { flushdb } from "../test/support";
|
||||||
import accountProvisioner from "./accountProvisioner";
|
import accountProvisioner from "./accountProvisioner";
|
||||||
|
|
||||||
|
jest.mock("../mailer");
|
||||||
|
|
||||||
jest.mock("aws-sdk", () => {
|
jest.mock("aws-sdk", () => {
|
||||||
const mS3 = { putObject: jest.fn().mockReturnThis(), promise: jest.fn() };
|
const mS3 = { putObject: jest.fn().mockReturnThis(), promise: jest.fn() };
|
||||||
return {
|
return {
|
||||||
@ -12,7 +15,12 @@ jest.mock("aws-sdk", () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => flushdb());
|
beforeEach(() => {
|
||||||
|
flushdb();
|
||||||
|
|
||||||
|
// $FlowFixMe
|
||||||
|
sendEmail.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
describe("accountProvisioner", () => {
|
describe("accountProvisioner", () => {
|
||||||
const ip = "127.0.0.1";
|
const ip = "127.0.0.1";
|
||||||
@ -51,6 +59,7 @@ describe("accountProvisioner", () => {
|
|||||||
expect(user.email).toEqual("jenny@example.com");
|
expect(user.email).toEqual("jenny@example.com");
|
||||||
expect(isNewUser).toEqual(true);
|
expect(isNewUser).toEqual(true);
|
||||||
expect(isNewTeam).toEqual(true);
|
expect(isNewTeam).toEqual(true);
|
||||||
|
expect(sendEmail).toHaveBeenCalled();
|
||||||
|
|
||||||
const collectionCount = await Collection.count();
|
const collectionCount = await Collection.count();
|
||||||
expect(collectionCount).toEqual(1);
|
expect(collectionCount).toEqual(1);
|
||||||
@ -65,7 +74,7 @@ describe("accountProvisioner", () => {
|
|||||||
const authentication = authentications[0];
|
const authentication = authentications[0];
|
||||||
const newEmail = "test@example.com";
|
const newEmail = "test@example.com";
|
||||||
|
|
||||||
const { user } = await accountProvisioner({
|
const { user, isNewUser, isNewTeam } = await accountProvisioner({
|
||||||
ip,
|
ip,
|
||||||
user: {
|
user: {
|
||||||
name: existing.name,
|
name: existing.name,
|
||||||
@ -93,6 +102,9 @@ describe("accountProvisioner", () => {
|
|||||||
expect(auth.scopes.length).toEqual(1);
|
expect(auth.scopes.length).toEqual(1);
|
||||||
expect(auth.scopes[0]).toEqual("read");
|
expect(auth.scopes[0]).toEqual("read");
|
||||||
expect(user.email).toEqual(newEmail);
|
expect(user.email).toEqual(newEmail);
|
||||||
|
expect(isNewTeam).toEqual(false);
|
||||||
|
expect(isNewUser).toEqual(false);
|
||||||
|
expect(sendEmail).not.toHaveBeenCalled();
|
||||||
|
|
||||||
const collectionCount = await Collection.count();
|
const collectionCount = await Collection.count();
|
||||||
expect(collectionCount).toEqual(0);
|
expect(collectionCount).toEqual(0);
|
||||||
@ -175,6 +187,7 @@ describe("accountProvisioner", () => {
|
|||||||
expect(auth.scopes[0]).toEqual("read");
|
expect(auth.scopes[0]).toEqual("read");
|
||||||
expect(user.email).toEqual("jenny@example.com");
|
expect(user.email).toEqual("jenny@example.com");
|
||||||
expect(isNewUser).toEqual(true);
|
expect(isNewUser).toEqual(true);
|
||||||
|
expect(sendEmail).toHaveBeenCalled();
|
||||||
|
|
||||||
const collectionCount = await Collection.count();
|
const collectionCount = await Collection.count();
|
||||||
expect(collectionCount).toEqual(0);
|
expect(collectionCount).toEqual(0);
|
||||||
|
@ -117,7 +117,7 @@ export default async function userCreator({
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { user: invite, authentication: auth, isNewUser: false };
|
return { user: invite, authentication: auth, isNewUser: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
// No auth, no user – this is an entirely new sign in.
|
// No auth, no user – this is an entirely new sign in.
|
||||||
|
@ -145,6 +145,6 @@ describe("userCreator", () => {
|
|||||||
expect(authentication.scopes.length).toEqual(1);
|
expect(authentication.scopes.length).toEqual(1);
|
||||||
expect(authentication.scopes[0]).toEqual("read");
|
expect(authentication.scopes[0]).toEqual("read");
|
||||||
expect(user.email).toEqual(invite.email);
|
expect(user.email).toEqual(invite.email);
|
||||||
expect(isNewUser).toEqual(false);
|
expect(isNewUser).toEqual(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,14 +6,12 @@ import JWT from "jsonwebtoken";
|
|||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { languages } from "../../shared/i18n";
|
import { languages } from "../../shared/i18n";
|
||||||
import { ValidationError } from "../errors";
|
import { ValidationError } from "../errors";
|
||||||
import { sendEmail } from "../mailer";
|
|
||||||
import { DataTypes, sequelize, encryptedFields, Op } from "../sequelize";
|
import { DataTypes, sequelize, encryptedFields, Op } from "../sequelize";
|
||||||
import { DEFAULT_AVATAR_HOST } from "../utils/avatars";
|
import { DEFAULT_AVATAR_HOST } from "../utils/avatars";
|
||||||
import { publicS3Endpoint, uploadToS3FromUrl } from "../utils/s3";
|
import { publicS3Endpoint, uploadToS3FromUrl } from "../utils/s3";
|
||||||
import {
|
import {
|
||||||
UserAuthentication,
|
UserAuthentication,
|
||||||
Star,
|
Star,
|
||||||
Team,
|
|
||||||
Collection,
|
Collection,
|
||||||
NotificationSetting,
|
NotificationSetting,
|
||||||
ApiKey,
|
ApiKey,
|
||||||
@ -236,10 +234,6 @@ const removeIdentifyingInfo = async (model, options) => {
|
|||||||
User.beforeDestroy(removeIdentifyingInfo);
|
User.beforeDestroy(removeIdentifyingInfo);
|
||||||
User.beforeSave(uploadAvatar);
|
User.beforeSave(uploadAvatar);
|
||||||
User.beforeCreate(setRandomJwtSecret);
|
User.beforeCreate(setRandomJwtSecret);
|
||||||
User.afterCreate(async (user) => {
|
|
||||||
const team = await Team.findByPk(user.teamId);
|
|
||||||
sendEmail("welcome", user.email, { teamUrl: team.url });
|
|
||||||
});
|
|
||||||
|
|
||||||
// By default when a user signs up we subscribe them to email notifications
|
// By default when a user signs up we subscribe them to email notifications
|
||||||
// when documents they created are edited by other team members and onboarding
|
// when documents they created are edited by other team members and onboarding
|
||||||
|
Reference in New Issue
Block a user