diff --git a/docs/api.md b/docs/api.md index c307bdbf89312121a1963fc01c2fd83b96010c26..25f04517bcffc801abae4b6fdd4e9115dc2f039a 100644 --- a/docs/api.md +++ b/docs/api.md @@ -10,6 +10,36 @@ API access is granted only authenticated you need to supply an Authorization hea Authorization: Bearer <API-Key> ``` +## `POST /api/json/key/create` + +Creates a key and charges it by specified amount. Creates a Order to do so. A note can be attached to the Order. +`manual` is currently the only valid payment processor for API. +Can take either amount or price. If both supplied only amount is taken into account. + +### Parameters + +```json +{ + "amount": <AMOUNT_TO_CHARGE>, + "price": <PRICE_TO_CHARGE>, + "payment_processor": "manual", + "note": <NOTE_FOR_ORDER> +} +``` + +### Example Response + +Successfull discharge will have a response code of `201` +If the key cannot be charged because it is already charged with too many Orders response code will be `403` + +```json +{ + "key": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "order_id": 123456, + "charged": <AMOUNT_CHARGED> +} +``` + ## `GET /api/json/key/:key` Retrieves detailed information for a given key. @@ -83,7 +113,7 @@ If the key cannot be charged because it is already charged with too many Orders ```json { "key": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", - "charge": 100, + "order_id": 123456, "charged": <AMOUNT_CHARGED> } ``` diff --git a/pass/app/Key.js b/pass/app/Key.js index e19f5d636191df0f1d15e8edc42a180d4820d67b..3554a17d7a18068348eeb3b4adabf2fce1750f76 100644 --- a/pass/app/Key.js +++ b/pass/app/Key.js @@ -358,23 +358,21 @@ class Key { /** * - * @returns A new MetaGer-Pass Key with 0 searches + * @returns {Promise<Key>} A new MetaGer-Pass Key with 0 searches */ static async GET_NEW_KEY() { - return new Promise(async (resolve, reject) => { - let redis_client = RedisClient.CLIENT(); - let crypto = require("crypto"); - do { - let key = crypto.randomUUID(); + let redis_client = RedisClient.CLIENT(); + let crypto = require("crypto"); + do { + let key = crypto.randomUUID(); - let key_exists = await redis_client.exists(Key.DATABASE_PREFIX + key); - if (!key_exists) { - await redis_client.quit(); - return resolve(key); - } - await new Promise((resolve) => setTimeout(resolve, 50)); - } while (true); - }); + let key_exists = await redis_client.exists(Key.DATABASE_PREFIX + key); + if (!key_exists) { + await redis_client.quit(); + return Key.GET_KEY(key); + } + await new Promise((resolve) => setTimeout(resolve, 50)); + } while (true); } } diff --git a/pass/routes/api.js b/pass/routes/api.js index 5bb9d65702223e0ebd71efb5486ccfd24feae8e0..23793e2c89de777005836ae493556aae9f67c7e5 100644 --- a/pass/routes/api.js +++ b/pass/routes/api.js @@ -26,6 +26,51 @@ router.use((req, res, next) => { } }); +router.post("/key/create", (req, res) => { + let amount = req.body.amount; + if (!amount) { + // If amount is not given but price is + // Calculate amount from price + let price = req.body.price; + if (price) { + amount = Math.ceil((price / config.get("price.per_300")) * 300); + } else { + res.status(403).json({ + code: 403, + error: "Either :amount or :price must be supplied.", + }); + return; + } + } + let note = req.body.note ?? ""; + /** + * @type {Order} + */ + let new_order; + Key.GET_NEW_KEY() + .then((key) => + Order.CREATE_NEW_ORDER(amount, key.get_key(), new Manual(note)) + ) + .then((order) => { + new_order = order; + return new_order.captureOrder(); + }) + .then(() => new_order.chargeKey()) + .then(async () => { + res.status(201).json({ + key: await new_order.getKeyFromOrderLink(), + order_id: new_order.getOrderID(), + charged: new_order.getAmount(), + }); + }) + .catch((reason) => { + res.status(403).json({ + code: 403, + error: "Cannot create Order for given Key", + }); + }); +}); + router.get("/key/:key", (req, res) => { Key.GET_KEY(req.params.key, false).then((key) => { res.json({ diff --git a/pass/routes/key.js b/pass/routes/key.js index 8e3f5d0b658180664ad322b7af8aed54f962cb56..1c1c64d0047d37db1bcd60c661773cbe7689fc80 100644 --- a/pass/routes/key.js +++ b/pass/routes/key.js @@ -1,10 +1,16 @@ var express = require("express"); var router = express.Router(); -var multer = require('multer'); -const { param, body, check, oneOf, validationResult, matchedData } = require("express-validator"); +var multer = require("multer"); +const { + param, + body, + check, + oneOf, + validationResult, + matchedData, +} = require("express-validator"); const config = require("config"); - var orderRouter = require("./orders/orders"); var checkout_router_paypal = require("./checkout/paypal"); var checkout_router_manual = require("./checkout/manual"); @@ -13,7 +19,7 @@ var Key = require("../app/Key"); router.get("/create", function (req, res, next) { Key.GET_NEW_KEY().then((key) => { - res.redirect(`${res.locals.baseDir}/key/` + key + "?new=true"); + res.redirect(`${res.locals.baseDir}/key/` + key.get_key() + "?new=true"); }); }); @@ -22,11 +28,13 @@ router.get("/remove", (req, res) => { res.clearCookie("key"); } res.redirect("/"); -}) +}); router.get("/enter", function (req, res, next) { if (req.cookies.key) { - res.redirect(`${res.locals.baseDir}/key/` + encodeURIComponent(req.cookies.key)); + res.redirect( + `${res.locals.baseDir}/key/` + encodeURIComponent(req.cookies.key) + ); } else { res.render("login/key"); } @@ -38,11 +46,15 @@ const upload = multer({ limits: { fileSize: 5 * 1024 * 1024, files: 1, - fields: 1 - } + fields: 1, + }, }); -router.post("/enter", upload.single('file'), - body("key").customSanitizer(value => Key.GET_KEY(value).then(key => key.get_key())), +router.post( + "/enter", + upload.single("file"), + body("key").customSanitizer((value) => + Key.GET_KEY(value).then((key) => key.get_key()) + ), function (req, res, next) { const errors = validationResult(req); if (!errors.isEmpty()) { @@ -56,7 +68,7 @@ router.post("/enter", upload.single('file'), } else if (typeof req.file === "undefined") { res.render("login/key", { errors: "File not provided or invalid" }); } else { - const jimp = require('jimp'); + const jimp = require("jimp"); jimp.read(req.file.buffer, (err, image) => { if (err) { @@ -79,7 +91,9 @@ router.post("/enter", upload.single('file'), } let key = url.searchParams.get("key"); if (key !== null) { - res.redirect(`${res.locals.baseDir}/key/` + encodeURIComponent(key)); + res.redirect( + `${res.locals.baseDir}/key/` + encodeURIComponent(key) + ); } else { res.render("login/key", { errors: ["Error parsing URL"] }); } @@ -87,8 +101,8 @@ router.post("/enter", upload.single('file'), qr.decode(image.bitmap); }); } - - }); + } +); router.use("/:key", param("key").isUUID(4), async (req, res, next) => { // Input Validation @@ -102,13 +116,16 @@ router.use("/:key", param("key").isUUID(4), async (req, res, next) => { encodeURIComponent(req.params.key); let QRCode = require("qrcode"); - let qr_data_uri = await QRCode.toDataURL(metager_url, { errorCorrectionLevel: 'H', scale: 8 }); + let qr_data_uri = await QRCode.toDataURL(metager_url, { + errorCorrectionLevel: "H", + scale: 8, + }); - Key.GET_KEY(req.params.key, false).then(key => { + Key.GET_KEY(req.params.key, false).then((key) => { req.data = Object.assign(req.data, { created_new: req.query.new === "true" ? true : false, price: { - per_300: config.get("price.per_300") + per_300: config.get("price.per_300"), }, key: { key: key, @@ -133,9 +150,12 @@ router.get("/:key", async (req, res) => { if (req.data.admin) { res.redirect(`${res.locals.baseDir}/logout`); return; - } else if (!req.cookies.key || req.cookies.key !== req.data.key.key.get_key()) { + } else if ( + !req.cookies.key || + req.cookies.key !== req.data.key.key.get_key() + ) { res.cookie("key", req.data.key.key.get_key(), { - sameSite: "lax" + sameSite: "lax", }); } res.render("key", req.data); @@ -171,7 +191,10 @@ router.use( // Add a URL to change the checkout amount req.data.change_url = { - amount: `${res.locals.baseDir}/key/` + encodeURIComponent(req.data.key.key.get_key()) + "#charge", + amount: + `${res.locals.baseDir}/key/` + + encodeURIComponent(req.data.key.key.get_key()) + + "#charge", }; next("route");