From 98de002e14b07304b1518bd9e8eac5d5f47d761f Mon Sep 17 00:00:00 2001 From: Enstrayed <48845980+Enstrayed@users.noreply.github.com> Date: Sun, 31 Mar 2024 12:52:26 -0700 Subject: [PATCH 1/8] god why --- routes/etyd.js | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/routes/etyd.js b/routes/etyd.js index 5ace98a..8c011ec 100644 --- a/routes/etyd.js +++ b/routes/etyd.js @@ -31,24 +31,26 @@ app.post("/etydwrite", (rreq,rres) => { switch(rreq.body.action) { case "set": // Write to db - if (rreq.body.random == true) { - let workingTarget = makeRandomHex() - - db.get(`/${workingTarget}`).then(dbres => { - if (dbres != null) { - let workingTarget = makeRandomHex() - - db.get(`/${workingTarget}`).then(dbres => { - if (dbres != null) { - // well fuck + let workingTarget = makeRandomHex() // Make a random URL + db.get(`/${workingTarget}`).then(dbres => { // Check if it exists + if (dbres != null) { // If it does + let workingTarget = makeRandomHex() // Make a new one + db.get(`/${workingTarget}`).then(dbres => { // Check if *that* exists + if (dbres != null) { // If it does + // Then everything is dumb and pointless so just give up + console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION set returned 409 (Two attempts to find an open key failed)`) rres.sendStatus(409) - + } else { // if it doesnt then set the stupid key I hate this code so much why did I do this serverside this is so dumb + console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION set returned 200 KEY:${rreq.get("Authorization")} TARGET: ${workingTarget}`) + db.set(`/${workingTarget}`,rreq.body.value) + rres.send(`https://etyd.cc/${workingTarget}`) } }) } else { + console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION set returned 200 KEY:${rreq.get("Authorization")} TARGET: ${workingTarget}`) db.set(`/${workingTarget}`,rreq.body.value) rres.send(`https://etyd.cc/${workingTarget}`) } @@ -71,12 +73,18 @@ app.post("/etydwrite", (rreq,rres) => { case "delete": - db.get(`/${rreq.body.target}`).then(dbres => { + let workingTarget = rreq.body.target.replace("https://etyd.cc/","") // Sanitize input + if (workingTarget.startsWith("/")) { + workingTarget = workingTarget.slice(1) + } + + db.get(`/${workingTarget}`).then(dbres => { if (dbres == null) { //if key doesnt exist then log and return 400 - console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION delete returned 400 KEY:${rreq.get("Authorization")}`) - rres.sendStatus(400) + console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION delete returned 404 KEY:${rreq.get("Authorization")} TARGET: ${workingTarget}`) + rres.sendStatus(404) } else { - db.del(`/${rreq.body.target}`) + console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION delete returned 200 KEY:${rreq.get("Authorization")} TARGET: ${workingTarget}`) + db.del(`/${workingTarget}`) rres.sendStatus(200) } }) @@ -84,6 +92,7 @@ app.post("/etydwrite", (rreq,rres) => { default: + console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION default returned 400 KEY:${rreq.get("Authorization")}`) rres.sendStatus(400) // request json didnt include a valid action break; } -- 2.49.1 From 3b96e07d13a6e3d94f2a417cd1c02a2851d86281 Mon Sep 17 00:00:00 2001 From: Enstrayed <48845980+Enstrayed@users.noreply.github.com> Date: Tue, 9 Apr 2024 15:02:16 -0700 Subject: [PATCH 2/8] working changes --- .gitignore | 3 +- Caddyfile | 21 ++++ etydFrontend/_static/etyd.js | 61 ++++++++++ etydFrontend/_static/index.css | 22 ++++ etydFrontend/index.html | 75 +++++++++++++ package-lock.json | 33 +++++- package.json | 6 +- routes/etyd.js | 198 ++++++++++++++++----------------- 8 files changed, 317 insertions(+), 102 deletions(-) create mode 100644 Caddyfile create mode 100644 etydFrontend/_static/etyd.js create mode 100644 etydFrontend/_static/index.css create mode 100644 etydFrontend/index.html diff --git a/.gitignore b/.gitignore index cf7c602..29b0255 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ -config.json \ No newline at end of file +config.json +bun.lockb \ No newline at end of file diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..eb42a99 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,21 @@ +:8082 { + reverse_proxy localhost:8081 +} + +:8083 { + @staticpaths { + path / + path /_static* + path /favicon.ico + } + + handle @staticpaths { + root ./etydFrontend + file_server + } + + handle /* { + rewrite * /etyd{uri} + reverse_proxy localhost:8081 + } +} \ No newline at end of file diff --git a/etydFrontend/_static/etyd.js b/etydFrontend/_static/etyd.js new file mode 100644 index 0000000..5879b9d --- /dev/null +++ b/etydFrontend/_static/etyd.js @@ -0,0 +1,61 @@ +//Firefox check +window.onload = function() { + document.getElementById('resultfeed').value = "hii :3" + if (navigator.userAgent.includes("Firefox")) { + document.getElementById('resultfeed').value += `\nClipboard functionality does not work on Firefox.` + document.getElementById('clipboard1').disabled = true + document.getElementById('clipboard2').disabled = true + } +} + +function makeRandomHex(amount) { + const characters = "1234567890abcdef" + let counter = 0 + let result = "" + while (counter < amount) { + result += characters.charAt(Math.floor(Math.random() * characters.length)) + counter += 1 + } + return result +} + +function randomUrlTick() { + if (document.getElementById("randomizationtoggle").checked == true) { + document.getElementById("targetfield").disabled = true + document.getElementById("targetfield").value = makeRandomHex(6) + } else { + document.getElementById("targetfield").disabled = false + document.getElementById("targetfield").value = null + } +} + +function buttonCopyResult() { + navigator.clipboard.writeText(`${document.location.href}${document.getElementById("urlfield").value}`) +} + +function buttonFillFromClipboard() { + navigator.clipboard.readText().then(res => { + document.getElementById("valuefield").value = res; + }) +} + +function postData() { + fetch("http://nrdesktop:8081/etydwrite", { + method: "POST", + mode: "cors", + headers: { + "Authorization": document.getElementById("authfield").value + }, + body: JSON.stringify({ + "target": document.getElementById("targetfield").value, + "value": document.getElementById("valuefield").value, + "action": document.getElementById("actiondropdown").value, + "random": document.getElementById("randomizationtoggle").checked + }) + }).then(response => { + document.getElementById("resultfeed").value += `\n${response.status} ${response.body}` + }).catch(error => { + document.getElementById("resultfeed").value += `\nError: ${error}` + }) +} + diff --git a/etydFrontend/_static/index.css b/etydFrontend/_static/index.css new file mode 100644 index 0000000..cf34de1 --- /dev/null +++ b/etydFrontend/_static/index.css @@ -0,0 +1,22 @@ +body { + font-family: Arial, Helvetica, sans-serif; +} + +.flexbox { + display: flex; + flex-wrap: wrap; +} + +.marginright1em { + margin-right: 1em; +} + +.resultfeed { + height: 100%; +} + +@media (max-width: 700px) { + .resultfeed { + min-height: 20vh; + } +} \ No newline at end of file diff --git a/etydFrontend/index.html b/etydFrontend/index.html new file mode 100644 index 0000000..d210c0d --- /dev/null +++ b/etydFrontend/index.html @@ -0,0 +1,75 @@ + + + + + + + etyd.cc + + + +

etyd.cc URL Shortener

+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+ + + + +

+
+ +
+ +
+
+ + +
+ + + +
+
+

Instructions

+

+ 1. Enter your API Key in the 'Authorization' field
+ 2. Enter the shortened URL you want to act upon under the 'URL' field
+ 3. Enter the URL that the user will be redirected to under the 'Value' field
+ 4. Change 'Action' depending if you want to create or delete a URL
+ 5. Press 'POST Data' to submit the form to the server +

+
+ +
+

Status Code Reference

+

+ 400: Bad Request - You will see this if you try and delete a non-existent URL
+ 401: Unauthorized - Did you enter your API key?
+ 405: Method Not Allowed - You will see this if you try a request with no arguments
+ 409: Conflict - The entered URL already exists, tick 'Random' and try again
+ 500: Internal Server Error - If this happens something has gone very wrong
+ 502: Bad Gateway - If you see this the backend is down/unreachable by Caddy
+

+
+
+ + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 21f47ad..43da3b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,20 @@ "version": "1.0.0", "license": "UNLICENSED", "dependencies": { - "express": "^4.18.2" + "express": "^4.18.2", + "typescript": "^5.4.3" + }, + "devDependencies": { + "@types/node": "^20.12.3" + } + }, + "node_modules/@types/node": { + "version": "20.12.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.3.tgz", + "integrity": "sha512-sD+ia2ubTeWrOu+YMF+MTAB7E+O7qsMqAbMfW7DG3K1URwhZ5hN1pLlRVGbf4wDFzSfikL05M17EyorS86jShw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" } }, "node_modules/accepts": { @@ -666,6 +679,24 @@ "node": ">= 0.6" } }, + "node_modules/typescript": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index 9b134b7..01392d7 100644 --- a/package.json +++ b/package.json @@ -18,5 +18,9 @@ "bugs": { "url": "https://github.com/enstrayed/enstrayedapi/issues" }, - "homepage": "https://api.enstrayed.com" + "homepage": "https://api.enstrayed.com", + "devDependencies": { + "@types/bun": "^1.0.12", + "@types/node": "^20.12.3" + } } diff --git a/routes/etyd.js b/routes/etyd.js index 8c011ec..140eb16 100644 --- a/routes/etyd.js +++ b/routes/etyd.js @@ -1,108 +1,108 @@ const { app, db, globalConfig } = require("../index.js") // Get globals from index -function makeRandomHex() { - const characters = "1234567890abcdef" - let counter = 0 - let result = "" - while (counter < globalConfig.etyd.randomHexLength) { - result += characters.charAt(Math.floor(Math.random() * characters.length)) - counter += 1 - } - return result -} +// 2024-04-05: Unused because trying to put randomization server side just made no sense +// function makeRandomHex() { +// const characters = "1234567890abcdef" +// let counter = 0 +// let result = "" +// while (counter < globalConfig.etyd.randomHexLength) { +// result += characters.charAt(Math.floor(Math.random() * characters.length)) +// counter += 1 +// } +// return result +// } -app.options("/etydwrite", (rreq,rres) => { - rres.set("Access-Control-Allow-Headers","Authorization") - rres.set("Access-Control-Allow-Origin","*") - rres.sendStatus(204) -}) +// 2024-04-05: Defining OPTIONS for browser prefetch is no longer necessary as CORS is not going to be used +// app.options("/etydwrite", (rreq,rres) => { +// rres.set("Access-Control-Allow-Headers","Authorization") +// rres.set("Access-Control-Allow-Origin","*") +// rres.sendStatus(204) +// }) -app.post("/etydwrite", (rreq,rres) => { - db.get(globalConfig.etyd.authKeyInDb).then(dbres => { - - if (dbres == null) { // If authkey key doesnt exist in redis then error out - console.log("ERROR: Configured key containing etyd authkeys is null") - rres.sendStatus(500) - } else { // if it does exist - let validKeys = dbres.split(",") // split the string into an array - if (validKeys.includes(rreq.get("Authorization"))) { // check if authorization header key exists in that array - - console.log(rreq.body) - - switch(rreq.body.action) { - case "set": // Write to db - if (rreq.body.random == true) { - - let workingTarget = makeRandomHex() // Make a random URL - db.get(`/${workingTarget}`).then(dbres => { // Check if it exists - if (dbres != null) { // If it does - let workingTarget = makeRandomHex() // Make a new one - db.get(`/${workingTarget}`).then(dbres => { // Check if *that* exists - if (dbres != null) { // If it does - // Then everything is dumb and pointless so just give up - console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION set returned 409 (Two attempts to find an open key failed)`) - rres.sendStatus(409) - } else { // if it doesnt then set the stupid key I hate this code so much why did I do this serverside this is so dumb - console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION set returned 200 KEY:${rreq.get("Authorization")} TARGET: ${workingTarget}`) - db.set(`/${workingTarget}`,rreq.body.value) - rres.send(`https://etyd.cc/${workingTarget}`) - } - }) - - } else { - console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION set returned 200 KEY:${rreq.get("Authorization")} TARGET: ${workingTarget}`) - db.set(`/${workingTarget}`,rreq.body.value) - rres.send(`https://etyd.cc/${workingTarget}`) - } - }) - - } else { - - db.get(rreq.body.target).then(dbres => { // check if key already exists - if (dbres != null) { // if it does then send 409 conflict - console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION set returned 409 KEY:${rreq.get("Authorization")}`) - rres.sendStatus(409) - } else { - db.set(`/${rreq.body.target}`,rreq.body.value) - rres.send(`https://etyd.cc/${rreq.body.target}`) - } - }) - - } - break; - - - case "delete": - let workingTarget = rreq.body.target.replace("https://etyd.cc/","") // Sanitize input - if (workingTarget.startsWith("/")) { - workingTarget = workingTarget.slice(1) - } - - db.get(`/${workingTarget}`).then(dbres => { - if (dbres == null) { //if key doesnt exist then log and return 400 - console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION delete returned 404 KEY:${rreq.get("Authorization")} TARGET: ${workingTarget}`) - rres.sendStatus(404) - } else { - console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION delete returned 200 KEY:${rreq.get("Authorization")} TARGET: ${workingTarget}`) - db.del(`/${workingTarget}`) - rres.sendStatus(200) - } - }) - break; - - - default: - console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite ACTION default returned 400 KEY:${rreq.get("Authorization")}`) - rres.sendStatus(400) // request json didnt include a valid action - break; - } - - } else { // if it doesnt then its a unauthorized request - console.log(`${rreq.get("cf-connecting-ip")} POST /etydwrite returned 401`) - rres.sendStatus(401) - } +app.get("/etyd*", (rreq,rres) => { + fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd","")}`, { + headers: { + "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` } + }).then(dbRes => { + if (dbRes.status == 404) { + rres.sendStatus(404) + } else { + dbRes.json().then(dbRes => { + rres.redirect(dbRes.content.url) + }) + } + }).catch(fetchError => { + rres.sendStatus(500) + console.log(`${rres.get("cf-connecting-ip")} GET ${rreq.path} returned 500: ${fetchError}`) }) }) +app.delete("/etyd*", (rreq,rres) => { + + fetch(`http://${globalConfig.couchdb.host}/apiauthkeys/${globalConfig.etyd.authKeysDoc}`, { + headers: { + "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` + } + }).then(dbRes => dbRes.json()).then(dbRes => { + + if (dbRes.status == 404) { // If document containing cider auth keys does not exist + console.log(`ERROR: Could not find apiauthkeys/${globalConfig.etyd.authKeysDoc}`) + rres.sendStatus(500) // Refuse request + } else { + if (rreq.get("Authorization") == null) { // If authorization header is not supplied + rres.sendStatus(400) // then return bad request (would return 500 otherwise) + } else { + if (dbRes["content"][rreq.get("Authorization").split("_")[0]] === rreq.get("Authorization").split("_")[1]) { + + fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd", "")}`, { + headers: { + "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` + } + }).then(dbRes => { + + if (dbRes.status == 404) { + rres.sendStatus(404) + } else { + dbRes.json().then(dbRes => { + + fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd", "")}`, { + method: "DELETE", + headers: { + "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}`, + "If-Match": dbRes["_rev"] + } + }).then(fetchRes => { + if (fetchRes.status == 200) { + console.log(`${rres.get("cf-connecting-ip")} DELETE ${rreq.path} returned 200 KEY: ${rreq.get("Authorization")}`) + rres.sendStatus(200) + } + }).catch(fetchError => { + rres.sendStatus(500) + console.log(`${rres.get("cf-connecting-ip")} DELETE ${rreq.path} returned 500: ${fetchError}`) + }) + + }) + } + + }).catch(fetchError => { + rres.sendStatus(500) + console.log(`${rres.get("cf-connecting-ip")} DELETE ${rreq.path} returned 500: ${fetchError}`) + }) + + } else { + console.log(`${rreq.get("cf-connecting-ip")} DELETE ${rreq.path} returned 401`) // log ip of unauthorized requests + rres.sendStatus(401) // received auth key was not in database + } + } + } + }).catch(fetchError => { + rres.sendStatus(500) + console.log(`${rres.get("cf-connecting-ip")} DELETE ${rreq.path} returned 500: ${fetchError}`) + }) + +}) + + + module.exports = {app} // export routes to be imported by index for execution \ No newline at end of file -- 2.49.1 From 2000a2f18d9e5cfb2009dd5f1c1bb51530308ecc Mon Sep 17 00:00:00 2001 From: Enstrayed <48845980+Enstrayed@users.noreply.github.com> Date: Tue, 23 Apr 2024 08:07:44 -0700 Subject: [PATCH 3/8] etyd Frontend add dark mode support --- etydFrontend/_static/index.css | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/etydFrontend/_static/index.css b/etydFrontend/_static/index.css index cf34de1..66ddd02 100644 --- a/etydFrontend/_static/index.css +++ b/etydFrontend/_static/index.css @@ -19,4 +19,22 @@ body { .resultfeed { min-height: 20vh; } +} + +@media (prefers-color-scheme: dark) { /* Dark mode support */ + body { + background-color: black; + color: white; + } + + input, select, textarea, button { + background: none; + color: white; + border: 2px solid white; + } + + input:disabled, button:disabled { + opacity: 0.8; + cursor: not-allowed; + } } \ No newline at end of file -- 2.49.1 From 89f2bfecdb0c5cd9ffdabbca54d4e46bfd894651 Mon Sep 17 00:00:00 2001 From: Enstrayed <48845980+Enstrayed@users.noreply.github.com> Date: Thu, 2 May 2024 17:32:03 -0700 Subject: [PATCH 4/8] working changes --- routes/etyd.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/routes/etyd.js b/routes/etyd.js index 699fd0e..1d8235c 100644 --- a/routes/etyd.js +++ b/routes/etyd.js @@ -84,6 +84,55 @@ app.delete("/etyd*", (rreq,rres) => { }) +// app.post("/etyd*", (rreq,rres) => { + +// fetch(`http://${globalConfig.couchdb.host}/apiauthkeys/${globalConfig.etyd.authKeysDoc}`, { +// headers: { +// "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` +// } +// }).then(dbRes => dbRes.json()).then(dbRes => { + +// if (dbRes.status == 404) { // If document containing cider auth keys does not exist +// console.log(`ERROR: Could not find apiauthkeys/${globalConfig.etyd.authKeysDoc}`) +// rres.sendStatus(500) // Refuse request +// } else { +// if (rreq.get("Authorization") == null) { // If authorization header is not supplied +// rres.sendStatus(400) // then return bad request (would return 500 otherwise) +// } else { +// if (dbRes["content"][rreq.get("Authorization").split("_")[0]] === rreq.get("Authorization").split("_")[1]) { + +// fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd", "")}`, { +// headers: { +// "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` +// } +// }).then(dbRes => { + +// if (dbRes.status !== 404) { +// console.log(`${rres.get("cf-connecting-ip")} POST ${rreq.path} returned 409 KEY: ${rreq.get("Authorization")}`) +// rres.sendStatus(409) +// } else { + + + +// } + +// }).catch(fetchError => { +// rres.sendStatus(500) +// console.log(`${rres.get("cf-connecting-ip")} DELETE ${rreq.path} returned 500: ${fetchError}`) +// }) + +// } else { +// console.log(`${rreq.get("cf-connecting-ip")} DELETE ${rreq.path} returned 401`) // log ip of unauthorized requests +// rres.sendStatus(401) // received auth key was not in database +// } +// } +// } +// }).catch(fetchError => { +// rres.sendStatus(500) +// console.log(`${rres.get("cf-connecting-ip")} DELETE ${rreq.path} returned 500: ${fetchError}`) +// }) + +// }) module.exports = {app} // export routes to be imported by index for execution \ No newline at end of file -- 2.49.1 From 2e6b4db4fd2a82d2d37666211aa5fad814c7003b Mon Sep 17 00:00:00 2001 From: Enstrayed <48845980+Enstrayed@users.noreply.github.com> Date: Wed, 12 Jun 2024 17:43:42 -0700 Subject: [PATCH 5/8] improve blogpost and disable cider --- routes/blog.js | 27 +++++++-- routes/cider.js | 154 ++++++++++++++++++++++++++---------------------- 2 files changed, 104 insertions(+), 77 deletions(-) diff --git a/routes/blog.js b/routes/blog.js index c1e6f22..b0415d7 100644 --- a/routes/blog.js +++ b/routes/blog.js @@ -7,19 +7,33 @@ app.get("/blogposts", (rreq, rres) => { if (Date.now() < timeSinceLastQuery+10000) { // if it has been <10 seconds since last request rres.set("Access-Control-Allow-Origin","*") - rres.send(cachedResult) // send cached json + + if (rreq.query.format === "html") { // if ?format=html then send HTML + rres.send(cachedResult.asHtml) + } else { // otherwise send json + rres.send(cachedResult.asJson) + } + } else { timeSinceLastQuery = Date.now() cachedResult = parseFiles() rres.set("Access-Control-Allow-Origin","*") - rres.send(cachedResult); + + if (rreq.query.format === "html") { // if ?format=html then send HTML + rres.send(cachedResult.asHtml) + } else { // otherwise send json + rres.send(cachedResult.asJson) + } } }) function parseFiles() { let files = fs.readdirSync(globalConfig.blog.postsDirectory) - let parsedFiles = [] + let result = { + asJson: [], + asHtml: "" + } for (x in files) { if (files[x].endsWith(".html") === false) { break } // If file/dir is not .html then ignore @@ -29,12 +43,13 @@ function parseFiles() { date = date.replace(/.{2}/g,"$&-").replace("-","").slice(0,-1) // Insert a dash every 2 characters, remove the first dash, remove the last character - let name = files[x].slice(9).replace(/รท/g," ").replace(".html","") // Strip Date, replace seperator with space & remove file extension + let name = files[x].slice(9).replace(/-/g," ").replace(".html","") // Strip Date, replace seperator with space & remove file extension - parsedFiles.push({ "date": date, "name": name, "path": `${globalConfig.blog.postsDirUrl}/${files[x]}`}) // Add metadata as JSON to array + result.asJson.unshift({ "date": date, "name": name, "path": `${globalConfig.blog.postsDirUrl}/${files[x]}`}) // Add to asJson array in the result + result.asHtml = `${date} ${name}`+result.asHtml } - return parsedFiles.reverse() + return result } module.exports = {app} \ No newline at end of file diff --git a/routes/cider.js b/routes/cider.js index b309411..22118b6 100644 --- a/routes/cider.js +++ b/routes/cider.js @@ -2,87 +2,99 @@ const { app, db, globalConfig } = require("../index.js") // Get globals from ind var timeSinceLastCiderQuery = Date.now()-2000; var currentListening = {} +var currentListeningHtml = "" -app.get("/cider", (rreq,rres) => { // GET current listening from target - - if (Date.now() < timeSinceLastCiderQuery+2000) { - rres.send(currentListening); // If it has been <2 seconds since the last request, return the cached result. - } else { - getCurrentListening(globalConfig.cider.targetHosts[0]).then(funcRes => { - if (funcRes == 1) { - rres.sendStatus(503) // If there was a problem getting the upstream JSON, return 503 Service Unavailable. - } else { - rres.set("Access-Control-Allow-Origin","*") // Required (I think?) because of CORS. - currentListening = funcRes - rres.send(funcRes) - } - }) - } - +app.get("/cider", (rreq,rres) => { + rres.send("Cider endpoint is temporarily unavailable.") }) +// app.get("/cider", (rreq,rres) => { // GET current listening from target -app.post("/cider", (rreq,rres) => { // POST stop listening on cider target +// if (Date.now() < timeSinceLastCiderQuery+2000) { +// rres.send(currentListening); // If it has been <2 seconds since the last request, return the cached result. +// } else { +// getCurrentListening(globalConfig.cider.targetHosts[0],"json").then(funcRes => { +// if (funcRes == 1) { +// rres.sendStatus(503) // If there was a problem getting the upstream JSON, return 503 Service Unavailable. +// } else { +// rres.set("Access-Control-Allow-Origin","*") // Required (I think?) because of CORS. +// currentListening = funcRes +// rres.send(funcRes) +// } +// }) +// } - fetch(`http://${globalConfig.couchdb.host}/apiauthkeys/${globalConfig.cider.authKeysDoc}`, { - headers: { - "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` - } - }).then(dbRes => dbRes.json()).then(dbRes => { +// }) - if (dbRes.status == 404) { // If document containing cider auth keys does not exist - console.log(`ERROR: Could not find apiauthkeys/${globalConfig.mailjet.authKeysDoc}`) - rres.sendStatus(500) // Refuse request - } else { - if (dbRes["content"][rreq.get("Authorization").split("_")[0]] === rreq.get("Authorization").split("_")[1]) { +// app.post("/cider", (rreq,rres) => { // POST stop listening on cider target - fetch(`http://${globalConfig.cider.targetHosts[0]}/stop`).then(fres => { // send GET /stop to cider target - if (fres.status == 204) { - console.log(`${rreq.get("cf-connecting-ip")} POST /cider returned 200 KEY:${rreq.get("Authorization")}`) - rres.sendStatus(200) // if that works then 200 - } else { - rres.sendStatus(500) // otherwise lol - } - }).catch(ferror => { - rres.sendStatus(503) // and if a problem happens its probably cause cider target is unavailable - }) +// fetch(`http://${globalConfig.couchdb.host}/apiauthkeys/${globalConfig.cider.authKeysDoc}`, { +// headers: { +// "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` +// } +// }).then(dbRes => dbRes.json()).then(dbRes => { - } else { - console.log(`${rreq.get("cf-connecting-ip")} POST /cider returned 401`) // log ip of unauthorized requests - rres.sendStatus(401) // received auth key was not in database - } - } - }) +// if (dbRes.status == 404) { // If document containing cider auth keys does not exist +// console.log(`ERROR: Could not find apiauthkeys/${globalConfig.mailjet.authKeysDoc}`) +// rres.sendStatus(500) // Refuse request +// } else { +// if (dbRes["content"][rreq.get("Authorization").split("_")[0]] === rreq.get("Authorization").split("_")[1]) { -}) +// fetch(`http://${globalConfig.cider.targetHosts[0]}/stop`).then(fres => { // send GET /stop to cider target +// if (fres.status == 204) { +// console.log(`${rreq.get("cf-connecting-ip")} POST /cider returned 200 KEY:${rreq.get("Authorization")}`) +// rres.sendStatus(200) // if that works then 200 +// } else { +// rres.sendStatus(500) // otherwise lol +// } +// }).catch(ferror => { +// rres.sendStatus(503) // and if a problem happens its probably cause cider target is unavailable +// }) + +// } else { +// console.log(`${rreq.get("cf-connecting-ip")} POST /cider returned 401`) // log ip of unauthorized requests +// rres.sendStatus(401) // received auth key was not in database +// } +// } +// }) + +// }) // 2024-04-10: Retrieves currentPlayingSong JSON from specified Cider host and -// returns JSON containing the useful bits if successful, returning 1 if not. -async function getCurrentListening(host) { // Host should be hostname/ip & port only. - timeSinceLastCiderQuery = Date.now(); // Save last time function was run, used to indicate when the cache needs refreshed. - return await fetch(`http://${host}/currentPlayingSong`).then(fetchRes => { - if (fetchRes.status == 502) { - return 1 // If the upstream server returns 502 (Bad Gateway) then internally return 1, indicating error. - } else { - return fetchRes.json().then(jsonRes => { - if (jsonRes.info.name == undefined) { - return 1 // If Cider is running but not playing a song this check prevents an undefined variable error. - } else { - return { - "songName": jsonRes.info.name, - "artistName": jsonRes.info.artistName, - "albumName": jsonRes.info.albumName, - "songLinkUrl": jsonRes.info.url.songLink, - "endtimeEpochInMs": jsonRes.info.endTime, - "artworkUrl": jsonRes.info.artwork.url.replace("{w}", jsonRes.info.artwork.width).replace("{h}", jsonRes.info.artwork.height) - } - } - }) - } - }).catch(fetchError => { - console.error("Error fetch()ing upstream Cider host: "+fetchError) - return 1 // If something else happens then log it and return 1, indicating error. - }) -} +// returns JSON/HTML containing the useful bits if successful, returning 1 if not. + +// async function getCurrentListening(host,contentType) { // Host should be hostname/ip & port only. +// timeSinceLastCiderQuery = Date.now(); // Save last time function was run, used to indicate when the cache needs refreshed. +// return await fetch(`http://${host}/currentPlayingSong`).then(fetchRes => { +// if (fetchRes.status == 502) { +// return 1 // If the upstream server returns 502 (Bad Gateway) then internally return 1, indicating error. +// } else { +// return fetchRes.json().then(jsonRes => { +// if (jsonRes.info.name == undefined) { +// return 1 // If Cider is running but not playing a song this check prevents an undefined variable error. +// } else { +// if (contentType === "json") { +// return { +// "songName": jsonRes.info.name, +// "artistName": jsonRes.info.artistName, +// "albumName": jsonRes.info.albumName, +// "songLinkUrl": jsonRes.info.url.songLink, +// "endtimeEpochInMs": jsonRes.info.endTime, +// "artworkUrl": jsonRes.info.artwork.url.replace("{w}", jsonRes.info.artwork.width).replace("{h}", jsonRes.info.artwork.height) +// } +// } else if (contentType === "html") { +// return `Album Art

