From 68f97d915f7aa20211d66944973d1330fcd8881a Mon Sep 17 00:00:00 2001
From: Dominik Hebeler <dominik@suma-ev.de>
Date: Fri, 18 Nov 2022 16:54:42 +0100
Subject: [PATCH] added first redeem checks

---
 pass/app.js                           |  4 +--
 pass/app/Crypto.js                    | 40 +++++++++++++++++++++++++++
 pass/resources/js/checkout.js         | 21 +++++++++++---
 pass/routes/{capture.js => redeem.js} | 22 +++++++++++----
 4 files changed, 75 insertions(+), 12 deletions(-)
 rename pass/routes/{capture.js => redeem.js} (70%)

diff --git a/pass/app.js b/pass/app.js
index 33e7fd9..2dfc737 100644
--- a/pass/app.js
+++ b/pass/app.js
@@ -8,7 +8,7 @@ var logger = require("morgan");
 
 var indexRouter = require("./routes/index");
 var checkoutRouter = require("./routes/checkout/checkout");
-var captureRouter = require("./routes/capture.js");
+var redeemRouter = require("./routes/redeem.js");
 
 var app = express();
 
@@ -25,7 +25,7 @@ app.use(express.static(path.join(__dirname, "public")));
 
 app.use("/", indexRouter);
 app.use("/checkout", checkoutRouter);
-app.use("/capture", captureRouter);
+app.use("/redeem", redeemRouter);
 
 // Browserified Javascript files
 app.get(
diff --git a/pass/app/Crypto.js b/pass/app/Crypto.js
index e7b2fe7..d7bed25 100644
--- a/pass/app/Crypto.js
+++ b/pass/app/Crypto.js
@@ -70,6 +70,46 @@ class Crypto {
     return private_key;
   }
 
+  async validateMetaGerPassCode(expiration_month, metager_pass_codes) {
+    expiration_month = this.#dayjs(expiration_month);
+    // Check if codes are expired
+    if (!this.#dayjs().isBefore(expiration_month, "month")) {
+      return Promise.reject("Redeem Codes are expired.");
+    }
+
+    let private_key = await this.#private_key_get(expiration_month);
+
+    let uuid_regexExp =
+      /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi;
+    for (let i = 0; i < metager_pass_codes.length; i++) {
+      // Check if code iss in correct format
+      let metager_pass_code = metager_pass_codes[i];
+      if (
+        !metager_pass_code.hasOwnProperty("code") ||
+        !metager_pass_code.code.match(uuid_regexExp)
+      ) {
+        return Promise.reject(metager_pass_code.code + " is not a valid UUID");
+      }
+      // Check if signature is in correct format
+      if (
+        !metager_pass_code.hasOwnProperty("signature") ||
+        !metager_pass_code.signature.match(/^\d+$/gi)
+      ) {
+        return Promise.reject(i + 1 + ". signature has not a valid format");
+      }
+
+      let verification_result = BlindSignature.verify2({
+        unblinded: metager_pass_code.signature,
+        key: private_key,
+        message: metager_pass_code.code,
+      });
+      if (!verification_result) {
+        console.log(i);
+        return Promise.reject("One or more signatures could not be verified");
+      }
+    }
+  }
+
   async sign(encrypted_sales_receipts, order_date) {
     let private_key = await this.#private_key_get(order_date);
     let signed_encrypted_sales_receipts = [];
diff --git a/pass/resources/js/checkout.js b/pass/resources/js/checkout.js
index 2c4c2cb..a040db1 100644
--- a/pass/resources/js/checkout.js
+++ b/pass/resources/js/checkout.js
@@ -138,15 +138,28 @@ function four_finish_purchase() {
 
   // Make Create Key button work
   let create_key_button = document.getElementById("create-key-button");
+  let order_month = require("dayjs")
+    .unix(metager_pass_order_id.substr(0, 10))
+    .format("YYYY-MM-01");
   create_key_button.addEventListener("pointerdown", () => {
-    fetch("/capture/create", {
+    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],
+      });
+    }
+
+    fetch("/redeem/create", {
       method: "POST",
       headers: {
         "Content-Type": "application/json;charset=utf-8",
       },
-      body: JSON.stringify({
-        expiration_month: metager_pass_expires_at,
-      }),
+      body: JSON.stringify(redeem_data),
     });
   });
 }
diff --git a/pass/routes/capture.js b/pass/routes/redeem.js
similarity index 70%
rename from pass/routes/capture.js
rename to pass/routes/redeem.js
index 0089cd5..5936b89 100644
--- a/pass/routes/capture.js
+++ b/pass/routes/redeem.js
@@ -3,6 +3,7 @@ var router = express.Router();
 const { query, body, validationResult } = require("express-validator");
 
 const dayjs = require("dayjs");
+const Crypto = require("../app/Crypto");
 var customParseFormat = require("dayjs/plugin/customParseFormat");
 dayjs.extend(customParseFormat);
 
@@ -18,12 +19,21 @@ router.use(
     .isDate()
     .matches(/^\d{4}-\d{2}-\d{2}$/)
     .withMessage("Invalid Purchase Date supplied"),
-  body("receipts")
-    .custom((receipts) => {
-      let expiration_month = dayjs(req.body.expiration_month);
-      return true;
-    })
-    .withMessage("Supplied Receipts are invalid"),
+  body("receipts").custom(async (receipts, { req }) => {
+    return new Promise((resolve, reject) => {
+      new Crypto()
+        .validateMetaGerPassCode(
+          req.body.expiration_month,
+          req.body.metager_pass_codes
+        )
+        .then(() => {
+          resolve(true);
+        })
+        .catch((reason) => {
+          reject(reason);
+        });
+    });
+  }),
   (req, res, next) => {
     const errors = validationResult(req);
     if (!errors.isEmpty()) {
-- 
GitLab