diff --git a/index.js b/index.js index 4f3116e..817e9c3 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,11 @@ -const express = require("express"); +const os = require("os"); +const fs = require("fs"); +const path = require("path"); const crypto = require("crypto"); const { spawn } = require("child_process"); -const os = require("os"); -const path = require("path"); const archiver = require("archiver"); +const express = require("express"); const async = require("async"); -const fs = require("fs"); const _configuration = Object.freeze(require("./config.json")); @@ -23,9 +23,7 @@ const Certificates = _configuration.certificates; */ function removeDotFiles(from) { - return from.filter(e => { - return e.charAt(0) !== "." - }); + return from.filter(e => e.charAt(0) !== "."); } function capitalizeFirst(str) { @@ -51,6 +49,13 @@ function checkSignatureRequirements() { return Promise.all([checkCertificate, checkKey]); } +/** + Generates a unique UUID + From Github Gist: https://gist.github.com/jed/982883 +*/ + +function UUIDGen(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,UUIDGen)} + /** Generates the cryptografic signature for the manifest file. Spawns Openssl process since Node.js has no support for PKCSs. @@ -60,7 +65,7 @@ function checkSignatureRequirements() { @returns {Object} Promise */ -function generateManifestSignature(manifestPath) { +function generateManifestSignature(manifestUUID) { return new Promise(function(done, rejected) { checkSignatureRequirements() .then(function() { @@ -74,7 +79,7 @@ function generateManifestSignature(manifestPath) { "-certfile", path.resolve(Certificates.dir, Certificates.files["wwdr_pem"]), "-signer", path.resolve(Certificates.dir, Certificates.files["certificate"]), "-inkey", path.resolve(Certificates.dir, Certificates.files["key"]), - "-in", path.resolve(`${manifestPath}/manifest.json`), + "-in", path.resolve(`${os.tmpdir()}/manifest-${manifestUUID}.json`), // "-out", path.resolve("passCreator", "event.pass", "./signature"), "-outform", "DER", "-passin", `pass:${Certificates.credentials["dev_pem_key"]}` @@ -103,18 +108,28 @@ function generateManifestSignature(manifestPath) { }); } -function generateManifest(fromObject, tempFolderPath) { +/** + Generates a Buffer of JSON file (manifest) + @function generateManifest + @params {Object} fromObject - Manifest content + @params {String} manifestUUID + @return {Promise} - Promise with the manifest buffer + @see https://apple.co/2IhJr0Q (PassKit Package Structure) + @see https://apple.co/2K2aY3v (Passes Are Cryptographically Signed and Compressed) +*/ + +function generateManifest(fromObject, manifestUUID) { return new Promise(function(done, failed) { if (!fromObject || typeof fromObject !== "object" && typeof fromObject !== "string") { return failed("generateManifest: Argument 0 is required and must be of an object or a string (source object)"); } - if (!tempFolderPath || typeof tempFolderPath !== "string") { - return failed("generateManifest: Argument 1 is required and must be a string (temporary folder path for manifest)"); + if (!manifestUUID || typeof manifestUUID !== "string") { + return failed("generateManifest: Argument 1 is required and must be a string (unique uuid)."); } const source = typeof fromObject === "object" ? JSON.stringify(fromObject) : fromObject; - let manifestWS = fs.createWriteStream(`${tempFolderPath}/manifest.json`); + let manifestWS = fs.createWriteStream(`${os.tmpdir()}/manifest-${manifestUUID}.json`); manifestWS.write(source); manifestWS.end(); @@ -164,86 +179,75 @@ instance.get("/gen/:type/", function (req, res) { return; } - /* - * Creating a temporary directory to keep the manifest.json of each pass. - * This is done to pass the file as openssl parameter. - * It would be better to pass openssl a buffer but sadly it seems not possible. - * Feel free to contribute if you think there's a better way to achieve this. - */ + // Manifest dictionary + let manifestRaw = {}; + let archive = archiver("zip"); - fs.mkdtemp(path.join(os.tmpdir(), "passkitWebServer-"), function(err, tempFolder) { - if (err) { - throw err; + async.each(list, function getHashAndArchive(file, callback) { + if (file !== "manifest.json" && file !== "signature") { + let passFileStream = fs.createReadStream(`${passModelsDir}/${req.params.type}.pass/${file}`); + let hashFlow = crypto.createHash("sha1"); + + // adding the files to the zip - i'm not using .directory method because it adds also hidden files like .DS_Store on macOS + archive.file(`${passModelsDir}/${req.params.type}.pass/${file}`, { name: file }); + + passFileStream.on("data", function(data) { + hashFlow.update(data); + }); + + passFileStream.on("error", function(e) { + return callback(e); + }); + + passFileStream.on("end", function() { + manifestRaw[file] = hashFlow.digest("hex").trim(); + return callback(); + }); + } elseĀ { + // skipping files + return callback(); + } + }, function end(error) { + if (error) { + throw new Error(`Unable to compile manifest. ${error}`); } - // Manifest dictionary - let manifestRaw = {}; - let archive = archiver("zip"); + let uuid = UUIDGen(); - async.each(list, function getHashAndArchive(file, callback) { - if (file !== "manifest.json" && file !== "signature") { - let passFileStream = fs.createReadStream(`${passModelsDir}/${req.params.type}.pass/${file}`); - let hashFlow = crypto.createHash("sha1"); + generateManifest(manifestRaw, uuid) + .then(function(manifestBuffer) { - // adding the files to the zip - i'm not using .directory method because it adds also hidden files like .DS_Store on macOS - archive.file(`${passModelsDir}/${req.params.type}.pass/${file}`, { name: file }); + archive.append(manifestBuffer, { name: "manifest.json" }); - passFileStream.on("data", function(data) { - hashFlow.update(data); - }); + generateManifestSignature(uuid) + .then(function(signatureBuffer) { - passFileStream.on("error", function(e) { - return callback(e); - }); + if (!fs.existsSync("output")) { + fs.mkdirSync("output"); + } - passFileStream.on("end", function() { - manifestRaw[file] = hashFlow.digest("hex").trim(); - return callback(); - }); - } elseĀ { - // skipping files - return callback(); - } - }, function end(error) { - if (error) { - throw new Error(`Unable to compile manifest. ${error}`); - } + archive.append(signatureBuffer, { name: "signature" }); + let outputWS = fs.createWriteStream(`${outputDir}/${req.params.type}.pkpass`); - generateManifest(manifestRaw, tempFolder) - .then(function(manifestBuffer) { + archive.pipe(outputWS); + archive.finalize(); - archive.append(manifestBuffer, { name: "manifest.json" }); - - generateManifestSignature(tempFolder) - .then(function(signatureBuffer) { - - if (!fs.existsSync("output")) { - fs.mkdirSync("output"); - } - - archive.append(signatureBuffer, { name: "signature" }); - let outputWS = fs.createWriteStream(`${outputDir}/${req.params.type}.pkpass`); - - archive.pipe(outputWS); - archive.finalize(); - - outputWS.on("close", function() { - res.status(201).download(`${outputDir}/${req.params.type}.pkpass`, `${req.params.type}.pkpass`, { - cacheControl: false, - headers: { - "Content-type": "application/vnd.apple.pkpass", - "Content-length": fs.statSync(`${outputDir}/${req.params.type}.pkpass`).size - } - }); + outputWS.on("close", function() { + res.status(201).download(`${outputDir}/${req.params.type}.pkpass`, `${req.params.type}.pkpass`, { + cacheControl: false, + headers: { + "Content-type": "application/vnd.apple.pkpass", + "Content-length": fs.statSync(`${outputDir}/${req.params.type}.pkpass`).size + } }); - }) - .catch(function(buffer) { - throw buffer.toString(); }); }) - .catch(function(error) { - throw error; + .catch(function(buffer) { + throw buffer.toString(); }); + }) + .catch(function(error) { + throw error; }); }); });