diff --git a/lib/router.transfer.js b/lib/router.transfer.js index eab5b020f..420debcb6 100644 --- a/lib/router.transfer.js +++ b/lib/router.transfer.js @@ -27,12 +27,15 @@ module.exports = function (app) { async function requestTransfer(req, res) { if (!Util.validateSession(req)) return res.status(401).end(); let owner = sanitka.owner(req.session.owner); - await transfer.request(owner, req.body, (success, response) => { - transferResultRedirect(success, res, response); + + let response = await transfer.request(owner, req.body).catch( (error) => { + return transferResultRedirect(false, res, error); }); + + transferResultRedirect(true, res, response); } - function getDeclineTransfer(req, res) { + async function getDeclineTransfer(req, res) { if (!Util.isDefined(sanitka.udid(req.body.transfer_id))) return Util.responder(res, false, "transfer_id_missing"); @@ -41,12 +44,14 @@ module.exports = function (app) { udids: [] }; - transfer.decline(body, (success, response) => { - transferResultRedirect(success, res, response); + await transfer.decline(body).catch((response)=> { + transferResultRedirect(false, res, response); }); + + transferResultRedirect(true, res, response); } - function postDeclineTransfer(req, res) { + async function postDeclineTransfer(req, res) { if (!Util.validateSession(req)) return res.status(401).end(); @@ -59,24 +64,28 @@ module.exports = function (app) { udids: sanitka.udid(req.body.udid) }; - transfer.decline(body, (success, response) => { - transferResultRedirect(success, res, response); + await transfer.decline(body).catch((response) => { + transferResultRedirect(false, res, response); }); + + transferResultRedirect(true, res, response); } - function getAcceptTransfer(req, res) { + async function getAcceptTransfer(req, res) { if (!Util.isDefined(req.query.transfer_id)) return Util.responder(res, false, "transfer_id_missing"); - transfer.accept({ + let response = transfer.accept({ transfer_id: req.query.transfer_id, udids: [] - }, (success, response) => { - transferResultRedirect(success, res, response); + }).catch((error) => { + return transferResultRedirect(false, res, error); }); + + transferResultRedirect(true, res, response); } - function postAcceptTransfer(req, res) { + async function postAcceptTransfer(req, res) { if (!Util.validateSession(req)) return res.status(401).end(); if (!Util.isDefined(req.body)) return Util.responder(res, false, "transfer_body_missing"); @@ -89,38 +98,36 @@ module.exports = function (app) { console.log("🔨 [debug] with body: ", {body}); - transfer.accept(body, (success, response) => { - if (success === false) { - console.log("postAcceptTransfer response", response); - res.redirect(app_config.public_url + "/error.html?success=failed"); - } else { - res.redirect(app_config.public_url + "/error.html?success=true"); - } + await transfer.accept(body).catch((response) => { + console.log("postAcceptTransfer response", response); + res.redirect(app_config.public_url + "/error.html?success=failed"); }); + + res.redirect(app_config.public_url + "/error.html?success=true"); } /////////////////////////////////////////////////////////////////////// // API ROUTES v2 // - app.post("/api/v2/transfer/request", function (req, res) { - requestTransfer(req, res); + app.post("/api/v2/transfer/request", async function (req, res) { + await requestTransfer(req, res); }); - app.get("/api/v2/transfer/decline", function (req, res) { - getDeclineTransfer(req, res); + app.get("/api/v2/transfer/decline", async function (req, res) { + await getDeclineTransfer(req, res); }); - app.post("/api/v2/transfer/decline", function (req, res) { - postDeclineTransfer(req, res); + app.post("/api/v2/transfer/decline", async function (req, res) { + await postDeclineTransfer(req, res); }); - app.get("/api/v2/transfer/accept", function (req, res) { - getAcceptTransfer(req, res); + app.get("/api/v2/transfer/accept", async function (req, res) { + await getAcceptTransfer(req, res); }); - app.post("/api/v2/transfer/accept", function (req, res) { - postAcceptTransfer(req, res); + app.post("/api/v2/transfer/accept", async function (req, res) { + await postAcceptTransfer(req, res); }); /////////////////////////////////////////////////////////////////////// @@ -128,28 +135,28 @@ module.exports = function (app) { // /* Request device transfer */ - app.post("/api/transfer/request", function (req, res) { - requestTransfer(req, res); + app.post("/api/transfer/request", async function (req, res) { + await requestTransfer(req, res); }); /* Decline device transfer (all by e-mail, selective will be POST) */ - app.get("/api/transfer/decline", function (req, res) { - getDeclineTransfer(req, res); + app.get("/api/transfer/decline", async function (req, res) { + await getDeclineTransfer(req, res); }); /* Decline selective device transfer */ - app.post("/api/transfer/decline", function (req, res) { - postDeclineTransfer(req, res); + app.post("/api/transfer/decline", async function (req, res) { + await postDeclineTransfer(req, res); }); /* Accept device transfer (all by e-mail, selective will be POST) */ - app.get("/api/transfer/accept", function (req, res) { - getAcceptTransfer(req, res); + app.get("/api/transfer/accept", async function (req, res) { + await getAcceptTransfer(req, res); }); /* Accept selective device transfer */ - app.post("/api/transfer/accept", function (req, res) { - postAcceptTransfer(req, res); + app.post("/api/transfer/accept", async function (req, res) { + await postAcceptTransfer(req, res); }); }; \ No newline at end of file diff --git a/lib/thinx/transfer.js b/lib/thinx/transfer.js index 2c5023c39..464d2122c 100644 --- a/lib/thinx/transfer.js +++ b/lib/thinx/transfer.js @@ -38,18 +38,16 @@ module.exports = class Transfer { // migration - transfer_valid(encoded_json_keys, dtid, callback) { + async transfer_valid(encoded_json_keys, dtid) { var json_keys = JSON.parse(encoded_json_keys); - if (json_keys === null) { console.log("[transfer] No udids remaining, expiring record..."); - this.redis.del(dtid); - callback(true, "transfer_completed"); - return false; + await this.redis.del(dtid); + return Promise.resolve(true); } - return true; // no callback called, continue with transfer... + return Promise.resolve(false); // no callback called, continue with transfer... } migrate_device(original_owner, xudid, recipient, body, json_keys, callback) { @@ -234,16 +232,16 @@ module.exports = class Transfer { const identifier = "dtr:" + udid; try { let data = await this.redis.get(identifier); - if ((typeof(data) !== "undefined") && (data !== null)) { + if ((typeof (data) !== "undefined") && (data !== null)) { return Promise.resolve(true); // transfer pending - } + } } catch (e) { console.log("[debug] is_pending", e); } return Promise.resolve(false); // transfer not pending } - store_pending_transfer(udid, transfer_id) { + store_pending_transfer(udid, transfer_id) { this.redis.set("dtr:" + udid, transfer_id); this.redis.expire("dtr:" + udid, 86400); // expire pending transfer in one day... } @@ -317,16 +315,13 @@ module.exports = class Transfer { console.log("ℹī¸ [info] Sending transfer e-mail to requestor: ", senderTransferEmail.to); - this.sendMail(recipientTransferEmail, "recipient_transfer", () => { /* nop */ }); + this.sendMail(recipientTransferEmail, "recipient_transfer", () => { /* nop */ }); } - // TODO: refactor from callback to promise? - async request(owner, body, callback) { + async request(owner, body) { // body should look like { "to":"some@email.com", "udids" : [ "some-udid", "another-udid" ] } - // THX-396 - // when true, sources will be COPIED to new owner as well if (!Util.isDefined(body.mig_sources)) body.mig_sources = false; @@ -336,8 +331,8 @@ module.exports = class Transfer { if (!Util.isDefined(body.mig_apikeys)) body.mig_apikeys = false; // Generic Check - if (!Util.isDefined(body.to)) return callback(false, "missing_recipient"); - if (!Util.isDefined(body.udids)) return callback(false, "missing_subject"); + if (!Util.isDefined(body.to)) return Promise.reject("missing_recipient"); + if (!Util.isDefined(body.udids)) return Promise.reject("missing_subject"); var recipient_id = sha256(prefix + body.to); @@ -345,47 +340,42 @@ module.exports = class Transfer { for (const udid in body.udids) { let state = await this.is_pending(udid); if (state) { - console.log("[debug] transfer already in progress:", {state}); - return callback(false, "transfer_already_in_progress"); + console.log("[debug] transfer already in progress:", { state }); + return Promise.reject("transfer_already_in_progress"); } } // Fetch original owner - userlib.get(owner, (couch_err, ownerdoc) => { - - if (couch_err) { - console.log("Owner", owner, "unknown in transfer request!"); - return callback(false, "owner_unknown"); - } + let ownerdoc = await userlib.get(owner).catch(() => { + console.log("Owner", owner, "unknown in transfer request!"); + return Promise.reject("owner_unknown"); + }); - // Fetch new recipient - userlib.get(recipient_id, (zerr/* , recipient */) => { + // Fetch new recipient + await userlib.get(recipient_id).catch((e) => { + console.log("â˜Ŗī¸ [error] Transfer target body.to id " + recipient_id + "not found", e); + return Promise.reject("recipient_unknown"); + }); - if (zerr) { - console.log("â˜Ŗī¸ [error] Transfer target body.to id " + recipient_id + "not found"); - return callback(false, "recipient_unknown"); - } + // 2. add recipient to body as "from" (ignore user-submitted value) + body.from = ownerdoc.email; - // 2. add recipient to body as "from" (ignore user-submitted value) - body.from = ownerdoc.email; + // 3. store as "dt:uuid()" to redis + var transfer_uuid = uuidV4(); // used for email + var transfer_id = "dt:" + transfer_uuid; + this.redis.set(transfer_id, JSON.stringify(body)); - // 3. store as "dt:uuid()" to redis - var transfer_uuid = uuidV4(); // used for email - var transfer_id = "dt:" + transfer_uuid; - this.redis.set(transfer_id, JSON.stringify(body)); + // 4. store pending transfer for each device + for (var did in body.udids) { + this.store_pending_transfer(did, transfer_id); + } - // 4. store pending transfer for each device - for (var did in body.udids) { - this.store_pending_transfer(did, transfer_id); - } + // 6. send the e-mail + this.sendRecipientTransferEmail(body, transfer_uuid); - // 5. respond with success/failure to the request - callback(true, transfer_uuid); + // 5. respond with success/failure to the request + return Promise.resolve(transfer_uuid); - // 6. send the e-mail async (later) - this.sendRecipientTransferEmail(body, transfer_uuid); - }); - }); } save_dtid(tid, keys, ac) { @@ -408,13 +398,13 @@ module.exports = class Transfer { }); } - async accept(body, accept_callback) { + async accept(body) { // minimum body should look like { "transfer_id":"uuid" } // optional body should look like { "transfer_id":"uuid", "udids" : [ ... ] } if (typeof (body.transfer_id) === "undefined") { - return accept_callback(false, "missing_transfer_id"); + return Promise.reject("missing_transfer_id"); } var transfer_id = body.transfer_id; @@ -430,27 +420,27 @@ module.exports = class Transfer { } const dtid = "dt:" + transfer_id; - let encoded_json_keys = await this.redis.get(dtid); - let keys = JSON.stringify(encoded_json_keys); console.log(`🔨 [debug] [transfer] Fetched DTID: ${dtid} with keys ${{ keys }}`); if (keys.length == 0) { console.log("⚠ī¸ [warning] [transfer] transfer_id not found (empty response array)"); - return accept_callback(false, "transfer_id_not_found"); + return Promise.reject("transfer_id_not_found"); } if (encoded_json_keys === null) { - return accept_callback(false, "transfer_id_not_found"); + return Promise.reject("transfer_id_not_found"); } var json_keys = JSON.parse(encoded_json_keys); - // In case this returns !true (=false), it calls accept_callback on its own. - if (true !== this.transfer_valid(encoded_json_keys, dtid, accept_callback)) { - return; + if (this.transfer_valid(encoded_json_keys, dtid) === true) { + // false ^^^ = no callback called, continue with transfer... + // true ^^^ = no remaining UDIDs, complete. + console.log("[debug] [transfer] No remaining UDIDs."); + return Promise.resolve(true); } if (typeof (json_keys.udids) === "undefined") { @@ -464,14 +454,14 @@ module.exports = class Transfer { var recipient_email = json_keys.to; if (typeof (recipient_email) === "undefined" || recipient_email === null) { - return accept_callback(false, "recipient_to_must_be_set"); + return Promise.reject("recipient_to_must_be_set"); } var recipient = sha256(prefix + recipient_email); var original_owner_email = json_keys.from; if ((typeof (original_owner_email) === "undefined") || (original_owner_email === null)) { - return accept_callback(false, "originator_from_must_be_set"); + return Promise.reject("originator_from_must_be_set"); } var original_owner = sha256(prefix + original_owner_email); @@ -483,7 +473,7 @@ module.exports = class Transfer { for (var udid in udids) { this.redis.del("dtr:" + udid); } - return accept_callback(true, "transfer_completed"); + return Promise.resolve("transfer_completed"); } let sentence = `Accepting device transfer ${transfer_id} for devices ${JSON.stringify(udids)}`; @@ -493,7 +483,6 @@ module.exports = class Transfer { console.log("[OID:" + recipient + "] [TRANSFER_ACCEPT] ", { udids }); const locked_udids = udids; - let promises = []; for (var dindex in locked_udids) { @@ -506,26 +495,24 @@ module.exports = class Transfer { }).catch(e => console.log("[transfer] promise exception", e)); } - storeRemainingKeys(dtid, json_keys, callback) { - this.redis.set(dtid, JSON.stringify(json_keys)); - console.log(`🔨 [debug] [transgfer] L4 Storing remaining keys: ${json_keys.udids}`); - if (json_keys.udids.length > 1) { - // 1 hour to let user accept/decline different devices - this.redis.expire(dtid, 3600); - callback(true, "transfer_partially_completed"); - } else { - this.redis.del(dtid); - callback(true, "transfer_completed"); - } + async storeRemainingKeys(dtid, json_keys) { + await this.redis.set(dtid, JSON.stringify(json_keys)); + console.log(`🔨 [debug] [transgfer] L4 Storing remaining keys: ${json_keys.udids}`); + if (json_keys.udids.length > 1) { + // 1 hour to let user accept/decline different devices + await this.redis.expire(dtid, 3600); + } else { + await this.redis.del(dtid); + } } - decline(body, callback) { + async decline(body) { // minimum body should look like { "transfer_id":"uuid" } // optional body should look like { "transfer_id":"uuid", "udids" : [ ... ] } if (typeof (body.transfer_id) === "undefined") { - return callback(false, "missing_transfer_id"); + return Promise.reject("missing_transfer_id"); } console.log(`[transfer][decline] body: ${JSON.stringify(body)}`); @@ -542,56 +529,54 @@ module.exports = class Transfer { console.log(`🔨 [debug] [transfer] getting DTID ${dtid} on decline`); - this.redis.get(dtid, (error, json) => { + const json = await this.redis.get(dtid); - let json_keys = JSON.parse(json); + let json_keys = JSON.parse(json); - if (json_keys.length == 0) { - console.log("[transfer] json_keys", json_keys); - return callback(false, "transfer_id_invalid"); - } - - if (json_keys === null) { - console.log("[transfer] no such transfer anymore"); - return callback(true, "decline_complete_no_such_dtid"); - } + if (json_keys.length == 0) { + console.log("[transfer] json_keys", json_keys); + return Promise.reject("transfer_id_invalid"); + } - console.log(`🔨 [debug] [transfer] L5 udids ${udids}`); + if (json_keys === null) { + console.log("[transfer] no such transfer anymore"); + return Promise.reject("decline_complete_no_such_dtid"); + } - if ((udids.length === 0) && (typeof (json_keys) !== "undefined")) { - // perform on all devices if udids not given - udids = json_keys.udids; - } + console.log(`🔨 [debug] [transfer] L5 udids ${udids}`); - // Check if there are some devices left - console.log(`🔨 [debug] [transfer] L6 udids ${json_keys.udids}`); + if ((udids.length === 0) && (typeof (json_keys) !== "undefined")) { + // perform on all devices if udids not given + udids = json_keys.udids; + } - if (json_keys.udids.length == 0) { - this.redis.del(dtid); - } + // Check if there are some devices left + console.log(`🔨 [debug] [transfer] L6 udids ${json_keys.udids}`); - var recipient_email = json_keys.to; - var recipient = sha256(prefix + recipient_email); - var original_owner_email = json_keys.from; - var original_owner = sha256(prefix + original_owner_email); + if (json_keys.udids.length == 0) { + this.redis.del(dtid); + } + var recipient_email = json_keys.to; + var recipient = sha256(prefix + recipient_email); + var original_owner_email = json_keys.from; + var original_owner = sha256(prefix + original_owner_email); - console.log(`🔨 [debug] [transfer] Declining transfer ${transfer_id}`); + console.log(`🔨 [debug] [transfer] Declining transfer ${transfer_id}`); - alog.log(original_owner, "Declining device transfer: " + transfer_id + " for devices: " + JSON.stringify(udids), "warning"); - alog.log(recipient, "Declining device transfer: " + transfer_id + " for devices: " + JSON.stringify(udids), "warning"); - console.log("[OID:" + recipient + "] [TRANSFER_DECLINE] " + JSON.stringify(udids)); + alog.log(original_owner, "Declining device transfer: " + transfer_id + " for devices: " + JSON.stringify(udids), "warning"); + alog.log(recipient, "Declining device transfer: " + transfer_id + " for devices: " + JSON.stringify(udids), "warning"); + console.log("[OID:" + recipient + "] [TRANSFER_DECLINE] " + JSON.stringify(udids)); - for (var dindex in udids) { - var udid = udids[dindex]; - delete json_keys.udids[udid]; - } + for (var dindex in udids) { + var udid = udids[dindex]; + delete json_keys.udids[udid]; + } - // Store remaining (not declined) keys - this.storeRemainingKeys(dtid, json_keys, callback); + // Store remaining (not declined) keys + await this.storeRemainingKeys(dtid, json_keys, callback); - callback(true, "decline_completed"); - }); + Promise.resolve("decline_completed"); } }; diff --git a/spec/jasmine/TransferSpec.js b/spec/jasmine/TransferSpec.js index c11d59108..f56c6bb4c 100644 --- a/spec/jasmine/TransferSpec.js +++ b/spec/jasmine/TransferSpec.js @@ -43,9 +43,7 @@ describe("Transfer", function () { console.log(`🚸 [chai] <<< completed Transfer spec`); }); - it("(00) should be able to initiate device transfer, decline and accept another one", async function (done) { - - let accepted = false; + it("(00) should be able to initiate device transfer, decline and accept another one", async function () { var body = { to: "cimrman@thinx.cloud", @@ -54,43 +52,30 @@ describe("Transfer", function () { var owner = envi.oid; - // TODO: Turn this into async - await transfer.request(owner, body, (t_success, response) => { - expect(t_success).to.equal(true); - expect(response).to.be.a('string'); - const tbody = { - transfer_id: response.replace("dt:", ""), - udids: [envi.udid] - }; - - // 00-02 Decline - transfer.decline(tbody, async (d_success, d_response) => { - expect(d_success).to.equal(true); - expect(d_response).to.be.a('string'); - - // TODO: Turn this into async - await transfer.request(owner, body, (b_success, b_response) => { - expect(b_success).to.equal(true); - expect(b_response).to.be.a('string'); // transfer_requested - - // 00-04 Accept - var transfer_body = { - transfer_id: b_response.replace("dt:", ""), - udids: [envi.udid] - }; - - // asyncCall - transfer.accept(transfer_body, (success3, response3) => { - expect(success3).to.equal(true); - expect(response3).to.be.a('string'); - if (!accepted) { - accepted = true; - done(); - } - }); - }); - }); - }); + let response = await transfer.request(owner, body); + + expect(response).to.be.a('string'); + const tbody = { + transfer_id: response.replace("dt:", ""), + udids: [envi.udid] + }; + + // 00-02 Decline + const d_response = await transfer.decline(tbody); + expect(d_response).to.be.a('string'); + + let b_response = await transfer.request(owner, body); + expect(b_success).to.equal(true); + expect(b_response).to.be.a('string'); // transfer_requested + + // 00-04 Accept + var transfer_body = { + transfer_id: b_response.replace("dt:", ""), + udids: [envi.udid] + }; + + const response3 = await transfer.accept(transfer_body); + expect(response3).to.be.a('string'); }); // it-00 // TODO: Fetch real device-id and do the same thing as specific transfer, then do it over v2 again with two new devices or another owner