From ce93c5a224fcf4870e6ef9e56905d7bd5c3d8c2f Mon Sep 17 00:00:00 2001 From: Abhijna Raghavendra Date: Wed, 14 Feb 2024 23:43:54 +0530 Subject: [PATCH 1/2] feat: implement info logging ; rfac: refactor backend code --- docker/Dockerfile.backend | 2 +- src/backend/.env.sample | 3 +- src/backend/auth/github.ts | 3 +- src/backend/db.ts | 177 +++++++--------------------- src/backend/dependencies.ts | 5 +- src/backend/main.ts | 90 ++++++++++++++ src/backend/scripts.ts | 39 ++++++ src/backend/server.ts | 26 +++- src/backend/types/.gitkeep | 0 src/backend/types/maps_interface.ts | 9 ++ 10 files changed, 212 insertions(+), 142 deletions(-) create mode 100644 src/backend/main.ts delete mode 100644 src/backend/types/.gitkeep create mode 100644 src/backend/types/maps_interface.ts diff --git a/docker/Dockerfile.backend b/docker/Dockerfile.backend index 96e88c3..01f70ab 100644 --- a/docker/Dockerfile.backend +++ b/docker/Dockerfile.backend @@ -6,4 +6,4 @@ COPY . ./ RUN deno cache src/backend/dependencies.ts -CMD ["run", "--allow-net", "--allow-env", "--allow-read","--allow-run","src/backend/server.ts"] +CMD ["run", "--allow-net", "--allow-env", "--allow-read","--allow-run","--allow-sys","src/backend/server.ts"] diff --git a/src/backend/.env.sample b/src/backend/.env.sample index 392dec9..a446357 100644 --- a/src/backend/.env.sample +++ b/src/backend/.env.sample @@ -1,4 +1,5 @@ GITHUB_OAUTH_CLIENT_ID=... GITHUB_OAUTH_CLIENT_SECRET=... MONGO_API_KEY=... -MONGO_APP_ID=... \ No newline at end of file +MONGO_APP_ID=... +SENTRY_DSN=... \ No newline at end of file diff --git a/src/backend/auth/github.ts b/src/backend/auth/github.ts index 4cfcf1e..af2d3e6 100644 --- a/src/backend/auth/github.ts +++ b/src/backend/auth/github.ts @@ -1,4 +1,4 @@ -import { Context } from "../dependencies.ts"; +import { Context, Sentry } from "../dependencies.ts"; import { checkUser } from "../db.ts"; import { checkJWT, createJWT } from "../utils/jwt.ts"; @@ -27,6 +27,7 @@ async function githubAuth(ctx: Context, id: string, secret: string) { ctx.response.headers.set("Access-Control-Allow-Origin", "*"); if (status.matchedCount == 1) { const id_jwt = await createJWT(githubId); + Sentry.captureMessage("User " + githubId + " logged in", "info"); ctx.response.body = id_jwt; } else { ctx.response.body = "not authorized"; diff --git a/src/backend/db.ts b/src/backend/db.ts index 7cda47e..450b8a5 100644 --- a/src/backend/db.ts +++ b/src/backend/db.ts @@ -1,10 +1,12 @@ import getGithubUser from "./utils/github-user.ts"; -import { Context, exec } from "./dependencies.ts"; +import { Context, exec, Sentry } from "./dependencies.ts"; import dockerize from "./utils/container.ts"; import { checkJWT } from "./utils/jwt.ts"; +import DfContentMap from "./types/maps_interface.ts"; const DATA_API_KEY = Deno.env.get("MONGO_API_KEY")!; const APP_ID = Deno.env.get("MONGO_APP_ID"); + const BASE_URI = `https://ap-south-1.aws.data.mongodb-api.com/app/${APP_ID}/endpoint/data/v1`; const DATA_SOURCE = "domain-forge-demo-db"; @@ -20,36 +22,39 @@ const options = { body: "", }; +const MONGO_URLs = { + update: new URL(`${BASE_URI}/action/updateOne`), + find: new URL(`${BASE_URI}/action/find`), + insert: new URL(`${BASE_URI}/action/insertOne`), + delete: new URL(`${BASE_URI}/action/deleteOne`), +}; + +// Function to update access token on db if user exists async function checkUser(accessToken: string) { - const auth_query = { + const githubId = await getGithubUser(accessToken); + + const query = { collection: "user_auth", database: DATABASE, dataSource: DATA_SOURCE, - filter: {}, - update: {}, - }; - const githubId = await getGithubUser(accessToken); - auth_query.filter = { "githubId": githubId }; - auth_query.update = { - $set: { - "githubId": githubId, - "authToken": accessToken, + filter: { "githubId": githubId }, + update: { + $set: { + "githubId": githubId, + "authToken": accessToken, + }, }, }; - const update_url = new URL(`${BASE_URI}/action/updateOne`); - options.body = JSON.stringify(auth_query); - const status_resp = await fetch(update_url.toString(), options); + + options.body = JSON.stringify(query); + + const status_resp = await fetch(MONGO_URLs.update.toString(), options); const status = await status_resp.json(); return { status, githubId }; } -async function getMaps(ctx: Context) { - console.log("get maps"); - const author = ctx.request.url.searchParams.get("user"); - const token = ctx.request.url.searchParams.get("token"); - if (author != await checkJWT(token)) { - ctx.throw(401); - } +// Get all content maps corresponding to user +async function getMaps(author: string) { const query = { collection: "content_maps", database: DATABASE, @@ -57,129 +62,44 @@ async function getMaps(ctx: Context) { filter: { "author": author }, }; options.body = JSON.stringify(query); - const url = new URL(`${BASE_URI}/action/find`); - const resp = await fetch(url.toString(), options); + const resp = await fetch(MONGO_URLs.find.toString(), options); const data = await resp.json(); - ctx.response.headers.set("Access-Control-Allow-Origin", "*"); - ctx.response.body = data.documents; + return data; } -async function addMaps(ctx: Context) { - if (!ctx.request.hasBody) { - ctx.throw(415); - } - let document, - author: string, - token: string, - env_content: string, - static_content: string, - stack: string, - port: string, - build_cmds: string; - const body = await ctx.request.body().value; - try { - document = JSON.parse(body); - author = document.author; - token = document.token; - //env_contents not getting saved to db - env_content = document.env_content; - static_content = document.static_content; - stack = document.stack; - port = document.port; - build_cmds = document.build_cmds; - delete document.token; - delete document.port; - delete document.build_cmds; - delete document.stack; - delete document.env_content; - delete document.static_content; - } catch (e) { - document = body; - author = document.author; - token = document.token; - env_content = document.env_content; - static_content = document.static_content; - stack = document.stack; - port = document.port; - build_cmds = document.build_cmds; - delete document.token; - delete document.port; - delete document.build_cmds; - delete document.stack; - delete document.env_content; - delete document.static_content; - } - if (author != await checkJWT(token)) { - ctx.throw(401); - } - let query = { +// Add content maps +async function addMaps(document: DfContentMap) { + const query = { collection: "content_maps", database: DATABASE, dataSource: DATA_SOURCE, filter: { "subdomain": document.subdomain }, }; options.body = JSON.stringify(query); - let url = new URL(`${BASE_URI}/action/find`); - let resp = await fetch(url.toString(), options); + + let resp = await fetch(MONGO_URLs.find.toString(), options); let data = await resp.json(); + if (data.documents.length == 0) { - let query = { + const query = { collection: "content_maps", database: DATABASE, dataSource: DATA_SOURCE, document: document, }; + options.body = JSON.stringify(query); - url = new URL(`${BASE_URI}/action/insertOne`); - resp = await fetch(url.toString(), options); + resp = await fetch(MONGO_URLs.insert.toString(), options); data = await resp.json(); - ctx.response.headers.set("Access-Control-Allow-Origin", "*"); - if (document.resource_type === "URL") { - await exec( - `bash -c "echo 'bash ../../src/backend/utils/automate.sh -u ${document.resource} ${document.subdomain}' > /hostpipe/pipe"`, - ); - } else if (document.resource_type === "PORT") { - await exec( - `bash -c "echo 'bash ../../src/backend/utils/automate.sh -p ${document.resource} ${document.subdomain}' > /hostpipe/pipe"`, - ); - } else if (document.resource_type === "GITHUB" && static_content == "Yes") { - await exec( - `bash -c "echo 'bash ../../src/backend/utils/container.sh -s ${document.subdomain} ${document.resource} ${env_content}' > /hostpipe/pipe"`, - ); - } else if (document.resource_type === "GITHUB" && static_content == "No") { - let dockerfile = dockerize(stack, port, build_cmds); - await exec( - `bash -c "echo 'bash ../../src/backend/utils/container.sh -g ${document.subdomain} ${document.resource} ${env_content} ${dockerfile} ${port}' > /hostpipe/pipe"`, - ); - } - - (data.insertedId !== undefined) - ? ctx.response.body = { "status": "success" } - : ctx.response.body = { "status": "failed" }; - } else { - ctx.response.headers.set("Access-Control-Allow-Origin", "*"); - ctx.response.body = { "status": "failed" }; + return (data.insertedId !== undefined); + } else { + return false; } } -async function deleteMaps(ctx: Context) { - if (!ctx.request.hasBody) { - ctx.throw(415); - } - let document; - const body = await ctx.request.body().value; - try { - document = JSON.parse(body); - } catch (e) { - document = body; - } - const author = document.author; - const token = document.token; - delete document.token; - if (author != await checkJWT(token)) { - ctx.throw(401); - } +// Delete content maps +async function deleteMaps(document: DfContentMap) { const query = { collection: "content_maps", database: DATABASE, @@ -187,18 +107,11 @@ async function deleteMaps(ctx: Context) { filter: document, }; options.body = JSON.stringify(query); - const url = new URL(`${BASE_URI}/action/deleteOne`); - const resp = await fetch(url.toString(), options); + + const resp = await fetch(MONGO_URLs.delete.toString(), options); const data = await resp.json(); - if (data.deletedCount) { - await exec( - `bash -c "echo 'bash ../../src/backend/utils/delete.sh ${document.subdomain}' > /hostpipe/pipe"`, - ); - } - ctx.response.headers.set("Access-Control-Allow-Origin", "*"); - ctx.response.body = data; + return data; } export { addMaps, checkUser, deleteMaps, getMaps }; - diff --git a/src/backend/dependencies.ts b/src/backend/dependencies.ts index a66ee4d..01eeda1 100644 --- a/src/backend/dependencies.ts +++ b/src/backend/dependencies.ts @@ -5,6 +5,7 @@ import { } from "https://deno.land/x/oak@v12.5.0/mod.ts"; import { Session } from "https://deno.land/x/oak_sessions@v4.1.9/mod.ts"; import { create, verify } from "https://deno.land/x/djwt@v2.9.1/mod.ts"; -import { exec } from "https://deno.land/x/exec/mod.ts"; +import { exec } from "https://deno.land/x/exec@0.0.5/mod.ts"; +import * as Sentry from "npm:@sentry/node"; -export { Application, Context, create, exec, Router, Session, verify }; +export { Application, Context, create, exec, Router, Sentry, Session, verify }; diff --git a/src/backend/main.ts b/src/backend/main.ts new file mode 100644 index 0000000..a30fd08 --- /dev/null +++ b/src/backend/main.ts @@ -0,0 +1,90 @@ +import { Context, Sentry } from "./dependencies.ts"; +import { addScript, deleteScript } from "./scripts.ts"; +import { checkJWT } from "./utils/jwt.ts"; +import { addMaps, deleteMaps, getMaps } from "./db.ts"; + +async function getSubdomains(ctx: Context) { + const author = ctx.request.url.searchParams.get("user"); + const token = ctx.request.url.searchParams.get("token"); + if (author != await checkJWT(token!)) { + ctx.throw(401); + } + const data = await getMaps(author); + ctx.response.headers.set("Access-Control-Allow-Origin", "*"); + ctx.response.body = data.documents; +} + +async function addSubdomain(ctx: Context) { + if (!ctx.request.hasBody) { + ctx.throw(415); + } + let document; + const body = await ctx.request.body().value; + try { + document = JSON.parse(body); + } catch (e) { + document = body; + } + const copy = document; + const token = document.token; + delete document.token; + delete document.port; + delete document.build_cmds; + delete document.stack; + delete document.env_content; + delete document.static_content; + if (document.author != await checkJWT(token)) { + ctx.throw(401); + } + const success: boolean = await addMaps(document); + ctx.response.headers.set("Access-Control-Allow-Origin", "*"); + + if (success) { + await addScript( + document, + copy.env_content, + copy.static_content, + copy.stack, + copy.port, + copy.build_cmds, + ); + ctx.response.body = { "status": "success" }; + Sentry.captureMessage( + "User " + document.author + " added subdomain " + document.subdomain, + "info", + ); + } else { + ctx.response.body = { "status": "failed" }; + } +} + +async function deleteSubdomain(ctx: Context) { + if (!ctx.request.hasBody) { + ctx.throw(415); + } + let document; + const body = await ctx.request.body().value; + try { + document = JSON.parse(body); + } catch (e) { + document = body; + } + const author = document.author; + const token = document.token; + delete document.token; + if (author != await checkJWT(token)) { + ctx.throw(401); + } + const data = await deleteMaps(document); + if (data.deletedCount) { + deleteScript(document); + Sentry.captureMessage( + "User " + document.author + " deleted subdomain " + document.subdomain, + "info", + ); + } + ctx.response.headers.set("Access-Control-Allow-Origin", "*"); + ctx.response.body = data; +} + +export { addSubdomain, deleteSubdomain, getSubdomains }; diff --git a/src/backend/scripts.ts b/src/backend/scripts.ts index e69de29..96e0335 100644 --- a/src/backend/scripts.ts +++ b/src/backend/scripts.ts @@ -0,0 +1,39 @@ +import { exec } from "./dependencies.ts"; +import dockerize from "./utils/container.ts"; +import DfContentMap from "./types/maps_interface.ts"; + +async function addScript( + document: DfContentMap, + env_content: string, + static_content: string, + stack: string, + port: string, + build_cmds: string, +) { + if (document.resource_type === "URL") { + await exec( + `bash -c "echo 'bash ../../src/backend/utils/automate.sh -u ${document.resource} ${document.subdomain}' > /hostpipe/pipe"`, + ); + } else if (document.resource_type === "PORT") { + await exec( + `bash -c "echo 'bash ../../src/backend/utils/automate.sh -p ${document.resource} ${document.subdomain}' > /hostpipe/pipe"`, + ); + } else if (document.resource_type === "GITHUB" && static_content == "Yes") { + await exec( + `bash -c "echo 'bash ../../src/backend/utils/container.sh -s ${document.subdomain} ${document.resource} ${env_content}' > /hostpipe/pipe"`, + ); + } else if (document.resource_type === "GITHUB" && static_content == "No") { + const dockerfile = dockerize(stack, port, build_cmds); + await exec( + `bash -c "echo 'bash ../../src/backend/utils/container.sh -g ${document.subdomain} ${document.resource} ${env_content} ${dockerfile} ${port}' > /hostpipe/pipe"`, + ); + } +} + +async function deleteScript(document: DfContentMap) { + await exec( + `bash -c "echo 'bash ../../src/backend/utils/delete.sh ${document.subdomain}' > /hostpipe/pipe"`, + ); +} + +export { addScript, deleteScript }; diff --git a/src/backend/server.ts b/src/backend/server.ts index f2137f6..d9e344e 100644 --- a/src/backend/server.ts +++ b/src/backend/server.ts @@ -1,6 +1,12 @@ -import { Application, Router, Session } from "./dependencies.ts"; +import { + Application, + Context, + Router, + Sentry, + Session, +} from "./dependencies.ts"; import { githubAuth, githubId } from "./auth/github.ts"; -import { addMaps, deleteMaps, getMaps } from "./db.ts"; +import { addSubdomain, deleteSubdomain, getSubdomains } from "./main.ts"; const router = new Router(); const app = new Application(); @@ -8,15 +14,25 @@ const PORT = 7000; const id: string = Deno.env.get("GITHUB_OAUTH_CLIENT_ID")!; const secret: string = Deno.env.get("GITHUB_OAUTH_CLIENT_SECRET")!; +const dsn: string = Deno.env.get("SENTRY_DSN")!; + +Sentry.init({ + dsn: dsn, + integrations: [ + new Sentry.Integrations.Http({ tracing: true }), + ], + debug: true, + tracesSampleRate: 1.0, +}); app.use(Session.initMiddleware()); router .post("/auth/github", (ctx) => githubAuth(ctx, id, secret)) .post("/auth/jwt", (ctx) => githubId(ctx)) - .get("/map", (ctx) => getMaps(ctx)) - .post("/map", (ctx) => addMaps(ctx)) - .post("/mapdel", (ctx) => deleteMaps(ctx)); + .get("/map", (ctx) => getSubdomains(ctx)) + .post("/map", (ctx) => addSubdomain(ctx)) + .post("/mapdel", (ctx) => deleteSubdomain(ctx)); app.use(router.routes()); app.use(router.allowedMethods()); diff --git a/src/backend/types/.gitkeep b/src/backend/types/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/backend/types/maps_interface.ts b/src/backend/types/maps_interface.ts new file mode 100644 index 0000000..e460ad3 --- /dev/null +++ b/src/backend/types/maps_interface.ts @@ -0,0 +1,9 @@ +interface DfContentMap { + subdomain: string; + resource_type: string; + resource: string; + author: string; + date: string; +} + +export default DfContentMap; From 443de7b876f7c4152767e5f4a8df711cf334c4a7 Mon Sep 17 00:00:00 2001 From: Abhijna Raghavendra Date: Thu, 15 Feb 2024 00:31:31 +0530 Subject: [PATCH 2/2] feat: add error logging in backend --- src/backend/dependencies.ts | 15 ++++++++++++++- src/backend/server.ts | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/backend/dependencies.ts b/src/backend/dependencies.ts index 01eeda1..6bb44b6 100644 --- a/src/backend/dependencies.ts +++ b/src/backend/dependencies.ts @@ -1,11 +1,24 @@ import { Application, Context, + isHttpError, Router, + Status, } from "https://deno.land/x/oak@v12.5.0/mod.ts"; import { Session } from "https://deno.land/x/oak_sessions@v4.1.9/mod.ts"; import { create, verify } from "https://deno.land/x/djwt@v2.9.1/mod.ts"; import { exec } from "https://deno.land/x/exec@0.0.5/mod.ts"; import * as Sentry from "npm:@sentry/node"; -export { Application, Context, create, exec, Router, Sentry, Session, verify }; +export { + Application, + Context, + create, + exec, + isHttpError, + Router, + Sentry, + Session, + Status, + verify, +}; diff --git a/src/backend/server.ts b/src/backend/server.ts index d9e344e..8f852b7 100644 --- a/src/backend/server.ts +++ b/src/backend/server.ts @@ -1,9 +1,11 @@ import { Application, Context, + isHttpError, Router, Sentry, Session, + Status, } from "./dependencies.ts"; import { githubAuth, githubId } from "./auth/github.ts"; import { addSubdomain, deleteSubdomain, getSubdomains } from "./main.ts"; @@ -25,6 +27,20 @@ Sentry.init({ tracesSampleRate: 1.0, }); +app.use(async (ctx: Context, next) => { + try { + await next(); + } catch (err) { + if (isHttpError(err)) { + ctx.response.status = err.status; + } else { + ctx.response.status = Status.InternalServerError; + } + Sentry.captureException(err); + ctx.response.body = { error: err.message }; + } +}); + app.use(Session.initMiddleware()); router