diff --git a/Caddyfile b/Caddyfile index eb42a99..aea8b6c 100644 --- a/Caddyfile +++ b/Caddyfile @@ -10,6 +10,7 @@ } handle @staticpaths { + respond /favicon.ico 204 root ./etydFrontend file_server } diff --git a/etydFrontend/_static/etyd.js b/etydFrontend/_static/etyd.js index 5879b9d..79483e7 100644 --- a/etydFrontend/_static/etyd.js +++ b/etydFrontend/_static/etyd.js @@ -1,11 +1,22 @@ -//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 + document.getElementById('resultfeed').value += `\nClipboard buttons only work on Firefox >127.` } + + // Event listeners can only be added after the page is loaded + document.getElementById("actiondropdown").addEventListener("change", function() { + if (document.getElementById("actiondropdown").value === "POST") { + document.getElementById("randomizationtoggle").disabled = false + document.getElementById("valuefield").disabled = false + } else if (document.getElementById("actiondropdown").value === "DELETE") { + document.getElementById("randomizationtoggle").disabled = true + document.getElementById("randomizationtoggle").checked = false + randomUrlTick() + document.getElementById("valuefield").disabled = true + } else { + console.error("UI Code Error: Action dropdown event listener function reached impossible state") + } + }) } function makeRandomHex(amount) { @@ -19,6 +30,8 @@ function makeRandomHex(amount) { return result } + + function randomUrlTick() { if (document.getElementById("randomizationtoggle").checked == true) { document.getElementById("targetfield").disabled = true @@ -29,9 +42,9 @@ function randomUrlTick() { } } -function buttonCopyResult() { - navigator.clipboard.writeText(`${document.location.href}${document.getElementById("urlfield").value}`) -} +// function buttonCopyResult() { +// navigator.clipboard.writeText(`${document.location.href}${document.getElementById("urlfield").value}`) +// } function buttonFillFromClipboard() { navigator.clipboard.readText().then(res => { @@ -39,9 +52,29 @@ function buttonFillFromClipboard() { }) } -function postData() { - fetch("http://nrdesktop:8081/etydwrite", { - method: "POST", +// Changes the buttons text to OK for 500ms for action feedback +// "internal" in this context just means not called from the page +function internalButtonConfirmation(element) { + let normalValue = document.getElementById(element).innerHTML + document.getElementById(element).innerHTML = "Ok" + setTimeout(function() { + document.getElementById(element).innerHTML = normalValue + }, 500) +} + +function buttonCopyUrl() { + navigator.clipboard.writeText(`this doesn't work rn lol`) + internalButtonConfirmation("buttonCopyUrl") +} + +function buttonClearLog() { + document.getElementById("resultfeed").value = "" + internalButtonConfirmation("buttonClearLog") +} + +function submitData() { + fetch(`http://nrdesktop:8081/etyd${document.getElementById("targetfield").value}`, { + method: document.getElementById("actiondropdown").value, mode: "cors", headers: { "Authorization": document.getElementById("authfield").value @@ -57,5 +90,4 @@ function postData() { }).catch(error => { document.getElementById("resultfeed").value += `\nError: ${error}` }) -} - +} \ No newline at end of file diff --git a/etydFrontend/_static/index.css b/etydFrontend/_static/index.css index 66ddd02..b4b4698 100644 --- a/etydFrontend/_static/index.css +++ b/etydFrontend/_static/index.css @@ -11,6 +11,10 @@ body { margin-right: 1em; } +.marginbottom1em { + margin-bottom: 1em; +} + .resultfeed { height: 100%; } @@ -30,7 +34,8 @@ body { input, select, textarea, button { background: none; color: white; - border: 2px solid white; + border: 1px solid white; + padding: 1px 2px; } input:disabled, button:disabled { diff --git a/etydFrontend/index.html b/etydFrontend/index.html index 4b9026d..64e4571 100644 --- a/etydFrontend/index.html +++ b/etydFrontend/index.html @@ -1,3 +1,4 @@ + @@ -7,66 +8,44 @@ 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
-

