Replaced the creation of one new folder in tmpDir for each manifest to the creation in tmpDir of one manifest with unique generated uuid

Added some comments;
Renamed some parameters
This commit is contained in:
Alexander Cerutti
2018-05-07 22:04:51 +02:00
parent d3b4a17e84
commit 1d0ed48084

162
index.js
View File

@@ -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 crypto = require("crypto");
const { spawn } = require("child_process"); const { spawn } = require("child_process");
const os = require("os");
const path = require("path");
const archiver = require("archiver"); const archiver = require("archiver");
const express = require("express");
const async = require("async"); const async = require("async");
const fs = require("fs");
const _configuration = Object.freeze(require("./config.json")); const _configuration = Object.freeze(require("./config.json"));
@@ -23,9 +23,7 @@ const Certificates = _configuration.certificates;
*/ */
function removeDotFiles(from) { function removeDotFiles(from) {
return from.filter(e => { return from.filter(e => e.charAt(0) !== ".");
return e.charAt(0) !== "."
});
} }
function capitalizeFirst(str) { function capitalizeFirst(str) {
@@ -51,6 +49,13 @@ function checkSignatureRequirements() {
return Promise.all([checkCertificate, checkKey]); 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. Generates the cryptografic signature for the manifest file.
Spawns Openssl process since Node.js has no support for PKCSs. Spawns Openssl process since Node.js has no support for PKCSs.
@@ -60,7 +65,7 @@ function checkSignatureRequirements() {
@returns {Object} Promise @returns {Object} Promise
*/ */
function generateManifestSignature(manifestPath) { function generateManifestSignature(manifestUUID) {
return new Promise(function(done, rejected) { return new Promise(function(done, rejected) {
checkSignatureRequirements() checkSignatureRequirements()
.then(function() { .then(function() {
@@ -74,7 +79,7 @@ function generateManifestSignature(manifestPath) {
"-certfile", path.resolve(Certificates.dir, Certificates.files["wwdr_pem"]), "-certfile", path.resolve(Certificates.dir, Certificates.files["wwdr_pem"]),
"-signer", path.resolve(Certificates.dir, Certificates.files["certificate"]), "-signer", path.resolve(Certificates.dir, Certificates.files["certificate"]),
"-inkey", path.resolve(Certificates.dir, Certificates.files["key"]), "-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"), // "-out", path.resolve("passCreator", "event.pass", "./signature"),
"-outform", "DER", "-outform", "DER",
"-passin", `pass:${Certificates.credentials["dev_pem_key"]}` "-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) { return new Promise(function(done, failed) {
if (!fromObject || typeof fromObject !== "object" && typeof fromObject !== "string") { 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)"); return failed("generateManifest: Argument 0 is required and must be of an object or a string (source object)");
} }
if (!tempFolderPath || typeof tempFolderPath !== "string") { if (!manifestUUID || typeof manifestUUID !== "string") {
return failed("generateManifest: Argument 1 is required and must be a string (temporary folder path for manifest)"); return failed("generateManifest: Argument 1 is required and must be a string (unique uuid).");
} }
const source = typeof fromObject === "object" ? JSON.stringify(fromObject) : fromObject; 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.write(source);
manifestWS.end(); manifestWS.end();
@@ -164,86 +179,75 @@ instance.get("/gen/:type/", function (req, res) {
return; return;
} }
/* // Manifest dictionary
* Creating a temporary directory to keep the manifest.json of each pass. let manifestRaw = {};
* This is done to pass the file as openssl parameter. let archive = archiver("zip");
* 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.
*/
fs.mkdtemp(path.join(os.tmpdir(), "passkitWebServer-"), function(err, tempFolder) { async.each(list, function getHashAndArchive(file, callback) {
if (err) { if (file !== "manifest.json" && file !== "signature") {
throw err; 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 uuid = UUIDGen();
let manifestRaw = {};
let archive = archiver("zip");
async.each(list, function getHashAndArchive(file, callback) { generateManifest(manifestRaw, uuid)
if (file !== "manifest.json" && file !== "signature") { .then(function(manifestBuffer) {
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.append(manifestBuffer, { name: "manifest.json" });
archive.file(`${passModelsDir}/${req.params.type}.pass/${file}`, { name: file });
passFileStream.on("data", function(data) { generateManifestSignature(uuid)
hashFlow.update(data); .then(function(signatureBuffer) {
});
passFileStream.on("error", function(e) { if (!fs.existsSync("output")) {
return callback(e); fs.mkdirSync("output");
}); }
passFileStream.on("end", function() { archive.append(signatureBuffer, { name: "signature" });
manifestRaw[file] = hashFlow.digest("hex").trim(); let outputWS = fs.createWriteStream(`${outputDir}/${req.params.type}.pkpass`);
return callback();
});
} else {
// skipping files
return callback();
}
}, function end(error) {
if (error) {
throw new Error(`Unable to compile manifest. ${error}`);
}
generateManifest(manifestRaw, tempFolder) archive.pipe(outputWS);
.then(function(manifestBuffer) { archive.finalize();
archive.append(manifestBuffer, { name: "manifest.json" }); outputWS.on("close", function() {
res.status(201).download(`${outputDir}/${req.params.type}.pkpass`, `${req.params.type}.pkpass`, {
generateManifestSignature(tempFolder) cacheControl: false,
.then(function(signatureBuffer) { headers: {
"Content-type": "application/vnd.apple.pkpass",
if (!fs.existsSync("output")) { "Content-length": fs.statSync(`${outputDir}/${req.params.type}.pkpass`).size
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
}
});
}); });
})
.catch(function(buffer) {
throw buffer.toString();
}); });
}) })
.catch(function(error) { .catch(function(buffer) {
throw error; throw buffer.toString();
}); });
})
.catch(function(error) {
throw error;
}); });
}); });
}); });