/**
* 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;