-
diff --git a/index.js b/index.js index 831794d..fbb7efb 100644 --- a/index.js +++ b/index.js @@ -14,7 +14,7 @@ function criticalFileLoader(file) { const globalConfig = JSON.parse(criticalFileLoader('config.json')) const globalVersion = criticalFileLoader('GITVERSION').split(" ")[0] -module.exports = { app, globalConfig, fs } // Export express app and fs objects and globalconfig +module.exports = { app, globalConfig, fs, globalVersion } // Export express app and fs objects and globalconfig app.use(express.json()) // Allows receiving JSON bodies // see important note: https://expressjs.com/en/api.html#express.json @@ -39,9 +39,5 @@ fs.readdir(globalConfig.startup.routesDir, (err, files) => { } }) -app.get("/", (rreq,rres) => { - rres.send(`Enstrayed API | Version: ${globalVersion} | Documentation: etyd.cc/apidocs`) -}) - console.log(`Enstrayed API | Version: ${globalVersion} | Port: ${globalConfig.startup.apiPort}`) app.listen(globalConfig.startup.apiPort) \ No newline at end of file diff --git a/liberals/auth.js b/liberals/auth.js new file mode 100644 index 0000000..07ff077 --- /dev/null +++ b/liberals/auth.js @@ -0,0 +1,32 @@ +const { globalConfig } = require("../index.js") + +async function checkToken(token,scope) { + return await fetch(`${globalConfig.couchdbHost}/auth/sessions`).then(fetchRes => { + + // CouchDB should only ever return 200/304 for success so this should work + // https://docs.couchdb.org/en/stable/api/document/common.html#get--db-docid + if (fetchRes.status !== 200 || fetchRes.status !== 304) { + console.log(`ERROR: auth.js: Database request returned ${fetchRes.status}`) + return false + } else { + + return fetchRes.json().then(dbRes => { + + if (dbRes.sessions[token] == undefined) { // If the token is not on the sessions list then reject + return false + } else if (dbRes.sessions[token].scopes.includes(scope)) { // If the token is on the seesions list and includes the scope then accept + return true + } else { // Otherwise reject + return false + } + + }) + } + + }).catch(error => { + console.log("ERROR: auth.js: " + error) + return false + }) +} + +module.exports = {checkToken} \ No newline at end of file diff --git a/routes/etyd.js b/routes/etyd.js index 60b9cd8..b381c2c 100644 --- a/routes/etyd.js +++ b/routes/etyd.js @@ -1,5 +1,5 @@ const { app, globalConfig } = require("../index.js") // Get globals from index -const { checkAuthorization } = require("../liberals/authorization.js") +const { checkToken } = require("../liberals/auth.js") app.get("/etyd*", (rreq,rres) => { fetch(`${globalConfig.couchdbHost}/etyd${rreq.path.replace("/etyd","")}`).then(dbRes => { @@ -26,9 +26,8 @@ app.delete("/etyd*", (rreq,rres) => { if (rreq.get("Authorization") === undefined) { rres.sendStatus(400) } else { - checkAuthorization(globalConfig.etyd.authKeysDoc,rreq.get("Authorization")).then(authRes => { + checkToken(rreq.get("Authorization"),"etyd").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 @@ -73,14 +72,12 @@ app.post("/etyd*", (rreq,rres) => { if (rreq.get("Authorization") === undefined) { rres.sendStatus(400) } else { - checkAuthorization(globalConfig.etyd.authKeysDoc,rreq.get("Authorization")).then(authRes => { + checkToken(rreq.get("Authorization"),"etyd").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(`${globalConfig.couchdbHost}/etyd${rreq.path.replace("/etyd", "")}`, { @@ -94,12 +91,10 @@ app.post("/etyd*", (rreq,rres) => { 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; @@ -120,5 +115,4 @@ app.post("/etyd*", (rreq,rres) => { }) - module.exports = {app} // export routes to be imported by index for execution \ No newline at end of file diff --git a/routes/frontpage.js b/routes/frontpage.js new file mode 100644 index 0000000..7c2297f --- /dev/null +++ b/routes/frontpage.js @@ -0,0 +1,44 @@ +const { app, globalConfig, fs, globalVersion } = require("../index.js") // Get globals from index + +var timeSinceLastQuery = Date.now()-10000 +var cachedResult = "" + +app.get("/static/*", (rreq,rres) => { + rres.sendFile(globalConfig.frontpage.frontpageDir+"/static/"+rreq.url.replace("/static/","")) +}) + +app.get("/posts/*", (rreq,rres) => { + rres.sendFile(globalConfig.frontpage.frontpageDir+"/posts/"+rreq.url.replace("/posts/","")) +}) + +app.get("/", (rreq, rres) => { + if (Date.now() < timeSinceLastQuery+10000) { + rres.send(cachedResult) + } else { + let indexFile = fs.readFileSync(globalConfig.frontpage.frontpageDir+"/index.html","utf-8") + cachedResult = indexFile.replace("",parseFiles()).replace("",`API Version ${globalVersion}`) + rres.send(cachedResult) + } +}) + +function parseFiles() { + let files = fs.readdirSync(globalConfig.frontpage.frontpageDir+"/posts/") + let result = "" + + for (x in files) { + if (files[x].endsWith(".html") === false) { break } // If file/dir is not .html then ignore + + let date = files[x].split("-")[0] + if (date < 10000000 || date > 99999999) { break } // If date does not fit ISO8601 format then ignore + + 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 + + result = `${date} ${name}`+result + } + + return result +} + +module.exports = {app} \ No newline at end of file diff --git a/routes/mailjet.js b/routes/mailjet.js index b26e9d5..19d5e35 100644 --- a/routes/mailjet.js +++ b/routes/mailjet.js @@ -1,9 +1,9 @@ const { app, globalConfig } = require("../index.js") // Get globals from index -const { checkAuthorization } = require("../liberals/authorization.js") +const { checkToken } = require("../liberals/auth.js") app.post("/sendemail", (rreq,rres) => { - checkAuthorization(globalConfig.mailjet.authKeysDoc,rreq.get("Authorization")).then(authRes => { + checkToken(rreq.get("Authorization"),"mailjet").then(authRes => { 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 @@ -21,8 +21,7 @@ app.post("/sendemail", (rreq,rres) => { "Messages": [ { "From": { - "Email": globalConfig.mailjet.senderAddress, - "Name": globalConfig.mailjet.senderName, + "Email": globalConfig.mailjet.senderAddress }, "To": [ {