diff --git a/angularfire/config.json b/angularfire/config.json index da14f4ed7..96d097785 100644 --- a/angularfire/config.json +++ b/angularfire/config.json @@ -1,17 +1,20 @@ { "bower": { - "firebase": "1.0.x", - "angularfire": "0.8.x", - "mockfirebase": "0.2.x", - "simplelogin": "1.6.x" + "firebase": "2.0.x", + "angularfire": "0.9.1", + "mockfirebase": "0.8.x" }, - "simpleLoginProviders": [ + "authProviders": [ { "name": "Email/Password", "value": "password", "checked": true }, { "name": "Anonymous", "value": "anonymous" }, { "name": "Facebook", "value": "facebook" }, { "name": "Google", "value": "google" }, { "name": "Twitter", "value": "twitter" }, { "name": "GitHub", "value": "github" } - ] + ], + + "specialRoutes": { + "chat": true, "login": true, "account": true + } } diff --git a/app/index.js b/app/index.js index 871aa6520..a03a93d26 100644 --- a/app/index.js +++ b/app/index.js @@ -36,7 +36,7 @@ var FIREBASE_PROMPTS = [ type: 'checkbox', name: 'providers', message: 'Which providers shall I install?', - choices: afconfig.simpleLoginProviders, + choices: afconfig.authProviders, when: function(answers) { return answers.loginModule; }, @@ -55,6 +55,7 @@ var Generator = module.exports = function Generator(args, options) { //angularfire this.afconfig = afconfig; + this.env.options.afconfig = afconfig; this.angularFireSourceFiles = []; this.option('app-suffix', { @@ -194,25 +195,21 @@ var Generator = module.exports = function Generator(args, options) { }); if (this.env.options.ngRoute) { + // this will not create controller or html because + // "chat" exists in config.json::specialRoutes this.invoke('angularfire:route', { //angularfire - args: ['chat', true] + args: ['chat'] }); } //angularfire if(this.env.options.loginModule) { if( this.env.options.ngRoute ) { + // this will not create controller or html because + // "login" exists in config.json::specialRoutes this.invoke('angularfire:route', { - args: ['login', true] - }); - } - else { - this.invoke('angularfire:controller', { - args: ['login', true] - }); - this.invoke('angularfire.view', { - args: ['login', true] + args: ['login'] }); } } @@ -248,12 +245,12 @@ Generator.prototype.welcome = function welcome() { Generator.prototype.askFirebaseQuestions = function askForCompass() { this.firebaseName = null; this.loginModule = false; - this.simpleLoginProviders = []; + this.authProviders = []; this.hasOauthProviders = false; this.hasPasswordProvider = false; // allow firebase instance to be set on command line - this._defaultNamespace(this.options['instance'], FIREBASE_PROMPTS); + this._defaultNamespace(this.options.instance, FIREBASE_PROMPTS); var cb = this.async(); this.prompt(FIREBASE_PROMPTS, function (props) { @@ -531,15 +528,15 @@ Generator.prototype._tpl = function(src, dest) { //angularfire Generator.prototype._processProviders = function(list) { - var providerMap = {}, i = afconfig.simpleLoginProviders.length, p; + var providerMap = {}, i = afconfig.authProviders.length, p; while(i--) { - p = afconfig.simpleLoginProviders[i]; + p = afconfig.authProviders[i]; providerMap[p.value] = {name: p.name, value: p.value}; } list.forEach(function(p) { if( p === 'password' ) { this.hasPasswordProvider = true; } else { this.hasOauthProviders = true; } - this.simpleLoginProviders.push(providerMap[p]); + this.authProviders.push(providerMap[p]); }, this); }; diff --git a/readme.md b/readme.md index b5028b31a..26ee3a1c3 100644 --- a/readme.md +++ b/readme.md @@ -42,7 +42,7 @@ Available generators: **Note: Generators are to be run from the root directory of your app.** ### App -Sets up a new AngularJS + Firebase app, generating all the boilerplate you need to get started. The app generator also optionally installs FirebaseSimpleLogin, Bootstrap and additional AngularJS modules, such as angular-resource (installed by default). +Sets up a new AngularJS + Firebase app, generating all the boilerplate you need to get started. The app generator also optionally installs Firebase authentication and account management, Bootstrap and additional AngularJS modules, such as angular-resource (installed by default). Example: ```bash diff --git a/route/index.js b/route/index.js index bb944fd38..d13329c3a 100644 --- a/route/index.js +++ b/route/index.js @@ -6,7 +6,7 @@ var ScriptBase = require('../script-base.js'); var angularUtils = require('../util.js'); -var Generator = module.exports = function Generator(name, skipFiles) { +var Generator = module.exports = function Generator(name) { ScriptBase.apply(this, arguments); this.option('uri', { desc: 'Allow a custom uri for routing', @@ -28,7 +28,7 @@ var Generator = module.exports = function Generator(name, skipFiles) { this.foundWhenForRoute = true; } - if( skipFiles !== true ) { + if( this.env.options.afconfig.specialRoutes[name] !== true) { this.hookFor('angularfire:controller'); this.hookFor('angularfire:view'); } @@ -37,7 +37,7 @@ var Generator = module.exports = function Generator(name, skipFiles) { util.inherits(Generator, ScriptBase); //angularFire -Generator.prototype.rewriteAppJs = function () { +Generator.prototype.rewriteRoutesJs = function () { var coffee = this.env.options.coffee; if (!this.foundWhenForRoute) { @@ -69,7 +69,6 @@ Generator.prototype.rewriteAppJs = function () { var whenMethod = this.env.options.authRequired || this.options['auth-required']? 'whenAuthenticated' : 'when'; - console.log(whenMethod, this.options); if (coffee) { config.splicable.unshift("." + whenMethod + " '/" + this.uri + "',"); diff --git a/templates/coffeescript/controllers/account.coffee b/templates/coffeescript/controllers/account.coffee index b3ed54846..57651e34a 100644 --- a/templates/coffeescript/controllers/account.coffee +++ b/templates/coffeescript/controllers/account.coffee @@ -27,13 +27,15 @@ angular.module("<%= scriptAppName %>").controller "AccountCtrl", ($scope, user, return <% } %>loadProfile = (user) -> - $scope.profile.$destroy() if $scope.profile - fbutil.syncObject("users/" + user.uid).$bindTo $scope, "profile" + profile.$destroy() if profile + profile = fbutil.syncObject("users/" + user.uid); + profile.$bindTo $scope, "profile" return $scope.user = user $scope.logout = simpleLogin.logout $scope.messages = [] + profile = null loadProfile user <% if( hasPasswordProvider ) { %> $scope.changePassword = (oldPass, newPass, confirm) -> @@ -43,7 +45,7 @@ angular.module("<%= scriptAppName %>").controller "AccountCtrl", ($scope, user, else if newPass isnt confirm error "Passwords do not match" else - simpleLogin.changePassword(user.email, oldPass, newPass).then (-> + simpleLogin.changePassword(profile.email, oldPass, newPass).then (-> success "Password changed" return ), error diff --git a/templates/coffeescript/controllers/chat.coffee b/templates/coffeescript/controllers/chat.coffee index 6bf3f1555..7a334c05b 100644 --- a/templates/coffeescript/controllers/chat.coffee +++ b/templates/coffeescript/controllers/chat.coffee @@ -15,7 +15,7 @@ angular.module("<%= scriptAppName %>").controller "ChatCtrl", ($scope, fbutil, $ ), 5000 # synchronize a read-only, synchronized array of messages, limit to most recent 10 - $scope.messages = fbutil.syncArray("messages", limit: 10) + $scope.messages = fbutil.syncArray("messages", limitToLast: 10) # display any errors $scope.messages.$loaded().then null, alert diff --git a/templates/coffeescript/routes.with.login.coffee b/templates/coffeescript/routes.with.login.coffee index 35bf5cfad..22be6689b 100644 --- a/templates/coffeescript/routes.with.login.coffee +++ b/templates/coffeescript/routes.with.login.coffee @@ -7,7 +7,7 @@ # routes.js Configure routes for use with Angular, and apply authentication security -Add new routes to the ROUTES constant or use yo angularfire:route to create them +Add new routes using `yo angularfire:route` with the optional --auth-required flag. Any controller can be secured so that it will only load if user is logged in by using `whenAuthenticated()` in place of `when()`. This requires the user to @@ -115,4 +115,4 @@ angular.module("<%= scriptAppName %>").config([ $location.path loginRedirectPath if angular.isObject(err) and err.authRequired return -]).constant "SECURED_ROUTES", {} \ No newline at end of file +]).constant "SECURED_ROUTES", {} diff --git a/templates/common/app/scripts/angularfire/config.js b/templates/common/app/scripts/angularfire/config.js index b0f602791..77027abe3 100644 --- a/templates/common/app/scripts/angularfire/config.js +++ b/templates/common/app/scripts/angularfire/config.js @@ -2,9 +2,9 @@ angular.module('firebase.config', []) .constant('FBURL', 'https://<%= firebaseName %>.firebaseio.com')<% if( loginModule ) { %> - .constant('SIMPLE_LOGIN_PROVIDERS', ['<%= _.map(simpleLoginProviders, function(p) { + .constant('SIMPLE_LOGIN_PROVIDERS', ['<%= _.map(authProviders, function(p) { return p.value; }).join("','") %>']) .constant('loginRedirectPath', '/login')<% } - %>; \ No newline at end of file + %>; diff --git a/templates/common/app/scripts/angularfire/firebase.utils.js b/templates/common/app/scripts/angularfire/firebase.utils.js index 3fd47255d..677d03890 100644 --- a/templates/common/app/scripts/angularfire/firebase.utils.js +++ b/templates/common/app/scripts/angularfire/firebase.utils.js @@ -75,7 +75,7 @@ angular.module('firebase.utils', ['firebase', 'firebase.config']) function syncData(path, props) { var ref = firebaseRef(path); props = angular.extend({}, props); - angular.forEach(['limit', 'startAt', 'endAt'], function(k) { + angular.forEach(['limitToFirst', 'limitToLast', 'orderByKey', 'orderByChild', 'orderByPriority', 'startAt', 'endAt'], function(k) { if( props.hasOwnProperty(k) ) { var v = props[k]; ref = ref[k].apply(ref, angular.isArray(v)? v : [v]); @@ -84,4 +84,4 @@ angular.module('firebase.utils', ['firebase', 'firebase.config']) }); return $firebase(ref, props); } - }]); \ No newline at end of file + }]); diff --git a/templates/common/app/scripts/angularfire/simpleLogin.js b/templates/common/app/scripts/angularfire/simpleLogin.js index 75a9af876..45d9fbec9 100644 --- a/templates/common/app/scripts/angularfire/simpleLogin.js +++ b/templates/common/app/scripts/angularfire/simpleLogin.js @@ -2,77 +2,81 @@ 'use strict'; angular.module('simpleLogin', ['firebase', 'firebase.utils', 'firebase.config']) - // a simple wrapper on simpleLogin.getUser() that rejects the promise + // a simple wrapper that rejects the promise // if the user does not exists (i.e. makes user required), useful for // setting up secure routes that require authentication .factory('authRequired', function(simpleLogin, $q) { return function() { - return simpleLogin.getUser().then(function (user) { + return simpleLogin.auth.$requireAuth().then(function (user) { return user ? user : $q.reject({ authRequired: true }); }); }; }) - .factory('simpleLogin', function($firebaseSimpleLogin, fbutil, $q, $rootScope<% if( hasPasswordProvider ) { %>, createProfile, changeEmail<% } %>) { - var auth = $firebaseSimpleLogin(fbutil.ref()); + .factory('simpleLogin', function($firebaseAuth, fbutil, $q, $rootScope<% if( hasPasswordProvider ) { %>, createProfile<% } %>) { + var auth = $firebaseAuth(fbutil.ref()); var listeners = []; function statusChange() { fns.initialized = true; - fns.user = auth.user || null; + fns.user = auth.$getAuth() || null; angular.forEach(listeners, function(fn) { fn(fns.user); }); } var fns = { - user: null, + auth: auth, + + user: null, //todo use getUser() and remove this var initialized: false, getUser: function() { - return auth.$getCurrentUser(); + return auth.$getAuth(); }, login: function(provider, opts) { - return auth.$login(provider, opts); + return auth.$authWithOAuthPopup(provider, opts); + }, + + passwordLogin: function(creds, opts) { + return auth.$authWithPassword(creds, opts); }, logout: function() { - auth.$logout(); + auth.$unauth(); },<% if( hasPasswordProvider ) { %> - createAccount: function(email, pass, name) { - return auth.$createUser(email, pass) + createAccount: function(email, pass, opts) { + return auth.$createUser({email: email, password: pass}) .then(function() { // authenticate so we have permission to write to Firebase - return fns.login('password', {email: email, password: pass}); + return fns.passwordLogin({email: email, password: pass}, opts); }) .then(function(user) { // store user data in Firebase after creating account - return createProfile(user.uid, email, name).then(function() { + return createProfile(user.uid, email/*, name*/).then(function() { return user; }); }); }, changePassword: function(email, oldpass, newpass) { - return auth.$changePassword(email, oldpass, newpass); + return auth.$changePassword({email: email, oldPassword: oldpass, newPassword: newpass}); }, changeEmail: function(password, newEmail) { - return changeEmail(password, fns.user.email, newEmail, this); + return auth.$changeEmail({password: password, oldEmail: fns.user.email, newEmail: newEmail}); }, removeUser: function(email, pass) { - return auth.$removeUser(email, pass); + return auth.$removeUser({email: email, password: pass}); },<% } %> watch: function(cb, $scope) { listeners.push(cb); - fns.getUser().then(function(user) { - cb(user); - }); + auth.$waitForAuth(cb); var unbind = function() { var i = listeners.indexOf(cb); if( i > -1 ) { listeners.splice(i, 1); } @@ -84,10 +88,7 @@ } }; - $rootScope.$on('$firebaseSimpleLogin:login', statusChange); - $rootScope.$on('$firebaseSimpleLogin:logout', statusChange); - $rootScope.$on('$firebaseSimpleLogin:error', statusChange); - auth.$getCurrentUser(statusChange); + auth.$onAuth(statusChange); return fns; })<% if( hasPasswordProvider ) { %> @@ -119,102 +120,5 @@ return def.promise; }; - }) - - .factory('changeEmail', function(fbutil, $q) { - return function(password, oldEmail, newEmail, simpleLogin) { - var ctx = { old: { email: oldEmail }, curr: { email: newEmail } }; - - // execute activities in order; first we authenticate the user - return authOldAccount() - // then we fetch old account details - .then( loadOldProfile ) - // then we create a new account - .then( createNewAccount ) - // then we copy old account info - .then( copyProfile ) - // and once they safely exist, then we can delete the old ones - // we have to authenticate as the old user again - .then( authOldAccount ) - .then( removeOldProfile ) - .then( removeOldLogin ) - // and now authenticate as the new user - .then( authNewAccount ) - .catch(function(err) { console.error(err); return $q.reject(err); }); - - function authOldAccount() { - return simpleLogin.login('password', {email: ctx.old.email, password: password}) - .then(function(user) { - ctx.old.uid = user.uid; - }); - } - - function loadOldProfile() { - var def = $q.defer(); - ctx.old.ref = fbutil.ref('users', ctx.old.uid); - ctx.old.ref.once('value', - function(snap){ - var dat = snap.val(); - if( dat === null ) { - def.reject(oldEmail + ' not found'); - } - else { - ctx.old.name = dat.name; - ctx.curr.name = dat.name; - def.resolve(); - } - }, - function(err){ - def.reject(err); - }); - return def.promise; - } - - function createNewAccount() { - return simpleLogin.createAccount(ctx.curr.email, password, ctx.old.name).then(function(user) { - ctx.curr.uid = user.uid; - }); - } - - function copyProfile() { - var d = $q.defer(); - ctx.curr.ref = fbutil.ref('users', ctx.curr.uid); - var profile = {email: ctx.curr.email, name: ctx.curr.name}; - ctx.curr.ref.set(profile, function(err) { - if (err) { - d.reject(err); - } else { - d.resolve(); - } - }); - return d.promise; - } - - function removeOldProfile() { - var d = $q.defer(); - ctx.old.ref.remove(function(err) { - if (err) { - d.reject(err); - } else { - d.resolve(); - } - }); - return d.promise; - } - - function removeOldLogin() { - var def = $q.defer(); - simpleLogin.removeUser(ctx.old.email, password).then(function() { - def.resolve(); - }, function(err) { - def.reject(err); - }); - return def.promise; - } - - function authNewAccount() { - return simpleLogin.login('password', {email: ctx.curr.email, password: password}); - } - }; })<% } %>; -})(); \ No newline at end of file +})(); diff --git a/templates/common/app/views/login.html b/templates/common/app/views/login.html index f5137d8e7..419519fa2 100644 --- a/templates/common/app/views/login.html +++ b/templates/common/app/views/login.html @@ -12,8 +12,8 @@
{{err}}
-<% } %> \ No newline at end of file +<% } %> diff --git a/templates/common/root/_bower.json b/templates/common/root/_bower.json index ee6b9f328..dcaba8b15 100644 --- a/templates/common/root/_bower.json +++ b/templates/common/root/_bower.json @@ -12,9 +12,8 @@ "angular-sanitize": "<%= ngVer %>"<% } %><% if (animateModule) { %>, "angular-animate": "<%= ngVer %>"<% } %><% if (touchModule) { %>, "angular-touch": "<%= ngVer %>"<% } %><% if (routeModule) { %>, - "angular-route": "<%= ngVer %>", - "firebase": "<%= afconfig.bower.firebase %>"<% } %><% if (loginModule) { %>, - "firebase-simple-login": "<%= afconfig.bower.simplelogin %>"<% } %>, + "angular-route": "<%= ngVer %>",<% } %> + "firebase": "<%= afconfig.bower.firebase %>", "angularfire": "<%= afconfig.bower.angularfire %>" }, "devDependencies": { diff --git a/templates/javascript/controllers/account.js b/templates/javascript/controllers/account.js index 5dfd61067..cb23837af 100644 --- a/templates/javascript/controllers/account.js +++ b/templates/javascript/controllers/account.js @@ -11,6 +11,7 @@ angular.module('<%= scriptAppName %>') $scope.user = user; $scope.logout = simpleLogin.logout; $scope.messages = []; + var profile; loadProfile(user);<% if( hasPasswordProvider ) { %> $scope.changePassword = function(oldPass, newPass, confirm) { @@ -22,7 +23,7 @@ angular.module('<%= scriptAppName %>') error('Passwords do not match'); } else { - simpleLogin.changePassword(user.email, oldPass, newPass) + simpleLogin.changePassword(profile.email, oldPass, newPass) .then(function() { success('Password changed'); }, error); @@ -56,9 +57,10 @@ angular.module('<%= scriptAppName %>') }<% } %> function loadProfile(user) { - if( $scope.profile ) { - $scope.profile.$destroy(); + if( profile ) { + profile.$destroy(); } - fbutil.syncObject('users/'+user.uid).$bindTo($scope, 'profile'); + profile = fbutil.syncObject('users/'+user.uid); + profile.$bindTo($scope, 'profile'); } - }); \ No newline at end of file + }); diff --git a/templates/javascript/controllers/chat.js b/templates/javascript/controllers/chat.js index e1e9cf75a..8c0a334e7 100644 --- a/templates/javascript/controllers/chat.js +++ b/templates/javascript/controllers/chat.js @@ -9,7 +9,7 @@ angular.module('<%= scriptAppName %>') .controller('ChatCtrl', function ($scope, fbutil, $timeout) { // synchronize a read-only, synchronized array of messages, limit to most recent 10 - $scope.messages = fbutil.syncArray('messages', {limit: 10}); + $scope.messages = fbutil.syncArray('messages', {limitToLast: 10}); // display any errors $scope.messages.$loaded().catch(alert); diff --git a/templates/javascript/controllers/login.js b/templates/javascript/controllers/login.js index 07f78a2a0..97bdd9747 100644 --- a/templates/javascript/controllers/login.js +++ b/templates/javascript/controllers/login.js @@ -8,18 +8,28 @@ */ angular.module('<%= scriptAppName %>') .controller('LoginCtrl', function ($scope, simpleLogin, $location) { - <% if( hasOauthProviders ) { %>$scope.oauthlogin = function(provider) { - login(provider, { - rememberMe: true - }); + <% if( hasOauthProviders ) { %>$scope.oauthLogin = function(provider) { + $scope.err = null; + simpleLogin.login(provider, {rememberMe: true}).then( + function() { + $location.path('/account'); + }, + function(err) { + $scope.err = err; + } + ); }; <% } %><% if( hasPasswordProvider ) { %>$scope.passwordLogin = function(email, pass) { - login('password', { - email: email, - password: pass, - rememberMe: true - }); + $scope.err = null; + simpleLogin.passwordLogin({email: email, password: pass}, {rememberMe: true}).then( + function() { + $location.path('/account'); + }, + function(err) { + $scope.err = err; + } + ); }; $scope.createAccount = function(email, pass, confirm) { @@ -31,7 +41,7 @@ angular.module('<%= scriptAppName %>') $scope.err = 'Passwords do not match'; } else { - simpleLogin.createAccount(email, pass/*, name*/) + simpleLogin.createAccount(email, pass, {rememberMe: true}) .then(function() { $location.path('/account'); }, function(err) { @@ -39,17 +49,6 @@ angular.module('<%= scriptAppName %>') }); } }; + <% } %> - <% } %>function login(provider, opts) { - $scope.err = null; - simpleLogin.login(provider, opts).then( - function() { - $location.path('/account'); - }, - function(err) { - $scope.err = err; - } - ); - } - - }); \ No newline at end of file + }); diff --git a/templates/javascript/routes.with.login.js b/templates/javascript/routes.with.login.js index e48132d4b..307c64153 100644 --- a/templates/javascript/routes.with.login.js +++ b/templates/javascript/routes.with.login.js @@ -6,7 +6,7 @@ * # routes.js * * Configure routes for use with Angular, and apply authentication security - * Add new routes to the ROUTES constant or use yo angularfire:route to create them + * Add new routes using `yo angularfire:route` with the optional --auth-required flag. * * Any controller can be secured so that it will only load if user is logged in by * using `whenAuthenticated()` in place of `when()`. This requires the user to