mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cb219a8e5a | |||
| a41a007a82 |
@@ -1,9 +1,6 @@
|
||||
import Router from "koa-router";
|
||||
import { NotificationEventType } from "@shared/types";
|
||||
import { parseDomain } from "@shared/utils/domains";
|
||||
import InviteAcceptedEmail from "@server/emails/templates/InviteAcceptedEmail";
|
||||
import SigninEmail from "@server/emails/templates/SigninEmail";
|
||||
import WelcomeEmail from "@server/emails/templates/WelcomeEmail";
|
||||
import env from "@server/env";
|
||||
import { AuthorizationError } from "@server/errors";
|
||||
import { rateLimiter } from "@server/middlewares/rateLimiter";
|
||||
@@ -117,26 +114,6 @@ router.get(
|
||||
return ctx.redirect("/?notice=user-suspended");
|
||||
}
|
||||
|
||||
if (user.isInvited) {
|
||||
await new WelcomeEmail({
|
||||
to: user.email,
|
||||
role: user.role,
|
||||
teamUrl: user.team.url,
|
||||
}).schedule();
|
||||
|
||||
const inviter = await user.$get("invitedBy");
|
||||
if (
|
||||
inviter?.subscribedToEventType(NotificationEventType.InviteAccepted)
|
||||
) {
|
||||
await new InviteAcceptedEmail({
|
||||
to: inviter.email,
|
||||
inviterId: inviter.id,
|
||||
invitedName: user.name,
|
||||
teamUrl: user.team.url,
|
||||
}).schedule();
|
||||
}
|
||||
}
|
||||
|
||||
// set cookies on response and redirect to team subdomain
|
||||
await signIn(ctx, "email", {
|
||||
user,
|
||||
|
||||
@@ -25,6 +25,11 @@ describe("WebhookProcessor", () => {
|
||||
teamId: subscription.teamId,
|
||||
actorId: signedInUser.id,
|
||||
ip,
|
||||
data: {
|
||||
inviteAccepted: false,
|
||||
name: signedInUser.name,
|
||||
service: "google",
|
||||
},
|
||||
};
|
||||
|
||||
await processor.perform(event);
|
||||
@@ -49,6 +54,11 @@ describe("WebhookProcessor", () => {
|
||||
teamId: subscription.teamId,
|
||||
actorId: signedInUser.id,
|
||||
ip,
|
||||
data: {
|
||||
inviteAccepted: false,
|
||||
name: signedInUser.name,
|
||||
service: "google",
|
||||
},
|
||||
};
|
||||
|
||||
await processor.perform(event);
|
||||
@@ -75,6 +85,11 @@ describe("WebhookProcessor", () => {
|
||||
teamId: subscription.teamId,
|
||||
actorId: signedInUser.id,
|
||||
ip,
|
||||
data: {
|
||||
inviteAccepted: false,
|
||||
name: signedInUser.name,
|
||||
service: "google",
|
||||
},
|
||||
};
|
||||
|
||||
await processor.perform(event);
|
||||
|
||||
@@ -34,6 +34,11 @@ describe("DeliverWebhookTask", () => {
|
||||
teamId: subscription.teamId,
|
||||
actorId: signedInUser.id,
|
||||
ip,
|
||||
data: {
|
||||
inviteAccepted: false,
|
||||
name: signedInUser.name,
|
||||
service: "google",
|
||||
},
|
||||
};
|
||||
await processor.perform({
|
||||
subscriptionId: subscription.id,
|
||||
@@ -79,6 +84,11 @@ describe("DeliverWebhookTask", () => {
|
||||
teamId: subscription.teamId,
|
||||
actorId: signedInUser.id,
|
||||
ip,
|
||||
data: {
|
||||
inviteAccepted: false,
|
||||
name: signedInUser.name,
|
||||
service: "google",
|
||||
},
|
||||
};
|
||||
await processor.perform({
|
||||
subscriptionId: subscription.id,
|
||||
@@ -156,6 +166,11 @@ describe("DeliverWebhookTask", () => {
|
||||
teamId: subscription.teamId,
|
||||
actorId: signedInUser.id,
|
||||
ip,
|
||||
data: {
|
||||
inviteAccepted: false,
|
||||
name: signedInUser.name,
|
||||
service: "google",
|
||||
},
|
||||
};
|
||||
|
||||
await task.perform({
|
||||
@@ -204,6 +219,11 @@ describe("DeliverWebhookTask", () => {
|
||||
teamId: subscription.teamId,
|
||||
actorId: signedInUser.id,
|
||||
ip,
|
||||
data: {
|
||||
inviteAccepted: false,
|
||||
name: signedInUser.name,
|
||||
service: "google",
|
||||
},
|
||||
};
|
||||
|
||||
await task.perform({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import WelcomeEmail from "@server/emails/templates/WelcomeEmail";
|
||||
import { TeamDomain } from "@server/models";
|
||||
import Collection from "@server/models/Collection";
|
||||
import UserAuthentication from "@server/models/UserAuthentication";
|
||||
@@ -13,7 +12,6 @@ describe("accountProvisioner", () => {
|
||||
|
||||
describe("hosted", () => {
|
||||
it("should create a new user and team", async () => {
|
||||
const spy = jest.spyOn(WelcomeEmail.prototype, "schedule");
|
||||
const email = faker.internet.email().toLowerCase();
|
||||
const { user, team, isNewTeam, isNewUser } = await accountProvisioner({
|
||||
ip,
|
||||
@@ -46,19 +44,15 @@ describe("accountProvisioner", () => {
|
||||
expect(user.email).toEqual(email);
|
||||
expect(isNewUser).toEqual(true);
|
||||
expect(isNewTeam).toEqual(true);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
const collectionCount = await Collection.count({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
},
|
||||
});
|
||||
expect(collectionCount).toEqual(1);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("should update exising user and authentication", async () => {
|
||||
const spy = jest.spyOn(WelcomeEmail.prototype, "schedule");
|
||||
const existingTeam = await buildTeam();
|
||||
const providers = await existingTeam.$get("authenticationProviders");
|
||||
const authenticationProvider = providers[0];
|
||||
@@ -97,9 +91,6 @@ describe("accountProvisioner", () => {
|
||||
expect(user.email).toEqual(newEmail);
|
||||
expect(isNewTeam).toEqual(false);
|
||||
expect(isNewUser).toEqual(false);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("should allow authentication by email matching", async () => {
|
||||
@@ -236,7 +227,6 @@ describe("accountProvisioner", () => {
|
||||
});
|
||||
|
||||
it("should create a new user in an existing team when the domain is allowed", async () => {
|
||||
const spy = jest.spyOn(WelcomeEmail.prototype, "schedule");
|
||||
const team = await buildTeam();
|
||||
const admin = await buildAdmin({ teamId: team.id });
|
||||
const authenticationProviders = await team.$get(
|
||||
@@ -279,7 +269,6 @@ describe("accountProvisioner", () => {
|
||||
expect(auth.scopes[0]).toEqual("read");
|
||||
expect(user.email).toEqual(email);
|
||||
expect(isNewUser).toEqual(true);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
// should provision welcome collection
|
||||
const collectionCount = await Collection.count({
|
||||
where: {
|
||||
@@ -287,12 +276,9 @@ describe("accountProvisioner", () => {
|
||||
},
|
||||
});
|
||||
expect(collectionCount).toEqual(1);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("should create a new user in an existing team", async () => {
|
||||
const spy = jest.spyOn(WelcomeEmail.prototype, "schedule");
|
||||
const team = await buildTeam();
|
||||
const authenticationProviders = await team.$get(
|
||||
"authenticationProviders"
|
||||
@@ -328,7 +314,6 @@ describe("accountProvisioner", () => {
|
||||
expect(auth.scopes[0]).toEqual("read");
|
||||
expect(user.email).toEqual(email);
|
||||
expect(isNewUser).toEqual(true);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
// should provision welcome collection
|
||||
const collectionCount = await Collection.count({
|
||||
where: {
|
||||
@@ -336,8 +321,6 @@ describe("accountProvisioner", () => {
|
||||
},
|
||||
});
|
||||
expect(collectionCount).toEqual(1);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import path from "path";
|
||||
import { readFile } from "fs-extra";
|
||||
import invariant from "invariant";
|
||||
import { CollectionPermission, UserRole } from "@shared/types";
|
||||
import WelcomeEmail from "@server/emails/templates/WelcomeEmail";
|
||||
import env from "@server/env";
|
||||
import {
|
||||
InvalidAuthenticationError,
|
||||
@@ -160,15 +159,6 @@ async function accountProvisioner({
|
||||
});
|
||||
const { isNewUser, user } = result;
|
||||
|
||||
// TODO: Move to processor
|
||||
if (isNewUser) {
|
||||
await new WelcomeEmail({
|
||||
to: user.email,
|
||||
role: user.role,
|
||||
teamUrl: team.url,
|
||||
}).schedule();
|
||||
}
|
||||
|
||||
if (isNewUser || isNewTeam) {
|
||||
let provision = isNewTeam;
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import WelcomeEmail from "@server/emails/templates/WelcomeEmail";
|
||||
import { User } from "@server/models";
|
||||
import { Event as TEvent, UserEvent } from "@server/types";
|
||||
import BaseProcessor from "./BaseProcessor";
|
||||
|
||||
export default class UserCreatedProcessor extends BaseProcessor {
|
||||
static applicableEvents: TEvent["name"][] = ["users.create"];
|
||||
|
||||
async perform(event: UserEvent) {
|
||||
const user = await User.scope("withTeam").findByPk(event.userId, {
|
||||
rejectOnEmpty: true,
|
||||
});
|
||||
|
||||
if (user.isInvited) {
|
||||
return;
|
||||
}
|
||||
await new WelcomeEmail({
|
||||
to: user.email,
|
||||
role: user.role,
|
||||
teamUrl: user.team.url,
|
||||
}).schedule();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { NotificationEventType } from "@shared/types";
|
||||
import InviteAcceptedEmail from "@server/emails/templates/InviteAcceptedEmail";
|
||||
import WelcomeEmail from "@server/emails/templates/WelcomeEmail";
|
||||
import { User } from "@server/models";
|
||||
import { Event as TEvent, UserEvent } from "@server/types";
|
||||
import BaseProcessor from "./BaseProcessor";
|
||||
|
||||
export default class UserSigninProcessor extends BaseProcessor {
|
||||
static applicableEvents: TEvent["name"][] = ["users.signin"];
|
||||
|
||||
async perform(event: UserEvent) {
|
||||
const inviteAccepted =
|
||||
"data" in event &&
|
||||
"inviteAccepted" in event.data &&
|
||||
event.data.inviteAccepted === true;
|
||||
if (!inviteAccepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await User.scope("withTeam").findByPk(event.userId, {
|
||||
rejectOnEmpty: true,
|
||||
});
|
||||
|
||||
await new WelcomeEmail({
|
||||
to: user.email,
|
||||
role: user.role,
|
||||
teamUrl: user.team.url,
|
||||
}).schedule();
|
||||
|
||||
const inviter = await user.$get("invitedBy");
|
||||
if (inviter?.subscribedToEventType(NotificationEventType.InviteAccepted)) {
|
||||
await new InviteAcceptedEmail({
|
||||
to: inviter.email,
|
||||
inviterId: inviter.id,
|
||||
invitedName: user.name,
|
||||
teamUrl: user.team.url,
|
||||
}).schedule();
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-1
@@ -141,7 +141,6 @@ export type UserEvent = BaseEvent<User> &
|
||||
(
|
||||
| {
|
||||
name:
|
||||
| "users.signin"
|
||||
| "users.signout"
|
||||
| "users.update"
|
||||
| "users.suspend"
|
||||
@@ -164,6 +163,15 @@ export type UserEvent = BaseEvent<User> &
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
name: "users.signin";
|
||||
userId: string;
|
||||
data: {
|
||||
inviteAccepted: boolean;
|
||||
name: string;
|
||||
service: string;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export type UserMembershipEvent = BaseEvent<UserMembership> & {
|
||||
|
||||
@@ -59,6 +59,8 @@ export async function signIn(
|
||||
}
|
||||
}
|
||||
|
||||
const inviteAccepted = user.isInvited;
|
||||
|
||||
// update the database when the user last signed in
|
||||
await user.updateSignedIn(ctx.request.ip);
|
||||
|
||||
@@ -70,6 +72,7 @@ export async function signIn(
|
||||
teamId: team.id,
|
||||
authType: AuthenticationType.APP,
|
||||
data: {
|
||||
inviteAccepted,
|
||||
name: user.name,
|
||||
service,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user