/** * A promise object provided by the q promise library. * @external Promise * @see {@link https://github.com/kriskowal/q/wiki/API-Reference} */ 'use strict'; var url = require('url'), request = require('request'), Firebase = require('firebase'), Q = require('q'); /** * Creates a new reference to a Firebase instance. * NOTE: don't use the constructor yourself, use a {@link FirebaseAccount} * instance instead. * @see {FirebaseAccount#createDatabase} * @see {FirebaseAccount#getDatabase} * @protected * @constructor * @param {String} name The name of the Firebase. * @param {String} adminToken The administrative token to use */ function FirebaseInstance(name, adminToken) { var deferred = Q.defer(); this.name = name; this.adminToken = adminToken; request.get({ url: 'https://admin.firebase.com/firebase/' + name + '/token', qs: { token: adminToken, namespace: name }, json: true }, function(err, response, body) { if (err) { deferred.reject(err); } else if (response.statusCode !== 200) { deferred.reject(new Error(response.statusCode)); } else if (body.error) { deferred.reject(new Error(body.error)); } else if (body.success === false) { deferred.reject(new Error('Bad credentials or server error.')); } else if (!body.personalToken) { deferred.reject(new Error('personalToken was not present.')); } else if (!body.firebaseToken) { deferred.reject(new Error('firebaseToken was not present.')); } else { this.personalToken = body.personalToken; this.firebaseToken = body.firebaseToken; deferred.resolve(this); } }.bind(this)); this.ready = deferred.promise; } /** * Gets the URL to the instance, for use with the Firebase API. * @returns {String} The full URL to the instance. * @example * var fb = new Firebase(instance.toString()); */ FirebaseInstance.prototype.toString = function() { return 'https://' + this.name + '.firebaseio.com'; }; /** * Promises to get all the auth tokens associated with the instance. * @returns {external:Promise} A promise that resolves with an Array of the * instance's currently-valid auth tokens and rejects with an Error * if there's an error. * @example * instance.getAuthTokens().then(function(tokens) { * fb.auth(tokens[0], function(err) { * // err should be null * }); * }); */ FirebaseInstance.prototype.getAuthTokens = function() { if (this.deleted) { return Q.reject( new Error('Cannot getAuthTokens from deleted database ' + this.toString()) ); } if (this.authTokens) { /* jshint newcap:false */ return Q(this.authTokens); } var deferred = Q.defer(); request.get({ url: 'https://' + this.name + '.firebaseio.com//.settings/secrets.json', qs: { auth: this.personalToken }, json: true }, function(err, response, body) { if (err) { deferred.reject(err); } else if (response.statusCode !== 200) { deferred.reject(new Error(response.statusCode)); } else if (body.error) { deferred.reject(new Error(body.error)); } else { this.authTokens = body; deferred.resolve(body); } }.bind(this)); return deferred.promise; }; /** * Promises to create and return a new auth token. * @returns {external:Promise} A promise that resolves with a new auth token * (String) that is guaranteed to be valid and rejects with an Error if * there's an error. * @example * instance.addAuthToken().then(function(token) { * fb.auth(token, function(err) { * // err should be null * }); * }); */ FirebaseInstance.prototype.addAuthToken = function() { if (this.deleted) { return Q.reject( new Error('Cannot addAuthToken to deleted database ' + this.toString()) ); } var deferred = Q.defer(); request.post({ url: 'https://' + this.name + '.firebaseio.com/.settings/secrets.json', qs: { auth: this.personalToken }, json: true }, function(err, response, body) { if (err) { deferred.reject(err); } else if (response.statusCode !== 200) { deferred.reject(new Error(response.statusCode)); } else if (body.error) { deferred.reject(new Error(body.error)); } else { if (!this.authTokens) { this.authTokens = []; } this.authTokens.push(body); deferred.resolve(body); } }.bind(this)); return deferred.promise; }; /** * Promises to remove an existing auth token. * @param {String} token The token to be removed. * @returns {external:Promise} A promise that resolves if token has been * removed successfully and rejects with an Error if token isn't valid * or if there's an error. * @example * instance.removeAuthToken(token).then(function() { * fb.auth(token, function(err) { * // should get an error indicating invalid credentials here * }); * }); */ FirebaseInstance.prototype.removeAuthToken = function(token) { if (this.deleted) { return Q.reject( new Error('Cannot removeAuthToken from deleted database ' + this.toString()) ); } return this.getAuthTokens() .then(function(tokens) { if (!Array.isArray(tokens) || tokens.indexOf(token) === -1) { return Q.reject( new Error('No such token exists on firebase ' + this.toString()) ); } var deferred = Q.defer(); request.del({ url: 'https://' + this.name + '.firebaseio.com/.settings/secrets/' + token + '.json', qs: { auth: this.personalToken, }, json: true }, function(err, response, body) { if (err) { deferred.reject(err); } else if (response.statusCode > 299) { deferred.reject(new Error(response.statusCode)); } else if (body && body.error) { deferred.reject(new Error(body.error)); } else { this.authTokens.splice(this.authTokens.indexOf(token), 1); deferred.resolve(this); } }.bind(this)); return deferred.promise; }.bind(this)); }; /** * Promises to get a Javascript object containing the current security rules. * NOTE: the top-level "rules" part of the JSON will be stripped. * @returns {external:Promise} A promise that resolves with an Object * containing the rules if they're retrieved successfully and * rejects with an Error if there's an error. * @example * instance.getRules().then(function(rules) { * if (rules['.read'] === 'true' && rules['.write'] === 'true') { * console.log('WARNING: this Firebase has default global rules!'); * } * }); */ FirebaseInstance.prototype.getRules = function() { if (this.deleted) { return Q.reject( new Error('Cannot getRules from deleted database ' + this.toString()) ); } var deferred = Q.defer(); request.get({ url: 'https://' + this.name + '.firebaseio.com/.settings/rules.json', qs: { auth: this.personalToken, }, json: true }, function(err, response, body) { if (err) { deferred.reject(err); } else if (response.statusCode > 299) { deferred.reject(new Error(response.statusCode)); } else if (body && body.error) { deferred.reject(new Error(body.error)); } else { body = body.rules; deferred.resolve(body); } }.bind(this)); return deferred.promise; }; /** * Promises to change current security rules. * @param {Object} newRules The new security rules as a Javascript object. * This object need not have a top-level "rules" key, although it will be * handled gracefully if it does. * @returns {external:Promise} A promise that resolves if the rules are changed * successfully and rejects with an Error if there's an error. * @example * instance.setRules({ * '.read': 'true', * '.write': 'false' * }).then(function() { * console.log('Disabled write access to this Firebase.'); * }).catch(function() { * console.log('Oops, something went wrong!'); * }); */ FirebaseInstance.prototype.setRules = function(newRules) { if (this.deleted) { return Q.reject( new Error('Cannot setRules on deleted database ' + this.toString()) ); } if (!(newRules.rules && Object.keys(newRules).length === 1)) { newRules = { rules: newRules }; } var deferred = Q.defer(); request.put({ url: 'https://' + this.name + '.firebaseio.com/.settings/rules.json', qs: { auth: this.personalToken, }, json: true, body: newRules }, function(err, response, body) { if (err) { deferred.reject(err); } else if (response.statusCode > 299) { deferred.reject(new Error(response.statusCode)); } else if (body && body.error) { deferred.reject(new Error(body.error)); } else if (body && body.status !== 'ok') { deferred.reject(new Error(body.status)); } else { deferred.resolve(this); } }.bind(this)); return deferred.promise; }; /** * Promises to obtain the current authentication configuration for the instance. * @returns {external:Promise} A promise that resolves with the auth config * and rejects with an Error if there's an error. */ FirebaseInstance.prototype.getAuthConfig = function() { var deferred = Q.defer(); request.get({ url: 'https://' + this.name + '.firebaseio.com/.settings/.json', qs: { auth: this.personalToken, }, json: true }, function(err, response, body) { if (err) { deferred.reject(err); } else if (response.statusCode > 299) { deferred.reject(new Error(response.statusCode)); } else if (body && body.error) { deferred.reject(new Error(body.error)); } else { if (typeof body.authConfig === 'string' && body.authConfig.length === 0) { deferred.resolve(null); } else { deferred.resolve(JSON.parse(body.authConfig)); } } }.bind(this)); return deferred.promise; }; FirebaseInstance.prototype.setAuthConfig = function(config) { var deferred = Q.defer(); request.post({ url: 'https://admin.firebase.com/firebase/' + this.name + '/authConfig', json: true, body: { token: this.adminToken, authConfig: JSON.stringify(config), _method: 'put' }, }, function(err, response, body) { if (err) { deferred.reject(err); } else if (response.statusCode > 299) { deferred.reject(new Error(response.statusCode)); } else if (body && body.error) { console.log(body.error); deferred.reject(new Error(body.error)); } else { deferred.resolve(); } }.bind(this)); return deferred.promise; }; FirebaseInstance.prototype._authMethodCallback = function(deferred, err, resp, body) { if (err) { deferred.reject(err); } else if (resp.statusCode > 299) { deferred.reject(new Error(resp.statusCode)); } else if (body && body.error) { var error = new Error(body.error.message); if (body.error.code) { error.code = body.error.code; } deferred.reject(error); } else if (body.status && body.status !== 'ok') { deferred.reject(new Error(body.status)); } else { deferred.resolve(body); } }; /** * Promises to create a Firebase Simple Login password-type user. * @param {String} email The email address of the new user. * @param {String} password The password of the new user. * @returns {external:Promise} A promise that resolves if the rules are changed * successfully and rejects with an Error if there's an error. */ FirebaseInstance.prototype.createUser = function(email, password) { var deferred = Q.defer(); var qs = { email: email, password: password, firebase: this.name }; request.get({ url: 'https://auth.firebase.com/auth/firebase/create', qs: qs, json: true }, this._authMethodCallback.bind(this, deferred)); return deferred.promise; }; /** * Promises to remove a Simple Login user. * @param {String} email The email address of the user to remove. * @returns {external:Promise} A promise that resolves with the new user info * if the user is removed successfully and rejects with an Error * if there's an error. */ FirebaseInstance.prototype.removeUser = function(email) { var deferred = Q.defer(); request.del({ url: 'https://auth.firebase.com/v2/' + this.name + '/users/' + email, qs: { token: this.adminToken }, json: true }, this._authMethodCallback.bind(this, deferred)); return deferred.promise; }; /** * Promises to change a Simple Login user's password. * @param {String} email The email address of the user to remove. * @param {String} newPassword The new password. * @returns {external:Promise} A promise that resolves with the new user info * if the user's password is changed successfully and rejects with an Error * if there's an error. */ FirebaseInstance.prototype.changeUserPassword = function(email, newPassword) { var deferred = Q.defer(); request.get({ url: 'https://auth.firebase.com/auth/firebase/reset_password', qs: { token: this.adminToken, firebase: this.name, email: email, newPassword: newPassword }, json: true }, this._authMethodCallback.bind(this, deferred)); return deferred.promise; }; /** * Promises to return a list of all Simple Login password users in the Firebase. * @returns {external:Promise} A promise that resolves with a list of users * and rejects with an Error if there's an error. */ FirebaseInstance.prototype.listUsers = function() { var deferred = Q.defer(); request.get({ url: 'https://auth.firebase.com/v2/' + this.name + '/users', qs: { token: this.adminToken, firebase: this.name }, json: true }, this._authMethodCallback.bind(this, deferred)); return deferred.promise .then(function(body) { if (!body.users) { throw new Error('No user body'); } return body.users; }); }; /** * Promises to send a password reset email to a Simple Login user. * @param {String} email The email address of the user to send a message to. * @returns {external:Promise} A promise that resolves if the message is sent * successfully and rejects with an Error if there's an error. */ FirebaseInstance.prototype.sendResetEmail = function(email) { var deferred = Q.defer(); request.get({ url: 'https://auth.firebase.com/auth/firebase/reset_password', qs: { token: this.adminToken, firebase: this.name, email: email }, json: true }, this._authMethodCallback.bind(this, deferred)); return deferred.promise; }; module.exports = FirebaseInstance;