diff --git a/src/inc/lib.mjs b/src/inc/lib.mjs index 98b21bc..4e01167 100644 --- a/src/inc/lib.mjs +++ b/src/inc/lib.mjs @@ -1,3 +1,5 @@ +import crypto from "crypto"; + const epochs = [ ["year", 31536000], ["month", 2592000], @@ -27,4 +29,7 @@ export default new class { const { interval, epoch } = getDuration(~~((new Date() - new Date(date)) / 1e3)); return `${interval} ${epoch}${interval === 1 ? "" : "s"} ago`; } + md5(str) { + return crypto.createHash('md5').update(str).digest("hex"); + } }; diff --git a/src/inc/routes/admin.mjs b/src/inc/routes/admin.mjs new file mode 100644 index 0000000..269d6d3 --- /dev/null +++ b/src/inc/routes/admin.mjs @@ -0,0 +1,98 @@ +import router from "../router.mjs"; +import sql from "../sql.mjs"; +import tpl from "../tpl.mjs"; +import lib from "../lib.mjs"; +import util from "util"; +import crypto from "crypto"; +import cfg from "../../../config.json"; + +const scrypt = util.promisify(crypto.scrypt); + +const hash = async str => { + const salt = crypto.randomBytes(16).toString("hex"); + const derivedKey = await scrypt(str, salt, 64); + return "$f0ck$" + salt + ":" + derivedKey.toString("hex"); +}; + +const verify = async (str, hash) => { + const [ salt, key ] = hash.substring(6).split(":"); + const keyBuffer = Buffer.from(key, "hex"); + const derivedKey = await scrypt(str, salt, 64); + return crypto.timingSafeEqual(keyBuffer, derivedKey); +}; + +const createID = () => crypto.randomBytes(16).toString("hex") + Date.now().toString(24); + +router.get(/^\/login(\/)?$/, async (req, res) => { + if(req.cookies.session) + return res.reply({ body: "du bist schon eingeloggt lol
"+util.inspect(req.session)+"" }); + res.reply({ + body: tpl.render("views/login", {}, req) + }); +}); + +router.post(/^\/login(\/)?$/, async (req, res) => { + const user = await sql("user").where("login", req.post.username.toLowerCase()).limit(1); + if(user.length === 0) + return res.reply({ body: "user doesn't exist or wrong password" }); + if(!(await verify(req.post.password, user[0].password))) + return res.reply({ body: "user doesn't exist or wrong password" }); + const stamp = Date.now() / 1e3; + + const session = lib.md5(createID()); + await sql("user_sessions").insert({ + user_id: user[0].id, + session: lib.md5(session), + browser: req.headers["user-agent"], + created_at: stamp, + last_used: stamp + }); + + return res.writeHead(301, { + "Cache-Control": "no-cache, public", + "Set-Cookie": `session=${session}; Path=/`, + "Location": "/" + }).end(); +}); + +router.get(/^\/logout$/, async (req, res) => { + if(!req.session) + return res.redirect("/"); + + const usersession = await sql("user_sessions").where("id", req.session.sess_id); + if(usersession.length === 0) + return res.reply({ body: "nope 2" }); + + await sql("user_sessions").where("id", req.session.sess_id).del(); + return res.writeHead(301, { + "Cache-Control": "no-cache, public", + "Set-Cookie": "session=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT", + "Location": "/login" + }).end(); +}); + +router.get(/^\/login\/pwdgen$/, async (req, res) => { + res.reply({ + body: "" + }); +}); +router.post(/^\/login\/pwdgen$/, async (req, res) => { + res.reply({ + body: await hash(req.post.pwd) + }); +}); + +router.get(/^\/login\/test$/, async (req, res) => { + res.reply({ + body: "
" + util.inspect(req) + "" + }); +}); + +router.get(/^\/admin(\/)?$/, async (req, res) => { + if(!req.session) + return res.redirect("/"); + + res.reply({ + body: tpl.render("views/admin", {}, req) + }); +}); diff --git a/src/inc/routes/index.mjs b/src/inc/routes/index.mjs index 13dfc87..5c0b5fe 100644 --- a/src/inc/routes/index.mjs +++ b/src/inc/routes/index.mjs @@ -47,12 +47,10 @@ router.get(/^\/(audio\/?|image\/?|video\/?)?(p\/\d+)?$/, async (req, res) => { link: `/${tmpmime ? tmpmime + "/" : ""}p/` }, last: rows[rows.length - 1].id, - filter: tmpmime ? tmpmime : undefined, - themes: cfg.websrv.themes, - theme: (typeof req.cookies.theme !== "undefined" && cfg.websrv.themes.includes(req.cookies.theme)) ? req.cookies.theme : cfg.websrv.themes[0] + filter: tmpmime ? tmpmime : undefined }; - res.reply({ body: tpl.render("views/index", data) }); + res.reply({ body: tpl.render("views/index", data, req) }); }); router.get(/^\/((audio\/|video\/|image\/)?[0-9]+)$/, async (req, res) => { @@ -121,18 +119,13 @@ router.get(/^\/((audio\/|video\/|image\/)?[0-9]+)$/, async (req, res) => { link: `/${tmpmime ? tmpmime + "/" : ""}` }, filter: tmpmime ? tmpmime : undefined, - themes: cfg.websrv.themes, - theme: (typeof req.cookies.theme !== "undefined" && cfg.websrv.themes.includes(req.cookies.theme)) ? req.cookies.theme : cfg.websrv.themes[0], lul: cfg.websrv.phrases[~~(Math.random() * cfg.websrv.phrases.length)] }; - res.reply({ body: tpl.render("views/item", data) }); + res.reply({ body: tpl.render("views/item", data, req) }); }); router.get(/^\/(about)$/, (req, res) => { res.reply({ - body: tpl.render(`views/${req.url.split[0]}`, { - themes: cfg.websrv.themes, - theme: (typeof req.cookies.theme !== "undefined" && cfg.websrv.themes.includes(req.cookies.theme)) ? req.cookies.theme : cfg.websrv.themes[0] - }) + body: tpl.render(`views/${req.url.split[0]}`, {}, req) }); }); diff --git a/src/inc/tpl.mjs b/src/inc/tpl.mjs index 229ee4e..7ecb77a 100644 --- a/src/inc/tpl.mjs +++ b/src/inc/tpl.mjs @@ -31,7 +31,12 @@ export default new class { else throw new Error(`${o} is not a iterable object`); } - render(tpl, data = {}, f) { + render(tpl, data = {}, req = false, f) { + if(req) { // globals + data.themes = cfg.websrv.themes; + data.theme = (typeof req.cookies.theme !== "undefined" && cfg.websrv.themes.includes(req.cookies.theme)) ? req.cookies.theme : cfg.websrv.themes[0]; + data.session = req.session ? req.session : false; + } return new Function("util", "data", "let html = \"\";with(data){" + (cfg.websrv.cache ? this.#templates[tpl] : this.readtpl(path.resolve(), `${tpl}.html`)) .trim() .replace(/[\n\r]/g, "") diff --git a/src/websrv.mjs b/src/websrv.mjs index 3f0574a..7ab5805 100644 --- a/src/websrv.mjs +++ b/src/websrv.mjs @@ -3,6 +3,8 @@ import url from "url"; import { promises as fs } from "fs"; import querystring from "querystring"; import cfg from "../config.json"; +import sql from "./inc/sql.mjs"; +import lib from "./inc/lib.mjs"; import router from "./inc/router.mjs"; (async () => { @@ -17,13 +19,13 @@ import router from "./inc/router.mjs"; req.url.split = req.url.pathname.split("/").slice(1); req.url.qs = querystring.parse(req.url.query); - req.post = new Promise((resolve, _, data = "") => req + req.post = await new Promise((resolve, _, data = "") => req .on("data", d => void (data += d)) .on("end", () => void resolve(Object.fromEntries(Object.entries(querystring.parse(data)).map(([k, v]) => { try { return [k, decodeURIComponent(v)]; } catch(err) { - console.error(err); + return [k, v]; } }))))); @@ -45,8 +47,29 @@ import router from "./inc/router.mjs"; req.cookies[parts.shift().trim()] = decodeURI(parts.join('=')); }); } + + req.session = false; + if(req.cookies.session) { + const user = await sql("user_sessions") + .select("user.id", "user.login", "user.user", "user.level", "user_sessions.id as sess_id") + .where("user_sessions.session", lib.md5(req.cookies.session)) + .leftJoin("user", "user.id", "user_sessions.user_id") + .limit(1); + + if(user.length > 0) { + req.session = user[0]; + await sql("user_sessions").update("last_used", (Date.now() / 1e3)).where("id", user[0].sess_id); + } + else { // delete session + return res.writeHead(301, { + "Cache-Control": "no-cache, public", + "Set-Cookie": "session=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT", + "Location": req.url.path + }).end(); + } + } - !(r = router.routes.getRoute(req.url.pathname, req.method)) ? res.writeHead(404).end(`404 - ${req.url.pathname}`) : await r(req, res); + !(r = router.routes.getRoute(req.url.pathname, req.method)) ? res.writeHead(404).end(`404 - ${req.method} ${req.url.pathname}`) : await r(req, res); console.log([ `[${(new Date()).toLocaleTimeString()}]`, `${(process.hrtime(t_start)[1] / 1e6).toFixed(2)}ms`, diff --git a/views/admin.html b/views/admin.html new file mode 100644 index 0000000..32aff3a --- /dev/null +++ b/views/admin.html @@ -0,0 +1,3 @@ +{{include main/header_admin}} + +{{include main/footer}} \ No newline at end of file diff --git a/views/item.html b/views/item.html index 624a312..dfbbf60 100644 --- a/views/item.html +++ b/views/item.html @@ -51,7 +51,13 @@ {{=item.src.short}} {{=item.mime}} / {{=item.size}} - {{=lul}} + + {{if session}} + delete + {{else}} + {{=lul}} + {{/if}} + {{if typeof item.tags !== "undefined"}} {{each item.tags as tag}} diff --git a/views/login.html b/views/login.html new file mode 100644 index 0000000..675656b --- /dev/null +++ b/views/login.html @@ -0,0 +1,17 @@ + + + + + +