Skip to content

Commit 60e52d0

Browse files
authored
Separate environment configs (outline#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)
1 parent 415383a commit 60e52d0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+489
-409
lines changed

.circleci/config.yml

+1-6
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,8 @@ defaults: &defaults
1313
resource_class: large
1414
environment:
1515
NODE_ENV: test
16-
SECRET_KEY: F0E5AD933D7F6FD8F4DBB3E038C501C052DC0593C686D21ACB30AE205D2F634B
17-
DATABASE_URL_TEST: postgres://postgres:password@localhost:5432/circle_test
1816
DATABASE_URL: postgres://postgres:password@localhost:5432/circle_test
1917
URL: http://localhost:3000
20-
SMTP_FROM_EMAIL: hello@example.com
21-
AWS_S3_UPLOAD_BUCKET_URL: https://s3.amazonaws.com
22-
AWS_S3_UPLOAD_BUCKET_NAME: outline-circle
2318
NODE_OPTIONS: --max-old-space-size=8000
2419

2520
executors:
@@ -89,7 +84,7 @@ jobs:
8984
key: dependency-cache-v1-{{ checksum "package.json" }}
9085
- run:
9186
name: migrate
92-
command: ./node_modules/.bin/sequelize db:migrate --url $DATABASE_URL_TEST
87+
command: ./node_modules/.bin/sequelize db:migrate
9388
- run:
9489
name: test
9590
command: |

.env.development

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
URL=https://local.outline.dev:3000
2+
3+
SMTP_FROM_EMAIL=hello@example.com
4+
5+
# Enable unsafe-inline in script-src CSP directive
6+
# Setting it to true allows React dev tools add-on in Firefox to successfully detect the project
7+
DEVELOPMENT_UNSAFE_INLINE_CSP=true
8+
9+
# Increase the log level to debug for development
10+
LOG_LEVEL=debug

.env.sample

+5-14
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ UTILS_SECRET=generate_a_new_key
1313
# For production point these at your databases, in development the default
1414
# should work out of the box.
1515
DATABASE_URL=postgres://user:pass@localhost:5432/outline
16-
DATABASE_URL_TEST=postgres://user:pass@localhost:5432/outline-test
1716
DATABASE_CONNECTION_POOL_MIN=
1817
DATABASE_CONNECTION_POOL_MAX=
1918
# Uncomment this to disable SSL for connecting to Postgres
@@ -30,7 +29,7 @@ REDIS_URL=redis://localhost:6379
3029

3130
# URL should point to the fully qualified, publicly accessible URL. If using a
3231
# proxy the port in URL and PORT may be different.
33-
URL=https://app.outline.dev:3000
32+
URL=
3433
PORT=3000
3534

3635
# See [documentation](docs/SERVICES.md) on running a separate collaboration
@@ -166,9 +165,6 @@ SLACK_VERIFICATION_TOKEN=your_token
166165
SLACK_APP_ID=A0XXXXXXX
167166
SLACK_MESSAGE_ACTIONS=true
168167

169-
# Optionally enable google analytics to track pageviews in the knowledge base
170-
GOOGLE_ANALYTICS_ID=
171-
172168
# Optionally enable Sentry (sentry.io) to track errors and performance,
173169
# and optionally add a Sentry proxy tunnel for bypassing ad blockers in the UI:
174170
# https://docs.sentry.io/platforms/javascript/troubleshooting/#using-the-tunnel-option)
@@ -181,8 +177,8 @@ SMTP_HOST=
181177
SMTP_PORT=
182178
SMTP_USERNAME=
183179
SMTP_PASSWORD=
184-
SMTP_FROM_EMAIL=hello@example.com
185-
SMTP_REPLY_EMAIL=hello@example.com
180+
SMTP_FROM_EMAIL=
181+
SMTP_REPLY_EMAIL=
186182
SMTP_TLS_CIPHERS=
187183
SMTP_SECURE=true
188184

@@ -198,10 +194,5 @@ RATE_LIMITER_REQUESTS=1000
198194
RATE_LIMITER_DURATION_WINDOW=60
199195

200196
# Iframely API config
201-
# IFRAMELY_URL=
202-
# IFRAMELY_API_KEY=
203-
204-
# Enable unsafe-inline in script-src CSP directive
205-
# Setting it to true allows React dev tools add-on in
206-
# Firefox to successfully detect the project
207-
DEVELOPMENT_UNSAFE_INLINE_CSP=false
197+
IFRAMELY_URL=
198+
IFRAMELY_API_KEY=

.env.test

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
NODE_ENV=test
2+
DATABASE_URL=postgres://user:pass@127.0.0.1:5432/outline-test
3+
SECRET_KEY=F0E5AD933D7F6FD8F4DBB3E038C501C052DC0593C686D21ACB30AE205D2F634B
4+
5+
SMTP_HOST=smtp.example.com
6+
SMTP_FROM_EMAIL=hello@example.com
7+
SMTP_REPLY_EMAIL=hello@example.com
8+
9+
GOOGLE_CLIENT_ID=123
10+
GOOGLE_CLIENT_SECRET=123
11+
12+
SLACK_CLIENT_ID=123
13+
SLACK_CLIENT_SECRET=123
14+
15+
OIDC_CLIENT_ID=client-id
16+
OIDC_CLIENT_SECRET=client-secret
17+
OIDC_AUTH_URI=http://localhost/authorize
18+
OIDC_TOKEN_URI=http://localhost/token
19+
OIDC_USERINFO_URI=http://localhost/userinfo
20+
21+
IFRAMELY_API_KEY=123
22+
23+
RATE_LIMITER_ENABLED=false
24+
25+
FILE_STORAGE=local
26+
FILE_STORAGE_LOCAL_ROOT_DIR=/tmp

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ dist
22
build
33
node_modules/*
44
.env
5+
.env.local
6+
.env.production
57
.log
68
.vscode/*
79
npm-debug.log

.jestconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"^@server/(.*)$": "<rootDir>/server/$1",
1010
"^@shared/(.*)$": "<rootDir>/shared/$1"
1111
},
12-
"setupFiles": ["<rootDir>/__mocks__/console.js", "<rootDir>/server/test/env.ts"],
12+
"setupFiles": ["<rootDir>/__mocks__/console.js"],
1313
"setupFilesAfterEnv": ["<rootDir>/server/test/setup.ts"],
1414
"globalSetup": "<rootDir>/server/test/globalSetup.js",
1515
"globalTeardown": "<rootDir>/server/test/globalTeardown.js",

.sequelizerc

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
require('dotenv').config({ silent: true });
1+
require("dotenv").config({
2+
path: process.env.NODE_ENV === "test" ? ".env.test" : ".env",
3+
});
24

35
var path = require('path');
46

57
module.exports = {
68
'config': path.resolve('server/config', 'database.json'),
79
'migrations-path': path.resolve('server', 'migrations'),
810
'models-path': path.resolve('server', 'models'),
9-
'seeders-path': path.resolve('server/models', 'fixtures'),
1011
}

Makefile

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
up:
2-
docker-compose up -d redis postgres
2+
docker compose up -d redis postgres
33
yarn install-local-ssl
44
yarn install --pure-lockfile
55
yarn dev:watch
66

77
build:
8-
docker-compose build --pull outline
8+
docker compose build --pull outline
99

1010
test:
11-
docker-compose up -d redis postgres
12-
yarn sequelize db:drop --env=test
13-
yarn sequelize db:create --env=test
14-
NODE_ENV=test yarn sequelize db:migrate --env=test
11+
docker compose up -d redis postgres
12+
NODE_ENV=test yarn sequelize db:drop
13+
NODE_ENV=test yarn sequelize db:create
14+
NODE_ENV=test yarn sequelize db:migrate
1515
yarn test
1616

1717
watch:
18-
docker-compose up -d redis postgres
19-
yarn sequelize db:drop --env=test
20-
yarn sequelize db:create --env=test
21-
NODE_ENV=test yarn sequelize db:migrate --env=test
18+
docker compose up -d redis postgres
19+
NODE_ENV=test yarn sequelize db:drop
20+
NODE_ENV=test yarn sequelize db:create
21+
NODE_ENV=test yarn sequelize db:migrate
2222
yarn test:watch
2323

2424
destroy:
25-
docker-compose stop
26-
docker-compose rm -f
25+
docker compose stop
26+
docker compose rm -f
2727

2828
.PHONY: up build destroy test watch # let's go to reserve rules names

app/actions/definitions/navigation.tsx

-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import SearchQuery from "~/models/SearchQuery";
2626
import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
2727
import { createAction } from "~/actions";
2828
import { NavigationSection, RecentSearchesSection } from "~/actions/sections";
29-
import env from "~/env";
3029
import Desktop from "~/utils/Desktop";
3130
import history from "~/utils/history";
3231
import isCloudHosted from "~/utils/isCloudHosted";
@@ -212,9 +211,6 @@ export const logout = createAction({
212211
icon: <LogoutIcon />,
213212
perform: () => {
214213
void stores.auth.logout();
215-
if (env.OIDC_LOGOUT_URI) {
216-
window.location.replace(env.OIDC_LOGOUT_URI);
217-
}
218214
},
219215
});
220216

app/scenes/Logout.tsx

-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import * as React from "react";
22
import { Redirect } from "react-router-dom";
3-
import env from "~/env";
43
import useStores from "~/hooks/useStores";
54

65
const Logout = () => {
76
const { auth } = useStores();
87
void auth.logout();
9-
if (env.OIDC_LOGOUT_URI) {
10-
window.location.replace(env.OIDC_LOGOUT_URI);
11-
return null;
12-
}
138
return <Redirect to="/" />;
149
};
1510

app/stores/AuthStore.ts

+18-11
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { PartialWithId } from "~/types";
1414
import { client } from "~/utils/ApiClient";
1515
import Desktop from "~/utils/Desktop";
1616
import Logger from "~/utils/Logger";
17+
import history from "~/utils/history";
1718
import isCloudHosted from "~/utils/isCloudHosted";
1819
import Store from "./base/Store";
1920

@@ -304,16 +305,15 @@ export default class AuthStore extends Store<Team> {
304305
}
305306
};
306307

308+
/**
309+
* Logs the user out and optionally revokes the authentication token.
310+
*
311+
* @param savePath Whether the current path should be saved and returned to after login.
312+
* @param tryRevokingToken Whether the auth token should attempt to be revoked, this should be
313+
* disabled with requests from ApiClient to prevent infinite loops.
314+
*/
307315
@action
308-
logout = async (
309-
/** Whether the current path should be saved and returned to after login */
310-
savePath = false,
311-
/**
312-
* Whether the auth token should attempt to be revoked, this should be disabled
313-
* with requests from ApiClient to prevent infinite loops.
314-
*/
315-
tryRevokingToken = true
316-
) => {
316+
logout = async (savePath = false, tryRevokingToken = true) => {
317317
// if this logout was forced from an authenticated route then
318318
// save the current path so we can go back there once signed in
319319
if (savePath) {
@@ -348,9 +348,16 @@ export default class AuthStore extends Store<Team> {
348348
this.currentUserId = null;
349349
this.currentTeamId = null;
350350
this.collaborationToken = null;
351+
this.rootStore.clear();
351352

352353
// Tell the host application we logged out, if any – allows window cleanup.
353-
void Desktop.bridge?.onLogout?.();
354-
this.rootStore.clear();
354+
if (Desktop.isElectron()) {
355+
void Desktop.bridge?.onLogout?.();
356+
} else if (env.OIDC_LOGOUT_URI) {
357+
window.location.replace(env.OIDC_LOGOUT_URI);
358+
return;
359+
}
360+
361+
history.replace("/");
355362
};
356363
}

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
"date-fns": "^2.30.0",
100100
"dd-trace": "^3.33.0",
101101
"diff": "^5.1.0",
102-
"dotenv": "^4.0.0",
102+
"dotenv": "^16.4.5",
103103
"email-providers": "^1.14.0",
104104
"emoji-mart": "^5.5.2",
105105
"emoji-regex": "^10.3.0",
@@ -247,6 +247,7 @@
247247
"@types/body-scroll-lock": "^3.1.0",
248248
"@types/crypto-js": "^4.2.1",
249249
"@types/diff": "^5.0.4",
250+
"@types/dotenv": "^8.2.0",
250251
"@types/emoji-regex": "^9.2.0",
251252
"@types/express-useragent": "^1.0.2",
252253
"@types/formidable": "^2.0.6",

plugins/azure/server/auth/azure.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import Router from "koa-router";
66
import { Profile } from "passport";
77
import { slugifyDomain } from "@shared/utils/domains";
88
import accountProvisioner from "@server/commands/accountProvisioner";
9-
import env from "@server/env";
109
import { MicrosoftGraphError } from "@server/errors";
1110
import passportMiddleware from "@server/middlewares/passport";
1211
import { User } from "@server/models";
@@ -17,6 +16,7 @@ import {
1716
getTeamFromContext,
1817
getClientFromContext,
1918
} from "@server/utils/passport";
19+
import env from "../env";
2020

2121
const router = new Router();
2222
const providerName = "azure";

server/utils/azure.ts plugins/azure/server/azure.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import invariant from "invariant";
12
import JWT from "jsonwebtoken";
2-
import env from "@server/env";
3-
import OAuthClient from "./oauth";
3+
import OAuthClient from "@server/utils/oauth";
4+
import env from "./env";
45

56
type AzurePayload = {
67
/** A GUID that represents the Azure AD tenant that the user is from */
@@ -14,6 +15,13 @@ export default class AzureClient extends OAuthClient {
1415
userinfo: "https://graph.microsoft.com/v1.0/me",
1516
};
1617

18+
constructor() {
19+
invariant(env.AZURE_CLIENT_ID, "AZURE_CLIENT_ID is required");
20+
invariant(env.AZURE_CLIENT_SECRET, "AZURE_CLIENT_SECRET is required");
21+
22+
super(env.AZURE_CLIENT_ID, env.AZURE_CLIENT_SECRET);
23+
}
24+
1725
async rotateToken(
1826
accessToken: string,
1927
refreshToken: string

plugins/azure/server/env.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { IsOptional } from "class-validator";
2+
import { Environment } from "@server/env";
3+
import environment from "@server/utils/environment";
4+
import { CannotUseWithout } from "@server/utils/validators";
5+
6+
class AzurePluginEnvironment extends Environment {
7+
/**
8+
* Azure OAuth2 client credentials. To enable authentication with Azure.
9+
*/
10+
@IsOptional()
11+
@CannotUseWithout("AZURE_CLIENT_SECRET")
12+
public AZURE_CLIENT_ID = this.toOptionalString(environment.AZURE_CLIENT_ID);
13+
14+
@IsOptional()
15+
@CannotUseWithout("AZURE_CLIENT_ID")
16+
public AZURE_CLIENT_SECRET = this.toOptionalString(
17+
environment.AZURE_CLIENT_SECRET
18+
);
19+
20+
@IsOptional()
21+
@CannotUseWithout("AZURE_CLIENT_ID")
22+
public AZURE_RESOURCE_APP_ID = this.toOptionalString(
23+
environment.AZURE_RESOURCE_APP_ID
24+
);
25+
}
26+
27+
export default new AzurePluginEnvironment();

plugins/google/server/auth/google.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { Profile } from "passport";
66
import { Strategy as GoogleStrategy } from "passport-google-oauth2";
77
import { slugifyDomain } from "@shared/utils/domains";
88
import accountProvisioner from "@server/commands/accountProvisioner";
9-
import env from "@server/env";
109
import {
1110
GmailAccountCreationError,
1211
TeamDomainRequiredError,
@@ -19,6 +18,7 @@ import {
1918
getTeamFromContext,
2019
getClientFromContext,
2120
} from "@server/utils/passport";
21+
import env from "../env";
2222

2323
const router = new Router();
2424
const providerName = "google";

plugins/google/server/env.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { IsOptional } from "class-validator";
2+
import { Environment } from "@server/env";
3+
import environment from "@server/utils/environment";
4+
import { CannotUseWithout } from "@server/utils/validators";
5+
6+
class GooglePluginEnvironment extends Environment {
7+
/**
8+
* Google OAuth2 client credentials. To enable authentication with Google.
9+
*/
10+
@IsOptional()
11+
@CannotUseWithout("GOOGLE_CLIENT_SECRET")
12+
public GOOGLE_CLIENT_ID = this.toOptionalString(environment.GOOGLE_CLIENT_ID);
13+
14+
@IsOptional()
15+
@CannotUseWithout("GOOGLE_CLIENT_ID")
16+
public GOOGLE_CLIENT_SECRET = this.toOptionalString(
17+
environment.GOOGLE_CLIENT_SECRET
18+
);
19+
}
20+
21+
export default new GooglePluginEnvironment();

0 commit comments

Comments
 (0)