From e9463cc66e182fe7d7100b001df4a859878efe5a Mon Sep 17 00:00:00 2001 From: Desmond Obisi Date: Sat, 21 Sep 2024 04:09:22 +0100 Subject: [PATCH 1/2] chore: add endpoint/github auth for submitting/authorizing event badging issues. --- helpers/crypto.js | 44 ++++++++++++++++++++++ package-lock.json | 16 +++++++- package.json | 3 +- providers/event-github/auth.js | 69 ++++++++++++++++++++++++++++++++++ providers/index.js | 6 +++ routes/index.js | 7 ++++ 6 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 helpers/crypto.js create mode 100644 providers/event-github/auth.js diff --git a/helpers/crypto.js b/helpers/crypto.js new file mode 100644 index 0000000..2f93f63 --- /dev/null +++ b/helpers/crypto.js @@ -0,0 +1,44 @@ +const crypto = require("crypto"); +const zlib = require("zlib"); +const TurndownService = require("turndown"); +require("dotenv").config(); + +const algorithm = "aes-256-ctr"; +const secretKey = crypto + .createHash("sha256") + .update(String(process.env.ENCRYPTION_SECRET_KEY)) + .digest("base64") + .slice(0, 32); + +const encrypt = (text) => { + const compressed = zlib.deflateSync(text); + const iv = crypto.randomBytes(16); + const cipher = crypto.createCipheriv(algorithm, secretKey, iv); + const encrypted = Buffer.concat([cipher.update(compressed), cipher.final()]); + return `${iv.toString("base64url")}:${encrypted.toString("base64url")}`; +}; + +const decrypt = (hash) => { + const [iv, content] = hash.split(":"); + const decipher = crypto.createDecipheriv( + algorithm, + secretKey, + Buffer.from(iv, "base64url") + ); + const decrypted = Buffer.concat([ + decipher.update(Buffer.from(content, "base64url")), + decipher.final(), + ]); + return zlib.inflateSync(decrypted).toString(); +}; + +const convertToMarkdown = (html) => { + const turndownService = new TurndownService({ + headingStyle: "atx", + bulletListMarker: "-", + }); + const markdown = turndownService.turndown(html); + return markdown; +}; + +module.exports = { encrypt, decrypt, convertToMarkdown }; diff --git a/package-lock.json b/package-lock.json index 3388406..097f75d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,8 @@ "nodemailer": "^6.9.3", "nodemon": "^2.0.22", "octokit": "^2.1.0", - "sequelize": "^6.32.1" + "sequelize": "^6.32.1", + "turndown": "^7.2.0" }, "devDependencies": { "eslint-config-prettier": "^8.8.0", @@ -407,6 +408,11 @@ "node": ">=14.18.0" } }, + "node_modules/@mixmark-io/domino": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", + "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -5631,6 +5637,14 @@ "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", "dev": true }, + "node_modules/turndown": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz", + "integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==", + "dependencies": { + "@mixmark-io/domino": "^2.2.0" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 7bf5878..e228711 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "nodemailer": "^6.9.3", "nodemon": "^2.0.22", "octokit": "^2.1.0", - "sequelize": "^6.32.1" + "sequelize": "^6.32.1", + "turndown": "^7.2.0" }, "devDependencies": { "eslint-config-prettier": "^8.8.0", diff --git a/providers/event-github/auth.js b/providers/event-github/auth.js new file mode 100644 index 0000000..e926c7a --- /dev/null +++ b/providers/event-github/auth.js @@ -0,0 +1,69 @@ +const axios = require("axios"); +const { Octokit } = require("@octokit/rest"); +const { encrypt, decrypt, convertToMarkdown } = require("../../helpers/crypto"); + +const issueCreationAuth = (req, res) => { + if (!process.env.GITHUB_AUTH_CLIENT_ID) { + res.status(500).send("GitHub Issue Creation is not configured"); + return; + } + + const scopes = ["repo"]; + const encryptedFormData = encrypt(JSON.stringify(req.body)); + const url = `https://github.com/login/oauth/authorize?client_id=${ + process.env.GITHUB_AUTH_CLIENT_ID + }&scope=${scopes.join(",")}&state=${encryptedFormData}`; + + res.send({ authorizationLink: url }); +}; + +const issueCreationCallback = async (req, res) => { + const code = req.query.code; + const encryptedState = req.query.state; + + const formData = decrypt(encryptedState); + const parsedFormData = JSON.parse(formData); + const issueTitle = parsedFormData.title; + const markdown = convertToMarkdown(parsedFormData.body); + + if (!formData) { + return res.status(400).json({ error: "No form data found" }); + } + + try { + const tokenResponse = await axios.post( + "https://github.com/login/oauth/access_token", + { + client_id: process.env.GITHUB_AUTH_CLIENT_ID, + client_secret: process.env.GITHUB_AUTH_CLIENT_SECRET, + code, + }, + { + headers: { + Accept: "application/json", + }, + } + ); + + const accessToken = tokenResponse.data.access_token; + + const octokit = new Octokit({ auth: accessToken }); + + const { data: issue } = await octokit.rest.issues.create({ + owner: "desmondsanctity", + repo: "event-diversity-and-inclusion", + title: issueTitle, + body: markdown, + }); + + res.redirect(issue.html_url); + } catch (error) { + console.error("Error in issue creation callback:", error); + res.status(500).send("An error occurred during issue creation"); + } +}; + +module.exports = { + issueCreationAuth, + issueCreationCallback, +}; diff --git a/providers/index.js b/providers/index.js index 5a8108b..fea9369 100644 --- a/providers/index.js +++ b/providers/index.js @@ -1,3 +1,7 @@ +const { + issueCreationCallback, + issueCreationAuth, +} = require("./event-github/auth.js"); const { githubAuth, githubAuthCallback, @@ -11,4 +15,6 @@ module.exports = { githubApp, gitlabAuth, gitlabAuthCallback, + issueCreationAuth, + issueCreationCallback, }; diff --git a/routes/index.js b/routes/index.js index b355fb1..6b55a9e 100644 --- a/routes/index.js +++ b/routes/index.js @@ -10,6 +10,8 @@ const { githubApp, gitlabAuth, gitlabAuthCallback, + issueCreationCallback, + issueCreationAuth, } = require("../providers/index.js"); /** @@ -159,6 +161,7 @@ const setupRoutes = (app) => { gitlabAuthCallback(app); app.get("/api/badgedRepos", badgedRepos); app.post("/api/repos-to-badge", reposToBadge); + app.get("/api/issue-callback", issueCreationCallback); // github app routes app.post("/api/event_badging", async (req, res) => { @@ -174,6 +177,10 @@ const setupRoutes = (app) => { res.send("ok"); }); + app.post("/api/submit-form", (req, res) => { + issueCreationAuth(req, res); + }); + // route to get all events app.get("/api/badged_events", getAllEvents); From eb9fe17e9111a8dae839a165af952efb5259c034 Mon Sep 17 00:00:00 2001 From: Desmond Obisi Date: Tue, 24 Sep 2024 15:42:26 +0100 Subject: [PATCH 2/2] chore: update owner to badging for issue creation --- providers/event-github/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/event-github/auth.js b/providers/event-github/auth.js index e926c7a..8d895a1 100644 --- a/providers/event-github/auth.js +++ b/providers/event-github/auth.js @@ -50,7 +50,7 @@ const issueCreationCallback = async (req, res) => { const octokit = new Octokit({ auth: accessToken }); const { data: issue } = await octokit.rest.issues.create({ - owner: "desmondsanctity", + owner: "badging", repo: "event-diversity-and-inclusion", title: issueTitle, body: markdown,