From 91ce5d71126c7eefe174a022362026e7a03178b9 Mon Sep 17 00:00:00 2001
From: Dominik Hebeler <dominik@suma-ev.de>
Date: Mon, 5 Dec 2022 16:00:31 +0100
Subject: [PATCH] added basic key page

---
 pass/app.js                          |   7 +
 pass/app/Key.js                      |  68 ++--
 pass/package-lock.json               | 493 +++++++++++++++++++++++++++
 pass/package.json                    |   1 +
 pass/public/images/copy.svg          | 172 ++++++++++
 pass/public/images/share.svg         | 149 ++++++++
 pass/public/styles/base.less         |  46 ++-
 pass/public/styles/key.less          |  41 +++
 pass/resources/js/base.js            |  29 ++
 pass/routes/key.js                   |  31 ++
 pass/views/index.ejs                 |   2 +-
 pass/views/key.ejs                   |  16 +
 pass/views/templates/page_footer.ejs |   2 +-
 pass/views/templates/page_header.ejs |   5 +-
 14 files changed, 1020 insertions(+), 42 deletions(-)
 create mode 100644 pass/public/images/copy.svg
 create mode 100644 pass/public/images/share.svg
 create mode 100644 pass/public/styles/key.less
 create mode 100644 pass/resources/js/base.js
 create mode 100644 pass/routes/key.js
 create mode 100644 pass/views/key.ejs

diff --git a/pass/app.js b/pass/app.js
index 2dfc737..c02a9ab 100644
--- a/pass/app.js
+++ b/pass/app.js
@@ -7,6 +7,7 @@ var lessMiddleware = require("less-middleware");
 var logger = require("morgan");
 
 var indexRouter = require("./routes/index");
+var keyRouter = require("./routes/key");
 var checkoutRouter = require("./routes/checkout/checkout");
 var redeemRouter = require("./routes/redeem.js");
 
@@ -24,10 +25,16 @@ app.use(lessMiddleware(path.join(__dirname, "public")));
 app.use(express.static(path.join(__dirname, "public")));
 
 app.use("/", indexRouter);
+app.use("/key", keyRouter);
 app.use("/checkout", checkoutRouter);
 app.use("/redeem", redeemRouter);
 
 // Browserified Javascript files
