tenfourfox/services/mobileid/MobileIdentityVerificationF...

216 lines
7.3 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["MobileIdentityVerificationFlow"];
const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
this.MobileIdentityVerificationFlow = function(aVerificationOptions,
aUI,
aClient,
aVerifyStrategy,
aCleanupStrategy) {
this.verificationOptions = aVerificationOptions;
this.ui = aUI;
this.client = aClient;
this.retries = VERIFICATIONCODE_RETRIES;
this.verifyStrategy = aVerifyStrategy;
this.cleanupStrategy = aCleanupStrategy;
};
MobileIdentityVerificationFlow.prototype = {
doVerification: function() {
log.debug("Start verification flow");
return this.register()
.then(
(registerResult) => {
log.debug("Register result ${}", registerResult);
if (!registerResult || !registerResult.msisdnSessionToken) {
return Promise.reject(ERROR_INTERNAL_UNEXPECTED);
}
this.sessionToken = registerResult.msisdnSessionToken;
// We save the timestamp of the start of the verification timeout to be
// able to provide to the UI the remaining time on each retry.
if (!this.timer) {
log.debug("Creating verification code timer");
this.timerCreation = Date.now();
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.timer.initWithCallback(this.onVerificationCodeTimeout.bind(this),
VERIFICATIONCODE_TIMEOUT,
this.timer.TYPE_ONE_SHOT);
}
if (!this.verifyStrategy) {
return Promise.reject(ERROR_INTERNAL_INVALID_VERIFICATION_FLOW);
}
return this.verifyStrategy()
.then(() => {
return this._doVerification();
}, (reason) => {
this.verificationCodeDeferred.reject(reason);
});
}
)
},
_doVerification: function() {
log.debug("_doVerification");
this.verificationCodeDeferred = Promise.defer();
// If the verification flow can be for an external phone number,
// we need to ask the user for the verification code.
// In that case we don't do a notification about the verification
// process being done until the user enters the verification code
// in the UI.
if (this.verificationOptions.external) {
let timeLeft = 0;
if (this.timer) {
timeLeft = this.timerCreation + VERIFICATIONCODE_TIMEOUT -
Date.now();
}
this.ui.verificationCodePrompt(this.retries,
VERIFICATIONCODE_TIMEOUT / 1000,
timeLeft / 1000)
.then(
(verificationCode) => {
if (!verificationCode) {
return this.verificationCodeDeferred.reject(
ERROR_INTERNAL_INVALID_PROMPT_RESULT);
}
// If the user got the verification code that means that the
// introduced phone number didn't belong to any of the inserted
// SIMs.
this.ui.verify();
this.verificationCodeDeferred.resolve(verificationCode);
}
);
} else {
this.ui.verify();
}
return this.verificationCodeDeferred.promise.then(
this.onVerificationCode.bind(this)
);
},
// When we receive a verification code from the UI, we check it against
// the server. If the verification code is incorrect, we decrease the
// number of retries left and allow the user to try again. If there is no
// possible retry left, we notify about this error so the UI can allow the
// user to request the resend of a new verification code.
onVerificationCode: function(aVerificationCode) {
log.debug("onVerificationCode " + aVerificationCode);
if (!aVerificationCode) {
this.ui.error(ERROR_INVALID_VERIFICATION_CODE);
return this._doVerification();
}
// Before checking the verification code against the server we set the
// "verifying" flag to queue timeout expiration events received before
// the server request is completed. If the server request is positive
// we will discard the timeout event, otherwise we will progress the
// event to the UI to allow the user to retry.
this.verifying = true;
return this.verifyCode(aVerificationCode)
.then(
(result) => {
if (!result) {
return Promise.reject(INTERNAL_UNEXPECTED);
}
// The code was correct!
// At this point the phone number is verified.
// We return the given verification options with the session token
// to be stored in the credentials store. With this data we will be
// asking the server to give us a certificate to generate assertions.
this.verificationOptions.sessionToken = this.sessionToken;
this.verificationOptions.msisdn = result.msisdn ||
this.verificationOptions.msisdn;
return this.verificationOptions;
},
(error) => {
log.error("Verification code error " + error);
this.retries--;
log.error("Retries left " + this.retries);
if (!this.retries) {
this.ui.error(ERROR_NO_RETRIES_LEFT);
this.timer.cancel();
this.timer = null;
return Promise.reject(ERROR_NO_RETRIES_LEFT);
}
this.ui.error(ERROR_INVALID_VERIFICATION_CODE);
this.verifying = false;
if (this.queuedTimeout) {
this.onVerificationCodeTimeout();
}
return this._doVerification();
}
);
},
onVerificationCodeTimeout: function() {
// It is possible that we get the timeout when we are checking a
// verification code with the server. In that case, we queue the
// timeout to be triggered after we receive the reply from the server
// if needed.
if (this.verifying) {
this.queuedTimeout = true;
return;
}
// When the verification process times out we do a clean up, reject
// the corresponding promise and notify the UI about the timeout.
if (this.verificationCodeDeferred) {
this.verificationCodeDeferred.reject(ERROR_VERIFICATION_CODE_TIMEOUT);
}
this.ui.error(ERROR_VERIFICATION_CODE_TIMEOUT);
},
register: function() {
return this.client.register();
},
verifyCode: function(aVerificationCode) {
return this.client.verifyCode(this.sessionToken, aVerificationCode);
},
unregister: function() {
return this.client.unregister(this.sessionToken);
},
cleanup: function(aUnregister = false) {
log.debug("Verification flow cleanup");
this.queuedTimeout = false;
this.retries = VERIFICATIONCODE_RETRIES;
if (this.timer) {
this.timer.cancel();
this.timer = null;
}
if (aUnregister) {
this.unregister().
then(
() => {
this.sessionToken = null;
}
);
}
if (this.cleanupStrategy) {
this.cleanupStrategy();
}
}
};