My goal is cancel a xhr request if
the lastest has a 401 status.
With the code below I can cancel
the next request but if I try
to do
an other request (ie do login) is aborted
(it need like canceller.reject(reason))
The flow
1 set a parent route (auth) to check if the login is required
2 set a child root which inheriting the auth state
3 when the app hits the route cancel the request to articles and show a modal login
(function() {
'use strict';
function Auth($http) {
return {
isLoggedIn: function() {
return $http.get('/api/auth/isloggedin');
},
signin: function(data) {
return $http.post('/api/auth/signin', data);
}
};
}
function loginModal($modal, $rootScope,$templateCache) {
function successCallback (data) {
console.log('success',data);
}
function errorCallback (data) {
console.log('fail',data);
}
return {
open : function(){
var modalInstance = $modal.open({
template: $templateCache.get('auth/templates/modal-login.html'),
controller: 'LoginModalController',
controllerAs: 'auth'
});
return modalInstance.result.then(successCallback).catch(errorCallback);
}
};
}
function HttpInterceptor($rootScope,$q) {
var canceller = $q.defer();
return {
'request': function(config) {
config.requestTimestamp = new Date().getTime();
config.timeout = canceller.promise;
return config;
},
'response': function(response) {
response.config.responseTimestamp = new Date().getTime();
return response;
},
'responseError': function(rejection) {
if (rejection.status === 401) {
$rootScope.$emit('no-auth', rejection);
canceller.resolve('Unauthorized');
}
return $q.reject(rejection);
}
};
}
angular.module('auth.services', [])
.factory('Auth', Auth)
.factory('HttpInterceptor', HttpInterceptor)
.factory('loginModal', loginModal);
})();
(function() {
'use strict';
angular.module('auth',
['auth.services','auth.controllers','auth.routes'])
.run(function($rootScope,loginModal,HAS_MODAL_LOGIN){
$rootScope.$on('no-auth', function(event, data) {
if(HAS_MODAL_LOGIN){
loginModal.open();
}
});
});
})();
(function() {
'use strict';
function config($stateProvider,$httpProvider) {
$stateProvider
.state('auth', {
abstract: true,
template: '<ui-view/>',
resolve:{
auth : function(Auth){
return Auth.isLoggedIn();
}
}
});
//Http Intercpetor to check auth failures for xhr requests
$httpProvider.interceptors.push('HttpInterceptor');
}
angular.module('auth.routes', [])
.config(config);
})();
(function() {
'use strict';
function config($stateProvider) {
$stateProvider
.state('auth.articles', {
url: '/articles',
templateUrl: 'articles/templates/index.html',
controller:'ArticlesController as articles',
resolve:{
articlesData : function(Articles){
return Articles.get();
}
}
});
}
angular.module('articles.routes', [])
.config(config);
})();
You do not need to reject. If you have 401 error, then you may simply open login dialog right there. And if user closed dialog, navigate to home.
Please read this article by Gabe Scholz
http://brewhouse.io/blog/2014/12/09/authentication-made-simple-in-single-page-angularjs-applications.html
This is exactly what you try to do but much cleaner way.
Related
I am using Facebook SDK to build an application however I am faced with a challenge, I try to get the login status of the user before I redirect him to his profile page, however during the call to get the login status I get the error that
ReferenceError: FB is not defined
now the SDK is being loaded asynchronously so the error makes sense, how can i resolve the problem. Here is my code:
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: "Views/ListPages.html",
controller: 'MainCtrl',
resolve: {
authentication:["$location", "LoginFactory", function($location, LoginFactory){
console.log("in resolve");
LoginFactory.getLoginStatus()
.then(function(response){
if(response){
$location.path('/login');
}
else{
$location.path('/');
}
});
}]
}
})
.when('/login', {
templateUrl: "Views/Login.html",
controller: 'LoginCtrl'
})
.otherwise
({redirectTo: '/'});
});
loginApp.factory("LoginFactory", function ($rootScope, $q, $location, UserInfo) {
return {
getLoginStatus: function () {
var deferred = $q.defer();
FB.getLoginStatus(function (response) {
if(!response || response.error){
deferred.reject(new error("User Not logged in."));
}
else{
deferred.resolve(response);
}
});
return deferred.promise;
},
login: function () {
FB.login(function (response) {
if (response.authResponse === "connected") {
$rootScope.$broadcast('fb_connected', {facebook_id:response.authResponse.userID});
} else {
$rootScope.$broadcast('fb_login_failed');
}
}, {scope: "read_insights, publish_pages, manage_pages"});
},
logout: function () {
FB.logout(function (response) {
if (response) {
$rootScope.$broadcast('fb_logout_succeded');
$location.path('/login');
} else {
$rootScope.$broadcast('fb_logout_failed');
}
});
}
};
angular.module("LoginCtrlModule", ["FacebookLogin"])
.controller("LoginCtrl", ["$scope", "$location", "LoginFactory", function ($scope, $location, LoginFactory) {
$scope.login = function () {
LoginFactory.login();
$scope.on("fb_connected", function () {
$location.path("/");
});
$scope.on("fb_login_failed", function(){
$location.path("/login");
});
}
}]);
app.run(function ($rootScope, $location, LoginFactory) {
window.fbAsyncInit = function () {
FB.init({
appId: '',
status: true,
cookie: true,
xfbml: true
});
};
(function (d) {
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement('script');
js.id = id;
js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
}(document));
});
I was following these posts:
AngularJS- Login and Authentication in each route and controller
http://www.sitepoint.com/implementing-authentication-angular-applications/
But the problem i am facing is different.
Thanks for the help.
I think the solution is to not call the FB.getLoginStatus() but rather have an object that contains the User information and the access token, each time I try to route I should check if the User info object is null, in case if its not null then call the check login status method and accordingly take the route or not to take the route.
I am using angularAMD with require js and I have written an interceptor service which I want to register with $httpProvider.interceptors in my app config file but it throwing error as
Error: [$injector:unpr]
http://errors.angularjs.org/1.4.8/$injector/unpr?p0=cookieInjectorProvider%…eInjector%20%3C-%20%24http%20%3C-%20%24templateRequest%20%3C-%20%24compile(…)
Below is my implimentation
define(["angularAMD", "angular-route", "ui-bootstrap","ngCookies","common/service/interceptor-service"], function(angularAMD){
"use strict";
var app = angular.module("app", ["ui.router", "ui.bootstrap","ngAnimate","ngTouch","angular-carousel","ngCookies"]);
//route
app.config(["$stateProvider", "$urlRouterProvider", "$locationProvider","$httpProvider", function($stateProvider, $urlRouterProvider, $locationProvider,$httpProvider){
$urlRouterProvider.otherwise('/');
$stateProvider
.state("home", angularAMD.route({
url: "/",
views: {
'header':{
/*...*/
},
},
'content': {
/*...*/
},
'footer': {
/*...*/
}
}));
**//angular is not able to find 'cookieInjector' service**
$httpProvider.interceptors.push('cookieInjector');
}]);
// Bootstrap Angular when DOM is ready
return angularAMD.bootstrap(app, false, document.getElementsByTagName("body")[0]);
});
and my "cookieInjector file is" this service requires another service and it is also implemented in a similar way
define([
'angularAMD',
"common/service/cookie-service"
], function(angularAMD){
angularAMD.service("cookieValidator",[function(){
this.isLoggedIn = false;
this.getIsLoggedIn = function(){
return this.isLoggedIn;
};
this.setIsLoggedIn = function(status){
this.isLoggedIn = status;
};
}])
.factory('cookieInjector', ['$q','cookieValidator', 'cookieService',function($q,cookieValidator,cookieService) {
var cookieInjector = {
request: function(config) {
var cookie = cookieService.getCookie();
if(!cookie){
cookieValidator.setIsLoggedIn(false);
//$location.path('/login');
}else{
cookieValidator.setIsLoggedIn(true);
}
config.headers['Token'] = cookie ? cookie : null;
return config;
},
response: function(response) {
response.config.responseTimestamp = new Date().getTime();
return response;
},
responseError: function(response) {
// Cookie has expired
if(response.status === 401 || response.status === 403) {
cookieService.destroyCookie();
cookieValidator.setIsLoggedIn(false);
}
return $q.reject(response);
}
};
return cookieInjector;
}]);
})
I am stuck completely. Thanks for any help.
angular is not able to find 'cookieInjector' service
This is probably because cookieInjector is not available at the moment app.config run. From what I remember angularAMD.factory() will only enject your factory/service into you app after app.config() has been ran.
So in this case, another global angular module should be used instead of using angularAMD
create a file cookieInjector.js
angular.module('cookieModule', []).facotry('cookieInjector', ['$q', 'cookieValidator', 'cookieService', function($q, cookieValidator, cookieService) {
var cookieInjector = {
request: function(config) {
var cookie = cookieService.getCookie();
if (!cookie) {
cookieValidator.setIsLoggedIn(false);
//$location.path('/login');
} else {
cookieValidator.setIsLoggedIn(true);
}
config.headers['Token'] = cookie ? cookie : null;
return config;
},
response: function(response) {
response.config.responseTimestamp = new Date().getTime();
return response;
},
responseError: function(response) {
// Cookie has expired
if (response.status === 401 || response.status === 403) {
cookieService.destroyCookie();
cookieValidator.setIsLoggedIn(false);
}
return $q.reject(response);
}
};
return cookieInjector;
}]);
Then init can config your app.
// requireJS config
requirejs.config({
path:{
cookieInjector: 'path/to/cookieInjector'
}
// ... your codes
shim: {
// ... your codes
// make sure cookie injector only load after angularJS is loaded.
cookieInjector: ["angular"];
}
});
// your app.js (?) file
define(["angularAMD", "cookieInjector", "angular-route", "ui-bootstrap", "ngCookies", "common/service/interceptor-service"], function(angularAMD) {
"use strict";
var app = angular.module("app", ["cookieModule", "ui.router", "ui.bootstrap", "ngAnimate", "ngTouch", "angular-carousel", "ngCookies"]);
//route
app.config(["$stateProvider", "$urlRouterProvider", "$locationProvider", "$httpProvider", function($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state("home", angularAMD.route({
url: "/",
views: {
'header': {
/*...*/
},
},
'content': {
/*...*/
},
'footer': {
/*...*/
}
}));
// It should be loaded now
$httpProvider.interceptors.push('cookieInjector');
}]);
// Bootstrap Angular when DOM is ready
return angularAMD.bootstrap(app, false, document.getElementsByTagName("body")[0]);
});
I have included my code below. Basically, when I am loading my view, I am using resolve to get some data. In my service, if my promise is rejected - on error - the resolve gets infinitely called. Is there a better way I should be performing this?
(function () {
function AppService($q, $http, $log, $timeout, pageOptionsModel) {
return {
getPageOptions: function () {
var deferred = $q.defer();
var pageOptions = pageOptionsModel.getPageOptions();
if (pageOptions === null) {
$http.get("api/HomeApi/GetPageOptions")
.success(function (response) {
deferred.resolve(response);
$log.info("Successfully retriedved page options from service.");
})
.error(function (response) {
deferred.reject("Error");
$log.error("Errored while retrieving page options from service.");
});
}
else {
deferred.resolve(pageOptions);
}
return deferred.promise;
}
}
};
function AppConfig($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise("line");
// Now set up the states
$stateProvider
.state('line', {
url: "/line",
templateUrl: "app/line/lineTemplate.html",
controller: "lineController",
controllerAs: "line",
resolve: {
pageOptions: function (appService) {
return appService.getPageOptions();
}
}
});
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
};
angular.module("app", ["ui.router", "ui.bootstrap", "app.line", "app.modal"])
.config(AppConfig)
.factory("appService", AppService);
})();
Here is my Line Controller which never initializes if my promise is rejected.
(function () {
function LineController($scope, pageOptions) {
var self = this;
// INITIALIZE
self.pageOptions = pageOptions;
};
angular.module("app.line")
.controller("lineController", LineController);
})();
I am using the ui-router in the following code and I have two resolves.
I check if there is an authentication token.
I get some data from the server.
If there is no token, I reject the resolve and redirect to user to the login page.
The problem is, the "movieList" resolve is still happening - how can I cancel the resolve?
Any ideas?
angular.module('app.code').config(['$stateProvider', function($stateProvider) {
$stateProvider.state('code', {
url: '/code',
templateUrl: 'app/sections/code/code.tpl.html',
controller: 'CodeCtrl',
resolve: {
/**
* Auth middleware.
*/
UserData: ['$q', 'Session', function ($q, Session) {
var userData = Session.getUserData();
if (userData.token) {
return userData;
}
return $q.reject('login');
}],
// IF I TRIGGER THE REJECT ABOVE, HOW DO I CANCEL THIS FOLLOWING RESOLVE?
movieList: ['$q', 'MoviesService', function ($q, MoviesService) {
var deferred = $q.defer();
MoviesService.query().then(
function (success) {
deferred.resolve(success.data);
},
function (error) {
$q.reject('home');
}
);
return deferred.promise;
}]
}
});
}]);
Add UserData as a dependency to the movieList resolve function. Now movieList will be called only if UserData is resolved.
movieList: ['$q', 'MoviesService', 'UserData', function ($q, MoviesService, UserData) {
var deferred = $q.defer();
MoviesService.query().then(
function (success) {
deferred.resolve(success.data);
},
function (error) {
$q.reject('home');
}
);
return deferred.promise;
}
I'm trying to redirect my user to Dashboard after Third Party Login. But when success callback is fired, the application still on Login Page, nothing happened. If I refresh the browser my interceptor catch already login and change to Dashboard... My Login Controller looks like this:
ThirdParty.login(function(result){
callbackSUCCESS(result);
},function(){});
function callbackSUCCESS(result){
AuthenticationService.login(result).then(
callbackServerSUCCESS(), function(reject) {
callbackServerERROR(reject);
});
}
function callbackServerSUCCESS() {
$scope.$apply(function() {
$state.go('dashboard');
});
}
My route in app.js
$stateProvider
.state('dashboard', {
url: '/dashboard',
views: {
'': {
templateUrl: 'views/dashboard/dashboard.html',
controller: 'DashboardCtrl'
}
}
});
My Header Controller
.controller('HeaderCtrl', ['$scope', 'AuthenticationService', '$state',
function($scope, AuthenticationService, $state) {
$scope.logout = function() {
AuthenticationService.logout().then(callbackServer(), callbackServer());
};
function callbackServer() {
$state.go('login');
}
}
]);
Authentication Controller Angular Factory
var headersConfig = {
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
};
return {
login: function(credentials) {
var deferred = $q.defer();
$http.post('/api/users/sign_in', sanitizeCredentials(credentials), {
headers: headersConfig,
timeout: deferred.promise
}).then(function(result) {
if (result.status === 200) {
UserSessionService.cacheSession(result.data);
deferred.resolve();
} else {
deferred.reject();
}
}, function(reject) {
UserSessionService.clean();
deferred.reject(reject);
});
$timeout(function() {
deferred.resolve();
}, 15000);
return deferred.promise;
}
};
I can't remember the exact semantics of $state.go, but usually you need to use $scope.$apply in some manner when responding to events Angular isn't aware of, to ensure a digest cycle occurs. I.e. you could try:
ThirdParty.login(function(result) {
$scope.$apply(function() {
$state.go('dashboard');
});
}, function() {});
You need to get a reference to a scope from somewhere, but one shouldn't be hard to find in an Angular app.
Get rid of both () in
then(callbackServer(), callbackServer())