admin stuff
This commit is contained in:
		@@ -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");
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										98
									
								
								src/inc/routes/admin.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/inc/routes/admin.mjs
									
									
									
									
									
										Normal file
									
								
							@@ -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<pre>"+util.inspect(req.session)+"</pre>" });
 | 
			
		||||
  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: "<form action=\"/login/pwdgen\" method=\"post\"><input type=\"text\" name=\"pwd\" placeholder=\"pwd\" /><input type=\"submit\" value=\"f0ck it\" /></form>"
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
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: "<pre>" + util.inspect(req) + "</pre>"
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.get(/^\/admin(\/)?$/, async (req, res) => {
 | 
			
		||||
  if(!req.session)
 | 
			
		||||
    return res.redirect("/");
 | 
			
		||||
 | 
			
		||||
  res.reply({
 | 
			
		||||
    body: tpl.render("views/admin", {}, req)
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -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)
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -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, "")
 | 
			
		||||
 
 | 
			
		||||
@@ -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`,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								views/admin.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								views/admin.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
{{include main/header_admin}}
 | 
			
		||||
 | 
			
		||||
{{include main/footer}}
 | 
			
		||||
@@ -51,7 +51,13 @@
 | 
			
		||||
      <span class="badge badge-dark image-source"><a class="post_source" title="{{=item.src.long}}" href="{{=item.src.long}}" target="_blank">{{=item.src.short}}</a></span>
 | 
			
		||||
      <span class="badge badge-dark"><a class="dest-link" href="{{=item.dest}}" target="_blank">{{=item.mime}}</a> / {{=item.size}}</span>
 | 
			
		||||
      <span class="badge badge-dark"><time class="timeago" title="{{=item.timestamp}}" datetime="{{=item.timestamp}}">{{=item.timestamp}}</time></span>
 | 
			
		||||
      <span class="badge badge-dark">{{=lul}}</span>
 | 
			
		||||
      <span class="badge badge-dark">
 | 
			
		||||
        {{if session}}
 | 
			
		||||
        <a href="#">delete</a>
 | 
			
		||||
        {{else}}
 | 
			
		||||
        {{=lul}}
 | 
			
		||||
        {{/if}}
 | 
			
		||||
      </span>
 | 
			
		||||
      <span class="badge badge-dark" id="tags">
 | 
			
		||||
      {{if typeof item.tags !== "undefined"}}
 | 
			
		||||
        {{each item.tags as tag}}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								views/login.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								views/login.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
<!doctype f0ck>
 | 
			
		||||
<html theme="{{if typeof theme !== "undefined" }}{{=theme}}{{/if}}">
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
 | 
			
		||||
    <title>f0ck login</title>
 | 
			
		||||
    <link href="/s/css/f0ck.css" rel="stylesheet" />
 | 
			
		||||
  </head>
 | 
			
		||||
  <body type="login">
 | 
			
		||||
    <form class="login-form" method="post" action="/login">
 | 
			
		||||
      <p><a href="/"><img src="/s/img/f0ck_small.png" /></a></p>
 | 
			
		||||
      <input type="text" name="username" placeholder="username" autocomplete="off" required />
 | 
			
		||||
      <input type="password" name="password" placeholder="password" autocomplete="off" required />
 | 
			
		||||
      <button type="submit">Login</button>
 | 
			
		||||
    </form>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										40
									
								
								views/main/footer_admin.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								views/main/footer_admin.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
<script>
 | 
			
		||||
  var userMenuDiv = document.getElementById("userMenu");
 | 
			
		||||
  var userMenu = document.getElementById("userButton");
 | 
			
		||||
  var navMenuDiv = document.getElementById("nav-content");
 | 
			
		||||
  var navMenu = document.getElementById("nav-toggle");
 | 
			
		||||
  document.onclick = check;
 | 
			
		||||
  function check(e) {
 | 
			
		||||
    var target = (e && e.target) || (event && event.srcElement);
 | 
			
		||||
    if(!checkParent(target, userMenuDiv)) {
 | 
			
		||||
      if(checkParent(target, userMenu)) {
 | 
			
		||||
        if(userMenuDiv.classList.contains("invisible"))
 | 
			
		||||
          userMenuDiv.classList.remove("invisible");
 | 
			
		||||
        else
 | 
			
		||||
          userMenuDiv.classList.add("invisible");
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
        userMenuDiv.classList.add("invisible");
 | 
			
		||||
    }
 | 
			
		||||
    if(!checkParent(target, navMenuDiv)) {
 | 
			
		||||
      if(checkParent(target, navMenu)) {
 | 
			
		||||
        if(navMenuDiv.classList.contains("hidden"))
 | 
			
		||||
          navMenuDiv.classList.remove("hidden");
 | 
			
		||||
        else
 | 
			
		||||
          navMenuDiv.classList.add("hidden");
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
        navMenuDiv.classList.add("hidden");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  function checkParent(t, elm) {
 | 
			
		||||
    while(t.parentNode) {
 | 
			
		||||
      if(t == elm)
 | 
			
		||||
        return true;
 | 
			
		||||
      t = t.parentNode;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										18
									
								
								views/main/header_admin.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								views/main/header_admin.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
<!cocktype big f0ck>
 | 
			
		||||
<html lang="en" theme="{{if typeof theme !== "undefined" }}{{=theme}}{{/if}}">
 | 
			
		||||
<head>
 | 
			
		||||
  <title>{{if data.title}}{{=data.title}}{{else}}f0ck!{{/if}}</title>
 | 
			
		||||
  <link rel="icon" type="image/gif" href="/s/img/favicon.gif" />
 | 
			
		||||
  <link rel="stylesheet" href="/s/css/f0ck.css">
 | 
			
		||||
  <meta charset="utf-8" />
 | 
			
		||||
  <meta name="viewport" content="width=device-width, initial-scale=1" />
 | 
			
		||||
  <meta name="description" content="f0ck.me is the place where internet purists gather to celebrate content of all kinds">
 | 
			
		||||
  {{if data.item}}
 | 
			
		||||
  <meta property="og:site_name" content="f0ck.me" />
 | 
			
		||||
  <meta property="og:description"/>
 | 
			
		||||
  <meta name="Description"/>
 | 
			
		||||
  <meta property="og:image" content="{{=item.thumbnail}}" />
 | 
			
		||||
  {{/if}}
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  {{include snippets/navbar_admin}}
 | 
			
		||||
@@ -3,7 +3,17 @@
 | 
			
		||||
  <div class="navigation-links">
 | 
			
		||||
    <ul class="navbar-nav">
 | 
			
		||||
      <li class="nav-item">
 | 
			
		||||
        {{if session}}
 | 
			
		||||
        <li class="nav-item dropdown">
 | 
			
		||||
          <a class="nav-link" href="/admin" content="{{=session.user}}" data-toggle="dropdown">Admin</a>
 | 
			
		||||
          <ul class="dropdown-menu">
 | 
			
		||||
            <li><a href="/admin">Adminpanel</a></li>
 | 
			
		||||
            <li><a href="/logout">Logout</a></li>
 | 
			
		||||
          </ul>
 | 
			
		||||
        </li>
 | 
			
		||||
        {{else}}
 | 
			
		||||
        <a class="nav-link" href="/about"><span class="nav-link-identifier">About</span></a>
 | 
			
		||||
        {{/if}}
 | 
			
		||||
      </li>
 | 
			
		||||
      <li class="nav-item dropdown" id="themes">
 | 
			
		||||
        <a class="nav-link" href="#" content="{{=theme}}" data-toggle="dropdown">Theme</a>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								views/snippets/navbar_admin.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								views/snippets/navbar_admin.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
<nav class="navbar navbar-expand-lg">
 | 
			
		||||
  <a class="navbar-brand" href="/admin"><span class="f0ck">F0CK</span></a>
 | 
			
		||||
  <div class="navigation-links">
 | 
			
		||||
    <ul class="navbar-nav">
 | 
			
		||||
      <li class="nav-item dropdown" id="themes">
 | 
			
		||||
        <a class="nav-link" href="#" content="{{=theme}}" data-toggle="dropdown">Theme</a>
 | 
			
		||||
        <ul class="dropdown-menu">
 | 
			
		||||
          {{each themes as t}}
 | 
			
		||||
            <li><a href="/theme/{{=t}}">{{=t}}</a></li>
 | 
			
		||||
          {{/each}}
 | 
			
		||||
        </ul>
 | 
			
		||||
      </li>
 | 
			
		||||
      <li class="nav-item">
 | 
			
		||||
        <a class="nav-link" href="#">
 | 
			
		||||
          <span class="nav-link-identifier">blah</span>
 | 
			
		||||
        </a>
 | 
			
		||||
      </li>
 | 
			
		||||
      <li class="nav-item">
 | 
			
		||||
        <a class="nav-link" href="#">
 | 
			
		||||
          <span class="nav-link-identifier">blah</span>
 | 
			
		||||
        </a>
 | 
			
		||||
      </li>
 | 
			
		||||
      <li class="nav-item">
 | 
			
		||||
        <a class="nav-link" href="#">
 | 
			
		||||
          <span class="nav-link-identifier">blah</span>
 | 
			
		||||
        </a>
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="collapse navbar-collapse show" id="navbarSupportedContent">
 | 
			
		||||
    <div class="pagination-container-fluid">
 | 
			
		||||
      <div class="pagination-wrapper">
 | 
			
		||||
        Henlo, {{=session.user}}  <a href="/logout">Logout</a>
 | 
			
		||||
        {{include partials/pagination}}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</nav>
 | 
			
		||||
		Reference in New Issue
	
	Block a user