chore: Centralize env parsing, validation, defaults, and deprecation notices (#3487)
* chore: Centralize env parsing, defaults, deprecation * wip * test * test * tsc * docs, more validation * fix: Allow empty REDIS_URL (defaults to localhost) * test * fix: SLACK_MESSAGE_ACTIONS not bool * fix: Add SMTP port validation
This commit is contained in:
@@ -15,25 +15,22 @@ import { StateStore, request } from "@server/utils/passport";
|
||||
|
||||
const router = new Router();
|
||||
const providerName = "azure";
|
||||
const AZURE_CLIENT_ID = process.env.AZURE_CLIENT_ID;
|
||||
const AZURE_CLIENT_SECRET = process.env.AZURE_CLIENT_SECRET;
|
||||
const AZURE_RESOURCE_APP_ID = process.env.AZURE_RESOURCE_APP_ID;
|
||||
const scopes: string[] = [];
|
||||
|
||||
export const config = {
|
||||
name: "Microsoft",
|
||||
enabled: !!AZURE_CLIENT_ID,
|
||||
enabled: !!env.AZURE_CLIENT_ID,
|
||||
};
|
||||
|
||||
if (AZURE_CLIENT_ID && AZURE_CLIENT_SECRET) {
|
||||
if (env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET) {
|
||||
const strategy = new AzureStrategy(
|
||||
{
|
||||
clientID: AZURE_CLIENT_ID,
|
||||
clientSecret: AZURE_CLIENT_SECRET,
|
||||
clientID: env.AZURE_CLIENT_ID,
|
||||
clientSecret: env.AZURE_CLIENT_SECRET,
|
||||
callbackURL: `${env.URL}/auth/azure.callback`,
|
||||
useCommonEndpoint: true,
|
||||
passReqToCallback: true,
|
||||
resource: AZURE_RESOURCE_APP_ID,
|
||||
resource: env.AZURE_RESOURCE_APP_ID,
|
||||
// @ts-expect-error StateStore
|
||||
store: new StateStore(),
|
||||
scope: scopes,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import TestServer from "fetch-test-server";
|
||||
import SigninEmail from "@server/emails/templates/SigninEmail";
|
||||
import WelcomeEmail from "@server/emails/templates/WelcomeEmail";
|
||||
import env from "@server/env";
|
||||
import webService from "@server/services/web";
|
||||
import { buildUser, buildGuestUser, buildTeam } from "@server/test/factories";
|
||||
import { flushdb } from "@server/test/support";
|
||||
@@ -40,8 +41,8 @@ describe("email", () => {
|
||||
});
|
||||
|
||||
it("should respond with redirect location when user is SSO enabled on another subdomain", async () => {
|
||||
process.env.URL = "http://localoutline.com";
|
||||
process.env.SUBDOMAINS_ENABLED = "true";
|
||||
env.URL = "http://localoutline.com";
|
||||
env.SUBDOMAINS_ENABLED = true;
|
||||
const user = await buildUser();
|
||||
const spy = jest.spyOn(WelcomeEmail, "schedule");
|
||||
await buildTeam({
|
||||
@@ -93,8 +94,8 @@ describe("email", () => {
|
||||
describe("with multiple users matching email", () => {
|
||||
it("should default to current subdomain with SSO", async () => {
|
||||
const spy = jest.spyOn(SigninEmail, "schedule");
|
||||
process.env.URL = "http://localoutline.com";
|
||||
process.env.SUBDOMAINS_ENABLED = "true";
|
||||
env.URL = "http://localoutline.com";
|
||||
env.SUBDOMAINS_ENABLED = true;
|
||||
const email = "sso-user@example.org";
|
||||
const team = await buildTeam({
|
||||
subdomain: "example",
|
||||
@@ -123,8 +124,8 @@ describe("email", () => {
|
||||
|
||||
it("should default to current subdomain with guest email", async () => {
|
||||
const spy = jest.spyOn(SigninEmail, "schedule");
|
||||
process.env.URL = "http://localoutline.com";
|
||||
process.env.SUBDOMAINS_ENABLED = "true";
|
||||
env.URL = "http://localoutline.com";
|
||||
env.SUBDOMAINS_ENABLED = true;
|
||||
const email = "guest-user@example.org";
|
||||
const team = await buildTeam({
|
||||
subdomain: "example",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { find } from "lodash";
|
||||
import { parseDomain, isCustomSubdomain } from "@shared/utils/domains";
|
||||
import SigninEmail from "@server/emails/templates/SigninEmail";
|
||||
import WelcomeEmail from "@server/emails/templates/WelcomeEmail";
|
||||
import env from "@server/env";
|
||||
import { AuthorizationError } from "@server/errors";
|
||||
import errorHandling from "@server/middlewares/errorHandling";
|
||||
import methodOverride from "@server/middlewares/methodOverride";
|
||||
@@ -43,7 +44,7 @@ router.post("email", errorHandling(), async (ctx) => {
|
||||
}
|
||||
|
||||
if (
|
||||
process.env.SUBDOMAINS_ENABLED === "true" &&
|
||||
env.SUBDOMAINS_ENABLED &&
|
||||
isCustomSubdomain(ctx.request.hostname) &&
|
||||
!isCustomDomain(ctx.request.hostname)
|
||||
) {
|
||||
|
||||
@@ -15,8 +15,6 @@ import { StateStore } from "@server/utils/passport";
|
||||
|
||||
const router = new Router();
|
||||
const providerName = "google";
|
||||
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
|
||||
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET;
|
||||
const scopes = [
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
@@ -24,7 +22,7 @@ const scopes = [
|
||||
|
||||
export const config = {
|
||||
name: "Google",
|
||||
enabled: !!GOOGLE_CLIENT_ID,
|
||||
enabled: !!env.GOOGLE_CLIENT_ID,
|
||||
};
|
||||
|
||||
type GoogleProfile = Profile & {
|
||||
@@ -35,12 +33,12 @@ type GoogleProfile = Profile & {
|
||||
};
|
||||
};
|
||||
|
||||
if (GOOGLE_CLIENT_ID && GOOGLE_CLIENT_SECRET) {
|
||||
if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
|
||||
passport.use(
|
||||
new GoogleStrategy(
|
||||
{
|
||||
clientID: GOOGLE_CLIENT_ID,
|
||||
clientSecret: GOOGLE_CLIENT_SECRET,
|
||||
clientID: env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: env.GOOGLE_CLIENT_SECRET,
|
||||
callbackURL: `${env.URL}/auth/google.callback`,
|
||||
passReqToCallback: true,
|
||||
// @ts-expect-error StateStore
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import passport from "@outlinewiki/koa-passport";
|
||||
import { Request } from "koa";
|
||||
import Router from "koa-router";
|
||||
import { get } from "lodash";
|
||||
import { Strategy } from "passport-oauth2";
|
||||
@@ -13,21 +14,15 @@ import { StateStore, request } from "@server/utils/passport";
|
||||
|
||||
const router = new Router();
|
||||
const providerName = "oidc";
|
||||
const OIDC_DISPLAY_NAME = process.env.OIDC_DISPLAY_NAME || "OpenID Connect";
|
||||
const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID || "";
|
||||
const OIDC_CLIENT_SECRET = process.env.OIDC_CLIENT_SECRET || "";
|
||||
const OIDC_AUTH_URI = process.env.OIDC_AUTH_URI || "";
|
||||
const OIDC_TOKEN_URI = process.env.OIDC_TOKEN_URI || "";
|
||||
const OIDC_USERINFO_URI = process.env.OIDC_USERINFO_URI || "";
|
||||
const OIDC_SCOPES = process.env.OIDC_SCOPES || "";
|
||||
const OIDC_USERNAME_CLAIM =
|
||||
process.env.OIDC_USERNAME_CLAIM || "preferred_username";
|
||||
const OIDC_AUTH_URI = env.OIDC_AUTH_URI || "";
|
||||
const OIDC_TOKEN_URI = env.OIDC_TOKEN_URI || "";
|
||||
const OIDC_USERINFO_URI = env.OIDC_USERINFO_URI || "";
|
||||
|
||||
export const config = {
|
||||
name: OIDC_DISPLAY_NAME,
|
||||
enabled: !!OIDC_CLIENT_ID,
|
||||
name: env.OIDC_DISPLAY_NAME,
|
||||
enabled: !!env.OIDC_CLIENT_ID,
|
||||
};
|
||||
const scopes = OIDC_SCOPES.split(" ");
|
||||
const scopes = env.OIDC_SCOPES.split(" ");
|
||||
|
||||
Strategy.prototype.userProfile = async function (accessToken, done) {
|
||||
try {
|
||||
@@ -38,18 +33,18 @@ Strategy.prototype.userProfile = async function (accessToken, done) {
|
||||
}
|
||||
};
|
||||
|
||||
if (OIDC_CLIENT_ID) {
|
||||
if (env.OIDC_CLIENT_ID && env.OIDC_CLIENT_SECRET) {
|
||||
passport.use(
|
||||
providerName,
|
||||
new Strategy(
|
||||
{
|
||||
authorizationURL: OIDC_AUTH_URI,
|
||||
tokenURL: OIDC_TOKEN_URI,
|
||||
clientID: OIDC_CLIENT_ID,
|
||||
clientSecret: OIDC_CLIENT_SECRET,
|
||||
clientID: env.OIDC_CLIENT_ID,
|
||||
clientSecret: env.OIDC_CLIENT_SECRET,
|
||||
callbackURL: `${env.URL}/auth/${providerName}.callback`,
|
||||
passReqToCallback: true,
|
||||
scope: OIDC_SCOPES,
|
||||
scope: env.OIDC_SCOPES,
|
||||
// @ts-expect-error custom state store
|
||||
store: new StateStore(),
|
||||
state: true,
|
||||
@@ -62,7 +57,7 @@ if (OIDC_CLIENT_ID) {
|
||||
// Any claim supplied in response to the userinfo request will be
|
||||
// available on the `profile` parameter
|
||||
async function (
|
||||
req: any,
|
||||
req: Request,
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
profile: Record<string, string>,
|
||||
@@ -97,7 +92,7 @@ if (OIDC_CLIENT_ID) {
|
||||
avatarUrl: profile.picture,
|
||||
// Claim name can be overriden using an env variable.
|
||||
// Default is 'preferred_username' as per OIDC spec.
|
||||
username: get(profile, OIDC_USERNAME_CLAIM),
|
||||
username: get(profile, env.OIDC_USERNAME_CLAIM),
|
||||
},
|
||||
authenticationProvider: {
|
||||
name: providerName,
|
||||
|
||||
@@ -39,8 +39,6 @@ type SlackProfile = Profile & {
|
||||
|
||||
const router = new Router();
|
||||
const providerName = "slack";
|
||||
const SLACK_CLIENT_ID = process.env.SLACK_KEY;
|
||||
const SLACK_CLIENT_SECRET = process.env.SLACK_SECRET;
|
||||
const scopes = [
|
||||
"identity.email",
|
||||
"identity.basic",
|
||||
@@ -50,14 +48,14 @@ const scopes = [
|
||||
|
||||
export const config = {
|
||||
name: "Slack",
|
||||
enabled: !!SLACK_CLIENT_ID,
|
||||
enabled: !!env.SLACK_CLIENT_ID,
|
||||
};
|
||||
|
||||
if (SLACK_CLIENT_ID && SLACK_CLIENT_SECRET) {
|
||||
if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) {
|
||||
const strategy = new SlackStrategy(
|
||||
{
|
||||
clientID: SLACK_CLIENT_ID,
|
||||
clientSecret: SLACK_CLIENT_SECRET,
|
||||
clientID: env.SLACK_CLIENT_ID,
|
||||
clientSecret: env.SLACK_CLIENT_SECRET,
|
||||
callbackURL: `${env.URL}/auth/slack.callback`,
|
||||
passReqToCallback: true,
|
||||
// @ts-expect-error StateStore
|
||||
@@ -151,7 +149,7 @@ if (SLACK_CLIENT_ID && SLACK_CLIENT_SECRET) {
|
||||
}
|
||||
}
|
||||
|
||||
const endpoint = `${process.env.URL || ""}/auth/slack.commands`;
|
||||
const endpoint = `${env.URL}/auth/slack.commands`;
|
||||
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string | string[] | undefined' i... Remove this comment to see the full error message
|
||||
const data = await Slack.oauthAccess(code, endpoint);
|
||||
const authentication = await IntegrationAuthentication.create({
|
||||
@@ -210,7 +208,7 @@ if (SLACK_CLIENT_ID && SLACK_CLIENT_SECRET) {
|
||||
}
|
||||
}
|
||||
|
||||
const endpoint = `${process.env.URL || ""}/auth/slack.post`;
|
||||
const endpoint = `${env.URL}/auth/slack.post`;
|
||||
const data = await Slack.oauthAccess(code as string, endpoint);
|
||||
const authentication = await IntegrationAuthentication.create({
|
||||
service: "slack",
|
||||
|
||||
Reference in New Issue
Block a user