From fb4eb44c4ce89bf620221a57c8ad976f0699263c Mon Sep 17 00:00:00 2001 From: Dominik Hebeler <dominik@hebeler.club> Date: Mon, 21 Nov 2022 11:46:04 +0100 Subject: [PATCH] creating a new key works --- pass/app/Key.js | 3 +- pass/config/default.json | 41 +++++++ pass/public/styles/base.css | 2 +- pass/public/styles/base.less | 4 + pass/resources/js/checkout.js | 44 ++++--- pass/routes/redeem.js | 79 +++++++++++- pass/views/checkout/checkout.ejs | 205 +++++++++++++++++-------------- 7 files changed, 265 insertions(+), 113 deletions(-) diff --git a/pass/app/Key.js b/pass/app/Key.js index a8bcbc1..7ab9ee8 100644 --- a/pass/app/Key.js +++ b/pass/app/Key.js @@ -4,6 +4,7 @@ class Key { } static get REDIS_CLIENT() { + let config = require("config"); let Redis = require("ioredis"); return new Redis({ @@ -24,7 +25,7 @@ class Key { * @returns A new MetaGer-Pass Key with 0 searches */ static async CREATE_NEW_KEY(expire_at) { - if (!expires_at) { + if (!expire_at) { let Dayjs = require("dayjs"); expire_at = new Dayjs(); expire_at = expire_at.add(6, "hour").unix(); diff --git a/pass/config/default.json b/pass/config/default.json index 9667aab..db5328c 100644 --- a/pass/config/default.json +++ b/pass/config/default.json @@ -5,6 +5,47 @@ "storage": { "data_path": "/data" }, + "keys": { + "charset": [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ], + "length": 8 + }, "crypto": { "hmac_integrity_seed": "<insert_secret_for_hmac_seed>", "private_key": { diff --git a/pass/public/styles/base.css b/pass/public/styles/base.css index e80d069..19ddb32 100644 --- a/pass/public/styles/base.css +++ b/pass/public/styles/base.css @@ -1 +1 @@ -html{font-family:Liberation Sans} \ No newline at end of file +html{font-family:Liberation Sans}.hidden{display:none} \ No newline at end of file diff --git a/pass/public/styles/base.less b/pass/public/styles/base.less index 3001bc2..e00c1b2 100644 --- a/pass/public/styles/base.less +++ b/pass/public/styles/base.less @@ -1,3 +1,7 @@ html { font-family: Liberation Sans; } + +.hidden { + display: none; +} \ No newline at end of file diff --git a/pass/resources/js/checkout.js b/pass/resources/js/checkout.js index a040db1..674dd72 100644 --- a/pass/resources/js/checkout.js +++ b/pass/resources/js/checkout.js @@ -136,30 +136,44 @@ function four_finish_purchase() { voucher_link.href = voucher_data; voucher_link.download = "mgpass_coupon.json"; - // Make Create Key button work - let create_key_button = document.getElementById("create-key-button"); + // Create redeem data let order_month = require("dayjs") .unix(metager_pass_order_id.substr(0, 10)) .format("YYYY-MM-01"); - create_key_button.addEventListener("pointerdown", () => { - let redeem_data = { - expiration_month: metager_pass_expires_at, - generation_month: order_month, - metager_pass_codes: [], - }; - for (let i = 0; i < metager_pass_sales_receipts.length; i++) { - redeem_data.metager_pass_codes.push({ - code: metager_pass_sales_receipts[i], - signature: metager_pass_signatures[i], - }); - } + let redeem_data = { + expiration_month: metager_pass_expires_at, + generation_month: order_month, + metager_pass_codes: [], + }; + for (let i = 0; i < metager_pass_sales_receipts.length; i++) { + redeem_data.metager_pass_codes.push({ + code: metager_pass_sales_receipts[i], + signature: metager_pass_signatures[i], + }); + } + // Make Create Key button work + let create_key_button = document.getElementById("create-key-button"); + create_key_button.addEventListener("pointerdown", () => { fetch("/redeem/create", { method: "POST", headers: { "Content-Type": "application/json;charset=utf-8", }, body: JSON.stringify(redeem_data), - }); + }).then(response => response.json()) + .then(response => { + let key = response.metager_pass_key; + // Prefill the recharge existing key button + let metager_pass_key_input = document.getElementById("metager-pass-key"); + metager_pass_key_input.value = key.key; + // Select The recharge existing key tab and show the correct text + document.getElementById("recharge-existing-key").classList.toggle("hidden"); + document.getElementById("recharge-new-key").classList.toggle("hidden"); + document.getElementById("recharge-key-radio").checked = true; + }); }); + + // Make Redeem Button Work + let redeem_button = document.getElementById("recharge-key-button"); } diff --git a/pass/routes/redeem.js b/pass/routes/redeem.js index 5936b89..9fd6182 100644 --- a/pass/routes/redeem.js +++ b/pass/routes/redeem.js @@ -2,6 +2,7 @@ var express = require("express"); var router = express.Router(); const { query, body, validationResult } = require("express-validator"); +const config = require("config"); const dayjs = require("dayjs"); const Crypto = require("../app/Crypto"); var customParseFormat = require("dayjs/plugin/customParseFormat"); @@ -44,10 +45,82 @@ router.use( ); /* Create a new MetaGer-Pass Key from purchase receipt */ -router.post("/create", function (req, res, next) { - res.json({ - status: "SUCCESS", +router.post("/create", async function (req, res, next) { + let key = ""; + + // Redis Client + let Redis = require("ioredis"); + let redis_client = new Redis({ + host: config.get("redis.host"), + }); + // Dayjs + let dayjs = require("dayjs"); + + let key_cache_redis_keys = []; + let key_redis_cache_prefix = "create_key_cache"; + // Check if the user already created a key which is in redis + for (let i = 0; i < req.body.metager_pass_codes.length; i++) { + let code = req.body.metager_pass_codes[i].code; + key_cache_redis_keys.push(key_redis_cache_prefix + "_" + code); + } + redis_client.mget(key_cache_redis_keys).then(async response => { + let existing_key = null; + let existing_key_expiration = null; + // Check if there is at least one existing key associated with the pass codes + for (let i = 0; i < response.length; i++) { + if (response[i]) { + existing_key = response[i]; + existing_key_expiration = await redis_client.expiretime(key_cache_redis_keys[i]); + break; + } + } + if (existing_key) { + // If there is we will return it and store this key to all the empty keys + for (let i = 0; i < response.length; i++) { + if (!response[i]) { + await redis_client.set(key_cache_redis_keys[i], existing_key); + await redis_client.expireat(key_cache_redis_keys[i], existing_key_expiration); + } + } + res.json({ + status: "SUCCESS", + metager_pass_key: { + key: existing_key, + valid_until: dayjs.unix(existing_key_expiration).format() + } + }); + } else { + // There is no Key yet. Let's generate one + let expiration = (new dayjs()).add(6, 'hour').unix(); + + let Key = require('../app/Key'); + Key.CREATE_NEW_KEY(expiration).then(async key => { + for (let i = 0; i < key_cache_redis_keys.length; i++) { + await redis_client.set(key_cache_redis_keys[i], key); + await redis_client.expireat(key_cache_redis_keys[i], expiration); + } + res.json({ + status: "SUCCESS", + metager_pass_key: { + key: key, + valid_until: dayjs.unix(expiration).format() + } + }); + }).catch(reason => { + res.status(400).json({ + status: "FAILURE", + msg: reason + }); + }); + } + }).catch(reason => { + res.status(400).json({ + status: "FAILURE", + msg: reason + }); }); + + }); module.exports = router; diff --git a/pass/views/checkout/checkout.ejs b/pass/views/checkout/checkout.ejs index 20bab3d..d58a123 100644 --- a/pass/views/checkout/checkout.ejs +++ b/pass/views/checkout/checkout.ejs @@ -1,112 +1,131 @@ <%- include('../templates/page_header', {css: ['/styles/checkout.css'], js: ['/js/checkout.js']}); %> -<main> - <input type="hidden" name="order_id" value="<%- order_id %>"> - <input type="hidden" name="expires_at" value="<%- expires_at %>"> - <input type="hidden" name="amount" value="<%- amount %>"> - <input type="hidden" name="unit_size" value="<%- unit_size %>"> - <input type="hidden" name="price_per_unit" value="<%- price_per_unit %>"> - <input type="hidden" name="public_key_e" value="<%- crypto.E %>"> - <input type="hidden" name="public_key_n" value="<%- crypto.N %>"> - <input type="hidden" name="integrity" value="<%- integrity %>"> - - <div id="payment-container"> - <div id="heading">Ihr Einkauf: <%- amount * unit_size %> Suchanfragen für <%- amount * price_per_unit %>€</div> - <div id="generate-sales-receipt" class="step current"> - <div class="section-heading"> - <div class="status"> - <img src="/images/loader.gif" alt="Loading" class="loading"> - <div class="finished">✓</div> - </div> - <div class="content">Kaufbeleg erstellen und verschlüsseln</div> - <div class="location">Lokal</div> - </div> - <div class="section-body"> + <main> + <input type="hidden" name="order_id" value="<%- order_id %>"> + <input type="hidden" name="expires_at" value="<%- expires_at %>"> + <input type="hidden" name="amount" value="<%- amount %>"> + <input type="hidden" name="unit_size" value="<%- unit_size %>"> + <input type="hidden" name="price_per_unit" value="<%- price_per_unit %>"> + <input type="hidden" name="public_key_e" value="<%- crypto.E %>"> + <input type="hidden" name="public_key_n" value="<%- crypto.N %>"> + <input type="hidden" name="integrity" value="<%- integrity %>"> + <div id="payment-container"> + <div id="heading">Ihr Einkauf: <%- amount * unit_size %> Suchanfragen für <%- amount * price_per_unit %>€ </div> - </div> - - <div id="execute-payment" class="step"> - <div class="section-heading"> - <div class="status"> - <img src="/images/loader.gif" alt="Loading" class="loading"> - <div class="finished">✓</div> + <div id="generate-sales-receipt" class="step current"> + <div class="section-heading"> + <div class="status"> + <img src="/images/loader.gif" alt="Loading" class="loading"> + <div class="finished">✓</div> + </div> + <div class="content">Kaufbeleg erstellen und verschlüsseln</div> + <div class="location">Lokal</div> + </div> + <div class="section-body"> + </div> - <div class="content">Zahlung durchführen</div> - <div class="location">Extern</div> </div> - <div class="section-body"> - <div id="payment-provider-container"> - <div id="payment-providers"> - <h1>Zahlungsmethode</h1> - <ul> - <% if (process.env.NODE_ENV === 'development') { %> - <li id="payment_method_development">Ohne Zahlung</li> - <% } %> - <li id="payment_method_paypal" data-active="false" data-client_id="<%- payments.paypal.client_id %>">Paypal</li> - <li>Google Pay</li> - <li>Apple Pay</li> - <li>Paysafe Card</li> - </ul> - </div> - <div id="payment-information"> - <div class="no-provider">Bitte Zahlungsmethode auswählen</div> + + <div id="execute-payment" class="step"> + <div class="section-heading"> + <div class="status"> + <img src="/images/loader.gif" alt="Loading" class="loading"> + <div class="finished">✓</div> </div> + <div class="content">Zahlung durchführen</div> + <div class="location">Extern</div> </div> - </div> - </div> - <div id="verify-and-decrypt" class="step"> - <div class="section-heading"> - <div class="status"> - <img src="/images/loader.gif" alt="Loading" class="loading"> - <div class="finished">✓</div> + <div class="section-body"> + <div id="payment-provider-container"> + <div id="payment-providers"> + <h1>Zahlungsmethode</h1> + <ul> + <% if (process.env.NODE_ENV==='development' ) { %> + <li id="payment_method_development">Ohne Zahlung</li> + <% } %> + <li id="payment_method_paypal" data-active="false" + data-client_id="<%- payments.paypal.client_id %>">Paypal</li> + <li>Google Pay</li> + <li>Apple Pay</li> + <li>Paysafe Card</li> + </ul> + </div> + <div id="payment-information"> + <div class="no-provider">Bitte Zahlungsmethode auswählen</div> + </div> + </div> </div> - <div class="content">Kaufbeleg verifizieren und entschlüsseln</div> - <div class="location">Lokal</div> </div> - </div> - <div id="finish-purchase" class="step"> - <div class="section-heading"> - <div class="status"> - <img src="/images/loader.gif" alt="Loading" class="loading"> - <div class="finished">✓</div> + <div id="verify-and-decrypt" class="step"> + <div class="section-heading"> + <div class="status"> + <img src="/images/loader.gif" alt="Loading" class="loading"> + <div class="finished">✓</div> + </div> + <div class="content">Kaufbeleg verifizieren und entschlüsseln</div> + <div class="location">Lokal</div> </div> - <div class="content">MetaGer-Pass Schlüssel erstellen/aufladen</div> - <div class="location">Server</div> </div> - <div class="section-body"> - <input type="radio" name="finish-purchase-options" value="create-voucher" id="create-voucher-radio"> - <input type="radio" name="finish-purchase-options" value="create-key" id="create-key-radio"> - <input type="radio" name="finish-purchase-options" value="recharge-key" id="recharge-key-radio"> - <div class="options"> - <label for="create-voucher-radio">Gutschein herunterladen</label> - <label for="create-key-radio">Neuen Schlüssel erstellen</label> - <label for="recharge-key-radio">Bestehenden Schlüssel aufladen</label> - </div> - <div id="create-voucher" class="option-body"> - <p> - Mit einem MetaGer-Pass Coupon können Sie Ihren Einkauf zu einem späteren Zeitpunkt einlösen. Der Vorteil wäre, dass es dadurch keinen zeitlichen Bezug zwischen Einkauf und dem verwendeten Schlüssel gibt. Außerdem können Sie den Coupon bei Bedarf auch an andere Personen zur Verwendung weiter geben. Alternativ können Sie die gekauften Suchanfragen auch sofort einlösen. - </p> - <div> - <a href="data:application/mgpass;charset=utf-8;base64,dGVzdHRlYWU=" download="voucher.mgpass" target="_blank" id="voucher-link">Gutschein herunterladen</a> + <div id="finish-purchase" class="step"> + <div class="section-heading"> + <div class="status"> + <img src="/images/loader.gif" alt="Loading" class="loading"> + <div class="finished">✓</div> </div> + <div class="content">MetaGer-Pass Schlüssel erstellen/aufladen</div> + <div class="location">Server</div> </div> - <div id="create-key" class="option-body"> - <p> - Erstellen Sie sich nachfolgend einen neuen anonymen MetaGer-Pass Schlüssel. Auf diesem werden Ihre gekauften Suchanfragen anschließend gutgeschrieben. - </p> - <div> - <button id="create-key-button">MetaGer-Pass Schlüssel erzeugen</button> + <div class="section-body"> + <input type="radio" name="finish-purchase-options" value="create-voucher" id="create-voucher-radio"> + <input type="radio" name="finish-purchase-options" value="create-key" id="create-key-radio"> + <input type="radio" name="finish-purchase-options" value="recharge-key" id="recharge-key-radio"> + <div class="options"> + <label for="create-voucher-radio">Gutschein herunterladen</label> + <label for="create-key-radio">Neuen Schlüssel erstellen</label> + <label for="recharge-key-radio">Bestehenden Schlüssel aufladen</label> + </div> + <div id="create-voucher" class="option-body"> + <p> + Mit einem MetaGer-Pass Coupon können Sie Ihren Einkauf zu einem späteren Zeitpunkt einlösen. + Der Vorteil wäre, dass es dadurch keinen zeitlichen Bezug zwischen Einkauf und dem + verwendeten Schlüssel gibt. Außerdem können Sie den Coupon bei Bedarf auch an andere + Personen zur Verwendung weiter geben. Alternativ können Sie die gekauften Suchanfragen auch + sofort einlösen. + </p> + <div> + <a href="data:application/mgpass;charset=utf-8;base64,dGVzdHRlYWU=" + download="voucher.mgpass" target="_blank" id="voucher-link">Gutschein herunterladen</a> + </div> + </div> + <div id="create-key" class="option-body"> + <p> + Erstellen Sie sich nachfolgend einen neuen anonymen MetaGer-Pass Schlüssel. Auf diesem + werden Ihre gekauften Suchanfragen anschließend gutgeschrieben. + </p> + <div> + <button id="create-key-button">MetaGer-Pass Schlüssel erzeugen</button> + </div> + </div> + <div id="recharge-key" class="option-body"> + <p id="recharge-existing-key"> + Haben Sie bereits einen MetaGer-Pass Schlüssel und möchten diesen weiter verwenden? Geben + Sie nachfolgend Ihren bekannten Schlüssel ein um die gekauften Suchanfragen darauf + gutzuschreiben und den Einkauf abzuschließen. + </p> + <p id="recharge-new-key" class="hidden"> + Ihr neuer MetaGer-Pass Schlüssel wurde erfolgreich erstellt. Bitte notieren Sie ihn sich + gut. Wir können Ihn bei Verlust nicht wiederherstellen. Nachfolgend können Sie dem neuen + Schlüssel Ihre gekauften Suchanfragen gutschreiben um Ihren Einkauf abzuschließen. + </p> + <div> + <input type="text" name="metager-pass-key" id="metager-pass-key"> + <button id="recharge-key-button">MetaGer-Pass Schlüssel aufladen</button> + </div> </div> - </div> - <div id="recharge-key" class="option-body"> - <p> - Haben Sie bereits einen MetaGer-Pass Schlüssel und möchten diesen weiter verwenden? Geben Sie nachfolgend Ihren bekannten Schlüssel ein um die gekauften Suchanfragen darauf gutzuschreiben. - </p> </div> </div> </div> - </div> -</main> + </main> -<%- include('../templates/page_footer'); -%> \ No newline at end of file + <%- include('../templates/page_footer'); -%> \ No newline at end of file -- GitLab