+app.get(
+  "/js/base.js",
+  browserify(path.join(__dirname, "resources", "js", "base.js"))
+);
+
 app.get(
   "/js/checkout.js",
   browserify(path.join(__dirname, "resources", "js", "checkout.js"))
diff --git a/pass/app/Key.js b/pass/app/Key.js
index f79e0a1..9dbcdfd 100644
--- a/pass/app/Key.js
+++ b/pass/app/Key.js
@@ -24,33 +24,30 @@ class Key {
     return require("config").get("keys.length");
   }
 
+  static async EXISTS(key) {
+    return new Promise((resolve, reject) => {
+      let redis_client = Key.REDIS_CLIENT;
+      redis_client.exists(Key.DATABASE_PREFIX + key).then((result) => {
+        console.log(result);
+        resolve(result);
+      });
+    });
+  }
+
   /**
    *
    * @returns A new MetaGer-Pass Key with 0 searches
    */
-  static async CREATE_NEW_KEY(expire_at) {
-    if (!expire_at) {
-      let Dayjs = require("dayjs");
-      expire_at = new Dayjs();
-      expire_at = expire_at.add(6, "hour").unix();
-    }
+  static async GET_NEW_KEY() {
     return new Promise(async (resolve, reject) => {
       let redis_client = Key.REDIS_CLIENT;
 
       do {
-        let key = "";
-
-        for (let i = 0; i < Key.KEY_LENGTH; i++) {
-          key +=
-            Key.KEY_CHARSET[Math.floor(Math.random() * Key.KEY_CHARSET.length)];
-        }
+        let crypto = require("crypto");
+        let key = crypto.randomUUID();
 
-        let key_created = await redis_client.setnx(
-          Key.DATABASE_PREFIX + key,
-          0
-        );
-        if (key_created === 1) {
-          await redis_client.expireat(Key.DATABASE_PREFIX + key, expire_at);
+        let key_exists = await redis_client.exists(Key.DATABASE_PREFIX + key);
+        if (!key_exists) {
           return resolve(key);
         }
         await new Promise((resolve) => setTimeout(resolve, 50));
@@ -63,25 +60,32 @@ class Key {
       let redis_client = Key.REDIS_CLIENT;
 
       // Check if key exists and is eligable for recharge
-      redis_client.get(Key.DATABASE_PREFIX + key).then(async key_charge => {
+      redis_client.get(Key.DATABASE_PREFIX + key).then(async (key_charge) => {
         if (!key_charge || key_charge >= 3000) {
-          reject("Der angegebene Schlüssel ist ungültig, oder beinhaltet bereits mehr als 3.000 Suchanfragen");
+          reject(
+            "Der angegebene Schlüssel ist ungültig, oder beinhaltet bereits mehr als 3.000 Suchanfragen"
+          );
         } else {
           let dayjs = require("dayjs");
           let expiration = dayjs().add(Key.EXPIRATION_AFTER_CHARGE_DAYS, "day");
-          redis_client.pipeline().incrby(Key.DATABASE_PREFIX + key, amount).expireat(Key.DATABASE_PREFIX + key, expiration.unix()).exec().then(result => {
-            resolve({
-              status: "SUCCESS",
-              metager_pass_key: {
-                key: key,
-                searches: result[0][1],
-                expire_at: expiration.format()
-              }
+          redis_client
+            .pipeline()
+            .incrby(Key.DATABASE_PREFIX + key, amount)
+            .expireat(Key.DATABASE_PREFIX + key, expiration.unix())
+            .exec()
+            .then((result) => {
+              resolve({
+                status: "SUCCESS",
+                metager_pass_key: {
+                  key: key,
+                  searches: result[0][1],
+                  expire_at: expiration.format(),
+                },
+              });
+            })
+            .catch((reason) => {
+              reject(reason);
             });
-          }).catch(reason => {
-            reject(reason);
-          });
-
         }
       });
     });
diff --git a/pass/package-lock.json b/pass/package-lock.json
index fa4a82d..b6df645 100644
--- a/pass/package-lock.json
+++ b/pass/package-lock.json
@@ -23,6 +23,7 @@
         "less-middleware": "~2.2.1",
         "morgan": "~1.9.1",
         "node-forge": "^1.3.1",
+        "qrcode": "^1.5.1",
         "uuid": "^9.0.0"
       },
       "devDependencies": {
@@ -99,6 +100,28 @@
         "json-stable-stringify": "^1.0.1"
       }
     },
+    "node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
     "node_modules/anymatch": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
@@ -687,6 +710,14 @@
       "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz",
       "integrity": "sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA=="
     },
+    "node_modules/camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/caseless": {
       "version": "0.12.0",
       "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@@ -819,6 +850,16 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/cliui": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+      "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.0",
+        "wrap-ansi": "^6.2.0"
+      }
+    },
     "node_modules/cluster-key-slot": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
@@ -849,6 +890,22 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+    },
     "node_modules/combine-source-map": {
       "version": "0.8.0",
       "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz",
@@ -1093,6 +1150,14 @@
         "ms": "2.0.0"
       }
     },
+    "node_modules/decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/decode-uri-component": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
@@ -1205,6 +1270,11 @@
       "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
       "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
     },
+    "node_modules/dijkstrajs": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz",
+      "integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg=="
+    },
     "node_modules/domain-browser": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
@@ -1269,6 +1339,16 @@
       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
     },
+    "node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+    },
+    "node_modules/encode-utf8": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz",
+      "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw=="
+    },
     "node_modules/encodeurl": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -1606,6 +1686,18 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/find-up": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+      "dependencies": {
+        "locate-path": "^5.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/for-in": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -1693,6 +1785,14 @@
       "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz",
       "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ=="
     },
+    "node_modules/get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "engines": {
+        "node": "6.* || 8.* || >= 10.*"
+      }
+    },
     "node_modules/get-value": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
@@ -2225,6 +2325,14 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/is-glob": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -2438,6 +2546,17 @@
         "node": ">= 0.7.1"
       }
     },
+    "node_modules/locate-path": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+      "dependencies": {
+        "p-locate": "^4.1.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/lodash": {
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -3068,6 +3187,39 @@
         "shell-quote": "^1.4.2"
       }
     },
