Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/core/server/app/middleware/graphql/apolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
} from "coral-server/graph/plugins";
import { Request, TenantCoralRequest } from "coral-server/types/express";

import { GetMiddlewareOptions } from "apollo-server-express/dist/ApolloServer";
import { isUrlAllowed } from "coral-server/helpers";
import { RedisCache } from "./cache/redis";

type ContextProviderOptions = Omit<AppOptions, "schema" | "metrics">;
Expand Down Expand Up @@ -132,14 +134,29 @@ export const apolloGraphQLMiddleware = ({
debug: false,
});

const cors: GetMiddlewareOptions["cors"] = options.config.get(
"restricted_origins"
)
? {
origin: (origin: string | undefined, cb) => {
if (!origin) {
cb(null, false);
return;
}
cb(null, isUrlAllowed(options.config, origin));
},
credentials: true,
}
: true;

// Get the GraphQL middleware.
return server.getMiddleware({
// Disable the health check endpoint, Coral does not use this endpoint and
// instead uses the /api/health endpoint.
disableHealthCheck: true,

// Disable CORS, Coral does not allow cross origin requests.
cors: false,
cors,

// Disable the body parser, we will add our own.
bodyParserConfig: false,
Expand Down
30 changes: 30 additions & 0 deletions src/core/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,30 @@ convict.addFormat({
},
});

// Add a custom format for the allowed origins.
convict.addFormat({
name: "origins",
validate: (urls: string[]) => {
for (const url of urls) {
Joi.assert(url, Joi.string().uri());
}
},
coerce: (list: string) =>
compact(
list
.split(",")
.map((i) => i.trim())
.map((item) => {
if (!item) {
return "";
}

const url = new URL(item);
return url.origin;
})
),
});

// Add a custom format for a list of comma seperated strings.
convict.addFormat({
name: "list",
Expand Down Expand Up @@ -434,6 +458,12 @@ const config = convict({
default: false,
env: "MOUNT_DOCUMENTATION",
},
restricted_origins: {
doc: "Restrict origins in CORS and comments link validation",
format: "origins",
default: "",
env: "RESTRICTED_ORIGINS",
},
});

export type Config = typeof config;
Expand Down
1 change: 1 addition & 0 deletions src/core/server/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as createTimer } from "./createTimer";
export { default as relativeTo } from "./relativeTo";
export { default as validateSchema } from "./validateSchema";
export { default as isUrlAllowed } from "./isUrlAllowed";
11 changes: 11 additions & 0 deletions src/core/server/helpers/isUrlAllowed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Config } from "../config";

export default function isUrlAllowed(config: Config, url: string) {
const restrictedOrigins = config.get("restricted_origins");
if (!restrictedOrigins) {
return true;
}
const { origin } = new URL(url);

return restrictedOrigins.includes(origin);
}
27 changes: 17 additions & 10 deletions src/core/server/services/comments/pipeline/phases/linkify.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import { isUrlAllowed } from "coral-server/helpers";
import linkifyjs, { Options } from "linkifyjs/html";

import {
IntermediateModerationPhase,
IntermediatePhaseResult,
} from "coral-server/services/comments/pipeline";

// linkify configuration.
const config: Options = {
className: "",
tagName: "a",
// If the href starts with `//`, then it shouldn't be treated as a valid URL
// because this is not commonly used in posted URL's.
validate: (href: string) => !href.startsWith("//"),
};

export const linkify: IntermediateModerationPhase = async ({
comment,
bodyText,
config,
}): Promise<IntermediatePhaseResult | void> => {
if (bodyText.trim().length > 0) {
const linkifyOptions: Options = {
className: "",
tagName: "a",
// If the href starts with `//`, then it shouldn't be treated as a valid URL
// because this is not commonly used in posted URL's.
validate: (href: string, type: string) => {
if (type === "url" && !isUrlAllowed(config, href)) {
return false;
}

return !href.startsWith("//");
},
};

return {
body: linkifyjs(comment.body, config),
body: linkifyjs(comment.body, linkifyOptions),
};
}
};