diff --git a/bin/firebase b/bin/firebase index c1af1949..2d972336 100755 --- a/bin/firebase +++ b/bin/firebase @@ -1,9 +1,10 @@ #!/usr/bin/env node var optimist = require('optimist'), - argv = optimist.usage('Usage: firebase (auth|app)').argv, - app = require('../lib/app') - auth = require('../lib/auth'); + argv = optimist.usage('Usage: firebase').argv, + Auth = require('../lib/auth'), + auth = new Auth(argv), + App = require('../lib/app'); if (argv._.length === 0) { @@ -11,15 +12,24 @@ if (argv._.length === 0) { optimist.showHelp(); } else { - // Router + // Top-level router switch (argv._[0]) { - case 'app': - app(argv); + case 'refreshtoken': + auth.login( + function(email, token) { + console.log('New token saved for ' + email + ': ' + token); + }, + function() { + console.log("Sorry, we couldn't refresh the token"); + }, + this + ); break; - case 'auth': - auth(argv); + case 'app': + new App(auth, argv); break; default: + // No route found optimist.showHelp(); } } diff --git a/lib/api.js b/lib/api.js new file mode 100644 index 00000000..9e7e31a8 --- /dev/null +++ b/lib/api.js @@ -0,0 +1,48 @@ +var https = require('https'), + querystring = require('querystring') + +/** + * Static API methods. + */ +var Api = { + request: function(method, resource, data, callback, scope) { + if (typeof(scope) === 'undefined') { + var scope = this; + } + + // TODO: Add support for other request types + if (method !== 'GET') { + method = 'GET'; + } + + if (method === 'GET') { + var separator = resource.match(/\?/) ? '&' : '?'; + resource += separator + querystring.stringify(data); + } + + var options = { + host: 'admin.firebase.com', + path: resource, + port: 443, + method: method + }; + + var req = https.request(options, function(res) { + var responseString = ''; + res.setEncoding('utf8'); + + res.on('data', function (chunk) { + responseString += chunk; + }); + + res.on('end', function() { + var response = JSON.parse(responseString); + callback.call(scope, res.statusCode, response); + }); + }) + + req.end(); + } +} + +module.exports = Api; diff --git a/lib/app.js b/lib/app.js index bf43e72d..2c65acd8 100644 --- a/lib/app.js +++ b/lib/app.js @@ -1,5 +1,21 @@ -module.exports = app; +var optimist = require('optimist'); -function app(argv) { - console.log('app', argv); +function App(auth, argv) { + // TODO: Ensure valid route + var validRoute = true; + if (validRoute) { + auth.requireLogin(function() { + // TODO: App-specific router + console.log('app', argv); + }, + function() { + console.log("Sorry, we couldn't log you in"); + }, + this + ); + } else { + optimist.showHelp(); + } } + +module.exports = App; diff --git a/lib/auth.js b/lib/auth.js index e64e4508..3e89a984 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -1,5 +1,191 @@ -module.exports = auth; +var prompt = require('prompt'), + fs = require('fs'), + configFile = process.env['HOME'] + '/.firebaserc', + Api = require('./api'); -function auth(argv) { - console.log('auth', argv); +/** + * Auth-related methods. + * Stores the current user's credentials and syncs them to disc. + */ +function Auth(argv) { + try { + var data = fs.readFileSync(configFile), + config; + config = JSON.parse(data); + if (typeof(config.authToken) === 'string') { + this.authToken = config.authToken; + } else { + this.authToken = ''; + } + if (typeof(config.email) === 'string') { + this.email = config.email; + } else { + this.email = ''; + } + } catch (err) { + this.authToken = ''; + this.email = ''; + } + this.argv = argv; } + +Auth.prototype.validate = function(successCallback, errorCallback, scope) { + if (typeof(scope) === 'undefined') { + var scope = this; + } + Api.request('GET', + '/token/validate', + { + token: this.authToken + }, + function(statusCode, response) { + if (response.success) { + if (typeof(successCallback) === 'function') { + successCallback.call(scope); + } + } else { + if (typeof(errorCallback) === 'function') { + errorCallback.call(scope); + } + } + } + ); +} + +Auth.prototype.loginRequest = function(successCallback, errorCallback, scope) { + if (typeof(scope) === 'undefined') { + var scope = this; + } + var that = this, + schema = { + properties: { + email: { + description: 'Email', + pattern: /@/, + message: 'Must be a valid email address', + required: true + }, + password: { + description: 'Password', + hidden: true, + required: true + } + } + }; + + if (this.email.length > 0) { + schema.properties.email.default = this.email; + } + + prompt.override = this.argv; + prompt.message = ''; + prompt.delimiter = ''; + + prompt.get(schema, function(err, result) { + if (err) { + if (typeof(errorCallback) === 'function') { + errorCallback.call(scope); + } + return; + } + that.email = result.email; + that.authenticate( + result.email, + result.password, + successCallback, + errorCallback, + scope + ); + }); +}; + +var maxRetries = 3; + +Auth.prototype.requireLogin = function(successCallback, errorCallback, scope) { + if (typeof(scope) === 'undefined') { + var scope = this; + } + var that = this; + + this.validate( + successCallback, + that.attemptLogin(maxRetries, successCallback, errorCallback, scope), + scope + ); +}; + +Auth.prototype.attemptLogin = function(tries, successCallback, errorCallback, scope) { + var that = this; + return function() { + if (tries > 0) { + if (tries == maxRetries) { + console.log('You need to be logged in to perform this action'); + } else { + console.log('Email or password incorrect, please try again'); + } + that.loginRequest( + successCallback, + that.attemptLogin(tries - 1, successCallback, errorCallback, scope), + scope + ); + } else { + if (typeof(errorCallback) === 'function') { + errorCallback.call(scope); + } + } + } +}; + +Auth.prototype.login = function(successCallback, errorCallback, scope) { + this.attemptLogin(maxRetries, successCallback, errorCallback, scope)(); +}; + +Auth.prototype.authenticate = function(email, password, successCallback, errorCallback, scope) { + var data = { + email: email, + password: password, + rememberMe: true + }; + Api.request( + 'GET', + '/account/login', + data, + this.handleLogin(successCallback, errorCallback, scope), + this + ); +}; + +Auth.prototype.handleLogin = function(successCallback, errorCallback, scope) { + return function(statusCode, response) { + var token = response.adminToken; + if (!token) { + if (typeof(errorCallback) === 'function') { + errorCallback.call(scope); + } + return; + } + this.storeToken(token, successCallback, errorCallback, scope); + } +}; + +Auth.prototype.storeToken = function(token, successCallback, errorCallback, scope) { + this.authToken = token; + var data = { + email: this.email, + authToken: this.authToken + }; + var dataString = JSON.stringify(data, null, 2) + "\n"; + fs.writeFile(configFile, dataString, function (err) { + if (err) { + if (typeof(errorCallback) === 'function') { + errorCallback.call(scope); + } + return; + } + if (typeof(successCallback) === 'function') { + successCallback.call(scope, data.email, data.authToken); + } + }); +}; + +module.exports = Auth; diff --git a/package.json b/package.json index e55d5727..3f09b4dd 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "repository": "https://github.com/firebase/firebase-cli.git", "homepage": "https://github.com/firebase/firebase-cli", "dependencies": { - "optimist": "0.6.x" + "optimist": "0.6.x", + "prompt": "0.2.x" }, "bin": { "firebase": "./bin/firebase"