Skip to content

Instantly share code, notes, and snippets.

@LeonardoGentile
Last active August 29, 2015 14:04
Show Gist options
  • Save LeonardoGentile/b4fda841f1ee07a68425 to your computer and use it in GitHub Desktop.
Save LeonardoGentile/b4fda841f1ee07a68425 to your computer and use it in GitHub Desktop.
(function() {
'use strict';
/**
* @ngdoc service
* @name myApp.loginService
* @description
* # loginService
*/
angular.module('myApp', []).provider('loginService', function() {
var errorState, logoutState, userToken;
userToken = localStorage.getItem("userToken");
errorState = "app.error";
logoutState = "app.home";
this.$get = function($rootScope, $http, $q, $state) {
/*
Low-level, private functions.
*/
var getLoginData, managePermissions, setHeaders, setToken, wrappedService;
setHeaders = function(token) {
if (!token) {
delete $http.defaults.headers.common["X-Token"];
return;
}
$http.defaults.headers.common["X-Token"] = token.toString();
};
setToken = function(token) {
if (!token) {
localStorage.removeItem("userToken");
} else {
localStorage.setItem("userToken", token);
}
setHeaders(token);
};
getLoginData = function() {
if (userToken) {
setHeaders(userToken);
} else {
wrappedService.userRole = userRoles["public"];
wrappedService.isLogged = false;
wrappedService.doneLoading = true;
}
};
managePermissions = function() {
$rootScope.$on("$stateChangeStart", function(event, to, toParams, from, fromParams) {
/*
$stateChangeStart is a synchronous check to the accessLevels property
if it's not set, it will setup a pendingStateChange and will let
the grandfather resolve do his job.
In short:
If accessLevels is still undefined, it let the user change the state.
Grandfather.resolve will either let the user in or reject the promise later!
*/
if (wrappedService.userRole === null) {
wrappedService.doneLoading = false;
wrappedService.pendingStateChange = {
to: to,
toParams: toParams
};
return;
}
if (to.accessLevel === undefined || to.accessLevel.bitMask & wrappedService.userRole.bitMask) {
angular.noop();
} else {
event.preventDefault();
$rootScope.$emit("$statePermissionError");
$state.go(errorState, {
error: "unauthorized"
}, {
location: false,
inherit: false
});
}
});
/*
Gets triggered when a resolve isn't fulfilled
NOTE: when the user doesn't have required permissions for a state, this event
it's not triggered.
In order to redirect to the desired state, the $http status code gets parsed.
If it's an HTTP code (ex: 403), could be prefixed with a string (ex: resolvename403),
to handle same status codes for different resolve(s).
This is defined inside $state.redirectMap.
*/
$rootScope.$on("$stateChangeError", function(event, to, toParams, from, fromParams, error) {
/*
This is a very clever way to implement failure redirection.
You can use the value of redirectMap, based on the value of the rejection
So you can setup DIFFERENT redirections based on different promise errors.
*/
var errorObj, redirectObj;
errorObj = void 0;
redirectObj = void 0;
error = (typeof error === "object" ? error.status.toString() : error);
if (/^[45]\d{2}$/.test(error)) {
wrappedService.logoutUser();
}
/*
Generic redirect handling.
If a state transition has been prevented and it's not one of the 2 above errors, means it's a
custom error in your application.
redirectMap should be defined in the $state(s) that can generate transition errors.
*/
if (angular.isDefined(to.redirectMap) && angular.isDefined(to.redirectMap[error])) {
if (typeof to.redirectMap[error] === "string") {
return $state.go(to.redirectMap[error], {
error: error
}, {
location: false,
inherit: false
});
} else if (typeof to.redirectMap[error] === "object") {
redirectObj = to.redirectMap[error];
return $state.go(redirectObj.state, {
error: redirectObj.prefix + error 
}, {
location: false,
inherit: false
});
}
}
return $state.go(errorState, {
error: error
}, {
location: false,
inherit: false
});
});
};
/*
High level, public methods
*/
wrappedService = {
loginHandler: function(user, status, headers, config) {
/*
Custom logic to manually set userRole goes here
Commented example shows an userObj coming with a 'completed'
property defining if the user has completed his registration process,
validating his/her email or not.
EXAMPLE:
if (user.hasValidatedEmail) {
wrappedService.userRole = userRoles.registered;
} else {
wrappedService.userRole = userRoles.invalidEmail;
$state.go('app.nagscreen');
}
*/
setToken(user.token);
angular.extend(wrappedService.user, user);
wrappedService.isLogged = true;
wrappedService.userRole = user.userRole;
return user;
},
loginUser: function(httpPromise) {
httpPromise.success(this.loginHandler);
},
logoutUser: function(httpPromise) {
/*
De-registers the userToken remotely
then clears the loginService as it was on startup
*/
setToken(null);
this.userRole = userRoles["public"];
this.user = {};
this.isLogged = false;
$state.go(logoutState);
},
resolvePendingState: function(httpPromise) {
var checkUser, pendingState, reject, self, success;
checkUser = $q.defer();
self = this;
pendingState = self.pendingStateChange;
httpPromise.success(self.loginHandler);
httpPromise.then((success = function(httpObj) {
self.doneLoading = true;
if (pendingState.to.accessLevel === undefined || pendingState.to.accessLevel.bitMask & self.userRole.bitMask) {
checkUser.resolve();
} else {
checkUser.reject("unauthorized");
}
}), reject = function(httpObj) {
checkUser.reject(httpObj.status.toString());
});
/*
I setted up the state change inside the promises success/error,
so i can safely assign pendingStateChange back to null.
*/
self.pendingStateChange = null;
return checkUser.promise;
},
/*
Public properties
*/
userRole: null,
user: {},
isLogged: null,
pendingStateChange: null,
doneLoading: null
};
getLoginData();
managePermissions();
return wrappedService;
};
});
}).call(this);
//# sourceMappingURL=login-service.js.map
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment