Skip to content

Commit f9a11a2

Browse files
chore: Plugin registration (outline#6623)
* first pass * test * test * priority * Reduce boilerplate further * Update server/utils/PluginManager.ts Co-authored-by: Apoorv Mishra <apoorvmishra101092@gmail.com> * fix: matchesNode error in destroyed editor transaction * fix: Individual imported files do not display source correctly in 'Insights' * chore: Add sleep before Slack notification * docs * fix: Error logged about missing plugin.json * Remove email template glob --------- Co-authored-by: Apoorv Mishra <apoorvmishra101092@gmail.com>
1 parent f3334ce commit f9a11a2

Some content is hidden

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

43 files changed

+400
-276
lines changed

.env.test

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ DATABASE_URL=postgres://user:pass@127.0.0.1:5432/outline-test
33
SECRET_KEY=F0E5AD933D7F6FD8F4DBB3E038C501C052DC0593C686D21ACB30AE205D2F634B
44

55
SMTP_HOST=smtp.example.com
6+
SMTP_USERNAME=test
67
SMTP_FROM_EMAIL=hello@example.com
78
SMTP_REPLY_EMAIL=hello@example.com
89

app/hooks/useSettingsConfig.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ const useSettingsConfig = () => {
197197
Object.values(PluginLoader.plugins).map((plugin) => {
198198
const hasSettings = !!plugin.settings;
199199
const enabledInDeployment =
200-
!plugin.config.deployments ||
200+
!plugin.config?.deployments ||
201201
plugin.config.deployments.length === 0 ||
202202
(plugin.config.deployments.includes("cloud") && isCloudHosted) ||
203203
(plugin.config.deployments.includes("enterprise") && !isCloudHosted);

app/utils/PluginLoader.ts

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ interface Plugin {
55
config: {
66
name: string;
77
description: string;
8-
requiredEnvVars?: string[];
98
deployments?: string[];
109
};
1110
settings: React.FC;

build.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ async function build() {
7979
execAsync("cp package.json ./build"),
8080
...d.map(async (plugin) =>
8181
execAsync(
82-
`mkdir -p ./build/plugins/${plugin} && cp ./plugins/${plugin}/plugin.json ./build/plugins/${plugin}/plugin.json`
82+
`mkdir -p ./build/plugins/${plugin} && cp ./plugins/${plugin}/plugin.json ./build/plugins/${plugin}/plugin.json 2>/dev/null || :`
8383
)
8484
),
8585
]);

plugins/azure/plugin.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
2+
"id": "azure",
23
"name": "Microsoft",
3-
"description": "Adds a Microsoft Azure authentication provider.",
4-
"requiredEnvVars": ["AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET"]
4+
"priority": 20,
5+
"description": "Adds a Microsoft Azure authentication provider."
56
}

plugins/azure/server/auth/azure.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ import {
1616
getTeamFromContext,
1717
getClientFromContext,
1818
} from "@server/utils/passport";
19+
import config from "../../plugin.json";
1920
import env from "../env";
2021

2122
const router = new Router();
22-
const providerName = "azure";
2323
const scopes: string[] = [];
2424

2525
if (env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET) {
@@ -109,7 +109,7 @@ if (env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET) {
109109
avatarUrl: profile.picture,
110110
},
111111
authenticationProvider: {
112-
name: providerName,
112+
name: config.id,
113113
providerId: profile.tid,
114114
},
115115
authentication: {
@@ -127,13 +127,11 @@ if (env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET) {
127127
}
128128
);
129129
passport.use(strategy);
130-
131130
router.get(
132-
"azure",
133-
passport.authenticate(providerName, { prompt: "select_account" })
131+
config.id,
132+
passport.authenticate(config.id, { prompt: "select_account" })
134133
);
135-
136-
router.get("azure.callback", passportMiddleware(providerName));
134+
router.get(`${config.id}.callback`, passportMiddleware(config.id));
137135
}
138136

139137
export default router;

plugins/azure/server/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { PluginManager, PluginType } from "@server/utils/PluginManager";
2+
import config from "../plugin.json";
3+
import router from "./auth/azure";
4+
import env from "./env";
5+
6+
PluginManager.register(PluginType.AuthProvider, router, {
7+
...config,
8+
enabled: !!env.AZURE_CLIENT_ID && !!env.AZURE_CLIENT_SECRET,
9+
});

plugins/email/plugin.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"id": "email",
23
"name": "Email",
34
"description": "Adds an email magic link authentication provider."
45
}

plugins/email/server/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import env from "@server/env";
2+
import { PluginManager, PluginType } from "@server/utils/PluginManager";
3+
import config from "../plugin.json";
4+
import router from "./auth/email";
5+
6+
PluginManager.register(PluginType.AuthProvider, router, {
7+
...config,
8+
enabled: (!!env.SMTP_HOST && !!env.SMTP_USERNAME) || env.isDevelopment,
9+
});

plugins/google/plugin.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
2+
"id": "google",
23
"name": "Google",
3-
"description": "Adds a Google authentication provider.",
4-
"requiredEnvVars": ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"]
4+
"priority": 10,
5+
"description": "Adds a Google authentication provider."
56
}

plugins/google/server/auth/google.ts

+6-7
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import {
1818
getTeamFromContext,
1919
getClientFromContext,
2020
} from "@server/utils/passport";
21+
import config from "../../plugin.json";
2122
import env from "../env";
2223

2324
const router = new Router();
24-
const providerName = "google";
2525

2626
const scopes = [
2727
"https://www.googleapis.com/auth/userinfo.profile",
@@ -42,7 +42,7 @@ if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
4242
{
4343
clientID: env.GOOGLE_CLIENT_ID,
4444
clientSecret: env.GOOGLE_CLIENT_SECRET,
45-
callbackURL: `${env.URL}/auth/google.callback`,
45+
callbackURL: `${env.URL}/auth/${config.id}.callback`,
4646
passReqToCallback: true,
4747
// @ts-expect-error StateStore
4848
store: new StateStore(),
@@ -110,7 +110,7 @@ if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
110110
avatarUrl,
111111
},
112112
authenticationProvider: {
113-
name: providerName,
113+
name: config.id,
114114
providerId: domain ?? "",
115115
},
116116
authentication: {
@@ -131,14 +131,13 @@ if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
131131
);
132132

133133
router.get(
134-
"google",
135-
passport.authenticate(providerName, {
134+
config.id,
135+
passport.authenticate(config.id, {
136136
accessType: "offline",
137137
prompt: "select_account consent",
138138
})
139139
);
140-
141-
router.get("google.callback", passportMiddleware(providerName));
140+
router.get(`${config.id}.callback`, passportMiddleware(config.id));
142141
}
143142

144143
export default router;

plugins/google/server/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { PluginManager, PluginType } from "@server/utils/PluginManager";
2+
import config from "../plugin.json";
3+
import router from "./auth/google";
4+
import env from "./env";
5+
6+
PluginManager.register(PluginType.AuthProvider, router, {
7+
...config,
8+
enabled: !!env.GOOGLE_CLIENT_ID && !!env.GOOGLE_CLIENT_SECRET,
9+
});

plugins/iframely/plugin.json

-5
This file was deleted.

plugins/iframely/server/iframely.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Unfurl } from "@shared/types";
12
import { Day } from "@shared/utils/time";
23
import { InternalError } from "@server/errors";
34
import Logger from "@server/logging/Logger";
@@ -33,7 +34,7 @@ class Iframely {
3334
}
3435
}
3536

36-
private static async fetch(url: string, type = "oembed") {
37+
public static async fetch(url: string, type = "oembed") {
3738
const res = await fetch(
3839
`${this.apiUrl}/${type}?url=${encodeURIComponent(url)}&api_key=${
3940
this.apiKey
@@ -55,20 +56,19 @@ class Iframely {
5556
}
5657

5758
/**
58-
* Fetches the preview data for the given url
59-
* using Iframely oEmbed API
59+
* Fetches the preview data for the given url using Iframely oEmbed API
6060
*
6161
* @param url
6262
* @returns Preview data for the url
6363
*/
64-
public static async get(url: string) {
64+
public static async get(url: string): Promise<Unfurl | false> {
6565
try {
66-
const cached = await this.cached(url);
66+
const cached = await Iframely.cached(url);
6767
if (cached) {
6868
return cached;
6969
}
70-
const res = await this.fetch(url);
71-
await this.cache(url, res);
70+
const res = await Iframely.fetch(url);
71+
await Iframely.cache(url, res);
7272
return res;
7373
} catch (err) {
7474
throw InternalError(err);

plugins/iframely/server/index.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {
2+
PluginManager,
3+
PluginPriority,
4+
PluginType,
5+
} from "@server/utils/PluginManager";
6+
import env from "./env";
7+
import Iframely from "./iframely";
8+
9+
PluginManager.register(PluginType.UnfurlProvider, Iframely.get, {
10+
id: "iframely",
11+
enabled: !!env.IFRAMELY_API_KEY && !!env.IFRAMELY_URL,
12+
13+
// Make sure this is last in the stack to be evaluated after all other unfurl providers
14+
priority: PluginPriority.VeryLow,
15+
});

plugins/iframely/server/unfurl.ts

-3
This file was deleted.

plugins/oidc/plugin.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
2+
"id": "oidc",
23
"name": "OIDC",
3-
"description": "Adds an OpenID compatible authentication provider.",
4-
"requiredEnvVars": ["OIDC_CLIENT_ID", "OIDC_CLIENT_SECRET", "OIDC_AUTH_URI", "OIDC_TOKEN_URI", "OIDC_USERINFO_URI"]
4+
"priority": 30,
5+
"description": "Adds an OpenID compatible authentication provider."
56
}

plugins/oidc/server/auth/oidc.ts

+6-9
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import {
1818
getTeamFromContext,
1919
getClientFromContext,
2020
} from "@server/utils/passport";
21+
import config from "../../plugin.json";
2122
import env from "../env";
2223

2324
const router = new Router();
24-
const providerName = "oidc";
2525
const scopes = env.OIDC_SCOPES.split(" ");
2626

2727
Strategy.prototype.userProfile = async function (accessToken, done) {
@@ -55,14 +55,14 @@ if (
5555
env.OIDC_USERINFO_URI
5656
) {
5757
passport.use(
58-
providerName,
58+
config.id,
5959
new Strategy(
6060
{
6161
authorizationURL: env.OIDC_AUTH_URI,
6262
tokenURL: env.OIDC_TOKEN_URI,
6363
clientID: env.OIDC_CLIENT_ID,
6464
clientSecret: env.OIDC_CLIENT_SECRET,
65-
callbackURL: `${env.URL}/auth/${providerName}.callback`,
65+
callbackURL: `${env.URL}/auth/${config.id}.callback`,
6666
passReqToCallback: true,
6767
scope: env.OIDC_SCOPES,
6868
// @ts-expect-error custom state store
@@ -134,7 +134,7 @@ if (
134134
avatarUrl: profile.picture,
135135
},
136136
authenticationProvider: {
137-
name: providerName,
137+
name: config.id,
138138
providerId: domain,
139139
},
140140
authentication: {
@@ -153,11 +153,8 @@ if (
153153
)
154154
);
155155

156-
router.get(providerName, passport.authenticate(providerName));
157-
158-
router.get(`${providerName}.callback`, passportMiddleware(providerName));
156+
router.get(config.id, passport.authenticate(config.id));
157+
router.get(`${config.id}.callback`, passportMiddleware(config.id));
159158
}
160159

161-
export const name = env.OIDC_DISPLAY_NAME;
162-
163160
export default router;

plugins/oidc/server/index.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { PluginManager, PluginType } from "@server/utils/PluginManager";
2+
import config from "../plugin.json";
3+
import router from "./auth/oidc";
4+
import env from "./env";
5+
6+
PluginManager.register(PluginType.AuthProvider, router, {
7+
...config,
8+
name: env.OIDC_DISPLAY_NAME || config.name,
9+
enabled: !!(
10+
env.OIDC_CLIENT_ID &&
11+
env.OIDC_CLIENT_SECRET &&
12+
env.OIDC_AUTH_URI &&
13+
env.OIDC_TOKEN_URI &&
14+
env.OIDC_USERINFO_URI
15+
),
16+
});

plugins/slack/plugin.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
2+
"id": "slack",
23
"name": "Slack",
3-
"description": "Adds a Slack authentication provider, support for the /outline slash command, and link unfurling.",
4-
"requiredEnvVars": ["SLACK_CLIENT_ID", "SLACK_CLIENT_SECRET"]
4+
"priority": 40,
5+
"description": "Adds a Slack authentication provider, support for the /outline slash command, and link unfurling."
56
}

plugins/slack/server/index.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { PluginManager, PluginType } from "@server/utils/PluginManager";
2+
import config from "../plugin.json";
3+
import hooks from "./api/hooks";
4+
import router from "./auth/slack";
5+
import env from "./env";
6+
import SlackProcessor from "./processors/SlackProcessor";
7+
8+
const enabled = !!env.SLACK_CLIENT_ID && !!env.SLACK_CLIENT_SECRET;
9+
10+
PluginManager.register(PluginType.AuthProvider, router, {
11+
...config,
12+
enabled,
13+
});
14+
15+
PluginManager.register(PluginType.API, hooks, {
16+
...config,
17+
enabled,
18+
});
19+
20+
PluginManager.registerProcessor(SlackProcessor, { enabled });

plugins/storage/plugin.json

-8
This file was deleted.

plugins/storage/server/api/files.ts

-3
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,8 @@ import FileStorage from "@server/storage/files";
1818
import { APIContext } from "@server/types";
1919
import { RateLimiterStrategy } from "@server/utils/RateLimiter";
2020
import { getJWTPayload } from "@server/utils/jwt";
21-
import { createRootDirForLocalStorage } from "../utils";
2221
import * as T from "./schema";
2322

24-
createRootDirForLocalStorage();
25-
2623
const router = new Router();
2724

2825
router.post(

0 commit comments

Comments
 (0)