WIP add jwt auth, myjqlite
This commit is contained in:
		
							parent
							
								
									b2ced1a492
								
							
						
					
					
						commit
						72e920c1dd
					
				
							
								
								
									
										156
									
								
								lib/httpd.js
									
									
									
									
									
								
							
							
						
						
									
										156
									
								
								lib/httpd.js
									
									
									
									
									
								
							@ -1,6 +1,86 @@
 | 
				
			|||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports.create = function (cli, engine/*, dnsd*/) {
 | 
					module.exports.create = function (cli, engine/*, dnsd*/) {
 | 
				
			||||||
 | 
					  var subparts = (cli.subject || '').split('@');
 | 
				
			||||||
 | 
					  /*
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "kty": "EC",
 | 
				
			||||||
 | 
					    "use": "sig",
 | 
				
			||||||
 | 
					    "crv": "P-256",
 | 
				
			||||||
 | 
					    "x": "ogbK2nP6SiEIIp4w8oXBn3dcs6kljFfTbgZYG591tUU",
 | 
				
			||||||
 | 
					    "y": "sB0AekMYwpvbQfAoW-2LlEWdapNhxynfj1zBtWpE9lo",
 | 
				
			||||||
 | 
					    "alg": "ES256"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  */
 | 
				
			||||||
 | 
					  var jwt;
 | 
				
			||||||
 | 
					  var jwk;
 | 
				
			||||||
 | 
					  var privpem;
 | 
				
			||||||
 | 
					  var pubpem;
 | 
				
			||||||
 | 
					  if (!subparts[1]) {
 | 
				
			||||||
 | 
					    subparts = [ 'root', 'localhost' ];
 | 
				
			||||||
 | 
					    // TODO generate new random key and store it
 | 
				
			||||||
 | 
					    jwk = {
 | 
				
			||||||
 | 
					      //"kid": "thumbnail(pubkey)",
 | 
				
			||||||
 | 
					      "kty": "EC",
 | 
				
			||||||
 | 
					      "d": "GRIT-yJVlhAsgIChbNanxv41iCxbZszbHHgK8kbZovs",
 | 
				
			||||||
 | 
					      "use": "sig",
 | 
				
			||||||
 | 
					      "crv": "P-256",
 | 
				
			||||||
 | 
					      "x": "ogbK2nP6SiEIIp4w8oXBn3dcs6kljFfTbgZYG591tUU",
 | 
				
			||||||
 | 
					      "y": "sB0AekMYwpvbQfAoW-2LlEWdapNhxynfj1zBtWpE9lo",
 | 
				
			||||||
 | 
					      "alg": "ES256"
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    jwt = require('jsonwebtoken');
 | 
				
			||||||
 | 
					    privpem = require('jwk-to-pem')(jwk, { private: true });
 | 
				
			||||||
 | 
					    pubpem = require('jwk-to-pem')(jwk, { private: false });
 | 
				
			||||||
 | 
					    console.log(privpem);
 | 
				
			||||||
 | 
					    console.log("================================");
 | 
				
			||||||
 | 
					    console.log(" JWT Write Authorization Token: ");
 | 
				
			||||||
 | 
					    console.log("================================");
 | 
				
			||||||
 | 
					    console.log(jwt.sign(
 | 
				
			||||||
 | 
					      { sub: subparts[0]
 | 
				
			||||||
 | 
					      , iss: subparts[1]
 | 
				
			||||||
 | 
					      , aud: 'localhost'
 | 
				
			||||||
 | 
					      , scp: '+rw@adns.org'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    , privpem
 | 
				
			||||||
 | 
					    , { notBefore: 0 // from now
 | 
				
			||||||
 | 
					      , expiresIn: '2h'
 | 
				
			||||||
 | 
					      , algorithm: 'ES256'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ));
 | 
				
			||||||
 | 
					    // expressed as "from now"
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					    { NotBeforeError: jwt not active
 | 
				
			||||||
 | 
					        at Object.module.exports [as verify] (digd.js/node_modules/jsonwebtoken/verify.js:117:19)
 | 
				
			||||||
 | 
					        at digd.js/lib/httpd.js:112:15
 | 
				
			||||||
 | 
					        at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5)
 | 
				
			||||||
 | 
					        at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13)
 | 
				
			||||||
 | 
					        at digd.js/node_modules/express/lib/router/index.js:284:7
 | 
				
			||||||
 | 
					        at Function.process_params (digd.js/node_modules/express/lib/router/index.js:335:12)
 | 
				
			||||||
 | 
					        at next (digd.js/node_modules/express/lib/router/index.js:275:10)
 | 
				
			||||||
 | 
					        at expressInit (digd.js/node_modules/express/lib/middleware/init.js:40:5)
 | 
				
			||||||
 | 
					        at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5)
 | 
				
			||||||
 | 
					        at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13)
 | 
				
			||||||
 | 
					      name: 'NotBeforeError',
 | 
				
			||||||
 | 
					      message: 'jwt not active',
 | 
				
			||||||
 | 
					      date: +050046-12-28T01:12:58.000Z }
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					    console.log("===============================");
 | 
				
			||||||
 | 
					    console.log(" JWT Read Authorization Token: ");
 | 
				
			||||||
 | 
					    console.log("===============================");
 | 
				
			||||||
 | 
					    console.log(jwt.sign(
 | 
				
			||||||
 | 
					      { sub: subparts[0]
 | 
				
			||||||
 | 
					      , iss: subparts[1]
 | 
				
			||||||
 | 
					      , aud: 'localhost'
 | 
				
			||||||
 | 
					      , scp: '+r@adns.org'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    , privpem
 | 
				
			||||||
 | 
					    , { notBefore: 0 // from now
 | 
				
			||||||
 | 
					      , algorithm: 'ES256'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ));
 | 
				
			||||||
 | 
					    console.log("==========================");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function runHttp() {
 | 
					  function runHttp() {
 | 
				
			||||||
    var path = require('path');
 | 
					    var path = require('path');
 | 
				
			||||||
@ -8,7 +88,79 @@ module.exports.create = function (cli, engine/*, dnsd*/) {
 | 
				
			|||||||
    var app = express();
 | 
					    var app = express();
 | 
				
			||||||
    var httpServer = require('http').createServer(app);
 | 
					    var httpServer = require('http').createServer(app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    app.use('/', express.static(path.join(__dirname, 'public')));
 | 
					    app.use('/api', function (req, res, next) {
 | 
				
			||||||
 | 
					      var auth = (req.headers.authorization || req.query.token || '').split(/\s+/)[1];
 | 
				
			||||||
 | 
					      var token;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!auth) {
 | 
				
			||||||
 | 
					        res.statusCode = 403;
 | 
				
			||||||
 | 
					        res.send({ error: { message: "need authorization" } });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      jwt = jwt || require('jsonwebtoken');
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        token = jwt.decode(auth);
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        token = null;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!token || !token.iss) {
 | 
				
			||||||
 | 
					        res.statusCode = 403;
 | 
				
			||||||
 | 
					        res.send({ error: { message: "need jwt-format authorization" } });
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (subparts[0] === token.sub && subparts[1] === token.iss) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          /*
 | 
				
			||||||
 | 
					          // { algorithm: 'ES256' }
 | 
				
			||||||
 | 
					          { JsonWebTokenError: invalid algorithm
 | 
				
			||||||
 | 
					              at Object.module.exports [as verify] (digd.js/node_modules/jsonwebtoken/verify.js:90:17)
 | 
				
			||||||
 | 
					              at digd.js/lib/httpd.js:82:15
 | 
				
			||||||
 | 
					              at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5)
 | 
				
			||||||
 | 
					              at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13)
 | 
				
			||||||
 | 
					              at digd.js/node_modules/express/lib/router/index.js:284:7
 | 
				
			||||||
 | 
					              at Function.process_params (digd.js/node_modules/express/lib/router/index.js:335:12)
 | 
				
			||||||
 | 
					              at next (digd.js/node_modules/express/lib/router/index.js:275:10)
 | 
				
			||||||
 | 
					              at expressInit (digd.js/node_modules/express/lib/middleware/init.js:40:5)
 | 
				
			||||||
 | 
					              at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5)
 | 
				
			||||||
 | 
					              at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13) name: 'JsonWebTokenError', message: 'invalid algorithm' }
 | 
				
			||||||
 | 
					          */
 | 
				
			||||||
 | 
					          // could be that it's private but expecting public, or public but expecting private
 | 
				
			||||||
 | 
					          /*
 | 
				
			||||||
 | 
					          Error: error:0906D06C:PEM routines:PEM_read_bio:no start line
 | 
				
			||||||
 | 
					              at Verify.verify (crypto.js:381:23)
 | 
				
			||||||
 | 
					              at verify (digd.js/node_modules/jwa/index.js:68:21)
 | 
				
			||||||
 | 
					              at Object.verify (digd.js/node_modules/jwa/index.js:85:18)
 | 
				
			||||||
 | 
					              at Object.jwsVerify [as verify] (digd.js/node_modules/jws/lib/verify-stream.js:54:15)
 | 
				
			||||||
 | 
					              at Object.module.exports [as verify] (digd.js/node_modules/jsonwebtoken/verify.js:96:17)
 | 
				
			||||||
 | 
					              at digd.js/lib/httpd.js:82:15
 | 
				
			||||||
 | 
					              at Layer.handle [as handle_request] (digd.js/node_modules/express/lib/router/layer.js:95:5)
 | 
				
			||||||
 | 
					              at trim_prefix (digd.js/node_modules/express/lib/router/index.js:317:13)
 | 
				
			||||||
 | 
					              at digd.js/node_modules/express/lib/router/index.js:284:7
 | 
				
			||||||
 | 
					              at Function.process_params (digd.js/node_modules/express/lib/router/index.js:335:12)
 | 
				
			||||||
 | 
					          */
 | 
				
			||||||
 | 
					          jwt.verify(auth, pubpem, { algorithms: [ 'ES256' ] });
 | 
				
			||||||
 | 
					        } catch(e) {
 | 
				
			||||||
 | 
					          res.statusCode = 403;
 | 
				
			||||||
 | 
					          console.error(e);
 | 
				
			||||||
 | 
					          console.log(auth);
 | 
				
			||||||
 | 
					          console.log(jwt.decode(auth, { complete: true }));
 | 
				
			||||||
 | 
					          res.send({ error: { message: "jwt was not verified authorization" } });
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      req.auth = auth;
 | 
				
			||||||
 | 
					      req.token = token;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      next();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    app.get('/api/verify-auth', function (req, res) {
 | 
				
			||||||
 | 
					      res.send({ success: true });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
    app.get('/api/peers', function (req, res) {
 | 
					    app.get('/api/peers', function (req, res) {
 | 
				
			||||||
      engine.peers.all(function (err, peers) {
 | 
					      engine.peers.all(function (err, peers) {
 | 
				
			||||||
        res.send({ peers: peers });
 | 
					        res.send({ peers: peers });
 | 
				
			||||||
@ -69,6 +221,8 @@ module.exports.create = function (cli, engine/*, dnsd*/) {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    app.use('/', express.static(path.join(__dirname, 'public')));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    httpServer.listen(cli.http, function () {
 | 
					    httpServer.listen(cli.http, function () {
 | 
				
			||||||
      console.log(httpServer.address().address + '#' + httpServer.address().port + ' (http)');
 | 
					      console.log(httpServer.address().address + '#' + httpServer.address().port + ' (http)');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,12 @@
 | 
				
			|||||||
    <title>ADNS</title>
 | 
					    <title>ADNS</title>
 | 
				
			||||||
  </head>
 | 
					  </head>
 | 
				
			||||||
  <body>
 | 
					  <body>
 | 
				
			||||||
 | 
					    <h1>ADNS Zones and Records</h1>
 | 
				
			||||||
 | 
					    <p>
 | 
				
			||||||
 | 
					      <label>API JWT:</label> <input class="js-jwt" type="text" placeholder="paste the api token here" />
 | 
				
			||||||
 | 
					      <button class="js-jwt" type="button">Authorize</button>
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <p><a href="/api/peers">/api/peers</a></p>
 | 
					    <p><a href="/api/peers">/api/peers</a></p>
 | 
				
			||||||
    <p><a href="/api/zones">/api/zones</a></p>
 | 
					    <p><a href="/api/zones">/api/zones</a></p>
 | 
				
			||||||
    <p><a data-href="/api/zones/:zone/records" class="js-zone">/api/zones/<code
 | 
					    <p><a data-href="/api/zones/:zone/records" class="js-zone">/api/zones/<code
 | 
				
			||||||
@ -15,6 +21,10 @@
 | 
				
			|||||||
      <input class="js-name"
 | 
					      <input class="js-name"
 | 
				
			||||||
        type="text" placeholder="example.com"/></p>
 | 
					        type="text" placeholder="example.com"/></p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ul class="js-zones">
 | 
				
			||||||
 | 
					      <li class="js-zone">blah</li>
 | 
				
			||||||
 | 
					    </ul>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <script src="/js/app.js"></script>
 | 
					    <script src="/js/app.js"></script>
 | 
				
			||||||
  </body>
 | 
					  </body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,28 +1,87 @@
 | 
				
			|||||||
(function () {
 | 
					(function () {
 | 
				
			||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!Element.prototype.matches) {
 | 
				
			||||||
 | 
					    Element.prototype.matches = Element.prototype.msMatchesSelector;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  function $qs(qs) {
 | 
					  function $qs(qs) {
 | 
				
			||||||
    return document.querySelector(qs);
 | 
					    return document.querySelector(qs);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  function $on(selector, eventname, cb) {
 | 
				
			||||||
 | 
					    if (!$on._events[eventname]) {
 | 
				
			||||||
 | 
					      $on._events[eventname] = $on._dispatcher(eventname);
 | 
				
			||||||
 | 
					      document.addEventListener(eventname, $on._events[eventname]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!$on._handlers[eventname]) {
 | 
				
			||||||
 | 
					      $on._handlers[eventname] = {};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!$on._handlers[eventname][selector]) {
 | 
				
			||||||
 | 
					      $on._handlers[eventname][selector] = [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    $on._handlers[eventname][selector].push(cb);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  $on._events = {};
 | 
				
			||||||
 | 
					  $on._handlers = {};
 | 
				
			||||||
 | 
					  $on._dispatcher = function (eventname) {
 | 
				
			||||||
 | 
					    return function (ev) {
 | 
				
			||||||
 | 
					      //console.log('event: ' + ev.type);
 | 
				
			||||||
 | 
					      if (!$on._handlers[eventname]) {
 | 
				
			||||||
 | 
					        console.warn('no handlers for event');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      var matches = Object.keys($on._handlers[eventname]).some(function (selector) {
 | 
				
			||||||
 | 
					        if (ev.target.matches(selector)) {
 | 
				
			||||||
 | 
					          $on._handlers[eventname][selector].forEach(function (cb) { cb(ev); });
 | 
				
			||||||
 | 
					          return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      if (!matches) {
 | 
				
			||||||
 | 
					        console.warn("no handlers for selector");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  $on('body', 'click', function () {
 | 
				
			||||||
 | 
					    console.log('woo-hoo, that tickles my body!');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  document.body.addEventListener('keyup', function (ev) {
 | 
					  var auth = localStorage.getItem('auth');
 | 
				
			||||||
    console.log('ev.target.tagName:');
 | 
					
 | 
				
			||||||
    console.log(ev.target.tagName);
 | 
					  $qs('input.js-jwt').value = auth || '';
 | 
				
			||||||
    console.log('/\\bjs-zone\\b/.test(ev.target.className):');
 | 
					
 | 
				
			||||||
    console.log(/\bjs-zone\b/.test(ev.target.className));
 | 
					
 | 
				
			||||||
    if ('INPUT' === ev.target.tagName && /\bjs-zone\b/.test(ev.target.className)) {
 | 
					  $on('button.js-jwt', 'click', function (/*ev*/) {
 | 
				
			||||||
      $qs('code.js-zone').innerHTML = ev.target.value || ':zone';
 | 
					    auth = $qs('input.js-jwt').value;
 | 
				
			||||||
      // $qs('a.js-zone').setAttribute('data-href', ...)
 | 
					    return window.fetch(
 | 
				
			||||||
      $qs('a.js-zone').href =
 | 
					      '/api/verify-auth'
 | 
				
			||||||
        $qs('a.js-zone').dataset.href.replace(/:zone/, ev.target.value || ':zone');
 | 
					    , { method: 'GET'
 | 
				
			||||||
      return;
 | 
					      , headers: new window.Headers({ 'Authorization': 'Bearer ' + auth })
 | 
				
			||||||
    }
 | 
					      }
 | 
				
			||||||
    if ('INPUT' === ev.target.tagName && /\bjs-name\b/.test(ev.target.className)) {
 | 
					    ).then(function (resp) {
 | 
				
			||||||
      $qs('code.js-name').innerHTML = ev.target.value || ':name';
 | 
					      return resp.json().then(function (data) {
 | 
				
			||||||
      $qs('a.js-name').href =
 | 
					        if (data.error) {
 | 
				
			||||||
        $qs('a.js-name').dataset.href.replace(/:name/, ev.target.value || ':name');
 | 
					          console.error(data.error);
 | 
				
			||||||
      return;
 | 
					          window.alert('Bad HTTP Response: ' + data.error.message);
 | 
				
			||||||
    }
 | 
					          throw new Error('Bad HTTP Response: ' + data.error.message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        localStorage.setItem('auth', auth);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $on('input.js-zone', 'keyup', function (ev) {
 | 
				
			||||||
 | 
					    $qs('code.js-zone').innerHTML = ev.target.value || ':zone';
 | 
				
			||||||
 | 
					    // $qs('a.js-zone').setAttribute('data-href', ...)
 | 
				
			||||||
 | 
					    $qs('a.js-zone').href =
 | 
				
			||||||
 | 
					      $qs('a.js-zone').dataset.href.replace(/:zone/, ev.target.value || ':zone');
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $on('input.js-name', 'keyup', function (ev) {
 | 
				
			||||||
 | 
					    $qs('code.js-name').innerHTML = ev.target.value || ':name';
 | 
				
			||||||
 | 
					    $qs('a.js-name').href =
 | 
				
			||||||
 | 
					      $qs('a.js-name').dataset.href.replace(/:name/, ev.target.value || ':name');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}());
 | 
					}());
 | 
				
			||||||
 | 
				
			|||||||
@ -49,6 +49,8 @@
 | 
				
			|||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "dig.js": "git+https://git.coolaj86.com/coolaj86/dig.js#v1.3",
 | 
					    "dig.js": "git+https://git.coolaj86.com/coolaj86/dig.js#v1.3",
 | 
				
			||||||
    "express": "^4.16.2",
 | 
					    "express": "^4.16.2",
 | 
				
			||||||
    "hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4"
 | 
					    "hexdump.js": "git+https://git.coolaj86.com/coolaj86/hexdump.js#v1.0.4",
 | 
				
			||||||
 | 
					    "jsonwebtoken": "^8.1.0",
 | 
				
			||||||
 | 
					    "jwk-to-pem": "^1.2.6"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user