Bumping manifests a=b2g-bump
[gecko.git] / services / mobileid / MobileIdentityVerificationFlow.jsm
blobbd0f66ccbede1e14752ef848ae76da4a777376d3
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 this.EXPORTED_SYMBOLS = ["MobileIdentityVerificationFlow"];
9 const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
11 Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
12 Cu.import("resource://gre/modules/Promise.jsm");
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
15 this.MobileIdentityVerificationFlow = function(aVerificationOptions,
16                                                aUI,
17                                                aClient,
18                                                aVerifyStrategy,
19                                                aCleanupStrategy) {
20   this.verificationOptions = aVerificationOptions;
21   this.ui = aUI;
22   this.client = aClient;
23   this.retries = VERIFICATIONCODE_RETRIES;
24   this.verifyStrategy = aVerifyStrategy;
25   this.cleanupStrategy = aCleanupStrategy;
28 MobileIdentityVerificationFlow.prototype = {
30   doVerification: function() {
31     log.debug("Start verification flow");
32     return this.register()
33     .then(
34       (registerResult) => {
35         log.debug("Register result ${}", registerResult);
36         if (!registerResult || !registerResult.msisdnSessionToken) {
37           return Promise.reject(ERROR_INTERNAL_UNEXPECTED);
38         }
39         this.sessionToken = registerResult.msisdnSessionToken;
40         // We save the timestamp of the start of the verification timeout to be
41         // able to provide to the UI the remaining time on each retry.
42         if (!this.timer) {
43           log.debug("Creating verification code timer");
44           this.timerCreation = Date.now();
45           this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
46           this.timer.initWithCallback(this.onVerificationCodeTimeout.bind(this),
47                                       VERIFICATIONCODE_TIMEOUT,
48                                       this.timer.TYPE_ONE_SHOT);
49         }
51         if (!this.verifyStrategy) {
52           return Promise.reject(ERROR_INTERNAL_INVALID_VERIFICATION_FLOW);
53         }
55         return this.verifyStrategy()
56         .then(() => {
57           return this._doVerification();
58         }, (reason) => {
59           this.verificationCodeDeferred.reject(reason);
60         });
61       }
62     )
63   },
65   _doVerification: function() {
66     log.debug("_doVerification");
68     this.verificationCodeDeferred = Promise.defer();
70     // If the verification flow can be for an external phone number,
71     // we need to ask the user for the verification code.
72     // In that case we don't do a notification about the verification
73     // process being done until the user enters the verification code
74     // in the UI.
75     if (this.verificationOptions.external) {
76       let timeLeft = 0;
77       if (this.timer) {
78         timeLeft = this.timerCreation + VERIFICATIONCODE_TIMEOUT -
79                    Date.now();
80       }
81       this.ui.verificationCodePrompt(this.retries,
82                                      VERIFICATIONCODE_TIMEOUT / 1000,
83                                      timeLeft / 1000)
84       .then(
85         (verificationCode) => {
86           if (!verificationCode) {
87             return this.verificationCodeDeferred.reject(
88               ERROR_INTERNAL_INVALID_PROMPT_RESULT);
89           }
90           // If the user got the verification code that means that the
91           // introduced phone number didn't belong to any of the inserted
92           // SIMs.
93           this.ui.verify();
94           this.verificationCodeDeferred.resolve(verificationCode);
95         }
96       );
97     } else {
98       this.ui.verify();
99     }
101     return this.verificationCodeDeferred.promise.then(
102       this.onVerificationCode.bind(this)
103     );
104   },
106   // When we receive a verification code from the UI, we check it against
107   // the server. If the verification code is incorrect, we decrease the
108   // number of retries left and allow the user to try again. If there is no
109   // possible retry left, we notify about this error so the UI can allow the
110   // user to request the resend of a new verification code.
111   onVerificationCode: function(aVerificationCode) {
112     log.debug("onVerificationCode " + aVerificationCode);
113     if (!aVerificationCode) {
114       this.ui.error(ERROR_INVALID_VERIFICATION_CODE);
115       return this._doVerification();
116     }
118     // Before checking the verification code against the server we set the
119     // "verifying" flag to queue timeout expiration events received before
120     // the server request is completed. If the server request is positive
121     // we will discard the timeout event, otherwise we will progress the
122     // event to the UI to allow the user to retry.
123     this.verifying = true;
125     return this.verifyCode(aVerificationCode)
126     .then(
127       (result) => {
128         if (!result) {
129           return Promise.reject(INTERNAL_UNEXPECTED);
130         }
131         // The code was correct!
132         // At this point the phone number is verified.
133         // We return the given verification options with the session token
134         // to be stored in the credentials store. With this data we will be
135         // asking the server to give us a certificate to generate assertions.
136         this.verificationOptions.sessionToken = this.sessionToken;
137         this.verificationOptions.msisdn = result.msisdn ||
138                                           this.verificationOptions.msisdn;
139         return this.verificationOptions;
140       },
141       (error) => {
142         log.error("Verification code error " + error);
143         this.retries--;
144         log.error("Retries left " + this.retries);
145         if (!this.retries) {
146           this.ui.error(ERROR_NO_RETRIES_LEFT);
147           this.timer.cancel();
148           this.timer = null;
149           return Promise.reject(ERROR_NO_RETRIES_LEFT);
150         }
151         this.ui.error(ERROR_INVALID_VERIFICATION_CODE);
152         this.verifying = false;
153         if (this.queuedTimeout) {
154           this.onVerificationCodeTimeout();
155         }
156         return this._doVerification();
157       }
158     );
159   },
161   onVerificationCodeTimeout: function() {
162     // It is possible that we get the timeout when we are checking a
163     // verification code with the server. In that case, we queue the
164     // timeout to be triggered after we receive the reply from the server
165     // if needed.
166     if (this.verifying) {
167       this.queuedTimeout = true;
168       return;
169     }
171     // When the verification process times out we do a clean up, reject
172     // the corresponding promise and notify the UI about the timeout.
173     if (this.verificationCodeDeferred) {
174       this.verificationCodeDeferred.reject(ERROR_VERIFICATION_CODE_TIMEOUT);
175     }
176     this.ui.error(ERROR_VERIFICATION_CODE_TIMEOUT);
177   },
179   register: function() {
180     return this.client.register();
181   },
183   verifyCode: function(aVerificationCode) {
184     return this.client.verifyCode(this.sessionToken, aVerificationCode);
185   },
187   unregister: function() {
188     return this.client.unregister(this.sessionToken);
189   },
191   cleanup: function(aUnregister = false) {
192     log.debug("Verification flow cleanup");
194     this.queuedTimeout = false;
195     this.retries = VERIFICATIONCODE_RETRIES;
197     if (this.timer) {
198       this.timer.cancel();
199       this.timer = null;
200     }
202     if (aUnregister) {
203       this.unregister().
204       then(
205         () => {
206           this.sessionToken = null;
207         }
208       );
209     }
211     if (this.cleanupStrategy) {
212       this.cleanupStrategy();
213     }
214   }