+    "node_modules/p-limit": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+      "dependencies": {
+        "p-try": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/p-locate": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+      "dependencies": {
+        "p-limit": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/p-try": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/pako": {
       "version": "1.0.11",
       "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@@ -3119,6 +3271,14 @@
       "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
       "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q=="
     },
+    "node_modules/path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -3178,6 +3338,14 @@
         "url": "https://github.com/sponsors/jonschlinkert"
       }
     },
+    "node_modules/pngjs": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+      "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
     "node_modules/posix-character-classes": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@@ -3288,6 +3456,23 @@
       "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
       "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
     },
+    "node_modules/qrcode": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.1.tgz",
+      "integrity": "sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==",
+      "dependencies": {
+        "dijkstrajs": "^1.0.1",
+        "encode-utf8": "^1.0.3",
+        "pngjs": "^5.0.0",
+        "yargs": "^15.3.1"
+      },
+      "bin": {
+        "qrcode": "bin/qrcode"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
     "node_modules/qs": {
       "version": "6.5.2",
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
@@ -3499,6 +3684,19 @@
         "uuid": "bin/uuid"
       }
     },
+    "node_modules/require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/require-main-filename": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
+    },
     "node_modules/resolve": {
       "version": "1.22.1",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@@ -3615,6 +3813,11 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
+    },
     "node_modules/set-value": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
@@ -4166,12 +4369,36 @@
         }
       ]
     },
+    "node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/stringstream": {
       "version": "0.0.6",
       "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz",
       "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==",
       "optional": true
     },
+    "node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/subarg": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
@@ -4801,6 +5028,24 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/which-module": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+      "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q=="
+    },
+    "node_modules/wrap-ansi": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+      "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -4813,6 +5058,44 @@
       "engines": {
         "node": ">=0.4"
       }
+    },
+    "node_modules/y18n": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+      "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
+    },
+    "node_modules/yargs": {
+      "version": "15.4.1",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+      "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+      "dependencies": {
+        "cliui": "^6.0.0",
+        "decamelize": "^1.2.0",
+        "find-up": "^4.1.0",
+        "get-caller-file": "^2.0.1",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^2.0.0",
+        "set-blocking": "^2.0.0",
+        "string-width": "^4.2.0",
+        "which-module": "^2.0.0",
+        "y18n": "^4.0.0",
+        "yargs-parser": "^18.1.2"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/yargs-parser": {
+      "version": "18.1.3",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+      "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+      "dependencies": {
+        "camelcase": "^5.0.0",
+        "decamelize": "^1.2.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
     }
   },
   "dependencies": {
@@ -4874,6 +5157,19 @@
         "json-stable-stringify": "^1.0.1"
       }
     },
+    "ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+    },
+    "ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "requires": {
+        "color-convert": "^2.0.1"
+      }
+    },
     "anymatch": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
@@ -5376,6 +5672,11 @@
       "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz",
       "integrity": "sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA=="
     },
+    "camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
+    },
     "caseless": {
       "version": "0.12.0",
       "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@@ -5479,6 +5780,16 @@
         }
       }
     },
+    "cliui": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+      "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+      "requires": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.0",
+        "wrap-ansi": "^6.2.0"
+      }
+    },
     "cluster-key-slot": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
@@ -5499,6 +5810,19 @@
         "object-visit": "^1.0.0"
       }
     },
+    "color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "requires": {
+        "color-name": "~1.1.4"
+      }
+    },
+    "color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+    },
     "combine-source-map": {
       "version": "0.8.0",
       "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz",
@@ -5710,6 +6034,11 @@
         "ms": "2.0.0"
       }
     },
+    "decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="
+    },
     "decode-uri-component": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
@@ -5797,6 +6126,11 @@
         }
       }
     },
+    "dijkstrajs": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz",
+      "integrity": "sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg=="
+    },
     "domain-browser": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
@@ -5856,6 +6190,16 @@
         }
       }
     },
+    "emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+    },
+    "encode-utf8": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz",
+      "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw=="
+    },
     "encodeurl": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -6128,6 +6472,15 @@
         "unpipe": "~1.0.0"
       }
     },
