Skip to content

Instantly share code, notes, and snippets.

@anantn
Created February 18, 2012 08:28
Show Gist options
  • Save anantn/1858230 to your computer and use it in GitHub Desktop.
Save anantn/1858230 to your computer and use it in GitHub Desktop.
How to verify a Mozilla Apps receipt
function doVerifyReceipt(cb, options) {
navigator.mozApps.getSelf(function(app) {
var record = app.receipt;
if (!record) {
cb({"error": "Application not installed"});
return;
}
if (typeof cb !== "function") {
throw "Callback not provided in doVerifyReceipt";
}
function base64urldecode(arg) {
var s = arg;
s = s.replace(/-/g, '+'); // 62nd char of encoding
s = s.replace(/_/g, '/'); // 63rd char of encoding
switch (s.length % 4) // Pad with trailing '='s
{
case 0: break; // No pad chars in this case
case 2: s += "=="; break; // Two pad chars
case 3: s += "="; break; // One pad char
default: throw new InputException("Illegal base64url string!");
}
return window.atob(s); // Standard base64 decoder
}
function parseReceipt(rcptData) {
// rcptData is a JWT. We should use a JWT library.
var data = rcptData.split(".");
if (data.length != 3)
return null;
// convert base64url to base64
var payload = base64urldecode(data[1]);
var parsed = JSON.parse(payload);
return parsed;
}
try {
if (!record.install_data) {
throw "Receipt not found";
}
var receipt = parseReceipt(record.install_data.receipt);
if (!receipt) {
throw "Invalid Receipt";
}
} catch (e) {
cb({"error": e});
return;
}
// Two status "flags", one for verify XHR other for BrowserID XHR
// These two XHRs run in parallel, and the first one to error out invokes cb()
// If both XHRs succeed, the last one to succeed will invoke cb()
var assertion;
var errorSent = false;
var verifyStatus = false;
var assertStatus = false;
var verifyURL = receipt.verify;
var verifyReq = new XMLHttpRequest();
verifyReq.open('POST', verifyURL, true);
verifyReq.onreadystatechange = function (aEvt) {
if (verifyReq.readyState == 4) {
try {
if (verifyReq.status == 200) {
var resp = JSON.parse(verifyReq.responseText);
if (resp.status != "ok") {
throw resp.status;
}
dump("verifyReq success! " + verifyReq.responseText + "\n");
verifyStatus = true;
if (options && options.receiptVerified && typeof options.receiptVerified == "function") {
options.receiptVerified(receipt);
}
if (verifyStatus && assertStatus) {
cb({"success": {"receipt": receipt, "assertion": assertion}});
}
} else {
throw verifyReq.status;
}
} catch(e) {
dump("error in verifyReq! " + verifyReq.responseText);
if (!errorSent) {
cb({"error": "Invalid Receipt: " + verifyReq.responseText});
errorSent = true;
}
}
}
};
try {
verifyReq.send(record.install_data.receipt);
} catch (e) {
// Offline
if (!errorSent) {
cb({"error": "Offline: Could not verify receipt"});
errorSent = true;
}
}
// Start BrowserID verification
var idOptions = {"silent": true, "requiredEmail": receipt.user.value};
navigator.id.get(function(ast) {
assertion = ast;
if (!assertion) {
cb({"error": "Invalid Identity"});
return;
}
var assertReq = new XMLHttpRequest();
assertReq.open('POST', 'https://browserid.org/verify', true);
assertReq.onreadystatechange = function(aEvt) {
if (assertReq.readyState == 4) {
try {
if (assertReq.status == 200) {
var resp = JSON.parse(assertReq.responseText);
if (resp["status"] != "okay") {
throw resp.status;
}
dump("assertReq success! " + assertReq.responseText + "\n");
assertStatus = true;
if (verifyStatus && assertStatus) {
cb({"success": {"receipt": receipt, "assertion": assertion}});
}
} else {
throw assertReq.status;
}
} catch(e) {
dump("error in assertReq! " + assertReq.responseText);
if (!errorSent) {
cb({"error": "Invalid Identity: " + assertReq.responseText});
errorSent = true;
}
}
}
};
var body = "assertion=" + encodeURIComponent(assertion) + "&audience=" +
encodeURIComponent(window.location.protocol + "//" + window.location.host);
assertReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
try {
assertReq.send(body);
} catch (e) {
// Offline
if (!errorSent) {
cb({"error": "Offline: Could not verify receipt"});
errorSent = true;
}
}
}, idOptions, function(err) {
// Ideally we'd implement a fallback here where we open a BrowserID
// popup dialog. But this is not trivial to do, punting for now.
if (!errorSent) {
cb({"error": "Could not obtain Identity: " + err});
errorSent = true;
}
});
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment