From 5d5fe66e77d50940b23768278b5e5d39a194d744 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sun, 16 Oct 2022 11:20:46 -0400 Subject: [PATCH] fix: Logging in with email on a subdomain should not forward to other subdomains (#4305) --- server/routes/auth/providers/email.test.ts | 25 ++++- server/routes/auth/providers/email.ts | 114 +++++++++------------ 2 files changed, 68 insertions(+), 71 deletions(-) diff --git a/server/routes/auth/providers/email.test.ts b/server/routes/auth/providers/email.test.ts index f659a8fd4..63aa332e5 100644 --- a/server/routes/auth/providers/email.test.ts +++ b/server/routes/auth/providers/email.test.ts @@ -33,9 +33,11 @@ describe("email", () => { spy.mockRestore(); }); - it("should respond with redirect location when user is SSO enabled on another subdomain", async () => { + it("should not send email when user is on another subdomain but respond with success", async () => { env.URL = sharedEnv.URL = "http://localoutline.com"; env.SUBDOMAINS_ENABLED = sharedEnv.SUBDOMAINS_ENABLED = true; + env.DEPLOYMENT = "hosted"; + const user = await buildUser(); const spy = jest.spyOn(WelcomeEmail, "schedule"); await buildTeam({ @@ -49,20 +51,29 @@ describe("email", () => { host: "example.localoutline.com", }, }); + const body = await res.json(); expect(res.status).toEqual(200); - expect(body.redirect).toMatch("slack"); + expect(body.success).toEqual(true); expect(spy).not.toHaveBeenCalled(); spy.mockRestore(); }); - it("should respond with success when user is not SSO enabled", async () => { + it("should respond with success and email to be sent when user is not SSO enabled", async () => { const spy = jest.spyOn(SigninEmail, "schedule"); - const user = await buildGuestUser(); + const team = await buildTeam({ + subdomain: "example", + }); + const user = await buildGuestUser({ + teamId: team.id, + }); const res = await server.post("/auth/email", { body: { email: user.email, }, + headers: { + host: "example.localoutline.com", + }, }); const body = await res.json(); expect(res.status).toEqual(200); @@ -73,10 +84,16 @@ describe("email", () => { it("should respond with success regardless of whether successful to prevent crawling email logins", async () => { const spy = jest.spyOn(WelcomeEmail, "schedule"); + await buildTeam({ + subdomain: "example", + }); const res = await server.post("/auth/email", { body: { email: "user@example.com", }, + headers: { + host: "example.localoutline.com", + }, }); const body = await res.json(); expect(res.status).toEqual(200); diff --git a/server/routes/auth/providers/email.ts b/server/routes/auth/providers/email.ts index 39227711c..a614163c8 100644 --- a/server/routes/auth/providers/email.ts +++ b/server/routes/auth/providers/email.ts @@ -31,83 +31,63 @@ router.post( async (ctx) => { const { email } = ctx.body; assertEmail(email, "email is required"); - const users = await User.scope("withAuthentications").findAll({ + + const domain = parseDomain(ctx.request.hostname); + + let team: Team | null | undefined; + if (env.DEPLOYMENT !== "hosted") { + team = await Team.scope("withAuthenticationProviders").findOne(); + } else if (domain.custom) { + team = await Team.scope("withAuthenticationProviders").findOne({ + where: { domain: domain.host }, + }); + } else if (env.SUBDOMAINS_ENABLED && domain.teamSubdomain) { + team = await Team.scope("withAuthenticationProviders").findOne({ + where: { subdomain: domain.teamSubdomain }, + }); + } + + if (!team?.emailSigninEnabled) { + throw AuthorizationError(); + } + + const user = await User.scope("withAuthentications").findOne({ where: { + teamId: team.id, email: email.toLowerCase(), }, }); - if (users.length) { - let team!: Team | null; - const domain = parseDomain(ctx.request.hostname); + if (!user) { + ctx.body = { + success: true, + }; + return; + } - if (domain.custom) { - team = await Team.scope("withAuthenticationProviders").findOne({ - where: { - domain: ctx.request.hostname, - }, - }); - } else if (env.SUBDOMAINS_ENABLED && domain.teamSubdomain) { - team = await Team.scope("withAuthenticationProviders").findOne({ - where: { - subdomain: domain.teamSubdomain, - }, - }); - } - - // If there are multiple users with this email address then give precedence - // to the one that is active on this subdomain/domain (if any) - let user = users.find((user) => team && user.teamId === team.id); - - // A user was found for the email address, but they don't belong to the team - // that this subdomain belongs to, we load their team and allow the logic to - // continue - if (!user) { - user = users[0]; - team = await Team.scope("withAuthenticationProviders").findByPk( - user.teamId - ); - } - - if (!team) { - team = await Team.scope("withAuthenticationProviders").findByPk( - user.teamId - ); - } - - if (!team) { - ctx.redirect(`/?notice=auth-error`); + // If the user matches an email address associated with an SSO + // provider then just forward them directly to that sign-in page + if (user.authentications.length) { + const authProvider = find(team.authenticationProviders, { + id: user.authentications[0].authenticationProviderId, + }); + if (authProvider?.enabled) { + ctx.body = { + redirect: `${team.url}/auth/${authProvider?.name}`, + }; return; } - - // If the user matches an email address associated with an SSO - // provider then just forward them directly to that sign-in page - if (user.authentications.length) { - const authProvider = find(team.authenticationProviders, { - id: user.authentications[0].authenticationProviderId, - }); - if (authProvider?.enabled) { - ctx.body = { - redirect: `${team.url}/auth/${authProvider?.name}`, - }; - return; - } - } - - if (!team.emailSigninEnabled) { - throw AuthorizationError(); - } - - // send email to users registered address with a short-lived token - await SigninEmail.schedule({ - to: user.email, - token: user.getEmailSigninToken(), - teamUrl: team.url, - }); - user.lastSigninEmailSentAt = new Date(); - await user.save(); } + // send email to users registered address with a short-lived token + await SigninEmail.schedule({ + to: user.email, + token: user.getEmailSigninToken(), + teamUrl: team.url, + }); + user.lastSigninEmailSentAt = new Date(); + await user.save(); + // respond with success regardless of whether an email was sent ctx.body = { success: true,