mirror of
https://github.com/outline/outline.git
synced 2025-03-14 10:07:11 +00:00
Separate environment configs (#6597)
* Separate environment configs * wip * wip * test * plugins * test * test * .sequelizerc, unfortunately can't go through /utils/environment due to not supporting TS * docker-compose -> docker compose * fix: .local wipes .development * Add custom validation message for invalid SECRET_KEY (often confused)
This commit is contained in:
@ -13,13 +13,8 @@ defaults: &defaults
|
||||
resource_class: large
|
||||
environment:
|
||||
NODE_ENV: test
|
||||
SECRET_KEY: F0E5AD933D7F6FD8F4DBB3E038C501C052DC0593C686D21ACB30AE205D2F634B
|
||||
DATABASE_URL_TEST: postgres://postgres:password@localhost:5432/circle_test
|
||||
DATABASE_URL: postgres://postgres:password@localhost:5432/circle_test
|
||||
URL: http://localhost:3000
|
||||
SMTP_FROM_EMAIL: hello@example.com
|
||||
AWS_S3_UPLOAD_BUCKET_URL: https://s3.amazonaws.com
|
||||
AWS_S3_UPLOAD_BUCKET_NAME: outline-circle
|
||||
NODE_OPTIONS: --max-old-space-size=8000
|
||||
|
||||
executors:
|
||||
@ -89,7 +84,7 @@ jobs:
|
||||
key: dependency-cache-v1-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: migrate
|
||||
command: ./node_modules/.bin/sequelize db:migrate --url $DATABASE_URL_TEST
|
||||
command: ./node_modules/.bin/sequelize db:migrate
|
||||
- run:
|
||||
name: test
|
||||
command: |
|
||||
|
10
.env.development
Normal file
10
.env.development
Normal file
@ -0,0 +1,10 @@
|
||||
URL=https://local.outline.dev:3000
|
||||
|
||||
SMTP_FROM_EMAIL=hello@example.com
|
||||
|
||||
# Enable unsafe-inline in script-src CSP directive
|
||||
# Setting it to true allows React dev tools add-on in Firefox to successfully detect the project
|
||||
DEVELOPMENT_UNSAFE_INLINE_CSP=true
|
||||
|
||||
# Increase the log level to debug for development
|
||||
LOG_LEVEL=debug
|
19
.env.sample
19
.env.sample
@ -13,7 +13,6 @@ UTILS_SECRET=generate_a_new_key
|
||||
# For production point these at your databases, in development the default
|
||||
# should work out of the box.
|
||||
DATABASE_URL=postgres://user:pass@localhost:5432/outline
|
||||
DATABASE_URL_TEST=postgres://user:pass@localhost:5432/outline-test
|
||||
DATABASE_CONNECTION_POOL_MIN=
|
||||
DATABASE_CONNECTION_POOL_MAX=
|
||||
# Uncomment this to disable SSL for connecting to Postgres
|
||||
@ -30,7 +29,7 @@ REDIS_URL=redis://localhost:6379
|
||||
|
||||
# URL should point to the fully qualified, publicly accessible URL. If using a
|
||||
# proxy the port in URL and PORT may be different.
|
||||
URL=https://app.outline.dev:3000
|
||||
URL=
|
||||
PORT=3000
|
||||
|
||||
# See [documentation](docs/SERVICES.md) on running a separate collaboration
|
||||
@ -166,9 +165,6 @@ SLACK_VERIFICATION_TOKEN=your_token
|
||||
SLACK_APP_ID=A0XXXXXXX
|
||||
SLACK_MESSAGE_ACTIONS=true
|
||||
|
||||
# Optionally enable google analytics to track pageviews in the knowledge base
|
||||
GOOGLE_ANALYTICS_ID=
|
||||
|
||||
# Optionally enable Sentry (sentry.io) to track errors and performance,
|
||||
# and optionally add a Sentry proxy tunnel for bypassing ad blockers in the UI:
|
||||
# https://docs.sentry.io/platforms/javascript/troubleshooting/#using-the-tunnel-option)
|
||||
@ -181,8 +177,8 @@ SMTP_HOST=
|
||||
SMTP_PORT=
|
||||
SMTP_USERNAME=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_FROM_EMAIL=hello@example.com
|
||||
SMTP_REPLY_EMAIL=hello@example.com
|
||||
SMTP_FROM_EMAIL=
|
||||
SMTP_REPLY_EMAIL=
|
||||
SMTP_TLS_CIPHERS=
|
||||
SMTP_SECURE=true
|
||||
|
||||
@ -198,10 +194,5 @@ RATE_LIMITER_REQUESTS=1000
|
||||
RATE_LIMITER_DURATION_WINDOW=60
|
||||
|
||||
# Iframely API config
|
||||
# IFRAMELY_URL=
|
||||
# IFRAMELY_API_KEY=
|
||||
|
||||
# Enable unsafe-inline in script-src CSP directive
|
||||
# Setting it to true allows React dev tools add-on in
|
||||
# Firefox to successfully detect the project
|
||||
DEVELOPMENT_UNSAFE_INLINE_CSP=false
|
||||
IFRAMELY_URL=
|
||||
IFRAMELY_API_KEY=
|
26
.env.test
Normal file
26
.env.test
Normal file
@ -0,0 +1,26 @@
|
||||
NODE_ENV=test
|
||||
DATABASE_URL=postgres://user:pass@127.0.0.1:5432/outline-test
|
||||
SECRET_KEY=F0E5AD933D7F6FD8F4DBB3E038C501C052DC0593C686D21ACB30AE205D2F634B
|
||||
|
||||
SMTP_HOST=smtp.example.com
|
||||
SMTP_FROM_EMAIL=hello@example.com
|
||||
SMTP_REPLY_EMAIL=hello@example.com
|
||||
|
||||
GOOGLE_CLIENT_ID=123
|
||||
GOOGLE_CLIENT_SECRET=123
|
||||
|
||||
SLACK_CLIENT_ID=123
|
||||
SLACK_CLIENT_SECRET=123
|
||||
|
||||
OIDC_CLIENT_ID=client-id
|
||||
OIDC_CLIENT_SECRET=client-secret
|
||||
OIDC_AUTH_URI=http://localhost/authorize
|
||||
OIDC_TOKEN_URI=http://localhost/token
|
||||
OIDC_USERINFO_URI=http://localhost/userinfo
|
||||
|
||||
IFRAMELY_API_KEY=123
|
||||
|
||||
RATE_LIMITER_ENABLED=false
|
||||
|
||||
FILE_STORAGE=local
|
||||
FILE_STORAGE_LOCAL_ROOT_DIR=/tmp
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,6 +2,8 @@ dist
|
||||
build
|
||||
node_modules/*
|
||||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
.log
|
||||
.vscode/*
|
||||
npm-debug.log
|
||||
|
@ -9,7 +9,7 @@
|
||||
"^@server/(.*)$": "<rootDir>/server/$1",
|
||||
"^@shared/(.*)$": "<rootDir>/shared/$1"
|
||||
},
|
||||
"setupFiles": ["<rootDir>/__mocks__/console.js", "<rootDir>/server/test/env.ts"],
|
||||
"setupFiles": ["<rootDir>/__mocks__/console.js"],
|
||||
"setupFilesAfterEnv": ["<rootDir>/server/test/setup.ts"],
|
||||
"globalSetup": "<rootDir>/server/test/globalSetup.js",
|
||||
"globalTeardown": "<rootDir>/server/test/globalTeardown.js",
|
||||
|
@ -1,4 +1,6 @@
|
||||
require('dotenv').config({ silent: true });
|
||||
require("dotenv").config({
|
||||
path: process.env.NODE_ENV === "test" ? ".env.test" : ".env",
|
||||
});
|
||||
|
||||
var path = require('path');
|
||||
|
||||
@ -6,5 +8,4 @@ module.exports = {
|
||||
'config': path.resolve('server/config', 'database.json'),
|
||||
'migrations-path': path.resolve('server', 'migrations'),
|
||||
'models-path': path.resolve('server', 'models'),
|
||||
'seeders-path': path.resolve('server/models', 'fixtures'),
|
||||
}
|
||||
|
24
Makefile
24
Makefile
@ -1,28 +1,28 @@
|
||||
up:
|
||||
docker-compose up -d redis postgres
|
||||
docker compose up -d redis postgres
|
||||
yarn install-local-ssl
|
||||
yarn install --pure-lockfile
|
||||
yarn dev:watch
|
||||
|
||||
build:
|
||||
docker-compose build --pull outline
|
||||
docker compose build --pull outline
|
||||
|
||||
test:
|
||||
docker-compose up -d redis postgres
|
||||
yarn sequelize db:drop --env=test
|
||||
yarn sequelize db:create --env=test
|
||||
NODE_ENV=test yarn sequelize db:migrate --env=test
|
||||
docker compose up -d redis postgres
|
||||
NODE_ENV=test yarn sequelize db:drop
|
||||
NODE_ENV=test yarn sequelize db:create
|
||||
NODE_ENV=test yarn sequelize db:migrate
|
||||
yarn test
|
||||
|
||||
watch:
|
||||
docker-compose up -d redis postgres
|
||||
yarn sequelize db:drop --env=test
|
||||
yarn sequelize db:create --env=test
|
||||
NODE_ENV=test yarn sequelize db:migrate --env=test
|
||||
docker compose up -d redis postgres
|
||||
NODE_ENV=test yarn sequelize db:drop
|
||||
NODE_ENV=test yarn sequelize db:create
|
||||
NODE_ENV=test yarn sequelize db:migrate
|
||||
yarn test:watch
|
||||
|
||||
destroy:
|
||||
docker-compose stop
|
||||
docker-compose rm -f
|
||||
docker compose stop
|
||||
docker compose rm -f
|
||||
|
||||
.PHONY: up build destroy test watch # let's go to reserve rules names
|
||||
|
@ -26,7 +26,6 @@ import SearchQuery from "~/models/SearchQuery";
|
||||
import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
|
||||
import { createAction } from "~/actions";
|
||||
import { NavigationSection, RecentSearchesSection } from "~/actions/sections";
|
||||
import env from "~/env";
|
||||
import Desktop from "~/utils/Desktop";
|
||||
import history from "~/utils/history";
|
||||
import isCloudHosted from "~/utils/isCloudHosted";
|
||||
@ -212,9 +211,6 @@ export const logout = createAction({
|
||||
icon: <LogoutIcon />,
|
||||
perform: () => {
|
||||
void stores.auth.logout();
|
||||
if (env.OIDC_LOGOUT_URI) {
|
||||
window.location.replace(env.OIDC_LOGOUT_URI);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1,15 +1,10 @@
|
||||
import * as React from "react";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import env from "~/env";
|
||||
import useStores from "~/hooks/useStores";
|
||||
|
||||
const Logout = () => {
|
||||
const { auth } = useStores();
|
||||
void auth.logout();
|
||||
if (env.OIDC_LOGOUT_URI) {
|
||||
window.location.replace(env.OIDC_LOGOUT_URI);
|
||||
return null;
|
||||
}
|
||||
return <Redirect to="/" />;
|
||||
};
|
||||
|
||||
|
@ -14,6 +14,7 @@ import { PartialWithId } from "~/types";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
import Desktop from "~/utils/Desktop";
|
||||
import Logger from "~/utils/Logger";
|
||||
import history from "~/utils/history";
|
||||
import isCloudHosted from "~/utils/isCloudHosted";
|
||||
import Store from "./base/Store";
|
||||
|
||||
@ -304,16 +305,15 @@ export default class AuthStore extends Store<Team> {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs the user out and optionally revokes the authentication token.
|
||||
*
|
||||
* @param savePath Whether the current path should be saved and returned to after login.
|
||||
* @param tryRevokingToken Whether the auth token should attempt to be revoked, this should be
|
||||
* disabled with requests from ApiClient to prevent infinite loops.
|
||||
*/
|
||||
@action
|
||||
logout = async (
|
||||
/** Whether the current path should be saved and returned to after login */
|
||||
savePath = false,
|
||||
/**
|
||||
* Whether the auth token should attempt to be revoked, this should be disabled
|
||||
* with requests from ApiClient to prevent infinite loops.
|
||||
*/
|
||||
tryRevokingToken = true
|
||||
) => {
|
||||
logout = async (savePath = false, tryRevokingToken = true) => {
|
||||
// if this logout was forced from an authenticated route then
|
||||
// save the current path so we can go back there once signed in
|
||||
if (savePath) {
|
||||
@ -348,9 +348,16 @@ export default class AuthStore extends Store<Team> {
|
||||
this.currentUserId = null;
|
||||
this.currentTeamId = null;
|
||||
this.collaborationToken = null;
|
||||
this.rootStore.clear();
|
||||
|
||||
// Tell the host application we logged out, if any – allows window cleanup.
|
||||
void Desktop.bridge?.onLogout?.();
|
||||
this.rootStore.clear();
|
||||
if (Desktop.isElectron()) {
|
||||
void Desktop.bridge?.onLogout?.();
|
||||
} else if (env.OIDC_LOGOUT_URI) {
|
||||
window.location.replace(env.OIDC_LOGOUT_URI);
|
||||
return;
|
||||
}
|
||||
|
||||
history.replace("/");
|
||||
};
|
||||
}
|
||||
|
@ -99,7 +99,7 @@
|
||||
"date-fns": "^2.30.0",
|
||||
"dd-trace": "^3.33.0",
|
||||
"diff": "^5.1.0",
|
||||
"dotenv": "^4.0.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"email-providers": "^1.14.0",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"emoji-regex": "^10.3.0",
|
||||
@ -247,6 +247,7 @@
|
||||
"@types/body-scroll-lock": "^3.1.0",
|
||||
"@types/crypto-js": "^4.2.1",
|
||||
"@types/diff": "^5.0.4",
|
||||
"@types/dotenv": "^8.2.0",
|
||||
"@types/emoji-regex": "^9.2.0",
|
||||
"@types/express-useragent": "^1.0.2",
|
||||
"@types/formidable": "^2.0.6",
|
||||
|
@ -6,7 +6,6 @@ import Router from "koa-router";
|
||||
import { Profile } from "passport";
|
||||
import { slugifyDomain } from "@shared/utils/domains";
|
||||
import accountProvisioner from "@server/commands/accountProvisioner";
|
||||
import env from "@server/env";
|
||||
import { MicrosoftGraphError } from "@server/errors";
|
||||
import passportMiddleware from "@server/middlewares/passport";
|
||||
import { User } from "@server/models";
|
||||
@ -17,6 +16,7 @@ import {
|
||||
getTeamFromContext,
|
||||
getClientFromContext,
|
||||
} from "@server/utils/passport";
|
||||
import env from "../env";
|
||||
|
||||
const router = new Router();
|
||||
const providerName = "azure";
|
||||
|
@ -1,6 +1,7 @@
|
||||
import invariant from "invariant";
|
||||
import JWT from "jsonwebtoken";
|
||||
import env from "@server/env";
|
||||
import OAuthClient from "./oauth";
|
||||
import OAuthClient from "@server/utils/oauth";
|
||||
import env from "./env";
|
||||
|
||||
type AzurePayload = {
|
||||
/** A GUID that represents the Azure AD tenant that the user is from */
|
||||
@ -14,6 +15,13 @@ export default class AzureClient extends OAuthClient {
|
||||
userinfo: "https://graph.microsoft.com/v1.0/me",
|
||||
};
|
||||
|
||||
constructor() {
|
||||
invariant(env.AZURE_CLIENT_ID, "AZURE_CLIENT_ID is required");
|
||||
invariant(env.AZURE_CLIENT_SECRET, "AZURE_CLIENT_SECRET is required");
|
||||
|
||||
super(env.AZURE_CLIENT_ID, env.AZURE_CLIENT_SECRET);
|
||||
}
|
||||
|
||||
async rotateToken(
|
||||
accessToken: string,
|
||||
refreshToken: string
|
27
plugins/azure/server/env.ts
Normal file
27
plugins/azure/server/env.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { IsOptional } from "class-validator";
|
||||
import { Environment } from "@server/env";
|
||||
import environment from "@server/utils/environment";
|
||||
import { CannotUseWithout } from "@server/utils/validators";
|
||||
|
||||
class AzurePluginEnvironment extends Environment {
|
||||
/**
|
||||
* Azure OAuth2 client credentials. To enable authentication with Azure.
|
||||
*/
|
||||
@IsOptional()
|
||||
@CannotUseWithout("AZURE_CLIENT_SECRET")
|
||||
public AZURE_CLIENT_ID = this.toOptionalString(environment.AZURE_CLIENT_ID);
|
||||
|
||||
@IsOptional()
|
||||
@CannotUseWithout("AZURE_CLIENT_ID")
|
||||
public AZURE_CLIENT_SECRET = this.toOptionalString(
|
||||
environment.AZURE_CLIENT_SECRET
|
||||
);
|
||||
|
||||
@IsOptional()
|
||||
@CannotUseWithout("AZURE_CLIENT_ID")
|
||||
public AZURE_RESOURCE_APP_ID = this.toOptionalString(
|
||||
environment.AZURE_RESOURCE_APP_ID
|
||||
);
|
||||
}
|
||||
|
||||
export default new AzurePluginEnvironment();
|
@ -6,7 +6,6 @@ import { Profile } from "passport";
|
||||
import { Strategy as GoogleStrategy } from "passport-google-oauth2";
|
||||
import { slugifyDomain } from "@shared/utils/domains";
|
||||
import accountProvisioner from "@server/commands/accountProvisioner";
|
||||
import env from "@server/env";
|
||||
import {
|
||||
GmailAccountCreationError,
|
||||
TeamDomainRequiredError,
|
||||
@ -19,6 +18,7 @@ import {
|
||||
getTeamFromContext,
|
||||
getClientFromContext,
|
||||
} from "@server/utils/passport";
|
||||
import env from "../env";
|
||||
|
||||
const router = new Router();
|
||||
const providerName = "google";
|
||||
|
21
plugins/google/server/env.ts
Normal file
21
plugins/google/server/env.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { IsOptional } from "class-validator";
|
||||
import { Environment } from "@server/env";
|
||||
import environment from "@server/utils/environment";
|
||||
import { CannotUseWithout } from "@server/utils/validators";
|
||||
|
||||
class GooglePluginEnvironment extends Environment {
|
||||
/**
|
||||
* Google OAuth2 client credentials. To enable authentication with Google.
|
||||
*/
|
||||
@IsOptional()
|
||||
@CannotUseWithout("GOOGLE_CLIENT_SECRET")
|
||||
public GOOGLE_CLIENT_ID = this.toOptionalString(environment.GOOGLE_CLIENT_ID);
|
||||
|
||||
@IsOptional()
|
||||
@CannotUseWithout("GOOGLE_CLIENT_ID")
|
||||
public GOOGLE_CLIENT_SECRET = this.toOptionalString(
|
||||
environment.GOOGLE_CLIENT_SECRET
|
||||
);
|
||||
}
|
||||
|
||||
export default new GooglePluginEnvironment();
|
18
plugins/google/server/google.ts
Normal file
18
plugins/google/server/google.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import invariant from "invariant";
|
||||
import OAuthClient from "@server/utils/oauth";
|
||||
import env from "./env";
|
||||
|
||||
export default class GoogleClient extends OAuthClient {
|
||||
endpoints = {
|
||||
authorize: "https://accounts.google.com/o/oauth2/auth",
|
||||
token: "https://accounts.google.com/o/oauth2/token",
|
||||
userinfo: "https://www.googleapis.com/oauth2/v3/userinfo",
|
||||
};
|
||||
|
||||
constructor() {
|
||||
invariant(env.GOOGLE_CLIENT_ID, "GOOGLE_CLIENT_ID is required");
|
||||
invariant(env.GOOGLE_CLIENT_SECRET, "GOOGLE_CLIENT_SECRET is required");
|
||||
|
||||
super(env.GOOGLE_CLIENT_ID, env.GOOGLE_CLIENT_SECRET);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "Iframely",
|
||||
"description": "Integrate Iframely to enable unfurling of arbitrary urls",
|
||||
"requiredEnvVars": ["IFRAMELY_URL", "IFRAMELY_API_KEY"]
|
||||
"requiredEnvVars": ["IFRAMELY_API_KEY"]
|
||||
}
|
||||
|
27
plugins/iframely/server/env.ts
Normal file
27
plugins/iframely/server/env.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { IsOptional, IsUrl } from "class-validator";
|
||||
import { Environment } from "@server/env";
|
||||
import environment from "@server/utils/environment";
|
||||
import { CannotUseWithout } from "@server/utils/validators";
|
||||
|
||||
class IframelyPluginEnvironment extends Environment {
|
||||
/**
|
||||
* Iframely url
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsUrl({
|
||||
require_tld: false,
|
||||
require_protocol: true,
|
||||
allow_underscores: true,
|
||||
protocols: ["http", "https"],
|
||||
})
|
||||
public IFRAMELY_URL = environment.IFRAMELY_URL ?? "https://iframe.ly";
|
||||
|
||||
/**
|
||||
* Iframely API key
|
||||
*/
|
||||
@IsOptional()
|
||||
@CannotUseWithout("IFRAMELY_URL")
|
||||
public IFRAMELY_API_KEY = this.toOptionalString(environment.IFRAMELY_API_KEY);
|
||||
}
|
||||
|
||||
export default new IframelyPluginEnvironment();
|
@ -1,9 +1,9 @@
|
||||
import { Day } from "@shared/utils/time";
|
||||
import env from "@server/env";
|
||||
import { InternalError } from "@server/errors";
|
||||
import Logger from "@server/logging/Logger";
|
||||
import Redis from "@server/storage/redis";
|
||||
import fetch from "@server/utils/fetch";
|
||||
import env from "./env";
|
||||
|
||||
class Iframely {
|
||||
private static apiUrl = `${env.IFRAMELY_URL}/api`;
|
||||
|
@ -1,12 +1,5 @@
|
||||
{
|
||||
"name": "OIDC",
|
||||
"description": "Adds an OpenID compatible authentication provider.",
|
||||
"requiredEnvVars": [
|
||||
"OIDC_CLIENT_ID",
|
||||
"OIDC_CLIENT_SECRET",
|
||||
"OIDC_AUTH_URI",
|
||||
"OIDC_TOKEN_URI",
|
||||
"OIDC_USERINFO_URI",
|
||||
"OIDC_DISPLAY_NAME"
|
||||
]
|
||||
"requiredEnvVars": ["OIDC_CLIENT_ID", "OIDC_CLIENT_SECRET", "OIDC_AUTH_URI", "OIDC_TOKEN_URI", "OIDC_USERINFO_URI"]
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import get from "lodash/get";
|
||||
import { Strategy } from "passport-oauth2";
|
||||
import { slugifyDomain } from "@shared/utils/domains";
|
||||
import accountProvisioner from "@server/commands/accountProvisioner";
|
||||
import env from "@server/env";
|
||||
import {
|
||||
OIDCMalformedUserInfoError,
|
||||
AuthenticationError,
|
||||
@ -19,6 +18,7 @@ import {
|
||||
getTeamFromContext,
|
||||
getClientFromContext,
|
||||
} from "@server/utils/passport";
|
||||
import env from "../env";
|
||||
|
||||
const router = new Router();
|
||||
const providerName = "oidc";
|
||||
|
79
plugins/oidc/server/env.ts
Normal file
79
plugins/oidc/server/env.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { IsOptional, IsUrl, MaxLength } from "class-validator";
|
||||
import { Environment } from "@server/env";
|
||||
import environment from "@server/utils/environment";
|
||||
import { CannotUseWithout } from "@server/utils/validators";
|
||||
|
||||
class OIDCPluginEnvironment extends Environment {
|
||||
/**
|
||||
* OIDC client credentials. To enable authentication with any
|
||||
* compatible provider.
|
||||
*/
|
||||
@IsOptional()
|
||||
@CannotUseWithout("OIDC_CLIENT_SECRET")
|
||||
@CannotUseWithout("OIDC_AUTH_URI")
|
||||
@CannotUseWithout("OIDC_TOKEN_URI")
|
||||
@CannotUseWithout("OIDC_USERINFO_URI")
|
||||
@CannotUseWithout("OIDC_DISPLAY_NAME")
|
||||
public OIDC_CLIENT_ID = this.toOptionalString(environment.OIDC_CLIENT_ID);
|
||||
|
||||
@IsOptional()
|
||||
@CannotUseWithout("OIDC_CLIENT_ID")
|
||||
public OIDC_CLIENT_SECRET = this.toOptionalString(
|
||||
environment.OIDC_CLIENT_SECRET
|
||||
);
|
||||
|
||||
/**
|
||||
* The name of the OIDC provider, eg "GitLab" – this will be displayed on the
|
||||
* sign-in button and other places in the UI. The default value is:
|
||||
* "OpenID Connect".
|
||||
*/
|
||||
@MaxLength(50)
|
||||
public OIDC_DISPLAY_NAME = environment.OIDC_DISPLAY_NAME ?? "OpenID Connect";
|
||||
|
||||
/**
|
||||
* The OIDC authorization endpoint.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsUrl({
|
||||
require_tld: false,
|
||||
allow_underscores: true,
|
||||
})
|
||||
public OIDC_AUTH_URI = this.toOptionalString(environment.OIDC_AUTH_URI);
|
||||
|
||||
/**
|
||||
* The OIDC token endpoint.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsUrl({
|
||||
require_tld: false,
|
||||
allow_underscores: true,
|
||||
})
|
||||
public OIDC_TOKEN_URI = this.toOptionalString(environment.OIDC_TOKEN_URI);
|
||||
|
||||
/**
|
||||
* The OIDC userinfo endpoint.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsUrl({
|
||||
require_tld: false,
|
||||
allow_underscores: true,
|
||||
})
|
||||
public OIDC_USERINFO_URI = this.toOptionalString(
|
||||
environment.OIDC_USERINFO_URI
|
||||
);
|
||||
|
||||
/**
|
||||
* The OIDC profile field to use as the username. The default value is
|
||||
* "preferred_username".
|
||||
*/
|
||||
public OIDC_USERNAME_CLAIM =
|
||||
environment.OIDC_USERNAME_CLAIM ?? "preferred_username";
|
||||
|
||||
/**
|
||||
* A space separated list of OIDC scopes to request. Defaults to "openid
|
||||
* profile email".
|
||||
*/
|
||||
public OIDC_SCOPES = environment.OIDC_SCOPES ?? "openid profile email";
|
||||
}
|
||||
|
||||
export default new OIDCPluginEnvironment();
|
18
plugins/oidc/server/oidc.ts
Normal file
18
plugins/oidc/server/oidc.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import invariant from "invariant";
|
||||
import OAuthClient from "@server/utils/oauth";
|
||||
import env from "./env";
|
||||
|
||||
export default class OIDCClient extends OAuthClient {
|
||||
endpoints = {
|
||||
authorize: env.OIDC_AUTH_URI || "",
|
||||
token: env.OIDC_TOKEN_URI || "",
|
||||
userinfo: env.OIDC_USERINFO_URI || "",
|
||||
};
|
||||
|
||||
constructor() {
|
||||
invariant(env.OIDC_CLIENT_ID, "OIDC_CLIENT_ID is required");
|
||||
invariant(env.OIDC_CLIENT_SECRET, "OIDC_CLIENT_SECRET is required");
|
||||
|
||||
super(env.OIDC_CLIENT_ID, env.OIDC_CLIENT_SECRET);
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import randomstring from "randomstring";
|
||||
import { IntegrationService } from "@shared/types";
|
||||
import env from "@server/env";
|
||||
import { IntegrationAuthentication, SearchQuery } from "@server/models";
|
||||
import {
|
||||
buildDocument,
|
||||
@ -9,6 +8,7 @@ import {
|
||||
buildUser,
|
||||
} from "@server/test/factories";
|
||||
import { getTestServer } from "@server/test/support";
|
||||
import env from "../env";
|
||||
import * as Slack from "../slack";
|
||||
|
||||
jest.mock("../slack", () => ({
|
||||
|
@ -4,7 +4,6 @@ import escapeRegExp from "lodash/escapeRegExp";
|
||||
import { Op } from "sequelize";
|
||||
import { z } from "zod";
|
||||
import { IntegrationService } from "@shared/types";
|
||||
import env from "@server/env";
|
||||
import {
|
||||
AuthenticationError,
|
||||
InvalidRequestError,
|
||||
@ -26,6 +25,7 @@ import SearchHelper from "@server/models/helpers/SearchHelper";
|
||||
import { APIContext } from "@server/types";
|
||||
import { safeEqual } from "@server/utils/crypto";
|
||||
import { opts } from "@server/utils/i18n";
|
||||
import env from "../env";
|
||||
import presentMessageAttachment from "../presenters/messageAttachment";
|
||||
import * as Slack from "../slack";
|
||||
import * as T from "./schema";
|
||||
|
@ -6,7 +6,6 @@ import { Strategy as SlackStrategy } from "passport-slack-oauth2";
|
||||
import { IntegrationService, IntegrationType } from "@shared/types";
|
||||
import { integrationSettingsPath } from "@shared/utils/routeHelpers";
|
||||
import accountProvisioner from "@server/commands/accountProvisioner";
|
||||
import env from "@server/env";
|
||||
import auth from "@server/middlewares/authentication";
|
||||
import passportMiddleware from "@server/middlewares/passport";
|
||||
import validate from "@server/middlewares/validate";
|
||||
@ -23,6 +22,7 @@ import {
|
||||
getTeamFromContext,
|
||||
StateStore,
|
||||
} from "@server/utils/passport";
|
||||
import env from "../env";
|
||||
import * as Slack from "../slack";
|
||||
import * as T from "./schema";
|
||||
|
||||
|
44
plugins/slack/server/env.ts
Normal file
44
plugins/slack/server/env.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { IsBoolean, IsOptional } from "class-validator";
|
||||
import { Environment } from "@server/env";
|
||||
import Deprecated from "@server/models/decorators/Deprecated";
|
||||
import environment from "@server/utils/environment";
|
||||
import { CannotUseWithout } from "@server/utils/validators";
|
||||
|
||||
class SlackPluginEnvironment extends Environment {
|
||||
/**
|
||||
* Slack OAuth2 client credentials. To enable authentication with Slack.
|
||||
*/
|
||||
@IsOptional()
|
||||
@Deprecated("Use SLACK_CLIENT_SECRET instead")
|
||||
public SLACK_SECRET = this.toOptionalString(environment.SLACK_SECRET);
|
||||
|
||||
@IsOptional()
|
||||
@Deprecated("Use SLACK_CLIENT_ID instead")
|
||||
public SLACK_KEY = this.toOptionalString(environment.SLACK_KEY);
|
||||
|
||||
@IsOptional()
|
||||
@CannotUseWithout("SLACK_CLIENT_ID")
|
||||
public SLACK_CLIENT_SECRET = this.toOptionalString(
|
||||
environment.SLACK_CLIENT_SECRET ?? environment.SLACK_SECRET
|
||||
);
|
||||
|
||||
/**
|
||||
* Secret to verify webhook requests received from Slack.
|
||||
*/
|
||||
@IsOptional()
|
||||
public SLACK_VERIFICATION_TOKEN = this.toOptionalString(
|
||||
environment.SLACK_VERIFICATION_TOKEN
|
||||
);
|
||||
|
||||
/**
|
||||
* If enabled a "Post to Channel" button will be added to search result
|
||||
* messages inside of Slack. This also requires setup in Slack UI.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
public SLACK_MESSAGE_ACTIONS = this.toBoolean(
|
||||
environment.SLACK_MESSAGE_ACTIONS ?? "false"
|
||||
);
|
||||
}
|
||||
|
||||
export default new SlackPluginEnvironment();
|
@ -2,7 +2,6 @@ import { differenceInMilliseconds } from "date-fns";
|
||||
import { Op } from "sequelize";
|
||||
import { IntegrationService, IntegrationType } from "@shared/types";
|
||||
import { Minute } from "@shared/utils/time";
|
||||
import env from "@server/env";
|
||||
import { Document, Integration, Collection, Team } from "@server/models";
|
||||
import BaseProcessor from "@server/queues/processors/BaseProcessor";
|
||||
import {
|
||||
@ -12,6 +11,7 @@ import {
|
||||
Event,
|
||||
} from "@server/types";
|
||||
import fetch from "@server/utils/fetch";
|
||||
import env from "../env";
|
||||
import presentMessageAttachment from "../presenters/messageAttachment";
|
||||
|
||||
export default class SlackProcessor extends BaseProcessor {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import querystring from "querystring";
|
||||
import env from "@server/env";
|
||||
import { InvalidRequestError } from "@server/errors";
|
||||
import fetch from "@server/utils/fetch";
|
||||
import env from "./env";
|
||||
|
||||
const SLACK_API_URL = "https://slack.com/api";
|
||||
|
||||
|
@ -5,7 +5,6 @@ import FormData from "form-data";
|
||||
import { ensureDirSync } from "fs-extra";
|
||||
import { v4 as uuidV4 } from "uuid";
|
||||
import env from "@server/env";
|
||||
import "@server/test/env";
|
||||
import FileStorage from "@server/storage/files";
|
||||
import { buildAttachment, buildUser } from "@server/test/factories";
|
||||
import { getTestServer } from "@server/test/support";
|
||||
|
@ -4,7 +4,7 @@
|
||||
"dialect": "postgres"
|
||||
},
|
||||
"test": {
|
||||
"use_env_variable": "DATABASE_URL_TEST",
|
||||
"use_env_variable": "DATABASE_URL",
|
||||
"dialect": "postgres"
|
||||
},
|
||||
"production": {
|
||||
@ -20,4 +20,4 @@
|
||||
"use_env_variable": "DATABASE_URL",
|
||||
"dialect": "postgres"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
332
server/env.ts
332
server/env.ts
@ -1,10 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
// Load the process environment variables
|
||||
require("dotenv").config({
|
||||
silent: true,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line import/order
|
||||
import environment from "./utils/environment";
|
||||
import os from "os";
|
||||
import {
|
||||
validate,
|
||||
@ -16,7 +11,6 @@ import {
|
||||
IsIn,
|
||||
IsEmail,
|
||||
IsBoolean,
|
||||
MaxLength,
|
||||
} from "class-validator";
|
||||
import uniq from "lodash/uniq";
|
||||
import { languages } from "@shared/i18n";
|
||||
@ -25,7 +19,7 @@ import Deprecated from "./models/decorators/Deprecated";
|
||||
import { getArg } from "./utils/args";
|
||||
|
||||
export class Environment {
|
||||
private validationPromise;
|
||||
protected validationPromise;
|
||||
|
||||
constructor() {
|
||||
this.validationPromise = validate(this);
|
||||
@ -44,21 +38,23 @@ export class Environment {
|
||||
* The current environment name.
|
||||
*/
|
||||
@IsIn(["development", "production", "staging", "test"])
|
||||
public ENVIRONMENT = process.env.NODE_ENV ?? "production";
|
||||
public ENVIRONMENT = environment.NODE_ENV ?? "production";
|
||||
|
||||
/**
|
||||
* The secret key is used for encrypting data. Do not change this value once
|
||||
* set or your users will be unable to login.
|
||||
*/
|
||||
@IsByteLength(32, 64)
|
||||
public SECRET_KEY = process.env.SECRET_KEY ?? "";
|
||||
@IsByteLength(32, 64, {
|
||||
message: `The SECRET_KEY environment variable is invalid (Use \`openssl rand -hex 32\` to generate a value).`,
|
||||
})
|
||||
public SECRET_KEY = environment.SECRET_KEY ?? "";
|
||||
|
||||
/**
|
||||
* The secret that should be passed to the cron utility endpoint to enable
|
||||
* triggering of scheduled tasks.
|
||||
*/
|
||||
@IsNotEmpty()
|
||||
public UTILS_SECRET = process.env.UTILS_SECRET ?? "";
|
||||
public UTILS_SECRET = environment.UTILS_SECRET ?? "";
|
||||
|
||||
/**
|
||||
* The url of the database.
|
||||
@ -69,7 +65,7 @@ export class Environment {
|
||||
allow_underscores: true,
|
||||
protocols: ["postgres", "postgresql"],
|
||||
})
|
||||
public DATABASE_URL = process.env.DATABASE_URL ?? "";
|
||||
public DATABASE_URL = environment.DATABASE_URL ?? "";
|
||||
|
||||
/**
|
||||
* The url of the database pool.
|
||||
@ -81,7 +77,7 @@ export class Environment {
|
||||
protocols: ["postgres", "postgresql"],
|
||||
})
|
||||
public DATABASE_CONNECTION_POOL_URL = this.toOptionalString(
|
||||
process.env.DATABASE_CONNECTION_POOL_URL
|
||||
environment.DATABASE_CONNECTION_POOL_URL
|
||||
);
|
||||
|
||||
/**
|
||||
@ -90,7 +86,7 @@ export class Environment {
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
public DATABASE_CONNECTION_POOL_MIN = this.toOptionalNumber(
|
||||
process.env.DATABASE_CONNECTION_POOL_MIN
|
||||
environment.DATABASE_CONNECTION_POOL_MIN
|
||||
);
|
||||
|
||||
/**
|
||||
@ -99,7 +95,7 @@ export class Environment {
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
public DATABASE_CONNECTION_POOL_MAX = this.toOptionalNumber(
|
||||
process.env.DATABASE_CONNECTION_POOL_MAX
|
||||
environment.DATABASE_CONNECTION_POOL_MAX
|
||||
);
|
||||
|
||||
/**
|
||||
@ -110,7 +106,7 @@ export class Environment {
|
||||
*/
|
||||
@IsIn(["disable", "allow", "require", "prefer", "verify-ca", "verify-full"])
|
||||
@IsOptional()
|
||||
public PGSSLMODE = process.env.PGSSLMODE;
|
||||
public PGSSLMODE = environment.PGSSLMODE;
|
||||
|
||||
/**
|
||||
* The url of redis. Note that redis does not have a database after the port.
|
||||
@ -118,7 +114,7 @@ export class Environment {
|
||||
* base64-encoded configuration.
|
||||
*/
|
||||
@IsNotEmpty()
|
||||
public REDIS_URL = process.env.REDIS_URL;
|
||||
public REDIS_URL = environment.REDIS_URL;
|
||||
|
||||
/**
|
||||
* The fully qualified, external facing domain name of the server.
|
||||
@ -129,7 +125,7 @@ export class Environment {
|
||||
require_protocol: true,
|
||||
require_tld: false,
|
||||
})
|
||||
public URL = process.env.URL || "";
|
||||
public URL = environment.URL || "";
|
||||
|
||||
/**
|
||||
* If using a Cloudfront/Cloudflare distribution or similar it can be set below.
|
||||
@ -143,7 +139,7 @@ export class Environment {
|
||||
require_protocol: true,
|
||||
require_tld: false,
|
||||
})
|
||||
public CDN_URL = this.toOptionalString(process.env.CDN_URL);
|
||||
public CDN_URL = this.toOptionalString(environment.CDN_URL);
|
||||
|
||||
/**
|
||||
* The fully qualified, external facing domain name of the collaboration
|
||||
@ -156,7 +152,7 @@ export class Environment {
|
||||
})
|
||||
@IsOptional()
|
||||
public COLLABORATION_URL = this.toOptionalString(
|
||||
process.env.COLLABORATION_URL
|
||||
environment.COLLABORATION_URL
|
||||
);
|
||||
|
||||
/**
|
||||
@ -166,7 +162,7 @@ export class Environment {
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
public COLLABORATION_MAX_CLIENTS_PER_DOCUMENT = parseInt(
|
||||
process.env.COLLABORATION_MAX_CLIENTS_PER_DOCUMENT || "100",
|
||||
environment.COLLABORATION_MAX_CLIENTS_PER_DOCUMENT || "100",
|
||||
10
|
||||
);
|
||||
|
||||
@ -175,18 +171,18 @@ export class Environment {
|
||||
*/
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
public PORT = this.toOptionalNumber(process.env.PORT) ?? 3000;
|
||||
public PORT = this.toOptionalNumber(environment.PORT) ?? 3000;
|
||||
|
||||
/**
|
||||
* Optional extra debugging. Comma separated
|
||||
*/
|
||||
public DEBUG = process.env.DEBUG || "";
|
||||
public DEBUG = environment.DEBUG || "";
|
||||
|
||||
/**
|
||||
* Configure lowest severity level for server logs
|
||||
*/
|
||||
@IsIn(["error", "warn", "info", "http", "verbose", "debug", "silly"])
|
||||
public LOG_LEVEL = process.env.LOG_LEVEL || "info";
|
||||
public LOG_LEVEL = environment.LOG_LEVEL || "info";
|
||||
|
||||
/**
|
||||
* How many processes should be spawned. As a reasonable rule divide your
|
||||
@ -194,7 +190,7 @@ export class Environment {
|
||||
*/
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
public WEB_CONCURRENCY = this.toOptionalNumber(process.env.WEB_CONCURRENCY);
|
||||
public WEB_CONCURRENCY = this.toOptionalNumber(environment.WEB_CONCURRENCY);
|
||||
|
||||
/**
|
||||
* How long a request should be processed before giving up and returning an
|
||||
@ -203,28 +199,28 @@ export class Environment {
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
public REQUEST_TIMEOUT =
|
||||
this.toOptionalNumber(process.env.REQUEST_TIMEOUT) ?? 10 * 1000;
|
||||
this.toOptionalNumber(environment.REQUEST_TIMEOUT) ?? 10 * 1000;
|
||||
|
||||
/**
|
||||
* Base64 encoded private key if Outline is to perform SSL termination.
|
||||
* Base64 encoded protected key if Outline is to perform SSL termination.
|
||||
*/
|
||||
@IsOptional()
|
||||
@CannotUseWithout("SSL_CERT")
|
||||
public SSL_KEY = this.toOptionalString(process.env.SSL_KEY);
|
||||
public SSL_KEY = this.toOptionalString(environment.SSL_KEY);
|
||||
|
||||
/**
|
||||
* Base64 encoded public certificate if Outline is to perform SSL termination.
|
||||
*/
|
||||
@IsOptional()
|
||||
@CannotUseWithout("SSL_KEY")
|
||||
public SSL_CERT = this.toOptionalString(process.env.SSL_CERT);
|
||||
public SSL_CERT = this.toOptionalString(environment.SSL_CERT);
|
||||
|
||||
/**
|
||||
* The default interface language. See translate.getoutline.com for a list of
|
||||
* available language codes and their percentage translated.
|
||||
*/
|
||||
@IsIn(languages)
|
||||
public DEFAULT_LANGUAGE = process.env.DEFAULT_LANGUAGE ?? "en_US";
|
||||
public DEFAULT_LANGUAGE = environment.DEFAULT_LANGUAGE ?? "en_US";
|
||||
|
||||
/**
|
||||
* A comma list of which services should be enabled on this instance – defaults to all.
|
||||
@ -235,7 +231,7 @@ export class Environment {
|
||||
public SERVICES = uniq(
|
||||
(
|
||||
getArg("services") ??
|
||||
process.env.SERVICES ??
|
||||
environment.SERVICES ??
|
||||
"collaboration,websockets,worker,web"
|
||||
)
|
||||
.split(",")
|
||||
@ -248,7 +244,7 @@ export class Environment {
|
||||
* loadbalancer.
|
||||
*/
|
||||
@IsBoolean()
|
||||
public FORCE_HTTPS = this.toBoolean(process.env.FORCE_HTTPS ?? "true");
|
||||
public FORCE_HTTPS = this.toBoolean(environment.FORCE_HTTPS ?? "true");
|
||||
|
||||
/**
|
||||
* Should the installation send anonymized statistics to the maintainers.
|
||||
@ -256,51 +252,51 @@ export class Environment {
|
||||
*/
|
||||
@IsBoolean()
|
||||
public TELEMETRY = this.toBoolean(
|
||||
process.env.ENABLE_UPDATES ?? process.env.TELEMETRY ?? "true"
|
||||
environment.ENABLE_UPDATES ?? environment.TELEMETRY ?? "true"
|
||||
);
|
||||
|
||||
/**
|
||||
* An optional comma separated list of allowed domains.
|
||||
*/
|
||||
public ALLOWED_DOMAINS =
|
||||
process.env.ALLOWED_DOMAINS ?? process.env.GOOGLE_ALLOWED_DOMAINS;
|
||||
environment.ALLOWED_DOMAINS ?? environment.GOOGLE_ALLOWED_DOMAINS;
|
||||
|
||||
// Third-party services
|
||||
|
||||
/**
|
||||
* The host of your SMTP server for enabling emails.
|
||||
*/
|
||||
public SMTP_HOST = process.env.SMTP_HOST;
|
||||
public SMTP_HOST = environment.SMTP_HOST;
|
||||
|
||||
/**
|
||||
* Optional hostname of the client, used for identifying to the server
|
||||
* defaults to hostname of the machine.
|
||||
*/
|
||||
public SMTP_NAME = process.env.SMTP_NAME;
|
||||
public SMTP_NAME = environment.SMTP_NAME;
|
||||
|
||||
/**
|
||||
* The port of your SMTP server.
|
||||
*/
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
public SMTP_PORT = this.toOptionalNumber(process.env.SMTP_PORT);
|
||||
public SMTP_PORT = this.toOptionalNumber(environment.SMTP_PORT);
|
||||
|
||||
/**
|
||||
* The username of your SMTP server, if any.
|
||||
*/
|
||||
public SMTP_USERNAME = process.env.SMTP_USERNAME;
|
||||
public SMTP_USERNAME = environment.SMTP_USERNAME;
|
||||
|
||||
/**
|
||||
* The password for the SMTP username, if any.
|
||||
*/
|
||||
public SMTP_PASSWORD = process.env.SMTP_PASSWORD;
|
||||
public SMTP_PASSWORD = environment.SMTP_PASSWORD;
|
||||
|
||||
/**
|
||||
* The email address from which emails are sent.
|
||||
*/
|
||||
@IsEmail({ allow_display_name: true, allow_ip_domain: true })
|
||||
@IsOptional()
|
||||
public SMTP_FROM_EMAIL = this.toOptionalString(process.env.SMTP_FROM_EMAIL);
|
||||
public SMTP_FROM_EMAIL = this.toOptionalString(environment.SMTP_FROM_EMAIL);
|
||||
|
||||
/**
|
||||
* The reply-to address for emails sent from Outline. If unset the from
|
||||
@ -308,12 +304,12 @@ export class Environment {
|
||||
*/
|
||||
@IsEmail({ allow_display_name: true, allow_ip_domain: true })
|
||||
@IsOptional()
|
||||
public SMTP_REPLY_EMAIL = this.toOptionalString(process.env.SMTP_REPLY_EMAIL);
|
||||
public SMTP_REPLY_EMAIL = this.toOptionalString(environment.SMTP_REPLY_EMAIL);
|
||||
|
||||
/**
|
||||
* Override the cipher used for SMTP SSL connections.
|
||||
*/
|
||||
public SMTP_TLS_CIPHERS = this.toOptionalString(process.env.SMTP_TLS_CIPHERS);
|
||||
public SMTP_TLS_CIPHERS = this.toOptionalString(environment.SMTP_TLS_CIPHERS);
|
||||
|
||||
/**
|
||||
* If true (the default) the connection will use TLS when connecting to server.
|
||||
@ -322,182 +318,56 @@ export class Environment {
|
||||
* Setting secure to false therefore does not mean that you would not use an
|
||||
* encrypted connection.
|
||||
*/
|
||||
public SMTP_SECURE = this.toBoolean(process.env.SMTP_SECURE ?? "true");
|
||||
public SMTP_SECURE = this.toBoolean(environment.SMTP_SECURE ?? "true");
|
||||
|
||||
/**
|
||||
* Sentry DSN for capturing errors and frontend performance.
|
||||
*/
|
||||
@IsUrl()
|
||||
@IsOptional()
|
||||
public SENTRY_DSN = this.toOptionalString(process.env.SENTRY_DSN);
|
||||
public SENTRY_DSN = this.toOptionalString(environment.SENTRY_DSN);
|
||||
|
||||
/**
|
||||
* Sentry tunnel URL for bypassing ad blockers
|
||||
*/
|
||||
@IsUrl()
|
||||
@IsOptional()
|
||||
public SENTRY_TUNNEL = this.toOptionalString(process.env.SENTRY_TUNNEL);
|
||||
public SENTRY_TUNNEL = this.toOptionalString(environment.SENTRY_TUNNEL);
|
||||
|
||||
/**
|
||||
* A release SHA or other identifier for Sentry.
|
||||
*/
|
||||
public RELEASE = this.toOptionalString(process.env.RELEASE);
|
||||
public RELEASE = this.toOptionalString(environment.RELEASE);
|
||||
|
||||
/**
|
||||
* A Google Analytics tracking ID, supports v3 or v4 properties.
|
||||
*/
|
||||
@IsOptional()
|
||||
public GOOGLE_ANALYTICS_ID = this.toOptionalString(
|
||||
process.env.GOOGLE_ANALYTICS_ID
|
||||
environment.GOOGLE_ANALYTICS_ID
|
||||
);
|
||||
|
||||
/**
|
||||
* A DataDog API key for tracking server metrics.
|
||||
*/
|
||||
public DD_API_KEY = process.env.DD_API_KEY;
|
||||
public DD_API_KEY = environment.DD_API_KEY;
|
||||
|
||||
/**
|
||||
* The name of the service to use in DataDog.
|
||||
*/
|
||||
public DD_SERVICE = process.env.DD_SERVICE ?? "outline";
|
||||
|
||||
/**
|
||||
* Google OAuth2 client credentials. To enable authentication with Google.
|
||||
*/
|
||||
@IsOptional()
|
||||
@CannotUseWithout("GOOGLE_CLIENT_SECRET")
|
||||
public GOOGLE_CLIENT_ID = this.toOptionalString(process.env.GOOGLE_CLIENT_ID);
|
||||
public DD_SERVICE = environment.DD_SERVICE ?? "outline";
|
||||
|
||||
@IsOptional()
|
||||
@CannotUseWithout("GOOGLE_CLIENT_ID")
|
||||
public GOOGLE_CLIENT_SECRET = this.toOptionalString(
|
||||
process.env.GOOGLE_CLIENT_SECRET
|
||||
);
|
||||
|
||||
/**
|
||||
* Slack OAuth2 client credentials. To enable authentication with Slack.
|
||||
*/
|
||||
@IsOptional()
|
||||
@Deprecated("Use SLACK_CLIENT_SECRET instead")
|
||||
public SLACK_SECRET = this.toOptionalString(process.env.SLACK_SECRET);
|
||||
|
||||
@IsOptional()
|
||||
@Deprecated("Use SLACK_CLIENT_ID instead")
|
||||
public SLACK_KEY = this.toOptionalString(process.env.SLACK_KEY);
|
||||
|
||||
@IsOptional()
|
||||
@CannotUseWithout("SLACK_CLIENT_SECRET")
|
||||
public SLACK_CLIENT_ID = this.toOptionalString(
|
||||
process.env.SLACK_CLIENT_ID ?? process.env.SLACK_KEY
|
||||
);
|
||||
|
||||
@IsOptional()
|
||||
@CannotUseWithout("SLACK_CLIENT_ID")
|
||||
public SLACK_CLIENT_SECRET = this.toOptionalString(
|
||||
process.env.SLACK_CLIENT_SECRET ?? process.env.SLACK_SECRET
|
||||
environment.SLACK_CLIENT_ID ?? environment.SLACK_KEY
|
||||
);
|
||||
|
||||
/**
|
||||
* This is used to verify webhook requests received from Slack.
|
||||
*/
|
||||
@IsOptional()
|
||||
public SLACK_VERIFICATION_TOKEN = this.toOptionalString(
|
||||
process.env.SLACK_VERIFICATION_TOKEN
|
||||
);
|
||||
|
||||
/**
|
||||
* This is injected into the slack-app-id header meta tag if provided.
|
||||
* Injected into the `slack-app-id` header meta tag if provided.
|
||||
*/
|
||||
@IsOptional()
|
||||
@CannotUseWithout("SLACK_CLIENT_ID")
|
||||
public SLACK_APP_ID = this.toOptionalString(process.env.SLACK_APP_ID);
|
||||
|
||||
/**
|
||||
* If enabled a "Post to Channel" button will be added to search result
|
||||
* messages inside of Slack. This also requires setup in Slack UI.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
public SLACK_MESSAGE_ACTIONS = this.toBoolean(
|
||||
process.env.SLACK_MESSAGE_ACTIONS ?? "false"
|
||||
);
|
||||
|
||||
/**
|
||||
* Azure OAuth2 client credentials. To enable authentication with Azure.
|
||||
*/
|
||||
@IsOptional()
|
||||
@CannotUseWithout("AZURE_CLIENT_SECRET")
|
||||
public AZURE_CLIENT_ID = this.toOptionalString(process.env.AZURE_CLIENT_ID);
|
||||
|
||||
@IsOptional()
|
||||
@CannotUseWithout("AZURE_CLIENT_ID")
|
||||
public AZURE_CLIENT_SECRET = this.toOptionalString(
|
||||
process.env.AZURE_CLIENT_SECRET
|
||||
);
|
||||
|
||||
@IsOptional()
|
||||
@CannotUseWithout("AZURE_CLIENT_ID")
|
||||
public AZURE_RESOURCE_APP_ID = this.toOptionalString(
|
||||
process.env.AZURE_RESOURCE_APP_ID
|
||||
);
|
||||
|
||||
/**
|
||||
* OIDC client credentials. To enable authentication with any
|
||||
* compatible provider.
|
||||
*/
|
||||
@IsOptional()
|
||||
@CannotUseWithout("OIDC_CLIENT_SECRET")
|
||||
@CannotUseWithout("OIDC_AUTH_URI")
|
||||
@CannotUseWithout("OIDC_TOKEN_URI")
|
||||
@CannotUseWithout("OIDC_USERINFO_URI")
|
||||
@CannotUseWithout("OIDC_DISPLAY_NAME")
|
||||
public OIDC_CLIENT_ID = this.toOptionalString(process.env.OIDC_CLIENT_ID);
|
||||
|
||||
@IsOptional()
|
||||
@CannotUseWithout("OIDC_CLIENT_ID")
|
||||
public OIDC_CLIENT_SECRET = this.toOptionalString(
|
||||
process.env.OIDC_CLIENT_SECRET
|
||||
);
|
||||
|
||||
/**
|
||||
* The name of the OIDC provider, eg "GitLab" – this will be displayed on the
|
||||
* sign-in button and other places in the UI. The default value is:
|
||||
* "OpenID Connect".
|
||||
*/
|
||||
@MaxLength(50)
|
||||
public OIDC_DISPLAY_NAME = process.env.OIDC_DISPLAY_NAME ?? "OpenID Connect";
|
||||
|
||||
/**
|
||||
* The OIDC authorization endpoint.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsUrl({
|
||||
require_tld: false,
|
||||
allow_underscores: true,
|
||||
})
|
||||
public OIDC_AUTH_URI = this.toOptionalString(process.env.OIDC_AUTH_URI);
|
||||
|
||||
/**
|
||||
* The OIDC token endpoint.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsUrl({
|
||||
require_tld: false,
|
||||
allow_underscores: true,
|
||||
})
|
||||
public OIDC_TOKEN_URI = this.toOptionalString(process.env.OIDC_TOKEN_URI);
|
||||
|
||||
/**
|
||||
* The OIDC userinfo endpoint.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsUrl({
|
||||
require_tld: false,
|
||||
allow_underscores: true,
|
||||
})
|
||||
public OIDC_USERINFO_URI = this.toOptionalString(
|
||||
process.env.OIDC_USERINFO_URI
|
||||
);
|
||||
public SLACK_APP_ID = this.toOptionalString(environment.SLACK_APP_ID);
|
||||
|
||||
/**
|
||||
* Disable autoredirect to the OIDC login page if there is only one
|
||||
@ -506,7 +376,7 @@ export class Environment {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
public OIDC_DISABLE_REDIRECT = this.toOptionalBoolean(
|
||||
process.env.OIDC_DISABLE_REDIRECT
|
||||
environment.OIDC_DISABLE_REDIRECT
|
||||
);
|
||||
|
||||
/**
|
||||
@ -517,20 +387,7 @@ export class Environment {
|
||||
require_tld: false,
|
||||
allow_underscores: true,
|
||||
})
|
||||
public OIDC_LOGOUT_URI = this.toOptionalString(process.env.OIDC_LOGOUT_URI);
|
||||
|
||||
/**
|
||||
* The OIDC profile field to use as the username. The default value is
|
||||
* "preferred_username".
|
||||
*/
|
||||
public OIDC_USERNAME_CLAIM =
|
||||
process.env.OIDC_USERNAME_CLAIM ?? "preferred_username";
|
||||
|
||||
/**
|
||||
* A space separated list of OIDC scopes to request. Defaults to "openid
|
||||
* profile email".
|
||||
*/
|
||||
public OIDC_SCOPES = process.env.OIDC_SCOPES ?? "openid profile email";
|
||||
public OIDC_LOGOUT_URI = this.toOptionalString(environment.OIDC_LOGOUT_URI);
|
||||
|
||||
/**
|
||||
* A string representing the version of the software.
|
||||
@ -539,7 +396,7 @@ export class Environment {
|
||||
* SOURCE_VERSION is used by Heroku
|
||||
*/
|
||||
public VERSION = this.toOptionalString(
|
||||
process.env.SOURCE_COMMIT || process.env.SOURCE_VERSION
|
||||
environment.SOURCE_COMMIT || environment.SOURCE_VERSION
|
||||
);
|
||||
|
||||
/**
|
||||
@ -548,7 +405,7 @@ export class Environment {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
public RATE_LIMITER_ENABLED = this.toBoolean(
|
||||
process.env.RATE_LIMITER_ENABLED ?? "false"
|
||||
environment.RATE_LIMITER_ENABLED ?? "false"
|
||||
);
|
||||
|
||||
/**
|
||||
@ -559,7 +416,7 @@ export class Environment {
|
||||
@IsNumber()
|
||||
@CannotUseWithout("RATE_LIMITER_ENABLED")
|
||||
public RATE_LIMITER_REQUESTS =
|
||||
this.toOptionalNumber(process.env.RATE_LIMITER_REQUESTS) ?? 1000;
|
||||
this.toOptionalNumber(environment.RATE_LIMITER_REQUESTS) ?? 1000;
|
||||
|
||||
/**
|
||||
* Set max allowed realtime connections before throttling. Defaults to 50
|
||||
@ -568,7 +425,7 @@ export class Environment {
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
public RATE_LIMITER_COLLABORATION_REQUESTS =
|
||||
this.toOptionalNumber(process.env.RATE_LIMITER_COLLABORATION_REQUESTS) ??
|
||||
this.toOptionalNumber(environment.RATE_LIMITER_COLLABORATION_REQUESTS) ??
|
||||
50;
|
||||
|
||||
/**
|
||||
@ -579,7 +436,7 @@ export class Environment {
|
||||
@IsNumber()
|
||||
@CannotUseWithout("RATE_LIMITER_ENABLED")
|
||||
public RATE_LIMITER_DURATION_WINDOW =
|
||||
this.toOptionalNumber(process.env.RATE_LIMITER_DURATION_WINDOW) ?? 60;
|
||||
this.toOptionalNumber(environment.RATE_LIMITER_DURATION_WINDOW) ?? 60;
|
||||
|
||||
/**
|
||||
* Set max allowed upload size for file attachments.
|
||||
@ -589,7 +446,7 @@ export class Environment {
|
||||
@IsNumber()
|
||||
@Deprecated("Use FILE_STORAGE_UPLOAD_MAX_SIZE instead")
|
||||
public AWS_S3_UPLOAD_MAX_SIZE = this.toOptionalNumber(
|
||||
process.env.AWS_S3_UPLOAD_MAX_SIZE
|
||||
environment.AWS_S3_UPLOAD_MAX_SIZE
|
||||
);
|
||||
|
||||
/**
|
||||
@ -597,7 +454,7 @@ export class Environment {
|
||||
*/
|
||||
@IsOptional()
|
||||
public AWS_ACCESS_KEY_ID = this.toOptionalString(
|
||||
process.env.AWS_ACCESS_KEY_ID
|
||||
environment.AWS_ACCESS_KEY_ID
|
||||
);
|
||||
|
||||
/**
|
||||
@ -606,35 +463,35 @@ export class Environment {
|
||||
@IsOptional()
|
||||
@CannotUseWithout("AWS_ACCESS_KEY_ID")
|
||||
public AWS_SECRET_ACCESS_KEY = this.toOptionalString(
|
||||
process.env.AWS_SECRET_ACCESS_KEY
|
||||
environment.AWS_SECRET_ACCESS_KEY
|
||||
);
|
||||
|
||||
/**
|
||||
* The name of the AWS S3 region to use.
|
||||
*/
|
||||
@IsOptional()
|
||||
public AWS_REGION = this.toOptionalString(process.env.AWS_REGION);
|
||||
public AWS_REGION = this.toOptionalString(environment.AWS_REGION);
|
||||
|
||||
/**
|
||||
* Optional AWS S3 endpoint URL for file attachments.
|
||||
*/
|
||||
@IsOptional()
|
||||
public AWS_S3_ACCELERATE_URL = this.toOptionalString(
|
||||
process.env.AWS_S3_ACCELERATE_URL
|
||||
environment.AWS_S3_ACCELERATE_URL
|
||||
);
|
||||
|
||||
/**
|
||||
* Optional AWS S3 endpoint URL for file attachments.
|
||||
*/
|
||||
@IsOptional()
|
||||
public AWS_S3_UPLOAD_BUCKET_URL = process.env.AWS_S3_UPLOAD_BUCKET_URL ?? "";
|
||||
public AWS_S3_UPLOAD_BUCKET_URL = environment.AWS_S3_UPLOAD_BUCKET_URL ?? "";
|
||||
|
||||
/**
|
||||
* The bucket name to store file attachments in.
|
||||
*/
|
||||
@IsOptional()
|
||||
public AWS_S3_UPLOAD_BUCKET_NAME = this.toOptionalString(
|
||||
process.env.AWS_S3_UPLOAD_BUCKET_NAME
|
||||
environment.AWS_S3_UPLOAD_BUCKET_NAME
|
||||
);
|
||||
|
||||
/**
|
||||
@ -643,26 +500,26 @@ export class Environment {
|
||||
*/
|
||||
@IsOptional()
|
||||
public AWS_S3_FORCE_PATH_STYLE = this.toBoolean(
|
||||
process.env.AWS_S3_FORCE_PATH_STYLE ?? "true"
|
||||
environment.AWS_S3_FORCE_PATH_STYLE ?? "true"
|
||||
);
|
||||
|
||||
/**
|
||||
* Set default AWS S3 ACL for file attachments.
|
||||
*/
|
||||
@IsOptional()
|
||||
public AWS_S3_ACL = process.env.AWS_S3_ACL ?? "private";
|
||||
public AWS_S3_ACL = environment.AWS_S3_ACL ?? "private";
|
||||
|
||||
/**
|
||||
* Which file storage system to use
|
||||
*/
|
||||
@IsIn(["local", "s3"])
|
||||
public FILE_STORAGE = this.toOptionalString(process.env.FILE_STORAGE) ?? "s3";
|
||||
public FILE_STORAGE = this.toOptionalString(environment.FILE_STORAGE) ?? "s3";
|
||||
|
||||
/**
|
||||
* Set default root dir path for local file storage
|
||||
*/
|
||||
public FILE_STORAGE_LOCAL_ROOT_DIR =
|
||||
this.toOptionalString(process.env.FILE_STORAGE_LOCAL_ROOT_DIR) ??
|
||||
this.toOptionalString(environment.FILE_STORAGE_LOCAL_ROOT_DIR) ??
|
||||
"/var/lib/outline/data";
|
||||
|
||||
/**
|
||||
@ -670,8 +527,8 @@ export class Environment {
|
||||
*/
|
||||
@IsNumber()
|
||||
public FILE_STORAGE_UPLOAD_MAX_SIZE =
|
||||
this.toOptionalNumber(process.env.FILE_STORAGE_UPLOAD_MAX_SIZE) ??
|
||||
this.toOptionalNumber(process.env.AWS_S3_UPLOAD_MAX_SIZE) ??
|
||||
this.toOptionalNumber(environment.FILE_STORAGE_UPLOAD_MAX_SIZE) ??
|
||||
this.toOptionalNumber(environment.AWS_S3_UPLOAD_MAX_SIZE) ??
|
||||
1000000;
|
||||
|
||||
/**
|
||||
@ -679,9 +536,9 @@ export class Environment {
|
||||
*/
|
||||
@IsNumber()
|
||||
public FILE_STORAGE_IMPORT_MAX_SIZE =
|
||||
this.toOptionalNumber(process.env.FILE_STORAGE_IMPORT_MAX_SIZE) ??
|
||||
this.toOptionalNumber(process.env.MAXIMUM_IMPORT_SIZE) ??
|
||||
this.toOptionalNumber(process.env.FILE_STORAGE_UPLOAD_MAX_SIZE) ??
|
||||
this.toOptionalNumber(environment.FILE_STORAGE_IMPORT_MAX_SIZE) ??
|
||||
this.toOptionalNumber(environment.MAXIMUM_IMPORT_SIZE) ??
|
||||
this.toOptionalNumber(environment.FILE_STORAGE_UPLOAD_MAX_SIZE) ??
|
||||
1000000;
|
||||
|
||||
/**
|
||||
@ -689,9 +546,9 @@ export class Environment {
|
||||
*/
|
||||
@IsNumber()
|
||||
public FILE_STORAGE_WORKSPACE_IMPORT_MAX_SIZE =
|
||||
this.toOptionalNumber(process.env.FILE_STORAGE_WORKSPACE_IMPORT_MAX_SIZE) ??
|
||||
this.toOptionalNumber(process.env.MAXIMUM_IMPORT_SIZE) ??
|
||||
this.toOptionalNumber(process.env.FILE_STORAGE_UPLOAD_MAX_SIZE) ??
|
||||
this.toOptionalNumber(environment.FILE_STORAGE_WORKSPACE_IMPORT_MAX_SIZE) ??
|
||||
this.toOptionalNumber(environment.MAXIMUM_IMPORT_SIZE) ??
|
||||
this.toOptionalNumber(environment.FILE_STORAGE_UPLOAD_MAX_SIZE) ??
|
||||
1000000;
|
||||
|
||||
/**
|
||||
@ -705,7 +562,7 @@ export class Environment {
|
||||
@IsNumber()
|
||||
@Deprecated("Use FILE_STORAGE_IMPORT_MAX_SIZE instead")
|
||||
public MAXIMUM_IMPORT_SIZE = this.toOptionalNumber(
|
||||
process.env.MAXIMUM_IMPORT_SIZE
|
||||
environment.MAXIMUM_IMPORT_SIZE
|
||||
);
|
||||
|
||||
/**
|
||||
@ -714,33 +571,14 @@ export class Environment {
|
||||
*/
|
||||
@IsNumber()
|
||||
public MAXIMUM_EXPORT_SIZE =
|
||||
this.toOptionalNumber(process.env.MAXIMUM_EXPORT_SIZE) ?? os.totalmem();
|
||||
|
||||
/**
|
||||
* Iframely url
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsUrl({
|
||||
require_tld: false,
|
||||
require_protocol: true,
|
||||
allow_underscores: true,
|
||||
protocols: ["http", "https"],
|
||||
})
|
||||
public IFRAMELY_URL = process.env.IFRAMELY_URL ?? "https://iframe.ly";
|
||||
|
||||
/**
|
||||
* Iframely API key
|
||||
*/
|
||||
@IsOptional()
|
||||
@CannotUseWithout("IFRAMELY_URL")
|
||||
public IFRAMELY_API_KEY = this.toOptionalString(process.env.IFRAMELY_API_KEY);
|
||||
this.toOptionalNumber(environment.MAXIMUM_EXPORT_SIZE) ?? os.totalmem();
|
||||
|
||||
/**
|
||||
* Enable unsafe-inline in script-src CSP directive
|
||||
*/
|
||||
@IsBoolean()
|
||||
public DEVELOPMENT_UNSAFE_INLINE_CSP = this.toBoolean(
|
||||
process.env.DEVELOPMENT_UNSAFE_INLINE_CSP ?? "false"
|
||||
environment.DEVELOPMENT_UNSAFE_INLINE_CSP ?? "false"
|
||||
);
|
||||
|
||||
/**
|
||||
@ -781,11 +619,11 @@ export class Environment {
|
||||
return this.ENVIRONMENT === "test";
|
||||
}
|
||||
|
||||
private toOptionalString(value: string | undefined) {
|
||||
protected toOptionalString(value: string | undefined) {
|
||||
return value ? value : undefined;
|
||||
}
|
||||
|
||||
private toOptionalNumber(value: string | undefined) {
|
||||
protected toOptionalNumber(value: string | undefined) {
|
||||
return value ? parseInt(value, 10) : undefined;
|
||||
}
|
||||
|
||||
@ -801,7 +639,7 @@ export class Environment {
|
||||
* @param value The string to convert
|
||||
* @returns A boolean
|
||||
*/
|
||||
private toBoolean(value: string) {
|
||||
protected toBoolean(value: string) {
|
||||
try {
|
||||
return value ? !!JSON.parse(value) : false;
|
||||
} catch (err) {
|
||||
@ -823,7 +661,7 @@ export class Environment {
|
||||
* @param value The string to convert
|
||||
* @returns A boolean or undefined
|
||||
*/
|
||||
private toOptionalBoolean(value: string | undefined) {
|
||||
protected toOptionalBoolean(value: string | undefined) {
|
||||
try {
|
||||
return value ? !!JSON.parse(value) : undefined;
|
||||
} catch (err) {
|
||||
@ -832,6 +670,4 @@ export class Environment {
|
||||
}
|
||||
}
|
||||
|
||||
const env = new Environment();
|
||||
|
||||
export default env;
|
||||
export default new Environment();
|
||||
|
@ -16,17 +16,18 @@ import {
|
||||
IsUUID,
|
||||
PrimaryKey,
|
||||
} from "sequelize-typescript";
|
||||
import env from "@server/env";
|
||||
import Model from "@server/models/base/Model";
|
||||
import AzureClient from "@server/utils/azure";
|
||||
import GoogleClient from "@server/utils/google";
|
||||
import OIDCClient from "@server/utils/oidc";
|
||||
import { ValidationError } from "../errors";
|
||||
import Team from "./Team";
|
||||
import UserAuthentication from "./UserAuthentication";
|
||||
import Fix from "./decorators/Fix";
|
||||
import Length from "./validators/Length";
|
||||
|
||||
// TODO: Avoid this hardcoding of plugins
|
||||
import AzureClient from "plugins/azure/server/azure";
|
||||
import GoogleClient from "plugins/google/server/google";
|
||||
import OIDCClient from "plugins/oidc/server/oidc";
|
||||
|
||||
@Table({
|
||||
tableName: "authentication_providers",
|
||||
modelName: "authentication_provider",
|
||||
@ -86,20 +87,11 @@ class AuthenticationProvider extends Model<
|
||||
get oauthClient() {
|
||||
switch (this.name) {
|
||||
case "google":
|
||||
return new GoogleClient(
|
||||
env.GOOGLE_CLIENT_ID || "",
|
||||
env.GOOGLE_CLIENT_SECRET || ""
|
||||
);
|
||||
return new GoogleClient();
|
||||
case "azure":
|
||||
return new AzureClient(
|
||||
env.AZURE_CLIENT_ID || "",
|
||||
env.AZURE_CLIENT_SECRET || ""
|
||||
);
|
||||
return new AzureClient();
|
||||
case "oidc":
|
||||
return new OIDCClient(
|
||||
env.OIDC_CLIENT_ID || "",
|
||||
env.OIDC_CLIENT_SECRET || ""
|
||||
);
|
||||
return new OIDCClient();
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import find from "lodash/find";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import env from "@server/env";
|
||||
import Team from "@server/models/Team";
|
||||
import environment from "@server/utils/environment";
|
||||
|
||||
export type AuthenticationProviderConfig = {
|
||||
id: string;
|
||||
@ -49,7 +50,7 @@ export default class AuthenticationHelper {
|
||||
|
||||
// Test the all required env vars are set for the auth provider
|
||||
const enabled = (config.requiredEnvVars ?? []).every(
|
||||
(name: string) => !!env[name]
|
||||
(name: string) => !!environment[name]
|
||||
);
|
||||
|
||||
if (enabled) {
|
||||
|
@ -1,27 +0,0 @@
|
||||
import env from "../env";
|
||||
|
||||
// test environment variables
|
||||
env.SMTP_HOST = "smtp.example.com";
|
||||
env.ENVIRONMENT = "test";
|
||||
env.GOOGLE_CLIENT_ID = "123";
|
||||
env.GOOGLE_CLIENT_SECRET = "123";
|
||||
env.SLACK_CLIENT_ID = "123";
|
||||
env.SLACK_CLIENT_SECRET = "123";
|
||||
|
||||
env.AZURE_CLIENT_ID = undefined;
|
||||
env.AZURE_CLIENT_SECRET = undefined;
|
||||
env.OIDC_CLIENT_ID = "client-id";
|
||||
env.OIDC_CLIENT_SECRET = "client-secret";
|
||||
env.OIDC_AUTH_URI = "http://localhost/authorize";
|
||||
env.OIDC_TOKEN_URI = "http://localhost/token";
|
||||
env.OIDC_USERINFO_URI = "http://localhost/userinfo";
|
||||
|
||||
env.RATE_LIMITER_ENABLED = false;
|
||||
|
||||
env.FILE_STORAGE = "local";
|
||||
env.FILE_STORAGE_LOCAL_ROOT_DIR = "/tmp";
|
||||
env.IFRAMELY_API_KEY = "123";
|
||||
|
||||
if (process.env.DATABASE_URL_TEST) {
|
||||
env.DATABASE_URL = process.env.DATABASE_URL_TEST;
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import "./env";
|
||||
import { sequelize } from "@server/storage/database";
|
||||
|
||||
module.exports = async function () {
|
||||
|
2
server/typings/index.d.ts
vendored
2
server/typings/index.d.ts
vendored
@ -6,8 +6,6 @@ declare module "formidable/lib/file";
|
||||
|
||||
declare module "oy-vey";
|
||||
|
||||
declare module "dotenv";
|
||||
|
||||
declare module "email-providers" {
|
||||
const list: string[];
|
||||
export default list;
|
||||
|
39
server/utils/environment.ts
Normal file
39
server/utils/environment.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
let environment: Record<string, string> = {};
|
||||
|
||||
const envPath = path.resolve(process.cwd(), `.env`);
|
||||
const envDefault = fs.existsSync(envPath)
|
||||
? dotenv.parse(fs.readFileSync(envPath, "utf8"))
|
||||
: {};
|
||||
|
||||
// Load environment specific variables, in reverse order of precedence
|
||||
const environments = ["production", "development", "local", "test"];
|
||||
|
||||
for (const env of environments) {
|
||||
const isEnv = process.env.NODE_ENV === env || envDefault.NODE_ENV === env;
|
||||
const isLocalDevelopment =
|
||||
env === "local" &&
|
||||
(process.env.NODE_ENV === "development" ||
|
||||
envDefault.NODE_ENV === "development");
|
||||
|
||||
if (isEnv || isLocalDevelopment) {
|
||||
const resolvedPath = path.resolve(process.cwd(), `.env.${env}`);
|
||||
if (fs.existsSync(resolvedPath)) {
|
||||
environment = {
|
||||
...environment,
|
||||
...dotenv.parse(fs.readFileSync(resolvedPath, "utf8")),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.env = {
|
||||
...envDefault,
|
||||
...environment,
|
||||
...process.env,
|
||||
};
|
||||
|
||||
export default process.env;
|
@ -1,9 +0,0 @@
|
||||
import OAuthClient from "./oauth";
|
||||
|
||||
export default class GoogleClient extends OAuthClient {
|
||||
endpoints = {
|
||||
authorize: "https://accounts.google.com/o/oauth2/auth",
|
||||
token: "https://accounts.google.com/o/oauth2/token",
|
||||
userinfo: "https://www.googleapis.com/oauth2/v3/userinfo",
|
||||
};
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import env from "@server/env";
|
||||
import OAuthClient from "./oauth";
|
||||
|
||||
export default class OIDCClient extends OAuthClient {
|
||||
endpoints = {
|
||||
authorize: env.OIDC_AUTH_URI || "",
|
||||
token: env.OIDC_TOKEN_URI || "",
|
||||
userinfo: env.OIDC_USERINFO_URI || "",
|
||||
};
|
||||
}
|
@ -4,6 +4,7 @@ import glob from "glob";
|
||||
import env from "@server/env";
|
||||
import Logger from "@server/logging/Logger";
|
||||
import { UnfurlResolver } from "@server/types";
|
||||
import environment from "./environment";
|
||||
|
||||
const rootDir = env.ENVIRONMENT === "test" ? "" : "build";
|
||||
|
||||
@ -25,7 +26,7 @@ const resolvers: Record<string, UnfurlResolver> = plugins.reduce(
|
||||
|
||||
// Test the all required env vars are set for the resolver
|
||||
const enabled = (config.requiredEnvVars ?? []).every(
|
||||
(name: string) => !!env[name]
|
||||
(name: string) => !!environment[name]
|
||||
);
|
||||
if (!enabled) {
|
||||
return resolvers;
|
||||
|
@ -2,20 +2,15 @@ import fs from "fs";
|
||||
import path from "path";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import browserslistToEsbuild from "browserslist-to-esbuild";
|
||||
import dotenv from "dotenv";
|
||||
import { webpackStats } from "rollup-plugin-webpack-stats";
|
||||
import { CommonServerOptions, defineConfig } from "vite";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||
|
||||
// Load the process environment variables
|
||||
dotenv.config({
|
||||
silent: true,
|
||||
});
|
||||
import environment from "./server/utils/environment";
|
||||
|
||||
let httpsConfig: CommonServerOptions["https"] | undefined;
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
if (environment.NODE_ENV === "development") {
|
||||
try {
|
||||
httpsConfig = {
|
||||
key: fs.readFileSync("./server/config/certs/private.key"),
|
||||
@ -31,13 +26,13 @@ export default () =>
|
||||
defineConfig({
|
||||
root: "./",
|
||||
publicDir: "./server/static",
|
||||
base: (process.env.CDN_URL ?? "") + "/static/",
|
||||
base: (environment.CDN_URL ?? "") + "/static/",
|
||||
server: {
|
||||
port: 3001,
|
||||
host: true,
|
||||
https: httpsConfig,
|
||||
fs:
|
||||
process.env.NODE_ENV === "development"
|
||||
environment.NODE_ENV === "development"
|
||||
? {
|
||||
// Allow serving files from one level up to the project root
|
||||
allow: [".."],
|
||||
@ -91,7 +86,7 @@ export default () =>
|
||||
globPatterns: ["**/*.{js,css,ico,png,svg}"],
|
||||
navigateFallback: null,
|
||||
modifyURLPrefix: {
|
||||
"": `${process.env.CDN_URL ?? ""}/static/`,
|
||||
"": `${environment.CDN_URL ?? ""}/static/`,
|
||||
},
|
||||
runtimeCaching: [
|
||||
{
|
||||
|
17
yarn.lock
17
yarn.lock
@ -2864,6 +2864,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/diff/-/diff-5.0.4.tgz#ba774c225ee68ce13a090fec16cf34b97a78537b"
|
||||
integrity "sha1-undMIl7mjOE6CQ/sFs80uXp4U3s= sha512-d7489/WO4B65k0SIqxXtviR9+MrPDipWQF6w+5D7YPrqgu6Qb87JsTdWQaNZo7itcdbViQSev3Jaz7dtKO0+Dg=="
|
||||
|
||||
"@types/dotenv@^8.2.0":
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/dotenv/-/dotenv-8.2.0.tgz#5cd64710c3c98e82d9d15844375a33bf1b45d053"
|
||||
integrity sha512-ylSC9GhfRH7m1EUXBXofhgx4lUWmFeQDINW5oLuS+gxWdfUeW4zJdeVTYVkexEW+e2VUvlZR2kGnGGipAWR7kw==
|
||||
dependencies:
|
||||
dotenv "*"
|
||||
|
||||
"@types/emoji-regex@^9.2.0":
|
||||
version "9.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/emoji-regex/-/emoji-regex-9.2.0.tgz#2e117de04f5fa561c5dcbe43a860ecd856517525"
|
||||
@ -5895,16 +5902,16 @@ dot-prop@^5.2.0:
|
||||
dependencies:
|
||||
is-obj "^2.0.0"
|
||||
|
||||
dotenv@*, dotenv@^16.4.5:
|
||||
version "16.4.5"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
|
||||
integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
|
||||
|
||||
dotenv@16.3.1:
|
||||
version "16.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
|
||||
integrity "sha1-NpA03n1+WxIJcmkzUqO/ESFyzD4= sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ=="
|
||||
|
||||
dotenv@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d"
|
||||
integrity "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0= sha512-XcaMACOr3JMVcEv0Y/iUM2XaOsATRZ3U1In41/1jjK6vJZ2PZbQ1bzCG8uvaByfaBpl9gqc9QWJovpUGBXLLYQ=="
|
||||
|
||||
dottie@^2.0.6:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/dottie/-/dottie-2.0.6.tgz#34564ebfc6ec5e5772272d466424ad5b696484d4"
|
||||
|
Reference in New Issue
Block a user