Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | this.EXPORTED_SYMBOLS = [ |
michael@0 | 8 | "UserAPI10Client", |
michael@0 | 9 | ]; |
michael@0 | 10 | |
michael@0 | 11 | const {utils: Cu} = Components; |
michael@0 | 12 | |
michael@0 | 13 | Cu.import("resource://gre/modules/Log.jsm"); |
michael@0 | 14 | Cu.import("resource://services-common/rest.js"); |
michael@0 | 15 | Cu.import("resource://services-common/utils.js"); |
michael@0 | 16 | Cu.import("resource://services-sync/identity.js"); |
michael@0 | 17 | Cu.import("resource://services-sync/util.js"); |
michael@0 | 18 | |
michael@0 | 19 | /** |
michael@0 | 20 | * A generic client for the user API 1.0 service. |
michael@0 | 21 | * |
michael@0 | 22 | * http://docs.services.mozilla.com/reg/apis.html |
michael@0 | 23 | * |
michael@0 | 24 | * Instances are constructed with the base URI of the service. |
michael@0 | 25 | */ |
michael@0 | 26 | this.UserAPI10Client = function UserAPI10Client(baseURI) { |
michael@0 | 27 | this._log = Log.repository.getLogger("Sync.UserAPI"); |
michael@0 | 28 | this._log.level = Log.Level[Svc.Prefs.get("log.logger.userapi")]; |
michael@0 | 29 | |
michael@0 | 30 | this.baseURI = baseURI; |
michael@0 | 31 | } |
michael@0 | 32 | UserAPI10Client.prototype = { |
michael@0 | 33 | USER_CREATE_ERROR_CODES: { |
michael@0 | 34 | 2: "Incorrect or missing captcha.", |
michael@0 | 35 | 4: "User exists.", |
michael@0 | 36 | 6: "JSON parse failure.", |
michael@0 | 37 | 7: "Missing password field.", |
michael@0 | 38 | 9: "Requested password not strong enough.", |
michael@0 | 39 | 12: "No email address on file.", |
michael@0 | 40 | }, |
michael@0 | 41 | |
michael@0 | 42 | /** |
michael@0 | 43 | * Determine whether a specified username exists. |
michael@0 | 44 | * |
michael@0 | 45 | * Callback receives the following arguments: |
michael@0 | 46 | * |
michael@0 | 47 | * (Error) Describes error that occurred or null if request was |
michael@0 | 48 | * successful. |
michael@0 | 49 | * (boolean) True if user exists. False if not. null if there was an error. |
michael@0 | 50 | */ |
michael@0 | 51 | usernameExists: function usernameExists(username, cb) { |
michael@0 | 52 | if (typeof(cb) != "function") { |
michael@0 | 53 | throw new Error("cb must be a function."); |
michael@0 | 54 | } |
michael@0 | 55 | |
michael@0 | 56 | let url = this.baseURI + username; |
michael@0 | 57 | let request = new RESTRequest(url); |
michael@0 | 58 | request.get(this._onUsername.bind(this, cb, request)); |
michael@0 | 59 | }, |
michael@0 | 60 | |
michael@0 | 61 | /** |
michael@0 | 62 | * Obtain the Weave (Sync) node for a specified user. |
michael@0 | 63 | * |
michael@0 | 64 | * The callback receives the following arguments: |
michael@0 | 65 | * |
michael@0 | 66 | * (Error) Describes error that occurred or null if request was successful. |
michael@0 | 67 | * (string) Username request is for. |
michael@0 | 68 | * (string) URL of user's node. If null and there is no error, no node could |
michael@0 | 69 | * be assigned at the time of the request. |
michael@0 | 70 | */ |
michael@0 | 71 | getWeaveNode: function getWeaveNode(username, password, cb) { |
michael@0 | 72 | if (typeof(cb) != "function") { |
michael@0 | 73 | throw new Error("cb must be a function."); |
michael@0 | 74 | } |
michael@0 | 75 | |
michael@0 | 76 | let request = this._getRequest(username, "/node/weave", password); |
michael@0 | 77 | request.get(this._onWeaveNode.bind(this, cb, request)); |
michael@0 | 78 | }, |
michael@0 | 79 | |
michael@0 | 80 | /** |
michael@0 | 81 | * Change a password for the specified user. |
michael@0 | 82 | * |
michael@0 | 83 | * @param username |
michael@0 | 84 | * (string) The username whose password to change. |
michael@0 | 85 | * @param oldPassword |
michael@0 | 86 | * (string) The old, current password. |
michael@0 | 87 | * @param newPassword |
michael@0 | 88 | * (string) The new password to switch to. |
michael@0 | 89 | */ |
michael@0 | 90 | changePassword: function changePassword(username, oldPassword, newPassword, cb) { |
michael@0 | 91 | let request = this._getRequest(username, "/password", oldPassword); |
michael@0 | 92 | request.onComplete = this._onChangePassword.bind(this, cb, request); |
michael@0 | 93 | request.post(CommonUtils.encodeUTF8(newPassword)); |
michael@0 | 94 | }, |
michael@0 | 95 | |
michael@0 | 96 | createAccount: function createAccount(email, password, captchaChallenge, |
michael@0 | 97 | captchaResponse, cb) { |
michael@0 | 98 | let username = IdentityManager.prototype.usernameFromAccount(email); |
michael@0 | 99 | let body = JSON.stringify({ |
michael@0 | 100 | "email": email, |
michael@0 | 101 | "password": Utils.encodeUTF8(password), |
michael@0 | 102 | "captcha-challenge": captchaChallenge, |
michael@0 | 103 | "captcha-response": captchaResponse |
michael@0 | 104 | }); |
michael@0 | 105 | |
michael@0 | 106 | let url = this.baseURI + username; |
michael@0 | 107 | let request = new RESTRequest(url); |
michael@0 | 108 | |
michael@0 | 109 | if (this.adminSecret) { |
michael@0 | 110 | request.setHeader("X-Weave-Secret", this.adminSecret); |
michael@0 | 111 | } |
michael@0 | 112 | |
michael@0 | 113 | request.onComplete = this._onCreateAccount.bind(this, cb, request); |
michael@0 | 114 | request.put(body); |
michael@0 | 115 | }, |
michael@0 | 116 | |
michael@0 | 117 | _getRequest: function _getRequest(username, path, password=null) { |
michael@0 | 118 | let url = this.baseURI + username + path; |
michael@0 | 119 | let request = new RESTRequest(url); |
michael@0 | 120 | |
michael@0 | 121 | if (password) { |
michael@0 | 122 | let up = username + ":" + password; |
michael@0 | 123 | request.setHeader("authorization", "Basic " + btoa(up)); |
michael@0 | 124 | } |
michael@0 | 125 | |
michael@0 | 126 | return request; |
michael@0 | 127 | }, |
michael@0 | 128 | |
michael@0 | 129 | _onUsername: function _onUsername(cb, request, error) { |
michael@0 | 130 | if (error) { |
michael@0 | 131 | cb(error, null); |
michael@0 | 132 | return; |
michael@0 | 133 | } |
michael@0 | 134 | |
michael@0 | 135 | let body = request.response.body; |
michael@0 | 136 | if (body == "0") { |
michael@0 | 137 | cb(null, false); |
michael@0 | 138 | return; |
michael@0 | 139 | } else if (body == "1") { |
michael@0 | 140 | cb(null, true); |
michael@0 | 141 | return; |
michael@0 | 142 | } else { |
michael@0 | 143 | cb(new Error("Unknown response from server: " + body), null); |
michael@0 | 144 | return; |
michael@0 | 145 | } |
michael@0 | 146 | }, |
michael@0 | 147 | |
michael@0 | 148 | _onWeaveNode: function _onWeaveNode(cb, request, error) { |
michael@0 | 149 | if (error) { |
michael@0 | 150 | cb.network = true; |
michael@0 | 151 | cb(error, null); |
michael@0 | 152 | return; |
michael@0 | 153 | } |
michael@0 | 154 | |
michael@0 | 155 | let response = request.response; |
michael@0 | 156 | |
michael@0 | 157 | if (response.status == 200) { |
michael@0 | 158 | let body = response.body; |
michael@0 | 159 | if (body == "null") { |
michael@0 | 160 | cb(null, null); |
michael@0 | 161 | return; |
michael@0 | 162 | } |
michael@0 | 163 | |
michael@0 | 164 | cb(null, body); |
michael@0 | 165 | return; |
michael@0 | 166 | } |
michael@0 | 167 | |
michael@0 | 168 | let error = new Error("Sync node retrieval failed."); |
michael@0 | 169 | switch (response.status) { |
michael@0 | 170 | case 400: |
michael@0 | 171 | error.denied = true; |
michael@0 | 172 | break; |
michael@0 | 173 | case 404: |
michael@0 | 174 | error.notFound = true; |
michael@0 | 175 | break; |
michael@0 | 176 | default: |
michael@0 | 177 | error.message = "Unexpected response code: " + response.status; |
michael@0 | 178 | } |
michael@0 | 179 | |
michael@0 | 180 | cb(error, null); |
michael@0 | 181 | return; |
michael@0 | 182 | }, |
michael@0 | 183 | |
michael@0 | 184 | _onChangePassword: function _onChangePassword(cb, request, error) { |
michael@0 | 185 | this._log.info("Password change response received: " + |
michael@0 | 186 | request.response.status); |
michael@0 | 187 | if (error) { |
michael@0 | 188 | cb(error); |
michael@0 | 189 | return; |
michael@0 | 190 | } |
michael@0 | 191 | |
michael@0 | 192 | let response = request.response; |
michael@0 | 193 | if (response.status != 200) { |
michael@0 | 194 | cb(new Error("Password changed failed: " + response.body)); |
michael@0 | 195 | return; |
michael@0 | 196 | } |
michael@0 | 197 | |
michael@0 | 198 | cb(null); |
michael@0 | 199 | }, |
michael@0 | 200 | |
michael@0 | 201 | _onCreateAccount: function _onCreateAccount(cb, request, error) { |
michael@0 | 202 | let response = request.response; |
michael@0 | 203 | |
michael@0 | 204 | this._log.info("Create account response: " + response.status + " " + |
michael@0 | 205 | response.body); |
michael@0 | 206 | |
michael@0 | 207 | if (error) { |
michael@0 | 208 | cb(new Error("HTTP transport error."), null); |
michael@0 | 209 | return; |
michael@0 | 210 | } |
michael@0 | 211 | |
michael@0 | 212 | if (response.status == 200) { |
michael@0 | 213 | cb(null, response.body); |
michael@0 | 214 | return; |
michael@0 | 215 | } |
michael@0 | 216 | |
michael@0 | 217 | let error = new Error("Could not create user."); |
michael@0 | 218 | error.body = response.body; |
michael@0 | 219 | |
michael@0 | 220 | cb(error, null); |
michael@0 | 221 | return; |
michael@0 | 222 | }, |
michael@0 | 223 | }; |
michael@0 | 224 | Object.freeze(UserAPI10Client.prototype); |