I'm listening to

${`${jsonRes.info.name} by ${jsonRes.info.artistName}`}

from ${jsonRes.info.albumName}

song.link
` +// } else { +// return 1 +// } + +// } +// }) +// } +// }).catch(fetchError => { +// console.error("Error fetch()ing upstream Cider host: "+fetchError) +// return 1 // If something else happens then log it and return 1, indicating error. +// }) +// } module.exports = {app} // export routes to be imported by index for execution \ No newline at end of file -- 2.49.1 From 45b7b3541f861b63aa357af325f4ec246a61625c Mon Sep 17 00:00:00 2001 From: Enstrayed <48845980+Enstrayed@users.noreply.github.com> Date: Wed, 12 Jun 2024 17:44:59 -0700 Subject: [PATCH 6/8] god I fucking hate CORS I should just do this in caddy --- routes/cider.js | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/cider.js b/routes/cider.js index 22118b6..7f26a18 100644 --- a/routes/cider.js +++ b/routes/cider.js @@ -5,6 +5,7 @@ var currentListening = {} var currentListeningHtml = "" app.get("/cider", (rreq,rres) => { + rres.set("Access-Control-Allow-Origin","*") rres.send("Cider endpoint is temporarily unavailable.") }) -- 2.49.1 From c615d9c3f986540424ee710ca4655c67902395e6 Mon Sep 17 00:00:00 2001 From: Enstrayed <48845980+Enstrayed@users.noreply.github.com> Date: Wed, 12 Jun 2024 17:55:38 -0700 Subject: [PATCH 7/8] I should read the manual on how to squash commits (fix cider again) --- routes/cider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/cider.js b/routes/cider.js index 7f26a18..4960cea 100644 --- a/routes/cider.js +++ b/routes/cider.js @@ -6,7 +6,7 @@ var currentListeningHtml = "" app.get("/cider", (rreq,rres) => { rres.set("Access-Control-Allow-Origin","*") - rres.send("Cider endpoint is temporarily unavailable.") + rres.send("Cider endpoint is temporarily unavailable.") }) // app.get("/cider", (rreq,rres) => { // GET current listening from target -- 2.49.1 From e5ab498161be94ae7f3f08be07e09c95ecc0377a Mon Sep 17 00:00:00 2001 From: Enstrayed <48845980+Enstrayed@users.noreply.github.com> Date: Wed, 12 Jun 2024 20:21:14 -0700 Subject: [PATCH 8/8] move authorization code into new file --- liberals/authorization.js | 38 ++++++++++++++ routes/mailjet.js | 102 +++++++++++++++++--------------------- 2 files changed, 84 insertions(+), 56 deletions(-) create mode 100644 liberals/authorization.js diff --git a/liberals/authorization.js b/liberals/authorization.js new file mode 100644 index 0000000..4ca6348 --- /dev/null +++ b/liberals/authorization.js @@ -0,0 +1,38 @@ +const { globalConfig } = require("../index.js") + +async function checkAuthorization(documentToUse,keyToCheck) { + return await fetch(`http://${globalConfig.couchdb.host}/apiauthkeys/${documentToUse}`, { + headers: { + "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` + } + }).then(fetchRes => { + + if (fetchRes.status === 404) { // If document doesnt exist fail gracefully + + console.log("ERROR: Failed to check authorization: Requested document returned 404") + return false + + } else if (fetchRes.status === 401) { // If couchdb is reporting unauthorized fail gracefully + + console.log("ERROR: Failed to check authorization: Database authorization is incorrect") + return false + + } else { + return fetchRes.json().then(dbRes => { // Get response json and check it + + if (dbRes["content"][keyToCheck.split("_")[0]] === keyToCheck.split("_")[1]) { + return true + } else { + return false + } + + }) + } + + }).catch(error => { + console.log("ERROR: Failed to check authorization: " + error) + return false + }) +} + +module.exports = {checkAuthorization} \ No newline at end of file diff --git a/routes/mailjet.js b/routes/mailjet.js index e53f3f1..1ef3cc1 100644 --- a/routes/mailjet.js +++ b/routes/mailjet.js @@ -1,67 +1,57 @@ -const { app, db, globalConfig } = require("../index.js") // Get globals from index +const { app, globalConfig } = require("../index.js") // Get globals from index +const { checkAuthorization } = require("../liberals/authorization.js") app.post("/sendemail", (rreq,rres) => { - fetch(`http://${globalConfig.couchdb.host}/apiauthkeys/${globalConfig.mailjet.authKeysDoc}`, { - headers: { - "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` - } - }).then(dbRes => dbRes.json()).then(dbRes => { + checkAuthorization(globalConfig.mailjet.authKeysDoc,rreq.get("Authorization")).then(authRes => { + if (authRes === false) { // If the supplied authorization is invalid or an error occured - if (dbRes.status == 404) { // If document containing mailjet auth keys does not exist - console.log(`ERROR: Could not find apiauthkeys/${globalConfig.mailjet.authKeysDoc}`) - rres.sendStatus(500) // Refuse request - } else { - if (dbRes["content"][rreq.get("Authorization").split("_")[0]] === rreq.get("Authorization").split("_")[1]) { + console.log(`${rreq.get("cf-connecting-ip")} POST /sendemail returned 401`) // Log the request + rres.sendStatus(401) // Return 401 Unauthorized - // 2024-05-11: Turbo bodge check to make sure request JSON is valid, probably wont work but whatever - if (rreq.body == undefined || rreq.body.recipient == undefined) { - console.log(`${rreq.get("cf-connecting-ip")} POST /sendemail returned 400 KEY:${rreq.get("Authorization").split("_")[1]}`) - rres.sendStatus(400) - } else { - - let message = { - "Messages": [ - { - "From": { - "Email": globalConfig.mailjet.senderAddress, - "Name": globalConfig.mailjet.senderName, - }, - "To": [ - { - "Email": rreq.body.recipient, - } - ], - - "Subject": rreq.body.subject || "Request did not include a subject.", - "TextPart": rreq.body.message || "Request did not include a message.", - } - ] - } - - fetch("https://api.mailjet.com/v3.1/send", { - method: "POST", - headers: { - "Authorization": `Basic ${btoa(globalConfig.mailjet.apiKey)}`, - "Content-Type": "application/json" - }, - body: JSON.stringify(message) - }).then(fetchRes => { - if (fetchRes.status == 200) { - console.log(`${rreq.get("cf-connecting-ip")} POST /sendemail returned 200 KEY:${rreq.get("Authorization").split("_")[1]}`) - rres.sendStatus(200) - } else { - console.log(`Mailjet Fetch returned result other than OK: ${fetchRes.status} ${fetchRes.statusText}`) - rres.sendStatus(500) + } else if (authRes === true) { // If the authorization was valid, continue function + + // 2024-05-11: Turbo bodge check to make sure request JSON is valid, probably wont work but whatever + if (rreq.body == undefined || rreq.body.recipient == undefined) { + console.log(`${rreq.get("cf-connecting-ip")} POST /sendemail returned 400 KEY:${rreq.get("Authorization").split("_")[0]}`) + rres.sendStatus(400) + } else { + + let message = { + "Messages": [ + { + "From": { + "Email": globalConfig.mailjet.senderAddress, + "Name": globalConfig.mailjet.senderName, + }, + "To": [ + { + "Email": rreq.body.recipient, + } + ], + + "Subject": rreq.body.subject || "Request did not include a subject.", + "TextPart": rreq.body.message || "Request did not include a message.", } - }) + ] } - - - } else { - console.log(`${rreq.get("cf-connecting-ip")} POST /sendemail returned 401`) // log ip of unauthorized requests - rres.sendStatus(401) // received auth key was not in database + fetch("https://api.mailjet.com/v3.1/send", { + method: "POST", + headers: { + "Authorization": `Basic ${btoa(globalConfig.mailjet.apiKey)}`, + "Content-Type": "application/json" + }, + body: JSON.stringify(message) + }).then(fetchRes => { + if (fetchRes.status == 200) { + console.log(`${rreq.get("cf-connecting-ip")} POST /sendemail returned 200 KEY:${rreq.get("Authorization").split("_")[1]}`) + rres.sendStatus(200) + } else { + console.log(`Mailjet Fetch returned result other than OK: ${fetchRes.status} ${fetchRes.statusText}`) + rres.sendStatus(500) + } + }) } } }) -- 2.49.1