chore: Remove DEPLOYMENT and SUBDOMAINS_ENABLED (#5742)

This commit is contained in:
Tom Moor
2023-08-28 20:39:58 -04:00
committed by GitHub
parent 7725f29dc7
commit 30a4303a8e
35 changed files with 136 additions and 135 deletions

View File

@@ -1,10 +1,14 @@
import WelcomeEmail from "@server/emails/templates/WelcomeEmail";
import env from "@server/env";
import { TeamDomain } from "@server/models";
import Collection from "@server/models/Collection";
import UserAuthentication from "@server/models/UserAuthentication";
import { buildUser, buildTeam } from "@server/test/factories";
import { setupTestDatabase, seed } from "@server/test/support";
import {
setupTestDatabase,
seed,
setCloudHosted,
setSelfHosted,
} from "@server/test/support";
import accountProvisioner from "./accountProvisioner";
setupTestDatabase();
@@ -13,9 +17,7 @@ describe("accountProvisioner", () => {
const ip = "127.0.0.1";
describe("hosted", () => {
beforeEach(() => {
env.DEPLOYMENT = "hosted";
});
beforeEach(setCloudHosted);
it("should create a new user and team", async () => {
const spy = jest.spyOn(WelcomeEmail.prototype, "schedule");
@@ -325,9 +327,7 @@ describe("accountProvisioner", () => {
});
describe("self hosted", () => {
beforeEach(() => {
env.DEPLOYMENT = undefined;
});
beforeEach(setSelfHosted);
it("should fail if existing team and domain not in allowed list", async () => {
let error;

View File

@@ -1,7 +1,10 @@
import env from "@server/env";
import TeamDomain from "@server/models/TeamDomain";
import { buildTeam, buildUser } from "@server/test/factories";
import { setupTestDatabase } from "@server/test/support";
import {
setCloudHosted,
setSelfHosted,
setupTestDatabase,
} from "@server/test/support";
import teamProvisioner from "./teamProvisioner";
setupTestDatabase();
@@ -10,9 +13,7 @@ describe("teamProvisioner", () => {
const ip = "127.0.0.1";
describe("hosted", () => {
beforeEach(() => {
env.DEPLOYMENT = "hosted";
});
beforeEach(setCloudHosted);
it("should create team and authentication provider", async () => {
const result = await teamProvisioner({
@@ -127,9 +128,7 @@ describe("teamProvisioner", () => {
});
describe("self hosted", () => {
beforeEach(() => {
env.DEPLOYMENT = undefined;
});
beforeEach(setSelfHosted);
it("should allow creating first team", async () => {
const { team, isNewTeam } = await teamProvisioner({

View File

@@ -78,7 +78,7 @@ async function teamProvisioner({
};
} else if (teamId) {
// The user is attempting to log into a team with an unfamiliar SSO provider
if (env.isCloudHosted()) {
if (env.isCloudHosted) {
throw InvalidAuthenticationError();
}

View File

@@ -34,7 +34,7 @@ const teamUpdater = async ({
preferences,
} = params;
if (subdomain !== undefined && env.SUBDOMAINS_ENABLED) {
if (subdomain !== undefined && env.isCloudHosted) {
team.subdomain = subdomain === "" ? null : subdomain;
}

View File

@@ -164,7 +164,7 @@ export class Mailer {
},
}
: undefined,
attachments: env.isCloudHosted()
attachments: env.isCloudHosted
? undefined
: [
{

View File

@@ -14,7 +14,7 @@ export default () => (
<img
alt={env.APP_NAME}
src={
env.isCloudHosted()
env.isCloudHosted
? `${url}/email/header-logo.png`
: "cid:header-image"
}

View File

@@ -11,7 +11,6 @@ import {
IsUrl,
IsOptional,
IsByteLength,
Equals,
IsNumber,
IsIn,
IsEmail,
@@ -206,13 +205,6 @@ export class Environment {
@CannotUseWithout("SSL_KEY")
public SSL_CERT = this.toOptionalString(process.env.SSL_CERT);
/**
* Should always be left unset in a self-hosted environment.
*/
@Equals("hosted")
@IsOptional()
public DEPLOYMENT = this.toOptionalString(process.env.DEPLOYMENT);
/**
* The default interface language. See translate.getoutline.com for a list of
* available language codes and their percentage translated.
@@ -240,15 +232,6 @@ export class Environment {
@IsBoolean()
public FORCE_HTTPS = this.toBoolean(process.env.FORCE_HTTPS ?? "true");
/**
* Whether to support multiple subdomains in a single instance.
*/
@IsBoolean()
@Deprecated("The community edition of Outline does not support subdomains")
public SUBDOMAINS_ENABLED = this.toBoolean(
process.env.SUBDOMAINS_ENABLED ?? "false"
);
/**
* Should the installation send anonymized statistics to the maintainers.
* Defaults to true.
@@ -666,8 +649,12 @@ export class Environment {
* Returns true if the current installation is the cloud hosted version at
* getoutline.com
*/
public isCloudHosted() {
return this.DEPLOYMENT === "hosted";
public get isCloudHosted() {
return [
"https://app.getoutline.com",
"https://app.outline.dev",
"https://app.outline.dev:3000",
].includes(this.URL);
}
private toOptionalString(value: string | undefined) {

View File

@@ -70,9 +70,9 @@ class Team extends ParanoidModel {
@Unique
@Length({
min: 2,
max: env.isCloudHosted() ? 32 : 255,
max: env.isCloudHosted ? 32 : 255,
msg: `subdomain must be between 2 and ${
env.isCloudHosted() ? 32 : 255
env.isCloudHosted ? 32 : 255
} characters`,
})
@Is({
@@ -169,7 +169,7 @@ class Team extends ParanoidModel {
return `${url.protocol}//${this.domain}${url.port ? `:${url.port}` : ""}`;
}
if (!this.subdomain || !env.SUBDOMAINS_ENABLED) {
if (!this.subdomain || !env.isCloudHosted) {
return env.URL;
}

View File

@@ -1,11 +1,12 @@
import env from "@server/env";
import { buildAdmin, buildTeam } from "@server/test/factories";
import { setupTestDatabase } from "@server/test/support";
import { setCloudHosted, setupTestDatabase } from "@server/test/support";
import TeamDomain from "./TeamDomain";
setupTestDatabase();
describe("team domain model", () => {
beforeEach(setCloudHosted);
describe("create", () => {
it("should allow creation of domains", async () => {
const team = await buildTeam();
@@ -37,7 +38,6 @@ describe("team domain model", () => {
});
it("should not allow creation of domains within restricted list", async () => {
env.DEPLOYMENT = "hosted";
const TeamDomain = await import("./TeamDomain");
const team = await buildTeam();
const user = await buildAdmin({ teamId: team.id });
@@ -57,7 +57,6 @@ describe("team domain model", () => {
});
it("should ignore casing and spaces when creating domains", async () => {
env.DEPLOYMENT = "hosted";
const TeamDomain = await import("./TeamDomain");
const team = await buildTeam();
const user = await buildAdmin({ teamId: team.id });

View File

@@ -23,7 +23,7 @@ import Length from "./validators/Length";
@Fix
class TeamDomain extends IdModel {
@NotIn({
args: env.isCloudHosted() ? [emailProviders] : [],
args: env.isCloudHosted ? [emailProviders] : [],
msg: "You chose a restricted domain, please try another.",
})
@NotEmpty

View File

@@ -74,7 +74,7 @@ export default class AuthenticationHelper {
* @returns A list of authentication providers
*/
public static providersForTeam(team?: Team) {
const isCloudHosted = env.isCloudHosted();
const isCloudHosted = env.isCloudHosted;
return AuthenticationHelper.providers
.sort((config) => (config.id === "email" ? 1 : -1))

View File

@@ -1,11 +1,16 @@
import env from "@server/env";
import { buildUser, buildTeam, buildAdmin } from "@server/test/factories";
import { setupTestDatabase } from "@server/test/support";
import {
setCloudHosted,
setSelfHosted,
setupTestDatabase,
} from "@server/test/support";
import { serialize } from "./index";
setupTestDatabase();
it("should allow reading only", async () => {
setSelfHosted();
const team = await buildTeam();
const user = await buildUser({
teamId: team.id,
@@ -21,6 +26,8 @@ it("should allow reading only", async () => {
});
it("should allow admins to manage", async () => {
setSelfHosted();
const team = await buildTeam();
const admin = await buildAdmin({
teamId: team.id,
@@ -36,7 +43,7 @@ it("should allow admins to manage", async () => {
});
it("should allow creation on hosted envs", async () => {
env.DEPLOYMENT = "hosted";
setCloudHosted();
const team = await buildTeam();
const admin = await buildAdmin({

View File

@@ -13,7 +13,7 @@ allow(User, "share", Team, (user, team) => {
});
allow(User, "createTeam", Team, () => {
if (!env.isCloudHosted()) {
if (!env.isCloudHosted) {
throw IncorrectEditionError("Functionality is only available on cloud");
}
return true;
@@ -27,7 +27,7 @@ allow(User, "update", Team, (user, team) => {
});
allow(User, ["delete", "audit"], Team, (user, team) => {
if (!env.isCloudHosted()) {
if (!env.isCloudHosted) {
throw IncorrectEditionError("Functionality is only available on cloud");
}
if (!team || user.isViewer || user.teamId !== team.id) {

View File

@@ -16,14 +16,12 @@ export default function present(
COLLABORATION_URL: (env.COLLABORATION_URL || env.URL)
.replace(/\/$/, "")
.replace(/^http/, "ws"),
DEPLOYMENT: env.DEPLOYMENT,
ENVIRONMENT: env.ENVIRONMENT,
SENTRY_DSN: env.SENTRY_DSN,
SENTRY_TUNNEL: env.SENTRY_TUNNEL,
SLACK_CLIENT_ID: env.SLACK_CLIENT_ID,
SLACK_APP_ID: env.SLACK_APP_ID,
MAXIMUM_IMPORT_SIZE: env.MAXIMUM_IMPORT_SIZE,
SUBDOMAINS_ENABLED: env.SUBDOMAINS_ENABLED,
PDF_EXPORT_ENABLED: false,
DEFAULT_LANGUAGE: env.DEFAULT_LANGUAGE,
EMAIL_ENABLED: !!env.SMTP_HOST || env.ENVIRONMENT === "development",

View File

@@ -1,7 +1,9 @@
import sharedEnv from "@shared/env";
import env from "@server/env";
import { buildUser, buildTeam } from "@server/test/factories";
import { getTestServer } from "@server/test/support";
import {
getTestServer,
setCloudHosted,
setSelfHosted,
} from "@server/test/support";
const mockTeamInSessionId = "1e023d05-951c-41c6-9012-c9fa0402e1c3";
@@ -14,6 +16,8 @@ jest.mock("@server/utils/authentication", () => ({
const server = getTestServer();
describe("#auth.info", () => {
beforeEach(setCloudHosted);
it("should return current authentication", async () => {
const team = await buildTeam();
const team2 = await buildTeam();
@@ -93,8 +97,6 @@ describe("#auth.delete", () => {
describe("#auth.config", () => {
it("should return available SSO providers", async () => {
env.DEPLOYMENT = "hosted";
const res = await server.post("/api/auth.config");
const body = await res.json();
expect(res.status).toEqual(200);
@@ -105,10 +107,6 @@ describe("#auth.config", () => {
});
it("should return available providers for team subdomain", async () => {
env.URL = sharedEnv.URL = "https://app.outline.dev";
env.SUBDOMAINS_ENABLED = sharedEnv.SUBDOMAINS_ENABLED = true;
env.DEPLOYMENT = "hosted";
await buildTeam({
guestSignin: false,
subdomain: "example",
@@ -131,8 +129,6 @@ describe("#auth.config", () => {
});
it("should return available providers for team custom domain", async () => {
env.DEPLOYMENT = "hosted";
await buildTeam({
guestSignin: false,
domain: "docs.mycompany.com",
@@ -155,9 +151,6 @@ describe("#auth.config", () => {
});
it("should return email provider for team when guest signin enabled", async () => {
env.URL = sharedEnv.URL = "https://app.outline.dev";
env.DEPLOYMENT = "hosted";
await buildTeam({
guestSignin: true,
subdomain: "example",
@@ -181,9 +174,6 @@ describe("#auth.config", () => {
});
it("should not return provider when disabled", async () => {
env.URL = sharedEnv.URL = "https://app.outline.dev";
env.DEPLOYMENT = "hosted";
await buildTeam({
guestSignin: false,
subdomain: "example",
@@ -206,8 +196,9 @@ describe("#auth.config", () => {
});
describe("self hosted", () => {
beforeEach(setSelfHosted);
it("should return all configured providers but respect email setting", async () => {
env.DEPLOYMENT = "";
await buildTeam({
guestSignin: false,
authenticationProviders: [
@@ -227,7 +218,6 @@ describe("#auth.config", () => {
});
it("should return email provider for team when guest signin enabled", async () => {
env.DEPLOYMENT = "";
await buildTeam({
guestSignin: true,
authenticationProviders: [

View File

@@ -26,7 +26,7 @@ router.post("auth.config", async (ctx: APIContext<T.AuthConfigReq>) => {
// 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.isCloudHosted()) {
if (!env.isCloudHosted) {
const team = await Team.scope("withAuthenticationProviders").findOne();
if (team) {
@@ -75,7 +75,7 @@ router.post("auth.config", async (ctx: APIContext<T.AuthConfigReq>) => {
// If subdomain signin page then we return minimal team details to allow
// for a custom screen showing only relevant signin options for that team.
else if (env.SUBDOMAINS_ENABLED && domain.teamSubdomain) {
else if (env.isCloudHosted && domain.teamSubdomain) {
const team = await Team.scope("withAuthenticationProviders").findOne({
where: {
subdomain: domain.teamSubdomain,
@@ -179,7 +179,7 @@ router.post(
ctx.cookies.set("accessToken", "", {
expires: subMinutes(new Date(), 1),
domain: getCookieDomain(ctx.hostname),
domain: getCookieDomain(ctx.hostname, env.isCloudHosted),
});
ctx.body = {

View File

@@ -1,13 +1,10 @@
import env from "@server/env";
import { buildEvent, buildUser } from "@server/test/factories";
import { seed, getTestServer } from "@server/test/support";
import { seed, getTestServer, setCloudHosted } from "@server/test/support";
const server = getTestServer();
describe("#events.list", () => {
beforeEach(() => {
env.DEPLOYMENT = "hosted";
});
beforeEach(setCloudHosted);
it("should only return activity events", async () => {
const { user, admin, document, collection } = await seed();

View File

@@ -1,13 +1,18 @@
import env from "@server/env";
import { TeamDomain } from "@server/models";
import { buildAdmin, buildCollection, buildTeam } from "@server/test/factories";
import { seed, getTestServer } from "@server/test/support";
import {
seed,
getTestServer,
setCloudHosted,
setSelfHosted,
} from "@server/test/support";
const server = getTestServer();
describe("teams.create", () => {
it("creates a team", async () => {
env.DEPLOYMENT = "hosted";
setCloudHosted();
const team = await buildTeam();
const user = await buildAdmin({ teamId: team.id });
const res = await server.post("/api/teams.create", {
@@ -23,7 +28,8 @@ describe("teams.create", () => {
});
it("requires a cloud hosted deployment", async () => {
env.DEPLOYMENT = "";
setSelfHosted();
const team = await buildTeam();
const user = await buildAdmin({ teamId: team.id });
const res = await server.post("/api/teams.create", {

View File

@@ -17,7 +17,6 @@ env.OIDC_TOKEN_URI = "http://localhost/token";
env.OIDC_USERINFO_URI = "http://localhost/userinfo";
env.RATE_LIMITER_ENABLED = false;
env.DEPLOYMENT = undefined;
if (process.env.DATABASE_URL_TEST) {
env.DATABASE_URL = process.env.DATABASE_URL_TEST;

View File

@@ -1,6 +1,8 @@
import TestServer from "fetch-test-server";
import { v4 as uuidv4 } from "uuid";
import sharedEnv from "@shared/env";
import { CollectionPermission } from "@shared/types";
import env from "@server/env";
import { User, Document, Collection, Team } from "@server/models";
import onerror from "@server/onerror";
import webService from "@server/services/web";
@@ -137,3 +139,17 @@ export function setupTestDatabase() {
afterAll(disconnect);
beforeEach(flush);
}
/**
* Set the environment to be cloud hosted
*/
export function setCloudHosted() {
return (env.URL = sharedEnv.URL = "https://app.outline.dev");
}
/**
* Set the environment to be self hosted
*/
export function setSelfHosted() {
return (env.URL = sharedEnv.URL = "https://wiki.example.com");
}

View File

@@ -71,7 +71,7 @@ export async function signIn(
},
ip: ctx.request.ip,
});
const domain = getCookieDomain(ctx.request.hostname);
const domain = getCookieDomain(ctx.request.hostname, env.isCloudHosted);
const expires = addMonths(new Date(), 3);
// set a cookie for which service we last signed in with. This is
@@ -85,7 +85,7 @@ export async function signIn(
// set a transfer cookie for the access token itself and redirect
// to the teams subdomain if subdomains are enabled
if (env.SUBDOMAINS_ENABLED && team.subdomain) {
if (env.isCloudHosted && team.subdomain) {
// get any existing sessions (teams signed in) and add this team
const existing = getSessionsInCookie(ctx);
const sessions = encodeURIComponent(

View File

@@ -22,7 +22,7 @@ export default class AzureClient extends OAuthClient {
refreshToken?: string;
expiresAt: Date;
}> {
if (env.isCloudHosted()) {
if (env.isCloudHosted) {
return super.rotateToken(accessToken, refreshToken);
}

View File

@@ -19,10 +19,10 @@ export default function fetch(
): Promise<Response> {
// In self-hosted, webhooks support proxying and are also allowed to connect
// to internal services, so use fetchWithProxy without the filtering agent.
const fetch = env.isCloudHosted() ? nodeFetch : fetchWithProxy;
const fetch = env.isCloudHosted ? nodeFetch : fetchWithProxy;
return fetch(url, {
...init,
agent: env.isCloudHosted() ? useAgent(url) : undefined,
agent: env.isCloudHosted ? useAgent(url) : undefined,
});
}

View File

@@ -28,7 +28,7 @@ export class StateStore {
ctx.cookies.set(this.key, state, {
expires: addMinutes(new Date(), 10),
domain: getCookieDomain(ctx.hostname),
domain: getCookieDomain(ctx.hostname, env.isCloudHosted),
});
callback(null, token);
@@ -54,7 +54,7 @@ export class StateStore {
// Destroy the one-time pad token and ensure it matches
ctx.cookies.set(this.key, "", {
expires: subMinutes(new Date(), 1),
domain: getCookieDomain(ctx.hostname),
domain: getCookieDomain(ctx.hostname, env.isCloudHosted),
});
if (!token || token !== providedToken) {
@@ -100,11 +100,11 @@ export async function getTeamFromContext(ctx: Context) {
const domain = parseDomain(host);
let team;
if (!env.isCloudHosted()) {
if (!env.isCloudHosted) {
team = await Team.findOne();
} else if (domain.custom) {
team = await Team.findOne({ where: { domain: domain.host } });
} else if (env.SUBDOMAINS_ENABLED && domain.teamSubdomain) {
} else if (domain.teamSubdomain) {
team = await Team.findOne({
where: { subdomain: domain.teamSubdomain },
});

View File

@@ -1,7 +1,7 @@
import env from "@server/env";
export const robotsResponse = () => {
if (env.isCloudHosted()) {
if (env.isCloudHosted) {
return `
User-agent: *
Allow: /

View File

@@ -10,7 +10,7 @@ export async function checkPendingMigrations() {
try {
const pending = await migrations.pending();
if (!isEmpty(pending)) {
if (env.isCloudHosted()) {
if (env.isCloudHosted) {
Logger.warn(chalk.red("Migrations are pending"));
process.exit(1);
} else {
@@ -34,7 +34,7 @@ export async function checkPendingMigrations() {
}
export async function checkDataMigrations() {
if (env.isCloudHosted()) {
if (env.isCloudHosted) {
return;
}