diff --git a/server/commands/teamProvisioner.ts b/server/commands/teamProvisioner.ts index 53ba1d226..38907a12e 100644 --- a/server/commands/teamProvisioner.ts +++ b/server/commands/teamProvisioner.ts @@ -72,7 +72,7 @@ async function teamProvisioner({ }; } else if (teamId) { // The user is attempting to log into a team with an unfamiliar SSO provider - if (env.DEPLOYMENT === "hosted") { + if (env.isCloudHosted()) { throw InvalidAuthenticationError(); } diff --git a/server/emails/mailer.tsx b/server/emails/mailer.tsx index 0d4dea367..5df80a051 100644 --- a/server/emails/mailer.tsx +++ b/server/emails/mailer.tsx @@ -4,7 +4,6 @@ import Oy from "oy-vey"; import env from "@server/env"; import Logger from "@server/logging/Logger"; import { trace } from "@server/logging/tracing"; -import isCloudHosted from "@server/utils/isCloudHosted"; import { baseStyles } from "./templates/components/EmailLayout"; const useTestEmailService = @@ -79,7 +78,7 @@ export class Mailer { subject: data.subject, html, text: data.text, - attachments: isCloudHosted + attachments: env.isCloudHosted() ? undefined : [ { diff --git a/server/emails/templates/components/Header.tsx b/server/emails/templates/components/Header.tsx index f0848abb9..bd3032c7d 100644 --- a/server/emails/templates/components/Header.tsx +++ b/server/emails/templates/components/Header.tsx @@ -1,7 +1,6 @@ import { Table, TBody, TR, TD } from "oy-vey"; import * as React from "react"; import env from "@server/env"; -import isCloudHosted from "@server/utils/isCloudHosted"; import EmptySpace from "./EmptySpace"; const url = env.CDN_URL ?? env.URL; @@ -16,7 +15,7 @@ export default () => { {env.APP_NAME} (config.id === "email" ? 1 : -1)) + .filter((config) => { + // guest sign-in is an exception as it does not have an authentication + // provider using passport, instead it exists as a boolean option on the team + if (config.id === "email") { + return team?.emailSigninEnabled; + } + if (!team) { + return true; + } + + const authProvider = find(team.authenticationProviders, { + name: config.id, + }); + + // If cloud hosted then the auth provider must be enabled for the team, + // If self-hosted then it must not be actively disabled, otherwise all + // providers are considered. + return ( + (!isCloudHosted && authProvider?.enabled !== false) || + (isCloudHosted && authProvider?.enabled) + ); + }); + } +} diff --git a/server/policies/team.ts b/server/policies/team.ts index d5b07a289..6109a6ea2 100644 --- a/server/policies/team.ts +++ b/server/policies/team.ts @@ -13,7 +13,7 @@ allow(User, "share", Team, (user, team) => { }); allow(User, "createTeam", Team, () => { - if (env.DEPLOYMENT !== "hosted") { + if (!env.isCloudHosted()) { throw IncorrectEditionError("createTeam only available on cloud"); } }); diff --git a/server/presenters/index.ts b/server/presenters/index.ts index bb3c2ab5f..23e6b499b 100644 --- a/server/presenters/index.ts +++ b/server/presenters/index.ts @@ -14,6 +14,7 @@ import presentMembership from "./membership"; import presentNotificationSetting from "./notificationSetting"; import presentPin from "./pin"; import presentPolicies from "./policy"; +import presentProviderConfig from "./providerConfig"; import presentRevision from "./revision"; import presentSearchQuery from "./searchQuery"; import presentShare from "./share"; @@ -43,6 +44,7 @@ export { presentNotificationSetting, presentPin, presentPolicies, + presentProviderConfig, presentRevision, presentSearchQuery, presentShare, diff --git a/server/presenters/providerConfig.ts b/server/presenters/providerConfig.ts new file mode 100644 index 000000000..7d66637c4 --- /dev/null +++ b/server/presenters/providerConfig.ts @@ -0,0 +1,12 @@ +import { signin } from "@shared/utils/urlHelpers"; +import { AuthenticationProviderConfig } from "@server/routes/auth/providers"; + +export default function presentProviderConfig( + config: AuthenticationProviderConfig +) { + return { + id: config.id, + name: config.name, + authUrl: signin(config.id), + }; +} diff --git a/server/routes/api/auth.ts b/server/routes/api/auth.ts index 72e7ec0e4..48c6a543a 100644 --- a/server/routes/api/auth.ts +++ b/server/routes/api/auth.ts @@ -1,55 +1,30 @@ import Router from "koa-router"; -import { find, uniqBy } from "lodash"; +import { uniqBy } from "lodash"; import { TeamPreference } from "@shared/types"; import { parseDomain } from "@shared/utils/domains"; import env from "@server/env"; import auth from "@server/middlewares/authentication"; import { transaction } from "@server/middlewares/transaction"; import { Event, Team } from "@server/models"; +import AuthenticationHelper from "@server/models/helpers/AuthenticationHelper"; import { presentUser, presentTeam, presentPolicies, + presentProviderConfig, presentAvailableTeam, } from "@server/presenters"; import ValidateSSOAccessTask from "@server/queues/tasks/ValidateSSOAccessTask"; import { APIContext } from "@server/types"; import { getSessionsInCookie } from "@server/utils/authentication"; -import providers from "../auth/providers"; const router = new Router(); -function filterProviders(team?: Team) { - return providers - .sort((provider) => (provider.id === "email" ? 1 : -1)) - .filter((provider) => { - // guest sign-in is an exception as it does not have an authentication - // provider using passport, instead it exists as a boolean option on the team - if (provider.id === "email") { - return team?.emailSigninEnabled; - } - - return ( - !team || - env.DEPLOYMENT !== "hosted" || - find(team.authenticationProviders, { - name: provider.id, - enabled: true, - }) - ); - }) - .map((provider) => ({ - id: provider.id, - name: provider.name, - authUrl: provider.authUrl, - })); -} - router.post("auth.config", async (ctx: APIContext) => { // If self hosted AND there is only one team then that team becomes the // brand for the knowledge base and it's guest signin option is used for the // root login page. - if (env.DEPLOYMENT !== "hosted") { + if (!env.isCloudHosted()) { const team = await Team.scope("withAuthenticationProviders").findOne(); if (team) { @@ -59,7 +34,9 @@ router.post("auth.config", async (ctx: APIContext) => { logo: team.getPreference(TeamPreference.PublicBranding) ? team.avatarUrl : undefined, - providers: filterProviders(team), + providers: AuthenticationHelper.providersForTeam(team).map( + presentProviderConfig + ), }, }; return; @@ -83,7 +60,9 @@ router.post("auth.config", async (ctx: APIContext) => { ? team.avatarUrl : undefined, hostname: ctx.request.hostname, - providers: filterProviders(team), + providers: AuthenticationHelper.providersForTeam(team).map( + presentProviderConfig + ), }, }; return; @@ -107,7 +86,9 @@ router.post("auth.config", async (ctx: APIContext) => { ? team.avatarUrl : undefined, hostname: ctx.request.hostname, - providers: filterProviders(team), + providers: AuthenticationHelper.providersForTeam(team).map( + presentProviderConfig + ), }, }; return; @@ -117,7 +98,9 @@ router.post("auth.config", async (ctx: APIContext) => { // Otherwise, we're requesting from the standard root signin page ctx.body = { data: { - providers: filterProviders(), + providers: AuthenticationHelper.providersForTeam().map( + presentProviderConfig + ), }, }; }); diff --git a/server/routes/auth/providers/email.ts b/server/routes/auth/providers/email.ts index 851a93047..47f40dc48 100644 --- a/server/routes/auth/providers/email.ts +++ b/server/routes/auth/providers/email.ts @@ -31,7 +31,7 @@ router.post( const domain = parseDomain(ctx.request.hostname); let team: Team | null | undefined; - if (env.DEPLOYMENT !== "hosted") { + if (!env.isCloudHosted()) { team = await Team.scope("withAuthenticationProviders").findOne(); } else if (domain.custom) { team = await Team.scope("withAuthenticationProviders").findOne({ diff --git a/server/routes/auth/providers/index.ts b/server/routes/auth/providers/index.ts index a013dc02d..e11af6652 100644 --- a/server/routes/auth/providers/index.ts +++ b/server/routes/auth/providers/index.ts @@ -1,17 +1,15 @@ import Router from "koa-router"; import { sortBy } from "lodash"; -import { signin } from "@shared/utils/urlHelpers"; import { requireDirectory } from "@server/utils/fs"; export type AuthenticationProviderConfig = { id: string; name: string; enabled: boolean; - authUrl: string; router: Router; }; -const providers: AuthenticationProviderConfig[] = []; +const authenticationProviderConfigs: AuthenticationProviderConfig[] = []; requireDirectory(__dirname).forEach(([module, id]) => { // @ts-expect-error ts-migrate(2339) FIXME: Property 'config' does not exist on type 'unknown'... Remove this comment to see the full error message @@ -34,14 +32,13 @@ requireDirectory(__dirname).forEach(([module, id]) => { } if (config && config.enabled) { - providers.push({ + authenticationProviderConfigs.push({ id, name: config.name, enabled: config.enabled, - authUrl: signin(id), router, }); } }); -export default sortBy(providers, "id"); +export default sortBy(authenticationProviderConfigs, "id"); diff --git a/server/utils/isCloudHosted.ts b/server/utils/isCloudHosted.ts deleted file mode 100644 index d93edd884..000000000 --- a/server/utils/isCloudHosted.ts +++ /dev/null @@ -1,8 +0,0 @@ -import env from "@server/env"; - -/** - * True if the current installation is the cloud hosted version at getoutline.com - */ -const isCloudHosted = env.DEPLOYMENT === "hosted"; - -export default isCloudHosted; diff --git a/server/utils/passport.ts b/server/utils/passport.ts index 7d9e46c84..a1b2ffd27 100644 --- a/server/utils/passport.ts +++ b/server/utils/passport.ts @@ -102,7 +102,7 @@ export async function getTeamFromContext(ctx: Context) { const domain = parseDomain(host); let team; - if (env.DEPLOYMENT !== "hosted") { + if (!env.isCloudHosted()) { team = await Team.findOne(); } else if (domain.custom) { team = await Team.findOne({ where: { domain: domain.host } }); diff --git a/server/utils/robots.ts b/server/utils/robots.ts index 41699ec70..c5f85bc1d 100644 --- a/server/utils/robots.ts +++ b/server/utils/robots.ts @@ -1,7 +1,7 @@ import env from "@server/env"; export const robotsResponse = () => { - if (env.DEPLOYMENT === "hosted") { + if (env.isCloudHosted()) { return ` User-agent: * Allow: / diff --git a/server/utils/startup.ts b/server/utils/startup.ts index 7554b854f..6a5c35729 100644 --- a/server/utils/startup.ts +++ b/server/utils/startup.ts @@ -46,7 +46,7 @@ export function checkPendingMigrations() { } export async function checkMigrations() { - if (env.DEPLOYMENT === "hosted") { + if (env.isCloudHosted()) { return; }