+    "find-up": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+      "requires": {
+        "locate-path": "^5.0.0",
+        "path-exists": "^4.0.0"
+      }
+    },
     "for-in": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -6190,6 +6543,11 @@
       "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz",
       "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ=="
     },
+    "get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
+    },
     "get-value": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
@@ -6583,6 +6941,11 @@
       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
       "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
     },
+    "is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+    },
     "is-glob": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -6747,6 +7110,14 @@
         "node.extend": "~2.0.0"
       }
     },
+    "locate-path": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+      "requires": {
+        "p-locate": "^4.1.0"
+      }
+    },
     "lodash": {
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -7248,6 +7619,27 @@
         "shell-quote": "^1.4.2"
       }
     },
+    "p-limit": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+      "requires": {
+        "p-try": "^2.0.0"
+      }
+    },
+    "p-locate": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+      "requires": {
+        "p-limit": "^2.2.0"
+      }
+    },
+    "p-try": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
+    },
     "pako": {
       "version": "1.0.11",
       "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@@ -7293,6 +7685,11 @@
       "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
       "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q=="
     },
+    "path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
+    },
     "path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -7337,6 +7734,11 @@
       "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
       "dev": true
     },
+    "pngjs": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+      "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
+    },
     "posix-character-classes": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@@ -7436,6 +7838,17 @@
       "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
       "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
     },
+    "qrcode": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.1.tgz",
+      "integrity": "sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==",
+      "requires": {
+        "dijkstrajs": "^1.0.1",
+        "encode-utf8": "^1.0.3",
+        "pngjs": "^5.0.0",
+        "yargs": "^15.3.1"
+      }
+    },
     "qs": {
       "version": "6.5.2",
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
@@ -7606,6 +8019,16 @@
         }
       }
     },
+    "require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
+    },
+    "require-main-filename": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
+    },
     "resolve": {
       "version": "1.22.1",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@@ -7702,6 +8125,11 @@
         "send": "0.16.2"
       }
     },
+    "set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
+    },
     "set-value": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
@@ -8137,12 +8565,30 @@
         }
       }
     },
+    "string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "requires": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      }
+    },
     "stringstream": {
       "version": "0.0.6",
       "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz",
       "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==",
       "optional": true
     },
+    "strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "requires": {
+        "ansi-regex": "^5.0.1"
+      }
+    },
     "subarg": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
@@ -8643,6 +9089,21 @@
         }
       }
     },
+    "which-module": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+      "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q=="
+    },
+    "wrap-ansi": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+      "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+      "requires": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      }
+    },
     "wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -8652,6 +9113,38 @@
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
       "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
+    },
+    "y18n": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+      "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
+    },
+    "yargs": {
+      "version": "15.4.1",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+      "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+      "requires": {
+        "cliui": "^6.0.0",
+        "decamelize": "^1.2.0",
+        "find-up": "^4.1.0",
+        "get-caller-file": "^2.0.1",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^2.0.0",
+        "set-blocking": "^2.0.0",
+        "string-width": "^4.2.0",
+        "which-module": "^2.0.0",
+        "y18n": "^4.0.0",
+        "yargs-parser": "^18.1.2"
+      }
+    },
+    "yargs-parser": {
+      "version": "18.1.3",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+      "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+      "requires": {
+        "camelcase": "^5.0.0",
+        "decamelize": "^1.2.0"
+      }
     }
   }
 }
diff --git a/pass/package.json b/pass/package.json
index fac9384..eaf3781 100644
--- a/pass/package.json
+++ b/pass/package.json
@@ -22,6 +22,7 @@
     "less-middleware": "~2.2.1",
     "morgan": "~1.9.1",
     "node-forge": "^1.3.1",
