Improved security checks;

Added back the formatted messages and added new ones;
This commit is contained in:
Alexander Cerutti
2019-06-20 00:30:12 +02:00
parent c733a4ea58
commit 894266de28
3 changed files with 141 additions and 78 deletions

View File

@@ -13,7 +13,7 @@ const readFile = promisify(_readFile);
export async function createPass(options: FactoryOptions): Promise<Pass> { export async function createPass(options: FactoryOptions): Promise<Pass> {
if (!(options && Object.keys(options).length)) { if (!(options && Object.keys(options).length)) {
throw new Error("Unable to create Pass: no options were passed"); throw new Error(formatMessage("CP_NO_OPTS"));
} }
try { try {
@@ -28,13 +28,23 @@ export async function createPass(options: FactoryOptions): Promise<Pass> {
overrides: options.overrides overrides: options.overrides
}); });
} catch (err) { } catch (err) {
// @TODO: analyze the error and stop the execution somehow console.log(err);
throw new Error(formatMessage("CP_INIT_ERROR"));
} }
} }
async function getModelContents(model: FactoryOptions["model"]) { async function getModelContents(model: FactoryOptions["model"]) {
if (!(model && (typeof model === "string" || (typeof model === "object" && Object.keys(model).length)))) { const isModelValid = (
throw new Error("Unable to create Pass: invalid model provided"); model && (
typeof model === "string" || (
typeof model === "object" &&
Object.keys(model).length
)
)
);
if (!isModelValid) {
throw new Error(formatMessage("MODEL_NOT_VALID"));
} }
let modelContents: PartitionedBundle; let modelContents: PartitionedBundle;
@@ -61,16 +71,24 @@ async function getModelContents(model: FactoryOptions["model"]) {
*/ */
async function getModelFolderContents(model: string): Promise<PartitionedBundle> { async function getModelFolderContents(model: string): Promise<PartitionedBundle> {
try {
const modelPath = path.resolve(model) + (!!model && !path.extname(model) ? ".pass" : ""); const modelPath = path.resolve(model) + (!!model && !path.extname(model) ? ".pass" : "");
const modelFilesList = await readDir(modelPath); const modelFilesList = await readDir(modelPath);
// No dot-starting files, manifest and signature // No dot-starting files, manifest and signature
const filteredFiles = removeHidden(modelFilesList).filter(f => !/(manifest|signature)/i.test(f)); const filteredFiles = removeHidden(modelFilesList).filter(f => !/(manifest|signature)/i.test(f));
const isModelInitialized = (
filteredFiles.length &&
filteredFiles.some(file => file.toLowerCase().includes("icon"))
);
// Icon is required to proceed // Icon is required to proceed
if (!(filteredFiles.length && filteredFiles.some(file => file.toLowerCase().includes("icon")))) { if (!isModelInitialized) {
const eMessage = formatMessage("MODEL_UNINITIALIZED", path.parse(this.model).name); throw new Error(formatMessage(
throw new Error(eMessage); "MODEL_UNINITIALIZED",
path.parse(this.model).name
));
} }
// Splitting files from localization folders // Splitting files from localization folders
@@ -126,6 +144,23 @@ async function getModelFolderContents(model: string): Promise<PartitionedBundle>
bundle, bundle,
l10nBundle l10nBundle
}; };
} catch (err) {
if (err.code && err.code === "ENOENT") {
if (err.syscall === "open") {
// file opening failed
throw new Error(formatMessage("MODELF_NOT_FOUND", err.path))
} else if (err.syscall === "scandir") {
// directory reading failed
const pathContents = (err.path as string).split(/(\/|\\\?)/);
throw new Error(formatMessage(
"MODELF_FILE_NOT_FOUND",
pathContents[pathContents.length-1]
))
}
}
throw err;
}
} }
/** /**
@@ -147,8 +182,14 @@ function getModelBufferContents(model: BundleUnit): PartitionedBundle {
const bundleKeys = Object.keys(rawBundle); const bundleKeys = Object.keys(rawBundle);
if (!bundleKeys.length) { const isModelInitialized = (
throw new Error("Cannot proceed with pass creation: bundle not initialized") bundleKeys.length &&
bundleKeys.some(file => file.toLowerCase().includes("icon"))
);
// Icon is required to proceed
if (!isModelInitialized) {
throw new Error(formatMessage("MODEL_UNINITIALIZED", "Buffers"))
} }
// separing localization folders // separing localization folders
@@ -179,7 +220,7 @@ function getModelBufferContents(model: BundleUnit): PartitionedBundle {
async function readCertificatesFromOptions(options: Certificates): Promise<FinalCertificates> { async function readCertificatesFromOptions(options: Certificates): Promise<FinalCertificates> {
if (!(options && Object.keys(options).length && isValid(options, "certificatesSchema"))) { if (!(options && Object.keys(options).length && isValid(options, "certificatesSchema"))) {
throw new Error("Unable to create Pass: certificates schema validation failed."); throw new Error(formatMessage("CP_NO_CERTS"));
} }
// if the signerKey is an object, we want to get // if the signerKey is an object, we want to get

View File

@@ -3,11 +3,15 @@ interface MessageGroup {
} }
const errors: MessageGroup = { const errors: MessageGroup = {
CP_INIT_ERROR: "Something went really bad in the initialization, dude! Please look at the log above this message. It should contain all the infos about the problem.",
CP_NO_OPTS: "Cannot initialize the pass creation: no options were passed.",
CP_NO_CERTS: "Cannot initialize the pass creation: no valid certificates were passed.",
PASSFILE_VALIDATION_FAILED: "Validation of pass type failed. Pass file is not a valid buffer or (more probably) does not respect the schema.\nRefer to https://apple.co/2Nvshvn to build a correct pass.", PASSFILE_VALIDATION_FAILED: "Validation of pass type failed. Pass file is not a valid buffer or (more probably) does not respect the schema.\nRefer to https://apple.co/2Nvshvn to build a correct pass.",
REQUIR_VALID_FAILED: "The options passed to Pass constructor does not meet the requirements.\nRefer to the documentation to compile them correctly.", REQUIR_VALID_FAILED: "The options passed to Pass constructor does not meet the requirements.\nRefer to the documentation to compile them correctly.",
MODEL_UNINITIALIZED: "Provided model ( %s ) matched but unitialized or may not contain icon.\nRefer to https://apple.co/2IhJr0Q, https://apple.co/2Nvshvn and documentation to fill the model correctly.", MODEL_UNINITIALIZED: "Provided model ( %s ) matched but unitialized or may not contain icon.\nRefer to https://apple.co/2IhJr0Q, https://apple.co/2Nvshvn and documentation to fill the model correctly.",
MODEL_NOT_STRING: "A string model name must be provided in order to continue.", MODEL_NOT_VALID: "A model must be provided in form of path (string) or object { 'fileName': Buffer } in order to continue.",
MODEL_NOT_FOUND: "Model %s not found. Provide a valid one to continue.", MODELF_NOT_FOUND: "Model %s not found. Provide a valid one to continue.",
MODELF_FILE_NOT_FOUND: "File %s not found.",
INVALID_CERTS: "Invalid certificate(s) loaded: %s. Please provide valid WWDR certificates and developer signer certificate and key (with passphrase).\nRefer to docs to obtain them.", INVALID_CERTS: "Invalid certificate(s) loaded: %s. Please provide valid WWDR certificates and developer signer certificate and key (with passphrase).\nRefer to docs to obtain them.",
INVALID_CERT_PATH: "Invalid certificate loaded. %s does not exist.", INVALID_CERT_PATH: "Invalid certificate loaded. %s does not exist.",
TRSTYPE_REQUIRED: "Cannot proceed with pass creation. transitType field is required for boardingPasses.", TRSTYPE_REQUIRED: "Cannot proceed with pass creation. transitType field is required for boardingPasses.",

View File

@@ -53,20 +53,38 @@ export class Pass implements PassIndexSignature {
[transitType]: string = ""; [transitType]: string = "";
constructor(options: schema.PassInstance) { constructor(options: schema.PassInstance) {
if (!schema.isValid(options, "instance")) {
throw new Error(formatMessage("REQUIR_VALID_FAILED"));
}
this.Certificates = options.certificates; this.Certificates = options.certificates;
this.l10nBundles = options.model.l10nBundle; this.l10nBundles = options.model.l10nBundle;
this.bundle = { ...options.model.bundle }; this.bundle = { ...options.model.bundle };
options.overrides = options.overrides || {}; // Parsing the options and extracting only the valid ones.
const validOvverrides = schema.getValidated(options.overrides || {}, "supportedOptions") as schema.OverridesSupportedOptions;
if (validOvverrides === null) {
throw new Error(formatMessage("OVV_KEYS_BADFORMAT"))
}
if (Object.keys(validOvverrides).length) {
this._props = { ...validOvverrides };
}
try {
// getting pass.json // getting pass.json
this.passCore = JSON.parse(this.bundle["pass.json"].toString("utf8")); this.passCore = JSON.parse(this.bundle["pass.json"].toString("utf8"));
} catch (err) {
throw new Error(formatMessage("PASSFILE_VALIDATION_FAILED"));
}
this.type = Object.keys(this.passCore) this.type = Object.keys(this.passCore)
.find(key => /(boardingPass|eventTicket|coupon|generic|storeCard)/.test(key)) as keyof schema.ValidPassType; .find(key => /(boardingPass|eventTicket|coupon|generic|storeCard)/.test(key)) as keyof schema.ValidPassType;
if (!this.type) { if (!this.type) {
throw new Error("Missing type in model"); // @TODO: change error message to say it is invalid or missing
throw new Error(formatMessage("NO_PASS_TYPE"));
} }
if (this.type === "boardingPass" && this.passCore[this.type]["transitType"]) { if (this.type === "boardingPass" && this.passCore[this.type]["transitType"]) {
@@ -432,7 +450,7 @@ export class Pass implements PassIndexSignature {
} }
if (typeof format !== "string") { if (typeof format !== "string") {
barcodeDebug(formatMessage("BRC_FORMAT_UNMATCH")); barcodeDebug(formatMessage("BRC_FORMATTYPE_UNMATCH"));
return this; return this;
} }