diff --git a/app/components/AuthLogo/GoogleLogo.tsx b/app/components/AuthLogo/GoogleLogo.tsx
deleted file mode 100644
index 4854512b9..000000000
--- a/app/components/AuthLogo/GoogleLogo.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import * as React from "react";
-
-type Props = {
- size?: number;
- fill?: string;
- className?: string;
-};
-
-function GoogleLogo({ size = 34, fill = "#FFF", className }: Props) {
- return (
-
- );
-}
-
-export default GoogleLogo;
diff --git a/app/components/AuthLogo/MicrosoftLogo.tsx b/app/components/AuthLogo/MicrosoftLogo.tsx
deleted file mode 100644
index 393b52488..000000000
--- a/app/components/AuthLogo/MicrosoftLogo.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import * as React from "react";
-
-type Props = {
- size?: number;
- fill?: string;
- className?: string;
-};
-
-function MicrosoftLogo({ size = 34, fill = "#FFF", className }: Props) {
- return (
-
- );
-}
-
-export default MicrosoftLogo;
diff --git a/app/components/AuthLogo/index.tsx b/app/components/AuthLogo/index.tsx
deleted file mode 100644
index c1bf8fda0..000000000
--- a/app/components/AuthLogo/index.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import * as React from "react";
-import styled from "styled-components";
-import GoogleLogo from "./GoogleLogo";
-import MicrosoftLogo from "./MicrosoftLogo";
-import SlackLogo from "./SlackLogo";
-
-type Props = {
- providerName: string;
- size?: number;
- color?: string;
-};
-
-function AuthLogo({ providerName, color, size = 16 }: Props) {
- switch (providerName) {
- case "slack":
- return (
-
-
-
- );
-
- case "google":
- return (
-
-
-
- );
-
- case "azure":
- return (
-
-
-
- );
-
- default:
- return null;
- }
-}
-
-const Logo = styled.div`
- display: flex;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
-`;
-
-export default AuthLogo;
diff --git a/app/components/PluginIcon.tsx b/app/components/PluginIcon.tsx
new file mode 100644
index 000000000..53dec09ae
--- /dev/null
+++ b/app/components/PluginIcon.tsx
@@ -0,0 +1,35 @@
+import * as React from "react";
+import styled from "styled-components";
+import PluginLoader from "~/utils/PluginLoader";
+
+type Props = {
+ id: string;
+ size?: number;
+ color?: string;
+};
+
+function PluginIcon({ id, color, size = 24 }: Props) {
+ const plugin = PluginLoader.plugins[id];
+ const Icon = plugin?.icon;
+
+ if (Icon) {
+ return (
+
+
+
+ );
+ }
+
+ return null;
+}
+
+const Wrapper = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 24px;
+ height: 24px;
+`;
+
+export default PluginIcon;
diff --git a/app/hooks/useSettingsConfig.ts b/app/hooks/useSettingsConfig.ts
index 24e0928a0..e8258a314 100644
--- a/app/hooks/useSettingsConfig.ts
+++ b/app/hooks/useSettingsConfig.ts
@@ -34,8 +34,8 @@ import Tokens from "~/scenes/Settings/Tokens";
import Zapier from "~/scenes/Settings/Zapier";
import GoogleIcon from "~/components/Icons/GoogleIcon";
import ZapierIcon from "~/components/Icons/ZapierIcon";
+import PluginLoader from "~/utils/PluginLoader";
import isCloudHosted from "~/utils/isCloudHosted";
-import { loadPlugins } from "~/utils/plugins";
import { accountPreferencesPath } from "~/utils/routeHelpers";
import useCurrentTeam from "./useCurrentTeam";
import usePolicy from "./usePolicy";
@@ -160,7 +160,7 @@ const useSettingsConfig = () => {
icon: ExportIcon,
},
// Integrations
- ...mapValues(loadPlugins(), (plugin) => {
+ ...mapValues(PluginLoader.plugins, (plugin) => {
return {
name: plugin.config.name,
path: integrationSettingsPath(plugin.id),
@@ -195,14 +195,7 @@ const useSettingsConfig = () => {
icon: ZapierIcon,
},
}),
- [
- t,
- can.createApiKey,
- can.update,
- can.createImport,
- can.createExport,
- can.createWebhookSubscription,
- ]
+ [t, can.createApiKey, can.update, can.createImport, can.createExport]
);
const enabledConfigs = React.useMemo(
diff --git a/app/scenes/Login/AuthenticationProvider.tsx b/app/scenes/Login/AuthenticationProvider.tsx
index 7e124c124..af218c06c 100644
--- a/app/scenes/Login/AuthenticationProvider.tsx
+++ b/app/scenes/Login/AuthenticationProvider.tsx
@@ -4,9 +4,9 @@ import { useTranslation } from "react-i18next";
import styled from "styled-components";
import { Client } from "@shared/types";
import { parseDomain } from "@shared/utils/domains";
-import AuthLogo from "~/components/AuthLogo";
import ButtonLarge from "~/components/ButtonLarge";
import InputLarge from "~/components/InputLarge";
+import PluginIcon from "~/components/PluginIcon";
import env from "~/env";
import { client } from "~/utils/ApiClient";
import Desktop from "~/utils/Desktop";
@@ -117,7 +117,7 @@ function AuthenticationProvider(props: Props) {
(window.location.href = href)}
- icon={}
+ icon={}
fullwidth
>
{t("Continue with {{ authProviderName }}", {
diff --git a/app/scenes/Settings/Security.tsx b/app/scenes/Settings/Security.tsx
index 4a274e86e..d1ad8eeff 100644
--- a/app/scenes/Settings/Security.tsx
+++ b/app/scenes/Settings/Security.tsx
@@ -6,11 +6,11 @@ import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { useTheme } from "styled-components";
import { TeamPreference } from "@shared/types";
-import AuthLogo from "~/components/AuthLogo";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import Flex from "~/components/Flex";
import Heading from "~/components/Heading";
import InputSelect from "~/components/InputSelect";
+import PluginIcon from "~/components/PluginIcon";
import Scene from "~/components/Scene";
import Switch from "~/components/Switch";
import Text from "~/components/Text";
@@ -155,7 +155,7 @@ function Security() {
key={provider.name}
label={
- {" "}
+ {" "}
{provider.displayName}
}
diff --git a/app/utils/PluginLoader.ts b/app/utils/PluginLoader.ts
new file mode 100644
index 000000000..4f230a234
--- /dev/null
+++ b/app/utils/PluginLoader.ts
@@ -0,0 +1,51 @@
+import React from "react";
+
+interface Plugin {
+ id: string;
+ config: {
+ name: string;
+ description: string;
+ requiredEnvVars?: string[];
+ };
+ settings: React.FC;
+ icon: React.FC<{ size?: number; fill?: string }>;
+}
+
+export default class PluginLoader {
+ private static pluginsCache: { [id: string]: Plugin };
+
+ public static get plugins(): { [id: string]: Plugin } {
+ if (this.pluginsCache) {
+ return this.pluginsCache;
+ }
+ const plugins = {};
+
+ function importAll(r: any, property: string) {
+ Object.keys(r).forEach((key: string) => {
+ const id = key.split("/")[3];
+ plugins[id] = plugins[id] || {
+ id,
+ };
+ plugins[id][property] = r[key].default ?? React.lazy(r[key]);
+ });
+ }
+
+ importAll(
+ import.meta.glob("../../plugins/*/client/Settings.{ts,js,tsx,jsx}"),
+ "settings"
+ );
+ importAll(
+ import.meta.glob("../../plugins/*/client/Icon.{ts,js,tsx,jsx}", {
+ eager: true,
+ }),
+ "icon"
+ );
+ importAll(
+ import.meta.glob("../../plugins/*/plugin.json", { eager: true }),
+ "config"
+ );
+
+ this.pluginsCache = plugins;
+ return plugins;
+ }
+}
diff --git a/app/utils/plugins.ts b/app/utils/plugins.ts
deleted file mode 100644
index 7d87c3546..000000000
--- a/app/utils/plugins.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-interface Plugin {
- id: string;
- config: {
- name: string;
- description: string;
- requiredEnvVars?: string[];
- };
- settings: React.FC;
- icon: React.FC;
-}
-
-export function loadPlugins(): { [id: string]: Plugin } {
- const plugins = {};
-
- function importAll(r: any, property: string) {
- Object.keys(r).forEach((key: string) => {
- const id = key.split("/")[3];
- plugins[id] = plugins[id] || {
- id,
- };
- plugins[id][property] = r[key].default;
- });
- }
-
- importAll(
- import.meta.glob("../../plugins/*/client/Settings.{ts,js,tsx,jsx}", {
- eager: true,
- }),
- "settings"
- );
- importAll(
- import.meta.glob("../../plugins/*/client/Icon.{ts,js,tsx,jsx}", {
- eager: true,
- }),
- "icon"
- );
- importAll(
- import.meta.glob("../../plugins/*/plugin.json", { eager: true }),
- "config"
- );
-
- return plugins;
-}
diff --git a/server/routes/auth/providers/README.md b/docs/AUTHENTICATION_PROVIDERS.md
similarity index 50%
rename from server/routes/auth/providers/README.md
rename to docs/AUTHENTICATION_PROVIDERS.md
index d0bbcf98a..0c1b32d0a 100644
--- a/server/routes/auth/providers/README.md
+++ b/docs/AUTHENTICATION_PROVIDERS.md
@@ -1,13 +1,14 @@
# Authentication Providers
-A new auth provider can be added with the addition of a single file in this
-folder, and (optionally) a matching logo in `/app/components/AuthLogo/index.js`
-that will appear on the signin button.
+A new auth provider can be added with the addition of a plugin with a koa router
+as the default export in /server/auth/[provider].ts and (optionally) a matching
+logo in `/client/Icon.tsx` that will appear on the sign-in button.
Auth providers generally use [Passport](http://www.passportjs.org/) strategies,
-although they can use any custom logic if needed. See the `google` auth provider for the cleanest example of what is required – some rules:
+although they can use any custom logic if needed. See the `google` auth provider
+for the cleanest example of what is required – some rules:
- The strategy name _must_ be lowercase
- The strategy _must_ call the `accountProvisioner` command in the verify callback
- The auth file _must_ export a `config` object with `name` and `enabled` keys
-- The auth file _must_ have a default export with a koa-router
\ No newline at end of file
+- The auth file _must_ have a default export with a koa-router
diff --git a/plugins/azure/client/Icon.tsx b/plugins/azure/client/Icon.tsx
new file mode 100644
index 000000000..f0e0c3cfa
--- /dev/null
+++ b/plugins/azure/client/Icon.tsx
@@ -0,0 +1,45 @@
+import * as React from "react";
+
+type Props = {
+ /** The size of the icon, 24px is default to match standard icons */
+ size?: number;
+ /** The color of the icon, defaults to the current text color */
+ fill?: string;
+ className?: string;
+};
+
+function MicrosoftLogo({ size = 24, fill = "#FFF", className }: Props) {
+ return (
+
+ );
+}
+
+export default MicrosoftLogo;
diff --git a/plugins/azure/plugin.json b/plugins/azure/plugin.json
new file mode 100644
index 000000000..2a33a206a
--- /dev/null
+++ b/plugins/azure/plugin.json
@@ -0,0 +1,5 @@
+{
+ "name": "Microsoft",
+ "description": "Adds a Microsoft Azure authentication provider.",
+ "requiredEnvVars": ["AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET"]
+}
diff --git a/plugins/azure/server/.babelrc b/plugins/azure/server/.babelrc
new file mode 100644
index 000000000..c87001bc4
--- /dev/null
+++ b/plugins/azure/server/.babelrc
@@ -0,0 +1,3 @@
+{
+ "extends": "../../../server/.babelrc"
+}
diff --git a/server/routes/auth/providers/azure.ts b/plugins/azure/server/auth/azure.ts
similarity index 98%
rename from server/routes/auth/providers/azure.ts
rename to plugins/azure/server/auth/azure.ts
index 9e71a9743..dbcee6727 100644
--- a/server/routes/auth/providers/azure.ts
+++ b/plugins/azure/server/auth/azure.ts
@@ -22,11 +22,6 @@ const router = new Router();
const providerName = "azure";
const scopes: string[] = [];
-export const config = {
- name: "Microsoft",
- enabled: !!env.AZURE_CLIENT_ID,
-};
-
if (env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET) {
const strategy = new AzureStrategy(
{
diff --git a/plugins/email/plugin.json b/plugins/email/plugin.json
new file mode 100644
index 000000000..8346107b0
--- /dev/null
+++ b/plugins/email/plugin.json
@@ -0,0 +1,4 @@
+{
+ "name": "Email",
+ "description": "Adds an email magic link authentication provider."
+}
diff --git a/plugins/email/server/.babelrc b/plugins/email/server/.babelrc
new file mode 100644
index 000000000..c87001bc4
--- /dev/null
+++ b/plugins/email/server/.babelrc
@@ -0,0 +1,3 @@
+{
+ "extends": "../../../server/.babelrc"
+}
diff --git a/server/routes/auth/providers/email.test.ts b/plugins/email/server/auth/email.test.ts
similarity index 100%
rename from server/routes/auth/providers/email.test.ts
rename to plugins/email/server/auth/email.test.ts
diff --git a/server/routes/auth/providers/email.ts b/plugins/email/server/auth/email.ts
similarity index 98%
rename from server/routes/auth/providers/email.ts
rename to plugins/email/server/auth/email.ts
index 47f40dc48..215bc417e 100644
--- a/server/routes/auth/providers/email.ts
+++ b/plugins/email/server/auth/email.ts
@@ -16,11 +16,6 @@ import { assertEmail, assertPresent } from "@server/validation";
const router = new Router();
-export const config = {
- name: "Email",
- enabled: true,
-};
-
router.post(
"email",
rateLimiter(RateLimiterStrategy.TenPerHour),
diff --git a/plugins/google/client/Icon.tsx b/plugins/google/client/Icon.tsx
new file mode 100644
index 000000000..a8f7cc3ac
--- /dev/null
+++ b/plugins/google/client/Icon.tsx
@@ -0,0 +1,26 @@
+import * as React from "react";
+
+type Props = {
+ /** The size of the icon, 24px is default to match standard icons */
+ size?: number;
+ /** The color of the icon, defaults to the current text color */
+ fill?: string;
+ className?: string;
+};
+
+function GoogleLogo({ size = 24, fill = "currentColor", className }: Props) {
+ return (
+
+ );
+}
+
+export default GoogleLogo;
diff --git a/plugins/google/plugin.json b/plugins/google/plugin.json
new file mode 100644
index 000000000..f2b687c2a
--- /dev/null
+++ b/plugins/google/plugin.json
@@ -0,0 +1,5 @@
+{
+ "name": "Google",
+ "description": "Adds a Google authentication provider.",
+ "requiredEnvVars": ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"]
+}
diff --git a/plugins/google/server/.babelrc b/plugins/google/server/.babelrc
new file mode 100644
index 000000000..c87001bc4
--- /dev/null
+++ b/plugins/google/server/.babelrc
@@ -0,0 +1,3 @@
+{
+ "extends": "../../../server/.babelrc"
+}
diff --git a/server/routes/auth/providers/google.ts b/plugins/google/server/auth/google.ts
similarity index 95%
rename from server/routes/auth/providers/google.ts
rename to plugins/google/server/auth/google.ts
index 7cd63b141..040f18890 100644
--- a/server/routes/auth/providers/google.ts
+++ b/plugins/google/server/auth/google.ts
@@ -21,17 +21,13 @@ import {
} from "@server/utils/passport";
const router = new Router();
-const GOOGLE = "google";
+const providerName = "google";
+
const scopes = [
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
];
-export const config = {
- name: "Google",
- enabled: !!env.GOOGLE_CLIENT_ID,
-};
-
type GoogleProfile = Profile & {
email: string;
picture: string;
@@ -114,7 +110,7 @@ if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
avatarUrl,
},
authenticationProvider: {
- name: GOOGLE,
+ name: providerName,
providerId: domain ?? "",
},
authentication: {
@@ -136,13 +132,13 @@ if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
router.get(
"google",
- passport.authenticate(GOOGLE, {
+ passport.authenticate(providerName, {
accessType: "offline",
prompt: "select_account consent",
})
);
- router.get("google.callback", passportMiddleware(GOOGLE));
+ router.get("google.callback", passportMiddleware(providerName));
}
export default router;
diff --git a/plugins/oidc/plugin.json b/plugins/oidc/plugin.json
new file mode 100644
index 000000000..7d2365788
--- /dev/null
+++ b/plugins/oidc/plugin.json
@@ -0,0 +1,12 @@
+{
+ "name": "OIDC",
+ "description": "Adds an OpenID compatible authentication provider.",
+ "requiredEnvVars": [
+ "OIDC_CLIENT_ID",
+ "OIDC_CLIENT_SECRET",
+ "OIDC_AUTH_URI",
+ "OIDC_TOKEN_URI",
+ "OIDC_USERINFO_URI",
+ "OIDC_DISPLAY_NAME"
+ ]
+}
diff --git a/plugins/oidc/server/.babelrc b/plugins/oidc/server/.babelrc
new file mode 100644
index 000000000..c87001bc4
--- /dev/null
+++ b/plugins/oidc/server/.babelrc
@@ -0,0 +1,3 @@
+{
+ "extends": "../../../server/.babelrc"
+}
diff --git a/server/routes/auth/providers/oidc.ts b/plugins/oidc/server/auth/oidc.ts
similarity index 90%
rename from server/routes/auth/providers/oidc.ts
rename to plugins/oidc/server/auth/oidc.ts
index 3203221c6..073a8cf7c 100644
--- a/server/routes/auth/providers/oidc.ts
+++ b/plugins/oidc/server/auth/oidc.ts
@@ -22,32 +22,30 @@ import {
const router = new Router();
const providerName = "oidc";
-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: env.OIDC_DISPLAY_NAME,
- enabled: !!env.OIDC_CLIENT_ID,
-};
const scopes = env.OIDC_SCOPES.split(" ");
Strategy.prototype.userProfile = async function (accessToken, done) {
try {
- const response = await request(OIDC_USERINFO_URI, accessToken);
+ const response = await request(env.OIDC_USERINFO_URI ?? "", accessToken);
return done(null, response);
} catch (err) {
return done(err);
}
};
-if (env.OIDC_CLIENT_ID && env.OIDC_CLIENT_SECRET) {
+if (
+ env.OIDC_CLIENT_ID &&
+ env.OIDC_CLIENT_SECRET &&
+ env.OIDC_AUTH_URI &&
+ env.OIDC_TOKEN_URI &&
+ env.OIDC_USERINFO_URI
+) {
passport.use(
providerName,
new Strategy(
{
- authorizationURL: OIDC_AUTH_URI,
- tokenURL: OIDC_TOKEN_URI,
+ authorizationURL: env.OIDC_AUTH_URI,
+ tokenURL: env.OIDC_TOKEN_URI,
clientID: env.OIDC_CLIENT_ID,
clientSecret: env.OIDC_CLIENT_SECRET,
callbackURL: `${env.URL}/auth/${providerName}.callback`,
@@ -144,4 +142,6 @@ if (env.OIDC_CLIENT_ID && env.OIDC_CLIENT_SECRET) {
router.get(`${providerName}.callback`, passportMiddleware(providerName));
}
+export const name = env.OIDC_DISPLAY_NAME;
+
export default router;
diff --git a/plugins/slack/client/Icon.tsx b/plugins/slack/client/Icon.tsx
index db79b9f3c..07088c863 100644
--- a/plugins/slack/client/Icon.tsx
+++ b/plugins/slack/client/Icon.tsx
@@ -4,19 +4,19 @@ type Props = {
/** The size of the icon, 24px is default to match standard icons */
size?: number;
/** The color of the icon, defaults to the current text color */
- color?: string;
+ fill?: string;
};
-export default function Icon({ size = 24, color = "currentColor" }: Props) {
+export default function Icon({ size = 24, fill = "currentColor" }: Props) {
return (
);
}
diff --git a/plugins/slack/client/Settings.tsx b/plugins/slack/client/Settings.tsx
index 71194b286..7c918c519 100644
--- a/plugins/slack/client/Settings.tsx
+++ b/plugins/slack/client/Settings.tsx
@@ -56,7 +56,7 @@ function Slack() {
const appName = env.APP_NAME;
return (
- }>
+ }>
Slack
{error === "access_denied" && (
@@ -106,7 +106,7 @@ function Slack() {
]}
redirectUri={`${env.URL}/auth/slack.commands`}
state={team.id}
- icon={}
+ icon={}
/>
)}
diff --git a/server/models/helpers/AuthenticationHelper.ts b/server/models/helpers/AuthenticationHelper.ts
index e910aa955..9a56e6101 100644
--- a/server/models/helpers/AuthenticationHelper.ts
+++ b/server/models/helpers/AuthenticationHelper.ts
@@ -1,9 +1,70 @@
-import { find } from "lodash";
+/* eslint-disable @typescript-eslint/no-var-requires */
+import path from "path";
+import { glob } from "glob";
+import Router from "koa-router";
+import { find, sortBy } from "lodash";
import env from "@server/env";
import Team from "@server/models/Team";
-import providerConfigs from "../../routes/auth/providers";
+
+export type AuthenticationProviderConfig = {
+ id: string;
+ name: string;
+ enabled: boolean;
+ router: Router;
+};
export default class AuthenticationHelper {
+ private static providersCache: AuthenticationProviderConfig[];
+
+ /**
+ * Returns the enabled authentication provider configurations for the current
+ * installation.
+ *
+ * @returns A list of authentication providers
+ */
+ public static get providers() {
+ if (this.providersCache) {
+ return this.providersCache;
+ }
+
+ const authenticationProviderConfigs: AuthenticationProviderConfig[] = [];
+ const rootDir = env.ENVIRONMENT === "test" ? "" : "build";
+
+ glob
+ .sync(path.join(rootDir, "plugins/*/server/auth/!(*.test).[jt]s"))
+ .forEach((filePath: string) => {
+ const { default: authProvider, name } = require(path.join(
+ process.cwd(),
+ filePath
+ ));
+ const id = filePath.replace("build/", "").split("/")[1];
+ const config = require(path.join(
+ process.cwd(),
+ rootDir,
+ "plugins",
+ id,
+ "plugin.json"
+ ));
+
+ // Test the all required env vars are set for the auth provider
+ const enabled = (config.requiredEnvVars ?? []).every(
+ (name: string) => !!env[name]
+ );
+
+ if (enabled) {
+ authenticationProviderConfigs.push({
+ id,
+ name: name ?? config.name,
+ enabled,
+ router: authProvider,
+ });
+ }
+ });
+
+ this.providersCache = sortBy(authenticationProviderConfigs, "id");
+ return this.providersCache;
+ }
+
/**
* Returns the enabled authentication provider configurations for a team,
* if given otherwise all enabled providers are returned.
@@ -11,17 +72,12 @@ export default class AuthenticationHelper {
* @param team The team to get enabled providers for
* @returns A list of authentication providers
*/
- static providersForTeam(team?: Team) {
+ public static providersForTeam(team?: Team) {
const isCloudHosted = env.isCloudHosted();
- return providerConfigs
+ return AuthenticationHelper.providers
.sort((config) => (config.id === "email" ? 1 : -1))
.filter((config) => {
- // Don't return authentication methods that are not enabled.
- if (!config.enabled) {
- return false;
- }
-
// Guest sign-in is an exception as it does not have an authentication
// provider using passport, instead it exists as a boolean option.
if (config.id === "email") {
diff --git a/server/presenters/providerConfig.ts b/server/presenters/providerConfig.ts
index b18c1e43a..91fdba9db 100644
--- a/server/presenters/providerConfig.ts
+++ b/server/presenters/providerConfig.ts
@@ -1,5 +1,5 @@
import { signin } from "@shared/utils/routeHelpers";
-import { AuthenticationProviderConfig } from "@server/routes/auth/providers";
+import { AuthenticationProviderConfig } from "@server/models/helpers/AuthenticationHelper";
export default function presentProviderConfig(
config: AuthenticationProviderConfig
diff --git a/server/routes/api/authenticationProviders/authenticationProviders.ts b/server/routes/api/authenticationProviders/authenticationProviders.ts
index 29a68c288..01c754e07 100644
--- a/server/routes/api/authenticationProviders/authenticationProviders.ts
+++ b/server/routes/api/authenticationProviders/authenticationProviders.ts
@@ -3,13 +3,13 @@ import { sequelize } from "@server/database/sequelize";
import auth from "@server/middlewares/authentication";
import validate from "@server/middlewares/validate";
import { AuthenticationProvider, Event } from "@server/models";
+import AuthenticationHelper from "@server/models/helpers/AuthenticationHelper";
import { authorize } from "@server/policies";
import {
presentAuthenticationProvider,
presentPolicies,
} from "@server/presenters";
import { APIContext } from "@server/types";
-import allAuthenticationProviders from "../../auth/providers";
import * as T from "./schema";
const router = new Router();
@@ -95,7 +95,7 @@ router.post(
"authenticationProviders"
)) as AuthenticationProvider[];
- const data = allAuthenticationProviders
+ const data = AuthenticationHelper.providers
.filter((p) => p.id !== "email")
.map((p) => {
const row = teamAuthenticationProviders.find((t) => t.name === p.id);
diff --git a/server/routes/auth/index.ts b/server/routes/auth/index.ts
index 4a71b944f..1a207f74e 100644
--- a/server/routes/auth/index.ts
+++ b/server/routes/auth/index.ts
@@ -6,8 +6,8 @@ import Router from "koa-router";
import { AuthenticationError } from "@server/errors";
import auth from "@server/middlewares/authentication";
import { Collection, Team, View } from "@server/models";
+import AuthenticationHelper from "@server/models/helpers/AuthenticationHelper";
import { AppState, AppContext, APIContext } from "@server/types";
-import providers from "./providers";
const app = new Koa();
const router = new Router();
@@ -15,10 +15,8 @@ const router = new Router();
router.use(passport.initialize());
// dynamically load available authentication provider routes
-providers.forEach((provider) => {
- if (provider.enabled) {
- router.use("/", provider.router.routes());
- }
+AuthenticationHelper.providers.forEach((provider) => {
+ router.use("/", provider.router.routes());
});
router.get("/redirect", auth(), async (ctx: APIContext) => {
diff --git a/server/routes/auth/providers/index.ts b/server/routes/auth/providers/index.ts
deleted file mode 100644
index 902db4cd0..000000000
--- a/server/routes/auth/providers/index.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-/* eslint-disable @typescript-eslint/no-var-requires */
-import path from "path";
-import { glob } from "glob";
-import Router from "koa-router";
-import { sortBy } from "lodash";
-import env from "@server/env";
-import { requireDirectory } from "@server/utils/fs";
-
-export type AuthenticationProviderConfig = {
- id: string;
- name: string;
- enabled: boolean;
- router: Router;
-};
-
-const authenticationProviderConfigs: AuthenticationProviderConfig[] = [];
-
-requireDirectory<{
- default: Router;
- config: { name: string; enabled: boolean };
-}>(__dirname).forEach(([module, id]) => {
- const { config, default: router } = module;
-
- if (id === "index") {
- return;
- }
-
- if (!config) {
- throw new Error(
- `Auth providers must export a 'config' object, missing in ${id}`
- );
- }
-
- if (!router || !router.routes) {
- throw new Error(
- `Default export of an auth provider must be a koa-router, missing in ${id}`
- );
- }
-
- if (config && config.enabled) {
- authenticationProviderConfigs.push({
- id,
- name: config.name,
- enabled: config.enabled,
- router,
- });
- }
-});
-
-// Temporarily also include plugins here until all auth methods are moved over.
-glob
- .sync(
- (env.ENVIRONMENT === "test" ? "" : "build/") +
- "plugins/*/server/auth/!(*.test).[jt]s"
- )
- .forEach((filePath: string) => {
- const authProvider = require(path.join(process.cwd(), filePath)).default;
- const id = filePath.replace("build/", "").split("/")[1];
- const config = require(path.join(
- process.cwd(),
- env.ENVIRONMENT === "test" ? "" : "build",
- "plugins",
- id,
- "plugin.json"
- ));
-
- // Test the all required env vars are set for the auth provider
- const enabled = (config.requiredEnvVars ?? []).every(
- (name: string) => !!env[name]
- );
-
- authenticationProviderConfigs.push({
- id,
- name: config.name,
- enabled,
- router: authProvider,
- });
- });
-
-export default sortBy(authenticationProviderConfigs, "id");