Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vrite v0.3 #45

Merged
merged 15 commits into from
Oct 10, 2023
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ yarn-error.log*
.pnpm-debug.log*

# local env files
docker.env
.env
.env.local
.env.development.local
Expand Down
50 changes: 26 additions & 24 deletions apps/backend/api/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
createOpenApiNodeHttpHandler,
CreateOpenApiNodeHttpHandlerOptions
} from "trpc-openapi/dist/adapters/node-http/core";
import corsPlugin from "@fastify/cors";
import corsPlugin, { OriginFunction } from "@fastify/cors";
import { OpenApiRouter } from "trpc-openapi";
import { AnyRouter } from "@trpc/server";
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
Expand Down Expand Up @@ -55,36 +55,38 @@ const fastifyTRPCOpenApiPlugin = <TRouter extends AnyRouter>(
done();
};
const apiService = publicPlugin(async (fastify) => {
const originCallback: OriginFunction = (origin, callback) => {
if (!origin || origin === "null") {
callback(null, true);

return;
}

const { hostname } = new URL(origin);
const appHostname = new URL(fastify.config.PUBLIC_APP_URL).hostname;

if (
hostname === "localhost" ||
hostname.endsWith(appHostname) ||
(fastify.config.VRITE_CLOUD && hostname.endsWith("swagger.io"))
) {
callback(null, true);

return;
}

callback(new Error("Not allowed"), false);
};

await fastify.register(rateLimitPlugin, {
max: 500,
timeWindow: "1 minute",
redis: fastify.redis
});
await fastify.register(corsPlugin, {
origin: true,
credentials: true,
methods: ["GET", "DELETE", "PUT", "POST"],
origin(origin, callback) {
if (!origin || origin === "null") {
callback(null, true);

return;
}

const { hostname } = new URL(origin);
const appHostname = new URL(fastify.config.PUBLIC_APP_URL).hostname;

if (
hostname === "localhost" ||
hostname.endsWith(appHostname) ||
(fastify.config.VRITE_CLOUD && hostname.endsWith("swagger.io"))
) {
callback(null, true);

return;
}

callback(new Error("Not allowed"), false);
}
methods: ["GET", "DELETE", "PUT", "POST"]
});
await fastify.register(fastifyTRPCOpenApiPlugin, {
basePath: "/",
Expand Down
11 changes: 9 additions & 2 deletions apps/backend/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ import { generateOpenApiDocument } from "trpc-openapi";
import { createServer, appRouter } from "@vrite/backend";

(async () => {
const server = await createServer();
const server = await createServer({
database: true,
pubSub: true,
auth: true,
email: true,
gitSync: true,
search: true
});

await server.register(apiService);
server.get("/swagger.json", (req, res) => {
res.send(
generateOpenApiDocument(appRouter, {
baseUrl: server.config.PUBLIC_API_URL,
title: "Vrite API",
version: "0.2.0"
version: "0.3.0"
})
);
});
Expand Down
56 changes: 2 additions & 54 deletions apps/backend/app/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { appRouter, errors, publicPlugin, trpcPlugin } from "@vrite/backend";
import { errors, publicPlugin, trpcPlugin, processAuth } from "@vrite/backend";
import staticPlugin from "@fastify/static";
import websocketPlugin from "@fastify/websocket";
import axios from "axios";
import viewPlugin from "@fastify/view";
import handlebars from "handlebars";
import { FastifyReply } from "fastify";
import { processAuth } from "@vrite/backend/src/lib/auth";
import { nanoid } from "nanoid";
import multipartPlugin from "@fastify/multipart";
import mime from "mime-types";
Expand All @@ -15,7 +14,7 @@ import path from "path";

const appService = publicPlugin(async (fastify) => {
const renderPage = async (reply: FastifyReply): Promise<void> => {
return reply.view("index.html", {
return reply.header("X-Frame-Options", "SAMEORIGIN").view("index.html", {
PUBLIC_APP_URL: fastify.config.PUBLIC_APP_URL,
PUBLIC_API_URL: fastify.config.PUBLIC_API_URL,
PUBLIC_COLLAB_URL: fastify.config.PUBLIC_COLLAB_URL,
Expand Down Expand Up @@ -51,57 +50,6 @@ const appService = publicPlugin(async (fastify) => {
fastify.setNotFoundHandler(async (_request, reply) => {
return renderPage(reply);
});
fastify.get<{ Querystring: { url: string } }>("/proxy*", async (request, reply) => {
const filterOutRegex =
/(localhost|\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?::\d{0,4})?\b)/;

if (request.headers.origin) {
reply.header("Access-Control-Allow-Origin", fastify.config.PUBLIC_APP_URL);
reply.header("Access-Control-Allow-Methods", "GET");
reply.header(
"Access-Control-Allow-Headers",
request.headers["access-control-request-headers"]
);
} else if (
fastify.config.NODE_ENV !== "development" &&
!fastify.config.PUBLIC_APP_URL.includes("localhost")
) {
// Prevent proxy abuse in production
return reply.status(400).send("Invalid Origin");
}

if (
filterOutRegex.test(request.query.url) &&
!request.query.url.includes(fastify.config.PUBLIC_ASSETS_URL)
) {
return reply.status(400).send("Invalid URL");
}

if (request.method === "OPTIONS") {
// CORS Preflight
reply.send();
} else {
const targetURL = request.query.url;

try {
const response = await axios.get(targetURL, {
responseType: "arraybuffer"
});

if (!`${response.headers["content-type"]}`.includes("image")) {
return reply.status(400).send("Invalid Content-Type");
}

reply.header("content-type", response.headers["content-type"]);
reply.send(Buffer.from(response.data, "binary"));
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);

return reply.status(500).send("Could not fetch");
}
}
});
fastify.post<{
Body: Buffer;
}>("/upload", async (req, res) => {
Expand Down
10 changes: 9 additions & 1 deletion apps/backend/app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import { appService } from "./app";
import { createServer } from "@vrite/backend";

(async () => {
const server = await createServer();
const server = await createServer({
database: true,
pubSub: true,
auth: true,
email: true,
gitSync: true,
search: true,
storage: true
});

await server.register(appService);
server.listen({ host: server.config.HOST, port: server.config.PORT }, (err) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ const assetsService = publicPlugin(async (fastify) => {
await reply.header("Content-Type", sourceContentType).send(sourceAsset);
};

reply.header("Access-Control-Allow-Origin", fastify.config.PUBLIC_APP_URL);
reply.header(
"Access-Control-Allow-Origin",
fastify.config.NODE_ENV === "development" ? "*" : fastify.config.PUBLIC_APP_URL
);
reply.header("Access-Control-Allow-Methods", "GET");

if (!sourceAsset) return reply.status(404).send();
Expand Down
6 changes: 4 additions & 2 deletions apps/backend/assets/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { assetsService } from "./api";
import { assetsService } from "./assets";
import { createServer } from "@vrite/backend";

(async () => {
const server = await createServer();
const server = await createServer({
storage: true
});

await server.register(assetsService);

Expand Down
18 changes: 4 additions & 14 deletions apps/backend/collaboration/src/extensions/git-sync.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
import { Extension, onChangePayload, onDisconnectPayload } from "@hocuspocus/server";
import {
GitData,
ObjectId,
createGenericOutputContentProcessor,
docToJSON,
getContentPiecesCollection,
getGitDataCollection,
jsonToBuffer
jsonToBuffer,
publishGitDataEvent
} from "@vrite/backend";
import { createEventPublisher } from "@vrite/backend/src/lib/pub-sub";
import { FastifyInstance } from "fastify";
import { ObjectId } from "mongodb";
import crypto from "node:crypto";

interface Configuration {
debounce: number | false | null;
debounceMaxWait: number;
}

type GitDataEvent = {
action: "update";
data: Partial<GitData>;
};

class GitSync implements Extension {
private configuration: Configuration = {
debounce: 5000,
Expand All @@ -36,10 +30,6 @@ class GitSync implements Extension {

private debounced: Map<string, { timeout: NodeJS.Timeout; start: number }> = new Map();

private publishGitDataEvent = createEventPublisher<GitDataEvent>((workspaceId) => {
return `gitData:${workspaceId}`;
});

public constructor(fastify: FastifyInstance, configuration?: Partial<Configuration>) {
this.fastify = fastify;
this.configuration = {
Expand Down Expand Up @@ -121,7 +111,7 @@ class GitSync implements Extension {
}
}
);
this.publishGitDataEvent({ fastify: this.fastify }, `${details.context.workspaceId}`, {
publishGitDataEvent({ fastify: this.fastify }, `${details.context.workspaceId}`, {
action: "update",
data: {
records: gitData.records.map((record: any) => {
Expand Down
3 changes: 2 additions & 1 deletion apps/backend/collaboration/src/extensions/search-indexing.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Extension, onChangePayload, onDisconnectPayload } from "@hocuspocus/server";
import { ObjectId, docToBuffer, getContentPiecesCollection } from "@vrite/backend";
import { docToBuffer, getContentPiecesCollection } from "@vrite/backend";
import { FastifyInstance } from "fastify";
import { ObjectId } from "mongodb";

interface Configuration {
debounce: number | false | null;
Expand Down
7 changes: 6 additions & 1 deletion apps/backend/collaboration/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { writingPlugin } from "./writing";
import { createServer } from "@vrite/backend";

(async () => {
const server = await createServer();
const server = await createServer({
database: true,
auth: true,
pubSub: true,
search: true
});

await server.register(writingPlugin);
})();
14 changes: 9 additions & 5 deletions apps/backend/collaboration/src/writing.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { publicPlugin, getContentsCollection, getContentVariantsCollection } from "@vrite/backend";
import {
publicPlugin,
getContentsCollection,
getContentVariantsCollection,
errors,
SessionData
} from "@vrite/backend";
import { Server } from "@hocuspocus/server";
import { Database } from "@hocuspocus/extension-database";
import { Redis } from "@hocuspocus/extension-redis";
import { ObjectId, Binary } from "mongodb";
import { SessionData } from "@vrite/backend/src/lib/session";
import { unauthorized } from "@vrite/backend/src/lib/errors";
import { SearchIndexing } from "#extensions/search-indexing";
import { GitSync } from "#extensions/git-sync";

Expand All @@ -18,13 +22,13 @@ const writingPlugin = publicPlugin(async (fastify) => {
const cookies = fastify.parseCookie(data.requestHeaders.cookie || "");

if (!cookies.accessToken) {
throw unauthorized();
throw errors.unauthorized();
}

const token = fastify.unsignCookie(cookies.accessToken || "")?.value || "";

if (!token) {
throw unauthorized();
throw errors.unauthorized();
}

const { sessionId } = fastify.jwt.verify<{ sessionId: string }>(token);
Expand Down
24 changes: 23 additions & 1 deletion apps/backend/extensions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,34 @@
},
"dependencies": {
"@fastify/cors": "^8.3.0",
"@prettier/sync": "^0.3.0",
"@trpc/server": "^10.35.0",
"@types/hast": "^3.0.1",
"@types/html-to-text": "^9.0.1",
"@types/js-yaml": "^4.0.6",
"@types/mdast": "^4.0.1",
"@vrite/backend": "workspace:*",
"@vrite/sdk": "workspace:*",
"dayjs": "^1.11.9",
"fastify": "^4.20.0",
"hast-util-to-html": "^9.0.0",
"html-to-text": "^9.0.5",
"js-yaml": "^4.1.0",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-frontmatter": "^2.0.1",
"mdast-util-gfm": "^3.0.0",
"mdast-util-mdx": "^3.0.0",
"mdast-util-to-hast": "^13.0.2",
"micromark-extension-frontmatter": "^2.0.0",
"micromark-extension-gfm": "^3.0.0",
"micromark-extension-mdxjs": "^2.0.0",
"openai": "^4.0.0",
"trpc-openapi": "^1.2.0"
"prettier": "^3.0.2",
"remark": "^15.0.1",
"remark-mdx": "^2.3.0",
"remark-parse": "^11.0.0",
"trpc-openapi": "^1.2.0",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@vrite/scripts": "workspace:*"
Expand Down
Loading