+    "qrcode": "^1.5.1",
     "uuid": "^9.0.0"
   },
   "devDependencies": {
diff --git a/pass/public/images/copy.svg b/pass/public/images/copy.svg
new file mode 100644
index 0000000..9d98b2e
--- /dev/null
+++ b/pass/public/images/copy.svg
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:cc="http://creativecommons.org/ns#"
+    xmlns:dc="http://purl.org/dc/elements/1.1/"
+    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+    xmlns:svg="http://www.w3.org/2000/svg"
+    xmlns:ns1="http://sozi.baierouge.fr"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    id="svg5612"
+    sodipodi:docname="copy.svg"
+    viewBox="0 0 128 128"
+    version="1.1"
+    inkscape:version="0.48.3.1 r9886"
+  >
+  <title
+      id="title6891"
+    >Copy/Clone Icon</title
+  >
+  <sodipodi:namedview
+      id="base"
+      bordercolor="#666666"
+      inkscape:pageshadow="2"
+      inkscape:window-y="0"
+      pagecolor="#ffffff"
+      inkscape:window-height="1009"
+      inkscape:window-maximized="1"
+      inkscape:zoom="2.8"
+      inkscape:window-x="1280"
+      showgrid="false"
+      borderopacity="1.0"
+      inkscape:current-layer="layer1"
+      inkscape:cx="8.1326839"
+      inkscape:cy="24.530997"
+      inkscape:window-width="1280"
+      inkscape:pageopacity="0.0"
+      inkscape:document-units="px"
+  />
+  <g
+      id="layer1"
+      inkscape:label="Layer 1"
+      inkscape:groupmode="layer"
+      transform="translate(0 -924.36)"
+    >
+    <g
+        id="g6783"
+        transform="matrix(.99829 0 0 .99829 -101.01 570.97)"
+      >
+      <path
+          id="rect6587"
+          d="m134.94 354c-3.324 0-6 2.676-6 6v96c0 3.324 2.676 6 6 6h82c3.324 0 6-2.676 6-6v-62h-40c-3.324 0-6-2.676-6-6v-34h-42z"
+          style="fill:#333333"
+          inkscape:connector-curvature="0"
+      />
+      <path
+          id="rect6587-3-9-6-6"
+          d="m113.66 374.22c-3.324 0-6 2.676-6 6v96c0 3.324 2.676 6 6 6h82c3.324 0 6-2.676 6-6v-7h-73c-3.324 0-6-2.676-6-6v-89h-9z"
+          style="fill:#333333"
+          inkscape:connector-curvature="0"
+      />
+      <path
+          id="rect6587-3-2-9-2"
+          d="m180.94 354v30c0 3.324 2.676 6 6 6h36z"
+          sodipodi:nodetypes="csscc"
+          style="fill:#333333"
+          inkscape:connector-curvature="0"
+      />
+    </g
+    >
+  </g
+  >
+  <metadata
+    >
+    <rdf:RDF
+      >
+      <cc:Work
+        >
+        <dc:format
+          >image/svg+xml</dc:format
+        >
+        <dc:type
+            rdf:resource="http://purl.org/dc/dcmitype/StillImage"
+        />
+        <cc:license
+            rdf:resource="http://creativecommons.org/licenses/publicdomain/"
+        />
+        <dc:publisher
+          >
+          <cc:Agent
+              rdf:about="http://openclipart.org/"
+            >
+            <dc:title
+              >Openclipart</dc:title
+            >
+          </cc:Agent
+          >
+        </dc:publisher
+        >
+        <dc:title
+          >Copy / Clone Icon</dc:title
+        >
+        <dc:date
+          >2012-05-11T12:49:15</dc:date
+        >
+        <dc:description
+          >A monochrome copy / clone icon</dc:description
+        >
+        <dc:source
+          >https://openclipart.org/detail/169987/copy--clone-icon-by-ben</dc:source
+        >
+        <dc:creator
+          >
+          <cc:Agent
+            >
+            <dc:title
+              >ben</dc:title
+            >
+          </cc:Agent
+          >
+        </dc:creator
+        >
+        <dc:subject
+          >
+          <rdf:Bag
+            >
+            <rdf:li
+              >clone</rdf:li
+            >
+            <rdf:li
+              >copy</rdf:li
+            >
+            <rdf:li
+              >duplicate</rdf:li
+            >
+            <rdf:li
+              >gray</rdf:li
+            >
+            <rdf:li
+              >icon</rdf:li
+            >
+            <rdf:li
+              >monochrome</rdf:li
+            >
+          </rdf:Bag
+          >
+        </dc:subject
+        >
+      </cc:Work
+      >
+      <cc:License
+          rdf:about="http://creativecommons.org/licenses/publicdomain/"
+        >
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#Reproduction"
+        />
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#Distribution"
+        />
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
+        />
+      </cc:License
+      >
+    </rdf:RDF
+    >
+  </metadata
+  >
+</svg
+>
diff --git a/pass/public/images/share.svg b/pass/public/images/share.svg
new file mode 100644
index 0000000..1576d99
--- /dev/null
+++ b/pass/public/images/share.svg
@@ -0,0 +1,149 @@
+<?xml version="1.0"?>
+<svg
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:cc="http://creativecommons.org/ns#"
+    xmlns:dc="http://purl.org/dc/elements/1.1/"
+    xmlns:svg="http://www.w3.org/2000/svg"
+    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+    xmlns:ns1="http://sozi.baierouge.fr"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    id="svg11699"
+    viewBox="0 0 724.18 806.76"
+    version="1.1"
+  >
+  <g
+      id="layer1"
+      transform="translate(1193.5 499.59)"
+    >
+    <g
+        id="g11676"
+        transform="matrix(0 -17.254 17.254 0 10443 -2252.9)"
+      >
+      <path
+          id="path11666"
+          d="m-134.38 309.5c0 4.1683-3.3791 7.5474-7.5474 7.5474s-7.5474-3.3791-7.5474-7.5474 3.3791-7.5474 7.5474-7.5474 7.5474 3.3791 7.5474 7.5474z"
+          style="color:#000000"
+          transform="translate(1.1045 -949.48)"
+      />
+      <path
+          id="path11668"
+          d="m-134.38 309.5c0 4.1683-3.3791 7.5474-7.5474 7.5474s-7.5474-3.3791-7.5474-7.5474 3.3791-7.5474 7.5474-7.5474 7.5474 3.3791 7.5474 7.5474z"
+          style="color:#000000"
+          transform="translate(32.767 -949.48)"
+      />
+      <path
+          id="path11670"
+          d="m-134.38 309.5c0 4.1683-3.3791 7.5474-7.5474 7.5474s-7.5474-3.3791-7.5474-7.5474 3.3791-7.5474 7.5474-7.5474 7.5474 3.3791 7.5474 7.5474z"
+          style="color:#000000"
+          transform="translate(16.936 -976.35)"
+      />
+      <rect
+          id="rect11672"
+          style="color:#000000"
+          transform="rotate(-60.001)"
+          height="3"
+          width="27.692"
+          y="-443.73"
+          x="488.22"
+      />
+      <rect
+          id="rect11674"
+          style="color:#000000"
+          transform="matrix(-.5 -.86603 -.86603 .5 0 0)"
+          height="3"
+          width="27.692"
+          y="-226.9"
+          x="613.16"
+      />
+    </g
+    >
+  </g
+  >
+  <metadata
+    >
+    <rdf:RDF
+      >
+      <cc:Work
+        >
+        <dc:format
+          >image/svg+xml</dc:format
+        >
+        <dc:type
+            rdf:resource="http://purl.org/dc/dcmitype/StillImage"
+        />
+        <cc:license
+            rdf:resource="http://creativecommons.org/licenses/publicdomain/"
+        />
+        <dc:publisher
+          >
+          <cc:Agent
+              rdf:about="http://openclipart.org/"
+            >
+            <dc:title
+              >Openclipart</dc:title
+            >
+          </cc:Agent
+          >
+        </dc:publisher
+        >
+        <dc:title
+          >Share icon</dc:title
+        >
+        <dc:date
+          >2013-07-13T11:45:42</dc:date
+        >
+        <dc:description
+          >A simple share icon</dc:description
+        >
+        <dc:source
+          >https://openclipart.org/detail/180832/share-icon-by-minduka-180832</dc:source
+        >
+        <dc:creator
+          >
+          <cc:Agent
+            >
+            <dc:title
+              >Minduka</dc:title
+            >
+          </cc:Agent
+          >
+        </dc:creator
+        >
+        <dc:subject
+          >
+          <rdf:Bag
+            >
+            <rdf:li
+              >icon</rdf:li
+            >
+            <rdf:li
+              >share</rdf:li
+            >
+          </rdf:Bag
+          >
+        </dc:subject
+        >
+      </cc:Work
+      >
+      <cc:License
+          rdf:about="http://creativecommons.org/licenses/publicdomain/"
+        >
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#Reproduction"
+        />
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#Distribution"
+        />
+        <cc:permits
+            rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
+        />
+      </cc:License
+      >
+    </rdf:RDF
+    >
+  </metadata
+  >
+</svg
+>
diff --git a/pass/public/styles/base.less b/pass/public/styles/base.less
index cf3717f..86340f6 100644
--- a/pass/public/styles/base.less
+++ b/pass/public/styles/base.less
@@ -53,6 +53,40 @@ button {
   cursor: pointer;
 }
 
+.copy {
+  display: none;
+  &.active {
+    display: block;
+  }
+  &::after {
+    content: "";
+    background-image: url("/images/copy.svg");
+    width: 1rem;
+    height: 1rem;
+    display: block;
+    background-size: 0.7rem;
+    background-repeat: no-repeat;
+    background-position: center;
+  }
+}
+
+.share {
+  display: none;
+  &.active {
+    display: block;
+  }
+  &::after {
+    content: "";
+    background-image: url("/images/share.svg");
+    width: 1rem;
+    height: 1rem;
+    display: block;
+    background-size: 0.7rem;
+    background-repeat: no-repeat;
+    background-position: center;
+  }
+}
+
 .button {
   display: block;
   border: 1px solid #777;
@@ -61,6 +95,12 @@ button {
   text-decoration: none;
   background-color: @color-secondary;
   width: max-content;
+  &:hover {
+    color: inherit;
+    background-color: fade(@color-secondary, 70%);
+    border-color: @color-main;
+    color: fade(white, 90%);
+  }
 }
 
 nav {
@@ -116,12 +156,6 @@ nav {
         font-weight: bold;
         &:hover {
           color: @color-secondary;
-          &.button {
-            color: inherit;
-            background-color: fade(@color-secondary, 70%);
-            border-color: @color-main;
-            color: fade(white, 90%);
-          }
         }
         &.button {
           padding: 0.7rem;
diff --git a/pass/public/styles/key.less b/pass/public/styles/key.less
new file mode 100644
index 0000000..3f94219
--- /dev/null
+++ b/pass/public/styles/key.less
@@ -0,0 +1,41 @@
+#key-info {
+  display: grid;
+  grid-template-columns: auto auto;
+  grid-template-rows: auto auto auto auto auto;
+  max-width: 1200px;
+  margin: 0 auto;
+  align-items: center;
+
+  > h1 {
+    grid-column: span 2;
+    justify-self: center;
+  }
+
+  #qr {
+    grid-row: span 3;
+    justify-self: center;
+  }
+
+  #key {
+    font-size: 1.5rem;
+    font-weight: bold;
+    width: max-content;
+  }
+
+  #setting-url {
+    display: flex;
+    > input {
+      line-height: 1.5;
+      padding: 0.1rem 0.5rem;
+      border-radius: 5px;
+      flex-grow: 1;
+    }
+  }
+
+  > #amount {
+    grid-column: span 2;
+    justify-self: center;
+    font-size: 3rem;
+    padding: 2rem;
+  }
+}
diff --git a/pass/resources/js/base.js b/pass/resources/js/base.js
new file mode 100644
index 0000000..a9aca10
--- /dev/null
+++ b/pass/resources/js/base.js
@@ -0,0 +1,29 @@
+document.querySelectorAll(".share").forEach((element) => {
+  if (navigator.share) {
+    element.classList.add("active");
+  }
+  element.addEventListener("pointerdown", (e) => {
+    let share_data = {
+      url: element.parentElement.querySelector("input[type=text]").value,
+      title: element.dataset.share_title,
+    };
+    console.log(share_data);
+    navigator.share(share_data).catch((reason) => {
+      console.error(reason);
+    });
+  });
+});
+
+document.querySelectorAll(".copy").forEach((element) => {
+  element.classList.add("active");
+  let input_field = element.parentElement.querySelector("input[type=text]");
+  element.addEventListener("click", () => {
+    input_field.focus();
+    input_field.setSelectionRange(0, input_field.value.length);
+    navigator.clipboard.writeText(input_field.value);
+  });
+  input_field.addEventListener("click", () => {
+    input_field.setSelectionRange(0, input_field.value.length);
+    navigator.clipboard.writeText(input_field.value);
+  });
+});
diff --git a/pass/routes/key.js b/pass/routes/key.js
new file mode 100644
index 0000000..174109e
--- /dev/null
+++ b/pass/routes/key.js
@@ -0,0 +1,31 @@
+var express = require("express");
+var router = express.Router();
+
+var Key = require("../app/Key");
+
+router.get("/create", function (req, res, next) {
+  Key.GET_NEW_KEY().then((key) => {
+    res.redirect("/key/" + key);
+  });
+});
+
+router.get("/:key", async (req, res) => {
+  let key = req.params.key;
+
+  let metager_url =
+    "https://metager.de/meta/settings/load-settings?key=" +
+    encodeURIComponent(key);
+  let QRCode = require("qrcode");
+
+  let qr_data_uri = await QRCode.toDataURL(metager_url);
+
+  res.render("key", {
+    key: {
+      key: key,
+      settings_url: metager_url,
+      qr: qr_data_uri,
+    },
+  });
+});
+
+module.exports = router;
diff --git a/pass/views/index.ejs b/pass/views/index.ejs
index 85f2f1e..ef3244c 100644
--- a/pass/views/index.ejs
+++ b/pass/views/index.ejs
@@ -80,7 +80,7 @@
       </li>
     </ol>
     <div id="how-it-works-action">
