1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/sync/modules/userapi.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,224 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +this.EXPORTED_SYMBOLS = [ 1.11 + "UserAPI10Client", 1.12 +]; 1.13 + 1.14 +const {utils: Cu} = Components; 1.15 + 1.16 +Cu.import("resource://gre/modules/Log.jsm"); 1.17 +Cu.import("resource://services-common/rest.js"); 1.18 +Cu.import("resource://services-common/utils.js"); 1.19 +Cu.import("resource://services-sync/identity.js"); 1.20 +Cu.import("resource://services-sync/util.js"); 1.21 + 1.22 +/** 1.23 + * A generic client for the user API 1.0 service. 1.24 + * 1.25 + * http://docs.services.mozilla.com/reg/apis.html 1.26 + * 1.27 + * Instances are constructed with the base URI of the service. 1.28 + */ 1.29 +this.UserAPI10Client = function UserAPI10Client(baseURI) { 1.30 + this._log = Log.repository.getLogger("Sync.UserAPI"); 1.31 + this._log.level = Log.Level[Svc.Prefs.get("log.logger.userapi")]; 1.32 + 1.33 + this.baseURI = baseURI; 1.34 +} 1.35 +UserAPI10Client.prototype = { 1.36 + USER_CREATE_ERROR_CODES: { 1.37 + 2: "Incorrect or missing captcha.", 1.38 + 4: "User exists.", 1.39 + 6: "JSON parse failure.", 1.40 + 7: "Missing password field.", 1.41 + 9: "Requested password not strong enough.", 1.42 + 12: "No email address on file.", 1.43 + }, 1.44 + 1.45 + /** 1.46 + * Determine whether a specified username exists. 1.47 + * 1.48 + * Callback receives the following arguments: 1.49 + * 1.50 + * (Error) Describes error that occurred or null if request was 1.51 + * successful. 1.52 + * (boolean) True if user exists. False if not. null if there was an error. 1.53 + */ 1.54 + usernameExists: function usernameExists(username, cb) { 1.55 + if (typeof(cb) != "function") { 1.56 + throw new Error("cb must be a function."); 1.57 + } 1.58 + 1.59 + let url = this.baseURI + username; 1.60 + let request = new RESTRequest(url); 1.61 + request.get(this._onUsername.bind(this, cb, request)); 1.62 + }, 1.63 + 1.64 + /** 1.65 + * Obtain the Weave (Sync) node for a specified user. 1.66 + * 1.67 + * The callback receives the following arguments: 1.68 + * 1.69 + * (Error) Describes error that occurred or null if request was successful. 1.70 + * (string) Username request is for. 1.71 + * (string) URL of user's node. If null and there is no error, no node could 1.72 + * be assigned at the time of the request. 1.73 + */ 1.74 + getWeaveNode: function getWeaveNode(username, password, cb) { 1.75 + if (typeof(cb) != "function") { 1.76 + throw new Error("cb must be a function."); 1.77 + } 1.78 + 1.79 + let request = this._getRequest(username, "/node/weave", password); 1.80 + request.get(this._onWeaveNode.bind(this, cb, request)); 1.81 + }, 1.82 + 1.83 + /** 1.84 + * Change a password for the specified user. 1.85 + * 1.86 + * @param username 1.87 + * (string) The username whose password to change. 1.88 + * @param oldPassword 1.89 + * (string) The old, current password. 1.90 + * @param newPassword 1.91 + * (string) The new password to switch to. 1.92 + */ 1.93 + changePassword: function changePassword(username, oldPassword, newPassword, cb) { 1.94 + let request = this._getRequest(username, "/password", oldPassword); 1.95 + request.onComplete = this._onChangePassword.bind(this, cb, request); 1.96 + request.post(CommonUtils.encodeUTF8(newPassword)); 1.97 + }, 1.98 + 1.99 + createAccount: function createAccount(email, password, captchaChallenge, 1.100 + captchaResponse, cb) { 1.101 + let username = IdentityManager.prototype.usernameFromAccount(email); 1.102 + let body = JSON.stringify({ 1.103 + "email": email, 1.104 + "password": Utils.encodeUTF8(password), 1.105 + "captcha-challenge": captchaChallenge, 1.106 + "captcha-response": captchaResponse 1.107 + }); 1.108 + 1.109 + let url = this.baseURI + username; 1.110 + let request = new RESTRequest(url); 1.111 + 1.112 + if (this.adminSecret) { 1.113 + request.setHeader("X-Weave-Secret", this.adminSecret); 1.114 + } 1.115 + 1.116 + request.onComplete = this._onCreateAccount.bind(this, cb, request); 1.117 + request.put(body); 1.118 + }, 1.119 + 1.120 + _getRequest: function _getRequest(username, path, password=null) { 1.121 + let url = this.baseURI + username + path; 1.122 + let request = new RESTRequest(url); 1.123 + 1.124 + if (password) { 1.125 + let up = username + ":" + password; 1.126 + request.setHeader("authorization", "Basic " + btoa(up)); 1.127 + } 1.128 + 1.129 + return request; 1.130 + }, 1.131 + 1.132 + _onUsername: function _onUsername(cb, request, error) { 1.133 + if (error) { 1.134 + cb(error, null); 1.135 + return; 1.136 + } 1.137 + 1.138 + let body = request.response.body; 1.139 + if (body == "0") { 1.140 + cb(null, false); 1.141 + return; 1.142 + } else if (body == "1") { 1.143 + cb(null, true); 1.144 + return; 1.145 + } else { 1.146 + cb(new Error("Unknown response from server: " + body), null); 1.147 + return; 1.148 + } 1.149 + }, 1.150 + 1.151 + _onWeaveNode: function _onWeaveNode(cb, request, error) { 1.152 + if (error) { 1.153 + cb.network = true; 1.154 + cb(error, null); 1.155 + return; 1.156 + } 1.157 + 1.158 + let response = request.response; 1.159 + 1.160 + if (response.status == 200) { 1.161 + let body = response.body; 1.162 + if (body == "null") { 1.163 + cb(null, null); 1.164 + return; 1.165 + } 1.166 + 1.167 + cb(null, body); 1.168 + return; 1.169 + } 1.170 + 1.171 + let error = new Error("Sync node retrieval failed."); 1.172 + switch (response.status) { 1.173 + case 400: 1.174 + error.denied = true; 1.175 + break; 1.176 + case 404: 1.177 + error.notFound = true; 1.178 + break; 1.179 + default: 1.180 + error.message = "Unexpected response code: " + response.status; 1.181 + } 1.182 + 1.183 + cb(error, null); 1.184 + return; 1.185 + }, 1.186 + 1.187 + _onChangePassword: function _onChangePassword(cb, request, error) { 1.188 + this._log.info("Password change response received: " + 1.189 + request.response.status); 1.190 + if (error) { 1.191 + cb(error); 1.192 + return; 1.193 + } 1.194 + 1.195 + let response = request.response; 1.196 + if (response.status != 200) { 1.197 + cb(new Error("Password changed failed: " + response.body)); 1.198 + return; 1.199 + } 1.200 + 1.201 + cb(null); 1.202 + }, 1.203 + 1.204 + _onCreateAccount: function _onCreateAccount(cb, request, error) { 1.205 + let response = request.response; 1.206 + 1.207 + this._log.info("Create account response: " + response.status + " " + 1.208 + response.body); 1.209 + 1.210 + if (error) { 1.211 + cb(new Error("HTTP transport error."), null); 1.212 + return; 1.213 + } 1.214 + 1.215 + if (response.status == 200) { 1.216 + cb(null, response.body); 1.217 + return; 1.218 + } 1.219 + 1.220 + let error = new Error("Could not create user."); 1.221 + error.body = response.body; 1.222 + 1.223 + cb(error, null); 1.224 + return; 1.225 + }, 1.226 +}; 1.227 +Object.freeze(UserAPI10Client.prototype);