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