-      <a href="#" class="button">Los geht's</a>
+      <a href="/key/create" class="button">Los geht's</a>
     </div>
   </div>
   <div id="offers">
diff --git a/pass/views/key.ejs b/pass/views/key.ejs
new file mode 100644
index 0000000..cecab38
--- /dev/null
+++ b/pass/views/key.ejs
@@ -0,0 +1,16 @@
+<%- include('templates/page_header', {css: ["/styles/key.css"], js: []}); %>
+
+<div id="key-info">
+    <h1>MetaGer Schlüssel</h1>
+    <img id="qr" src="<%= key.qr %> "></img>
+    <div id="key"><%= key.key %> </div>
+    <div id="setting-url">
+        <input type="text" readonly value="<%= key.settings_url %>" />
+        <button class="copy"></button>
+        <button class="share" data-share_title="MetaGer Schlüssel"></button>
+    </div>
+    <div id="hint">Benutzen Sie diese URL oder diesen QR Code um Ihren MetaGer Schlüssel auf beliebig vielen Endgeräten einzurichten.</div>
+    <div id="amount">0 Suchanfragen</div>
+</div>
+
+<%- include('templates/page_footer'); -%>
\ No newline at end of file
diff --git a/pass/views/templates/page_footer.ejs b/pass/views/templates/page_footer.ejs
index d378fbe..5e026f8 100644
--- a/pass/views/templates/page_footer.ejs
+++ b/pass/views/templates/page_footer.ejs
@@ -1,3 +1,3 @@
-</main>
+  </main>
 </body>
 </html>
