diff --git a/.gitignore b/.gitignore index 664c5e7..b068519 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ config.json +bun.lockb GITVERSION \ 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/bun.lockb b/bun.lockb deleted file mode 100644 index 9c2504a..0000000 Binary files a/bun.lockb and /dev/null differ 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..66ddd02 --- /dev/null +++ b/etydFrontend/_static/index.css @@ -0,0 +1,40 @@ +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; + } +} + +@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 diff --git a/etydFrontend/index.html b/etydFrontend/index.html new file mode 100644 index 0000000..4b9026d --- /dev/null +++ b/etydFrontend/index.html @@ -0,0 +1,74 @@ + + + + + + + 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?
+ 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 24abbc7..ca39d10 100644 --- a/routes/etyd.js +++ b/routes/etyd.js @@ -1,4 +1,5 @@ -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.get("/etyd*", (rreq,rres) => { fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd","")}`, { @@ -24,4 +25,112 @@ app.get("/etyd*", (rreq,rres) => { }) }) +app.delete("/etyd*", (rreq,rres) => { + + if (rreq.get("Authorization") === undefined) { + rres.sendStatus(400) + } else { + checkAuthorization(globalConfig.etyd.authKeysDoc,rreq.get("Authorization")).then(authRes => { + if (authRes === false) { + console.log(`${rreq.get("cf-connecting-ip")} DELETE ${rreq.path} returned 401`) // Log unauthorized requests + rres.sendStatus(401) + } else if (authRes === true) { // Authorization successful + + 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"] // Using the If-Match header is easiest for deleting entries in couchdb + } + }).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 => { + console.log(`${rres.get("cf-connecting-ip")} DELETE ${rreq.path} returned 500: ${fetchError}`) + rres.sendStatus(500) + }) + + }) + } + + }).catch(fetchError => { + console.log(`${rres.get("cf-connecting-ip")} DELETE ${rreq.path} returned 500: ${fetchError}`) + rres.sendStatus(500) + }) + + } + }) + } + +}) + +app.post("/etyd*", (rreq,rres) => { + + if (rreq.get("Authorization") === undefined) { + rres.sendStatus(400) + } else { + checkAuthorization(globalConfig.etyd.authKeysDoc,rreq.get("Authorization")).then(authRes => { + if (authRes === false) { + console.log(`${rreq.get("cf-connecting-ip")} POST ${rreq.path} returned 401`) // Log unauthorized requests + rres.sendStatus(401) + } else if (authRes === true) { // Authorization successful + + if (rreq.body["url"] == undefined) { + console.log(`${rreq.get("cf-connecting-ip")} POST ${rreq.path} returned 400 KEY: ${rreq.get("Authorization")}`) + rres.sendStatus(400) + } else { + fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd", "")}`, { + headers: { + "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}` + }, + method: "PUT", + body: JSON.stringify({ + "content": { + "url": rreq.body["url"] + } + }) + }).then(dbRes => { + + switch(dbRes.status) { + case 409: + console.log(`${rreq.get("cf-connecting-ip")} POST ${rreq.path} returned 409 KEY: ${rreq.get("Authorization")}`) + rres.sendStatus(409) + break; + + case 201: + console.log(`${rreq.get("cf-connecting-ip")} POST ${rreq.path} returned 200 KEY: ${rreq.get("Authorization")}`) + rres.status(200).send(rreq.path.replace("/etyd", "")) + break; + + default: + console.log(`ERROR: CouchDB PUT did not return expected code: ${dbRes.status}`) + break; + } + + }).catch(fetchError => { + console.log(`${rres.get("cf-connecting-ip")} DELETE ${rreq.path} returned 500: ${fetchError}`) + rres.sendStatus(500) + }) + } + + } + }) + } + +}) + + module.exports = {app} // export routes to be imported by index for execution \ No newline at end of file diff --git a/routes/mailjet.js b/routes/mailjet.js index 1ef3cc1..b26e9d5 100644 --- a/routes/mailjet.js +++ b/routes/mailjet.js @@ -7,7 +7,7 @@ app.post("/sendemail", (rreq,rres) => { if (authRes === false) { // If the supplied authorization is invalid or an error occured console.log(`${rreq.get("cf-connecting-ip")} POST /sendemail returned 401`) // Log the request - rres.sendStatus(401) // Return 401 Unauthorized + rres.sendStatus(401) } else if (authRes === true) { // If the authorization was valid, continue function