chore: Plugin registration (#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>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "azure",
|
||||
"name": "Microsoft",
|
||||
"description": "Adds a Microsoft Azure authentication provider.",
|
||||
"requiredEnvVars": ["AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET"]
|
||||
"priority": 20,
|
||||
"description": "Adds a Microsoft Azure authentication provider."
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ import {
|
||||
getTeamFromContext,
|
||||
getClientFromContext,
|
||||
} from "@server/utils/passport";
|
||||
import config from "../../plugin.json";
|
||||
import env from "../env";
|
||||
|
||||
const router = new Router();
|
||||
const providerName = "azure";
|
||||
const scopes: string[] = [];
|
||||
|
||||
if (env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET) {
|
||||
@@ -109,7 +109,7 @@ if (env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET) {
|
||||
avatarUrl: profile.picture,
|
||||
},
|
||||
authenticationProvider: {
|
||||
name: providerName,
|
||||
name: config.id,
|
||||
providerId: profile.tid,
|
||||
},
|
||||
authentication: {
|
||||
@@ -127,13 +127,11 @@ if (env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET) {
|
||||
}
|
||||
);
|
||||
passport.use(strategy);
|
||||
|
||||
router.get(
|
||||
"azure",
|
||||
passport.authenticate(providerName, { prompt: "select_account" })
|
||||
config.id,
|
||||
passport.authenticate(config.id, { prompt: "select_account" })
|
||||
);
|
||||
|
||||
router.get("azure.callback", passportMiddleware(providerName));
|
||||
router.get(`${config.id}.callback`, passportMiddleware(config.id));
|
||||
}
|
||||
|
||||
export default router;
|
||||
|
||||
9
plugins/azure/server/index.ts
Normal file
9
plugins/azure/server/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { PluginManager, PluginType } from "@server/utils/PluginManager";
|
||||
import config from "../plugin.json";
|
||||
import router from "./auth/azure";
|
||||
import env from "./env";
|
||||
|
||||
PluginManager.register(PluginType.AuthProvider, router, {
|
||||
...config,
|
||||
enabled: !!env.AZURE_CLIENT_ID && !!env.AZURE_CLIENT_SECRET,
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"id": "email",
|
||||
"name": "Email",
|
||||
"description": "Adds an email magic link authentication provider."
|
||||
}
|
||||
|
||||
9
plugins/email/server/index.ts
Normal file
9
plugins/email/server/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import env from "@server/env";
|
||||
import { PluginManager, PluginType } from "@server/utils/PluginManager";
|
||||
import config from "../plugin.json";
|
||||
import router from "./auth/email";
|
||||
|
||||
PluginManager.register(PluginType.AuthProvider, router, {
|
||||
...config,
|
||||
enabled: (!!env.SMTP_HOST && !!env.SMTP_USERNAME) || env.isDevelopment,
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "google",
|
||||
"name": "Google",
|
||||
"description": "Adds a Google authentication provider.",
|
||||
"requiredEnvVars": ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"]
|
||||
"priority": 10,
|
||||
"description": "Adds a Google authentication provider."
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
getTeamFromContext,
|
||||
getClientFromContext,
|
||||
} from "@server/utils/passport";
|
||||
import config from "../../plugin.json";
|
||||
import env from "../env";
|
||||
|
||||
const router = new Router();
|
||||
const providerName = "google";
|
||||
|
||||
const scopes = [
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
@@ -42,7 +42,7 @@ if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
|
||||
{
|
||||
clientID: env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: env.GOOGLE_CLIENT_SECRET,
|
||||
callbackURL: `${env.URL}/auth/google.callback`,
|
||||
callbackURL: `${env.URL}/auth/${config.id}.callback`,
|
||||
passReqToCallback: true,
|
||||
// @ts-expect-error StateStore
|
||||
store: new StateStore(),
|
||||
@@ -110,7 +110,7 @@ if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
|
||||
avatarUrl,
|
||||
},
|
||||
authenticationProvider: {
|
||||
name: providerName,
|
||||
name: config.id,
|
||||
providerId: domain ?? "",
|
||||
},
|
||||
authentication: {
|
||||
@@ -131,14 +131,13 @@ if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
|
||||
);
|
||||
|
||||
router.get(
|
||||
"google",
|
||||
passport.authenticate(providerName, {
|
||||
config.id,
|
||||
passport.authenticate(config.id, {
|
||||
accessType: "offline",
|
||||
prompt: "select_account consent",
|
||||
})
|
||||
);
|
||||
|
||||
router.get("google.callback", passportMiddleware(providerName));
|
||||
router.get(`${config.id}.callback`, passportMiddleware(config.id));
|
||||
}
|
||||
|
||||
export default router;
|
||||
|
||||
9
plugins/google/server/index.ts
Normal file
9
plugins/google/server/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { PluginManager, PluginType } from "@server/utils/PluginManager";
|
||||
import config from "../plugin.json";
|
||||
import router from "./auth/google";
|
||||
import env from "./env";
|
||||
|
||||
PluginManager.register(PluginType.AuthProvider, router, {
|
||||
...config,
|
||||
enabled: !!env.GOOGLE_CLIENT_ID && !!env.GOOGLE_CLIENT_SECRET,
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "Iframely",
|
||||
"description": "Integrate Iframely to enable unfurling of arbitrary urls",
|
||||
"requiredEnvVars": ["IFRAMELY_API_KEY"]
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Unfurl } from "@shared/types";
|
||||
import { Day } from "@shared/utils/time";
|
||||
import { InternalError } from "@server/errors";
|
||||
import Logger from "@server/logging/Logger";
|
||||
@@ -33,7 +34,7 @@ class Iframely {
|
||||
}
|
||||
}
|
||||
|
||||
private static async fetch(url: string, type = "oembed") {
|
||||
public static async fetch(url: string, type = "oembed") {
|
||||
const res = await fetch(
|
||||
`${this.apiUrl}/${type}?url=${encodeURIComponent(url)}&api_key=${
|
||||
this.apiKey
|
||||
@@ -55,20 +56,19 @@ class Iframely {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the preview data for the given url
|
||||
* using Iframely oEmbed API
|
||||
* Fetches the preview data for the given url using Iframely oEmbed API
|
||||
*
|
||||
* @param url
|
||||
* @returns Preview data for the url
|
||||
*/
|
||||
public static async get(url: string) {
|
||||
public static async get(url: string): Promise<Unfurl | false> {
|
||||
try {
|
||||
const cached = await this.cached(url);
|
||||
const cached = await Iframely.cached(url);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const res = await this.fetch(url);
|
||||
await this.cache(url, res);
|
||||
const res = await Iframely.fetch(url);
|
||||
await Iframely.cache(url, res);
|
||||
return res;
|
||||
} catch (err) {
|
||||
throw InternalError(err);
|
||||
|
||||
15
plugins/iframely/server/index.ts
Normal file
15
plugins/iframely/server/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {
|
||||
PluginManager,
|
||||
PluginPriority,
|
||||
PluginType,
|
||||
} from "@server/utils/PluginManager";
|
||||
import env from "./env";
|
||||
import Iframely from "./iframely";
|
||||
|
||||
PluginManager.register(PluginType.UnfurlProvider, Iframely.get, {
|
||||
id: "iframely",
|
||||
enabled: !!env.IFRAMELY_API_KEY && !!env.IFRAMELY_URL,
|
||||
|
||||
// Make sure this is last in the stack to be evaluated after all other unfurl providers
|
||||
priority: PluginPriority.VeryLow,
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
import Iframely from "./iframely";
|
||||
|
||||
export const unfurl = async (url: string) => Iframely.get(url);
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "oidc",
|
||||
"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"]
|
||||
"priority": 30,
|
||||
"description": "Adds an OpenID compatible authentication provider."
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
getTeamFromContext,
|
||||
getClientFromContext,
|
||||
} from "@server/utils/passport";
|
||||
import config from "../../plugin.json";
|
||||
import env from "../env";
|
||||
|
||||
const router = new Router();
|
||||
const providerName = "oidc";
|
||||
const scopes = env.OIDC_SCOPES.split(" ");
|
||||
|
||||
Strategy.prototype.userProfile = async function (accessToken, done) {
|
||||
@@ -55,14 +55,14 @@ if (
|
||||
env.OIDC_USERINFO_URI
|
||||
) {
|
||||
passport.use(
|
||||
providerName,
|
||||
config.id,
|
||||
new Strategy(
|
||||
{
|
||||
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`,
|
||||
callbackURL: `${env.URL}/auth/${config.id}.callback`,
|
||||
passReqToCallback: true,
|
||||
scope: env.OIDC_SCOPES,
|
||||
// @ts-expect-error custom state store
|
||||
@@ -134,7 +134,7 @@ if (
|
||||
avatarUrl: profile.picture,
|
||||
},
|
||||
authenticationProvider: {
|
||||
name: providerName,
|
||||
name: config.id,
|
||||
providerId: domain,
|
||||
},
|
||||
authentication: {
|
||||
@@ -153,11 +153,8 @@ if (
|
||||
)
|
||||
);
|
||||
|
||||
router.get(providerName, passport.authenticate(providerName));
|
||||
|
||||
router.get(`${providerName}.callback`, passportMiddleware(providerName));
|
||||
router.get(config.id, passport.authenticate(config.id));
|
||||
router.get(`${config.id}.callback`, passportMiddleware(config.id));
|
||||
}
|
||||
|
||||
export const name = env.OIDC_DISPLAY_NAME;
|
||||
|
||||
export default router;
|
||||
|
||||
16
plugins/oidc/server/index.ts
Normal file
16
plugins/oidc/server/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { PluginManager, PluginType } from "@server/utils/PluginManager";
|
||||
import config from "../plugin.json";
|
||||
import router from "./auth/oidc";
|
||||
import env from "./env";
|
||||
|
||||
PluginManager.register(PluginType.AuthProvider, router, {
|
||||
...config,
|
||||
name: env.OIDC_DISPLAY_NAME || config.name,
|
||||
enabled: !!(
|
||||
env.OIDC_CLIENT_ID &&
|
||||
env.OIDC_CLIENT_SECRET &&
|
||||
env.OIDC_AUTH_URI &&
|
||||
env.OIDC_TOKEN_URI &&
|
||||
env.OIDC_USERINFO_URI
|
||||
),
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "slack",
|
||||
"name": "Slack",
|
||||
"description": "Adds a Slack authentication provider, support for the /outline slash command, and link unfurling.",
|
||||
"requiredEnvVars": ["SLACK_CLIENT_ID", "SLACK_CLIENT_SECRET"]
|
||||
"priority": 40,
|
||||
"description": "Adds a Slack authentication provider, support for the /outline slash command, and link unfurling."
|
||||
}
|
||||
|
||||
20
plugins/slack/server/index.ts
Normal file
20
plugins/slack/server/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { PluginManager, PluginType } from "@server/utils/PluginManager";
|
||||
import config from "../plugin.json";
|
||||
import hooks from "./api/hooks";
|
||||
import router from "./auth/slack";
|
||||
import env from "./env";
|
||||
import SlackProcessor from "./processors/SlackProcessor";
|
||||
|
||||
const enabled = !!env.SLACK_CLIENT_ID && !!env.SLACK_CLIENT_SECRET;
|
||||
|
||||
PluginManager.register(PluginType.AuthProvider, router, {
|
||||
...config,
|
||||
enabled,
|
||||
});
|
||||
|
||||
PluginManager.register(PluginType.API, hooks, {
|
||||
...config,
|
||||
enabled,
|
||||
});
|
||||
|
||||
PluginManager.registerProcessor(SlackProcessor, { enabled });
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "Storage",
|
||||
"description": "Plugin for storing files on the local file system",
|
||||
"requiredEnvVars": [
|
||||
"FILE_STORAGE_UPLOAD_MAX_SIZE",
|
||||
"FILE_STORAGE_LOCAL_ROOT_DIR"
|
||||
]
|
||||
}
|
||||
@@ -18,11 +18,8 @@ import FileStorage from "@server/storage/files";
|
||||
import { APIContext } from "@server/types";
|
||||
import { RateLimiterStrategy } from "@server/utils/RateLimiter";
|
||||
import { getJWTPayload } from "@server/utils/jwt";
|
||||
import { createRootDirForLocalStorage } from "../utils";
|
||||
import * as T from "./schema";
|
||||
|
||||
createRootDirForLocalStorage();
|
||||
|
||||
const router = new Router();
|
||||
|
||||
router.post(
|
||||
|
||||
31
plugins/storage/server/index.ts
Normal file
31
plugins/storage/server/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import env from "@server/env";
|
||||
import Logger from "@server/logging/Logger";
|
||||
import { PluginManager, PluginType } from "@server/utils/PluginManager";
|
||||
import router from "./api/files";
|
||||
|
||||
if (env.FILE_STORAGE === "local") {
|
||||
const rootDir = env.FILE_STORAGE_LOCAL_ROOT_DIR;
|
||||
try {
|
||||
if (!existsSync(rootDir)) {
|
||||
mkdirSync(rootDir, { recursive: true });
|
||||
Logger.debug("utils", `Created ${rootDir} for local storage`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.fatal(
|
||||
`Failed to create directory for local file storage at ${env.FILE_STORAGE_LOCAL_ROOT_DIR}`,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PluginManager.register(PluginType.API, router, {
|
||||
id: "files",
|
||||
name: "Local file storage",
|
||||
description: "Plugin for storing files on the local file system",
|
||||
enabled: !!(
|
||||
env.FILE_STORAGE_UPLOAD_MAX_SIZE &&
|
||||
env.FILE_STORAGE_LOCAL_ROOT_DIR &&
|
||||
env.FILE_STORAGE === "local"
|
||||
),
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import env from "@server/env";
|
||||
import Logger from "@server/logging/Logger";
|
||||
|
||||
export const createRootDirForLocalStorage = () => {
|
||||
if (env.FILE_STORAGE === "local") {
|
||||
const rootDir = env.FILE_STORAGE_LOCAL_ROOT_DIR;
|
||||
try {
|
||||
if (!existsSync(rootDir)) {
|
||||
mkdirSync(rootDir, { recursive: true });
|
||||
Logger.debug("utils", `Created ${rootDir} for local storage`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.fatal(
|
||||
`Failed to create directory for local file storage at ${env.FILE_STORAGE_LOCAL_ROOT_DIR}`,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "webhooks",
|
||||
"name": "Webhooks",
|
||||
"description": "Adds HTTP webhooks for various events.",
|
||||
"requiredEnvVars": []
|
||||
"description": "Adds HTTP webhooks for various events."
|
||||
}
|
||||
|
||||
11
plugins/webhooks/server/index.ts
Normal file
11
plugins/webhooks/server/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { PluginManager, PluginType } from "@server/utils/PluginManager";
|
||||
import config from "../plugin.json";
|
||||
import webhookSubscriptions from "./api/webhookSubscriptions";
|
||||
import WebhookProcessor from "./processors/WebhookProcessor";
|
||||
import CleanupWebhookDeliveriesTask from "./tasks/CleanupWebhookDeliveriesTask";
|
||||
import DeliverWebhookTask from "./tasks/DeliverWebhookTask";
|
||||
|
||||
PluginManager.register(PluginType.API, webhookSubscriptions, config)
|
||||
.registerProcessor(WebhookProcessor)
|
||||
.registerTask(DeliverWebhookTask)
|
||||
.registerTask(CleanupWebhookDeliveriesTask);
|
||||
@@ -7,12 +7,12 @@ import BaseTask, {
|
||||
TaskSchedule,
|
||||
} from "@server/queues/tasks/BaseTask";
|
||||
|
||||
type Props = void;
|
||||
type Props = Record<string, never>;
|
||||
|
||||
export default class CleanupWebhookDeliveriesTask extends BaseTask<Props> {
|
||||
static cron = TaskSchedule.Daily;
|
||||
|
||||
public async perform(_: Props) {
|
||||
public async perform() {
|
||||
Logger.info("task", `Deleting WebhookDeliveries older than one week…`);
|
||||
const count = await WebhookDelivery.unscoped().destroy({
|
||||
where: {
|
||||
|
||||
Reference in New Issue
Block a user