\ No newline at end of file
diff --git a/pass/views/templates/page_header.ejs b/pass/views/templates/page_header.ejs
index 063f00b..e494ff3 100644
--- a/pass/views/templates/page_header.ejs
+++ b/pass/views/templates/page_header.ejs
@@ -9,6 +9,7 @@
   <link rel="stylesheet" href="<%- css_file %>">
   <%_ }) _%>
   <%_ } _%>
+  <script defer src="/js/base.js"></script>
   <%_ if (typeof js !== 'undefined') { -%>
     <%_ js.forEach(js_file => { -%>
   <script src="<%- js_file %>" defer></script>
@@ -20,7 +21,7 @@
     <div id="page-logo">
       <a href="#">
         <img src="/images/metager-schloss-orange.svg" alt="MetaGer Schloss">
-        <h1>Pass</h1>
+        <h1>Schlüssel</h1>
       </a>
     </div>
     <input type="checkbox" name="nav-opener" id="nav-opener" aria-hidden="true">
@@ -37,7 +38,7 @@
       <li><a href="#">Hilfe</a></li>
       <li class="whitespace"></li>
       <li><a href="#">Schlüssel verwalten</a></li>
-      <li><a href="#" class="button">Starten</a></li>
+      <li><a href="/key/create" class="button">Starten</a></li>
     </ul>
   </nav>
   <main>
\ No newline at end of file
-- 
GitLab