michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = [ michael@0: "UserAPI10Client", michael@0: ]; michael@0: michael@0: const {utils: Cu} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: Cu.import("resource://services-common/rest.js"); michael@0: Cu.import("resource://services-common/utils.js"); michael@0: Cu.import("resource://services-sync/identity.js"); michael@0: Cu.import("resource://services-sync/util.js"); michael@0: michael@0: /** michael@0: * A generic client for the user API 1.0 service. michael@0: * michael@0: * http://docs.services.mozilla.com/reg/apis.html michael@0: * michael@0: * Instances are constructed with the base URI of the service. michael@0: */ michael@0: this.UserAPI10Client = function UserAPI10Client(baseURI) { michael@0: this._log = Log.repository.getLogger("Sync.UserAPI"); michael@0: this._log.level = Log.Level[Svc.Prefs.get("log.logger.userapi")]; michael@0: michael@0: this.baseURI = baseURI; michael@0: } michael@0: UserAPI10Client.prototype = { michael@0: USER_CREATE_ERROR_CODES: { michael@0: 2: "Incorrect or missing captcha.", michael@0: 4: "User exists.", michael@0: 6: "JSON parse failure.", michael@0: 7: "Missing password field.", michael@0: 9: "Requested password not strong enough.", michael@0: 12: "No email address on file.", michael@0: }, michael@0: michael@0: /** michael@0: * Determine whether a specified username exists. michael@0: * michael@0: * Callback receives the following arguments: michael@0: * michael@0: * (Error) Describes error that occurred or null if request was michael@0: * successful. michael@0: * (boolean) True if user exists. False if not. null if there was an error. michael@0: */ michael@0: usernameExists: function usernameExists(username, cb) { michael@0: if (typeof(cb) != "function") { michael@0: throw new Error("cb must be a function."); michael@0: } michael@0: michael@0: let url = this.baseURI + username; michael@0: let request = new RESTRequest(url); michael@0: request.get(this._onUsername.bind(this, cb, request)); michael@0: }, michael@0: michael@0: /** michael@0: * Obtain the Weave (Sync) node for a specified user. michael@0: * michael@0: * The callback receives the following arguments: michael@0: * michael@0: * (Error) Describes error that occurred or null if request was successful. michael@0: * (string) Username request is for. michael@0: * (string) URL of user's node. If null and there is no error, no node could michael@0: * be assigned at the time of the request. michael@0: */ michael@0: getWeaveNode: function getWeaveNode(username, password, cb) { michael@0: if (typeof(cb) != "function") { michael@0: throw new Error("cb must be a function."); michael@0: } michael@0: michael@0: let request = this._getRequest(username, "/node/weave", password); michael@0: request.get(this._onWeaveNode.bind(this, cb, request)); michael@0: }, michael@0: michael@0: /** michael@0: * Change a password for the specified user. michael@0: * michael@0: * @param username michael@0: * (string) The username whose password to change. michael@0: * @param oldPassword michael@0: * (string) The old, current password. michael@0: * @param newPassword michael@0: * (string) The new password to switch to. michael@0: */ michael@0: changePassword: function changePassword(username, oldPassword, newPassword, cb) { michael@0: let request = this._getRequest(username, "/password", oldPassword); michael@0: request.onComplete = this._onChangePassword.bind(this, cb, request); michael@0: request.post(CommonUtils.encodeUTF8(newPassword)); michael@0: }, michael@0: michael@0: createAccount: function createAccount(email, password, captchaChallenge, michael@0: captchaResponse, cb) { michael@0: let username = IdentityManager.prototype.usernameFromAccount(email); michael@0: let body = JSON.stringify({ michael@0: "email": email, michael@0: "password": Utils.encodeUTF8(password), michael@0: "captcha-challenge": captchaChallenge, michael@0: "captcha-response": captchaResponse michael@0: }); michael@0: michael@0: let url = this.baseURI + username; michael@0: let request = new RESTRequest(url); michael@0: michael@0: if (this.adminSecret) { michael@0: request.setHeader("X-Weave-Secret", this.adminSecret); michael@0: } michael@0: michael@0: request.onComplete = this._onCreateAccount.bind(this, cb, request); michael@0: request.put(body); michael@0: }, michael@0: michael@0: _getRequest: function _getRequest(username, path, password=null) { michael@0: let url = this.baseURI + username + path; michael@0: let request = new RESTRequest(url); michael@0: michael@0: if (password) { michael@0: let up = username + ":" + password; michael@0: request.setHeader("authorization", "Basic " + btoa(up)); michael@0: } michael@0: michael@0: return request; michael@0: }, michael@0: michael@0: _onUsername: function _onUsername(cb, request, error) { michael@0: if (error) { michael@0: cb(error, null); michael@0: return; michael@0: } michael@0: michael@0: let body = request.response.body; michael@0: if (body == "0") { michael@0: cb(null, false); michael@0: return; michael@0: } else if (body == "1") { michael@0: cb(null, true); michael@0: return; michael@0: } else { michael@0: cb(new Error("Unknown response from server: " + body), null); michael@0: return; michael@0: } michael@0: }, michael@0: michael@0: _onWeaveNode: function _onWeaveNode(cb, request, error) { michael@0: if (error) { michael@0: cb.network = true; michael@0: cb(error, null); michael@0: return; michael@0: } michael@0: michael@0: let response = request.response; michael@0: michael@0: if (response.status == 200) { michael@0: let body = response.body; michael@0: if (body == "null") { michael@0: cb(null, null); michael@0: return; michael@0: } michael@0: michael@0: cb(null, body); michael@0: return; michael@0: } michael@0: michael@0: let error = new Error("Sync node retrieval failed."); michael@0: switch (response.status) { michael@0: case 400: michael@0: error.denied = true; michael@0: break; michael@0: case 404: michael@0: error.notFound = true; michael@0: break; michael@0: default: michael@0: error.message = "Unexpected response code: " + response.status; michael@0: } michael@0: michael@0: cb(error, null); michael@0: return; michael@0: }, michael@0: michael@0: _onChangePassword: function _onChangePassword(cb, request, error) { michael@0: this._log.info("Password change response received: " + michael@0: request.response.status); michael@0: if (error) { michael@0: cb(error); michael@0: return; michael@0: } michael@0: michael@0: let response = request.response; michael@0: if (response.status != 200) { michael@0: cb(new Error("Password changed failed: " + response.body)); michael@0: return; michael@0: } michael@0: michael@0: cb(null); michael@0: }, michael@0: michael@0: _onCreateAccount: function _onCreateAccount(cb, request, error) { michael@0: let response = request.response; michael@0: michael@0: this._log.info("Create account response: " + response.status + " " + michael@0: response.body); michael@0: michael@0: if (error) { michael@0: cb(new Error("HTTP transport error."), null); michael@0: return; michael@0: } michael@0: michael@0: if (response.status == 200) { michael@0: cb(null, response.body); michael@0: return; michael@0: } michael@0: michael@0: let error = new Error("Could not create user."); michael@0: error.body = response.body; michael@0: michael@0: cb(error, null); michael@0: return; michael@0: }, michael@0: }; michael@0: Object.freeze(UserAPI10Client.prototype);