mirror of
https://github.com/marcogll/passkit-generator.git
synced 2026-03-15 15:25:20 +00:00
251 lines
7.1 KiB
JavaScript
251 lines
7.1 KiB
JavaScript
const express = require("express");
|
|
const crypto = require("crypto");
|
|
const shellExec = require("child_process").exec;
|
|
const spawn = require("child_process").spawn;
|
|
const os = require("os");
|
|
const path = require("path");
|
|
const archiver = require("archiver");
|
|
const async = require("async");
|
|
const fs = require("fs");
|
|
|
|
const _configuration = Object.freeze(require("./config.json"));
|
|
|
|
const instance = express();
|
|
const supportedTypesOfPass = /(boarding|event|coupon|generic|store)/i;
|
|
const passModelsDir = _configuration.models.dir;
|
|
const outputDir = _configuration.output.dir;
|
|
const Certificates = _configuration.certificates;
|
|
|
|
function removeDotFiles(from) {
|
|
return from.filter(e => {
|
|
return e.charAt(0) !== "."
|
|
});
|
|
}
|
|
|
|
function capitalizeFirst(str) {
|
|
return str[0].toUpperCase()+str.slice(1);
|
|
}
|
|
|
|
/**
|
|
Checks if the certificate and the key files originated from che .p12 file are available
|
|
|
|
@function checkSignatureRequirements
|
|
@returns {Object} Promise
|
|
*/
|
|
|
|
function checkSignatureRequirements() {
|
|
let checkCertificate = new Promise(function(available, notAvailable) {
|
|
fs.access(`${Certificates.dir}/${Certificates.files.certificate}`, (e) => (!!e ? notAvailable : available)() );
|
|
});
|
|
|
|
let checkKey = new Promise(function(available, notAvailable) {
|
|
fs.access(`${Certificates.dir}/${Certificates.files.key}`, (e) => (!!e ? notAvailable : available)() );
|
|
});
|
|
|
|
return Promise.all([checkCertificate, checkKey]);
|
|
}
|
|
|
|
/**
|
|
Generates the cryptografic signature for the manifest file.
|
|
Spawns Openssl process since Node.js has no support for PKCSs.
|
|
|
|
@function generateManifestSignature
|
|
@params {String} manifestPath - temp dir path created to keep the manifest file.
|
|
@returns {Object} Promise
|
|
*/
|
|
|
|
function generateManifestSignature(manifestPath) {
|
|
return new Promise(function(done, rejected) {
|
|
checkSignatureRequirements()
|
|
.then(function() {
|
|
let opensslError = false;
|
|
let opensslBuffer = [];
|
|
|
|
let opensslProcess = spawn("openssl", [
|
|
"smime",
|
|
"-binary",
|
|
"-sign",
|
|
"-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`),
|
|
// "-out", path.resolve("passCreator", "event.pass", "./signature"),
|
|
"-outform", "DER",
|
|
"-passin", `pass:${Certificates.credentials["dev_pem_key"]}`
|
|
]);
|
|
|
|
opensslProcess.stdout.on("data", function(data) {
|
|
opensslBuffer.push(data);
|
|
});
|
|
|
|
opensslProcess.stderr.on("data", function(data) {
|
|
opensslBuffer.push(data);
|
|
opensslError = true;
|
|
});
|
|
|
|
opensslProcess.stdout.on("end", function() {
|
|
if (opensslError) {
|
|
return rejected(Buffer.concat(opensslBuffer));
|
|
}
|
|
|
|
return done(Buffer.concat(opensslBuffer));
|
|
});
|
|
})
|
|
.catch(function(e) {
|
|
return rejected(`Cannot fulfill signature requirements.\n${e}`);
|
|
});
|
|
});
|
|
}
|
|
|
|
function generateManifest(fromObject, tempFolderPath) {
|
|
return new Promise(function(done, failed) {
|
|
if (!fromObject || typeof fromObject !== "object" && typeof fromObject !== "string") {
|
|
return failed("generateManifest: Argument 0 must be of an object or a string");
|
|
}
|
|
|
|
let source = typeof fromObject === "object" ? JSON.stringify(fromObject) : fromObject;
|
|
|
|
let manifestBuffer = Buffer.from(source);
|
|
|
|
let manifestWS = fs.createWriteStream(`${tempFolderPath}/manifest.json`);
|
|
manifestWS.write(source);
|
|
manifestWS.end();
|
|
|
|
return done(manifestBuffer);
|
|
});
|
|
}
|
|
|
|
instance.listen(80, "0.0.0.0", function(req, res) {
|
|
console.log("Listening on 80")
|
|
});
|
|
|
|
instance.get("/", function (req, res) {
|
|
res.send("Hello there");
|
|
});
|
|
|
|
instance.get("/gen/:type", function (req, res) {
|
|
fs.readdir(passModelsDir, {}, function (err, result) {
|
|
/* Invalid path for passModelsDir */
|
|
if (err) {
|
|
console.error(err);
|
|
throw err;
|
|
}
|
|
|
|
/* Removing all the files and folders which start with "." (hidden files and folder) from the Array */
|
|
result = removeDotFiles(result);
|
|
|
|
/* No folders inside passModelsDir */
|
|
if (!result) {
|
|
res.write("No pass models found.");
|
|
return;
|
|
}
|
|
|
|
/* Type in URL not conformant to supportedTypesOfPass */
|
|
if (!supportedTypesOfPass.test(req.params.type)) {
|
|
res.send("The requested type of pass is not supported.");
|
|
return;
|
|
}
|
|
|
|
/* type in URL has not corresponding model in pass folder */
|
|
if (!result.some(model => model.toLowerCase().includes(req.params.type.toLowerCase()))) {
|
|
res.send("No models available for this query.");
|
|
return;
|
|
}
|
|
|
|
fs.mkdtemp(path.join(os.tmpdir(), "passkitWebServer-"), function(err, tempFolder) {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
|
|
let manifest = {};
|
|
|
|
//fs.readdir(`${tempFolderName}/${req.params.type}.pass`, function(err, fileList) {
|
|
fs.readdir(`${passModelsDir}/${req.params.type}.pass`, function(err, fileList) {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
|
|
fileList = removeDotFiles(fileList);
|
|
|
|
if (!fileList) {
|
|
throw "Unable to create pass. Model has not files inside.";
|
|
}
|
|
|
|
if (!fileList.includes("pass.json")) {
|
|
throw "Unable to create pass. Pass.json file is required but not found in model.";
|
|
}
|
|
|
|
let manifestRaw = {};
|
|
let archive = archiver("zip")
|
|
|
|
async.each(fileList, function getHashAndArchive(file, callback) {
|
|
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) {
|
|
console.log(e);
|
|
return callback(e);
|
|
});
|
|
|
|
passFileStream.on("end", function() {
|
|
manifestRaw[file] = hashFlow.digest("hex").trim();
|
|
return callback();
|
|
});
|
|
}, function end(error) {
|
|
if (error) {
|
|
throw new Error(`Unable to compile manifest. ${error}`);
|
|
}
|
|
|
|
generateManifest(manifestRaw, tempFolder)
|
|
.then(function(manifestBuffer) {
|
|
|
|
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.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;
|
|
});
|
|
});
|
|
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
instance.on("error", function() {
|
|
console.log("got error");
|
|
});
|