From ec0581c7140b1403ed99d4d0fe82758f2197ab10 Mon Sep 17 00:00:00 2001
From: Dominik Hebeler <dominik@hebeler.club>
Date: Mon, 21 Nov 2022 14:34:21 +0100
Subject: [PATCH] metager pass codes can only be redeemed once

---
 pass/app/Key.js               |  1 -
 pass/resources/js/checkout.js | 13 ++++++++++-
 pass/routes/redeem.js         | 41 ++++++++++++++++++++++++++++++++++-
 3 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/pass/app/Key.js b/pass/app/Key.js
index a048dbc..f79e0a1 100644
--- a/pass/app/Key.js
+++ b/pass/app/Key.js
@@ -70,7 +70,6 @@ class Key {
           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 => {
-            console.log(result);
             resolve({
               status: "SUCCESS",
               metager_pass_key: {
diff --git a/pass/resources/js/checkout.js b/pass/resources/js/checkout.js
index c6fc400..888a69a 100644
--- a/pass/resources/js/checkout.js
+++ b/pass/resources/js/checkout.js
@@ -176,7 +176,13 @@ function four_finish_purchase() {
 
   // Make Redeem Button Work
   let redeem_button = document.getElementById("recharge-key-button");
-  redeem_button.addEventListener("pointerdown", () => {
+  redeem_button.addEventListener("pointerdown", e => {
+    if (e.target.disabled) {
+      return;
+    } else {
+      e.target.disabled = true;
+      document.getElementById("metager-pass-key").disabled = true;
+    }
     let post_data = { ...redeem_data };
     post_data.metager_pass_key = document.getElementById("metager-pass-key").value;
     fetch("/redeem", {
@@ -187,7 +193,12 @@ function four_finish_purchase() {
       body: JSON.stringify(post_data),
     }).then(response => response.json())
       .then(response => {
+
         console.log(response);
+      }).catch(reason => {
+        e.target.disabled = false;
+        document.getElementById("metager-pass-key").disabled = false;
+        console.error(reason);
       });
   });
 }
diff --git a/pass/routes/redeem.js b/pass/routes/redeem.js
index a82acb0..d1e2477 100644
--- a/pass/routes/redeem.js
+++ b/pass/routes/redeem.js
@@ -8,6 +8,9 @@ const Crypto = require("../app/Crypto");
 var customParseFormat = require("dayjs/plugin/customParseFormat");
 const Key = require("../app/Key");
 const Order = require("../app/Order");
+const path = require('path');
+const fs = require('fs');
+const readline = require('readline');
 dayjs.extend(customParseFormat);
 
 /**
@@ -47,7 +50,7 @@ router.use(
 );
 
 /* Recharge a MetaGer-Pass Key */
-router.post("/", body("metager_pass_key").isWhitelisted(Key.KEY_CHARSET).isLength({ min: 6, max: 20 }), (req, res, next) => {
+router.post("/", body("metager_pass_key").isWhitelisted(Key.KEY_CHARSET).isLength({ min: 6, max: 20 }), async (req, res, next) => {
   const errors = validationResult(req);
   if (!errors.isEmpty()) {
     return res.status(400).json({ errors: errors.array() });
@@ -74,6 +77,37 @@ router.post("/", body("metager_pass_key").isWhitelisted(Key.KEY_CHARSET).isLengt
     charge_amount += Order.PACKET_SIZE;
   }
 
+  // Check one or more of the codes was already used to redeem a MetaGer-Pass Key
+  let order_month = dayjs(req.body.generation_month);
+  let redeem_file_path = path.join(
+    config.get("storage.data_path"),
+    process.env.NODE_ENV,
+    order_month.format("YYYY"),
+    order_month.format("MM"),
+    "redeemed.json");
+  if (!fs.existsSync(path.dirname(redeem_file_path))) {
+    fs.mkdirSync(path.dirname(redeem_file_path), { recursive: true });
+  }
+
+  if (fs.existsSync(redeem_file_path)) {
+    let rl = readline.createInterface({
+      input: fs.createReadStream(redeem_file_path),
+      output: process.stdout,
+      terminal: false
+    });
+    for await (const line of rl) {
+      for (let i = 0; i < req.body.metager_pass_codes.length; i++) {
+        if (req.body.metager_pass_codes[i].code === line.trim()) {
+          return res.status(400).json({
+            status: "FAILED",
+            msg: ["One or more of the provided MetaGer-Pass Codes are already redeemed."]
+          });
+        }
+      }
+    }
+  }
+
+
   let recharge_tries = 0;
   redis_client.mget(key_recharge_cache_keys).then(async response => {
     for (let i = 0; i < response.length; i++) {
@@ -100,6 +134,11 @@ router.post("/", body("metager_pass_key").isWhitelisted(Key.KEY_CHARSET).isLengt
       await redis_pipeline.exec();
 
       Key.CHARGE_EXISTING_KEY(req.body.metager_pass_key, charge_amount).then(result => {
+        // Key is charged store the redeem codes into filesystem so they can only be used once
+        for (let i = 0; i < req.body.metager_pass_codes.length; i++) {
+          fs.appendFileSync(redeem_file_path, req.body.metager_pass_codes[i].code + "\n");
+        }
+
         res.json(result);
       }).catch(reason => {
         res.status(400).json({
-- 
GitLab