diff --git a/src/module.js b/src/module.js index 0f08d0b..261b536 100644 --- a/src/module.js +++ b/src/module.js @@ -188,7 +188,8 @@ angular.module('stormpath', [ 'stormpath.userService', 'stormpath.viewModelService', 'stormpath.socialLogin', - 'stormpath.oauth' + 'stormpath.oauth', + 'stormpath.ui-router' ]) .factory('StormpathAgentInterceptor',['$isCurrentDomain', '$spHeaders', function($isCurrentDomain, $spHeaders){ @@ -246,10 +247,11 @@ angular.module('stormpath', [ */ this.$get = [ - '$user', '$injector', 'STORMPATH_CONFIG', '$rootScope', '$location', - function stormpathServiceFactory($user, $injector, STORMPATH_CONFIG, $rootScope, $location) { + 'StormpathUIRouter', '$user', '$injector', 'STORMPATH_CONFIG', '$rootScope', '$location', + function stormpathServiceFactory(StormpathUIRouter, $user, $injector, STORMPATH_CONFIG, $rootScope, $location) { var $state; var $route; + var $transitions; // ui-router 1 replacement for state events function StormpathService(){ var encoder = new UrlEncodedFormParser(); @@ -263,158 +265,16 @@ angular.module('stormpath', [ $route = $injector.get('$route'); } - return this; - } - function stateChangeUnauthenticatedEvent(toState, toParams){ - /** - * @ngdoc event - * - * @name stormpath.$stormpath#$stateChangeUnauthenticated - * - * @eventOf stormpath.$stormpath - * - * @eventType broadcast on root scope - * - * @param {Object} event - * - * Angular event object. - * - * @param {Object} toState The state that the user attempted to access. - * - * @param {Object} toParams The state params of the state that the user - * attempted to access. - * - * @description - * - * This event is broadcast when a UI state change is prevented, - * because the user is not logged in. - * - * Use this event if you want to implement your own strategy for - * presenting the user with a login form. - * - * To receive this event, you must be using the UI Router integration. - * - * @example - * - *
- * $rootScope.$on('$stateChangeUnauthenticated',function(e,toState,toParams){
- * // Your custom logic for deciding how the user should login, and
- * // if you want to redirect them to the desired state afterwards
- * });
- *
- */
- $rootScope.$broadcast(STORMPATH_CONFIG.STATE_CHANGE_UNAUTHENTICATED,toState,toParams);
- }
- function stateChangeUnauthorizedEvent(toState,toParams){
- /**
- * @ngdoc event
- *
- * @name stormpath.$stormpath#$stateChangeUnauthorized
- *
- * @eventOf stormpath.$stormpath
- *
- * @eventType broadcast on root scope
- *
- * @param {Object} event
- *
- * Angular event object.
- *
- * @param {Object} toState The state that the user attempted to access.
- *
- * @param {Object} toParams The state params of the state that the user
- * attempted to access.
- *
- * @description
- *
- * This event is broadcast when a UI state change is prevented,
- * because the user is not authorized by the rules defined in the
- * {@link stormpath.SpStateConfig:SpStateConfig Stormpath State Configuration}
- * for the requested state.
- *
- * Use this event if you want to implement your own strategy for telling
- * the user that they are forbidden from viewing that state.
- *
- * To receive this event, you must be using the UI Router integration.
- *
- * @example
- *
- *
- * $rootScope.$on('$stateChangeUnauthorized',function(e,toState,toParams){
- * // Your custom logic for deciding how the user should be
- * // notified that they are forbidden from this state
- * });
- *
- */
- $rootScope.$broadcast(STORMPATH_CONFIG.STATE_CHANGE_UNAUTHORIZED,toState,toParams);
- }
- StormpathService.prototype.stateChangeInterceptor = function stateChangeInterceptor(config) {
- $rootScope.$on('$stateChangeStart', function(e,toState,toParams){
- var sp = toState.sp || {}; // Grab the sp config for this state
- var authorities = (toState.data && toState.data.authorities) ? toState.data.authorities : undefined;
-
- if((sp.authenticate || sp.authorize || (authorities && authorities.length)) && (!$user.currentUser)){
- e.preventDefault();
- $user.get().then(function(){
- // The user is authenticated, continue to the requested state
- if(sp.authorize || (authorities && authorities.length)){
- if(authorizeStateConfig(sp, authorities)){
- $state.go(toState.name,toParams);
- }else{
- stateChangeUnauthorizedEvent(toState,toParams);
- }
- }else{
- $state.go(toState.name,toParams);
- }
- },function(){
- // The user is not authenticated, emit the necessary event
- stateChangeUnauthenticatedEvent(toState,toParams);
- });
- }else if(sp.waitForUser && ($user.currentUser===null)){
- e.preventDefault();
- $user.get().finally(function(){
- $state.go(toState.name,toParams);
- });
- }
- else if($user.currentUser && (sp.authorize || (authorities && authorities.length))){
- if(!authorizeStateConfig(sp, authorities)){
- e.preventDefault();
- stateChangeUnauthorizedEvent(toState,toParams);
- }
- }else if(toState.name===config.loginState){
- /*
- If the user is already logged in, we will redirect
- away from the login page and send the user to the
- post login state.
- */
- if($user.currentUser!==false){
- e.preventDefault();
- $user.get().finally(function(){
- if($user.currentUser && $user.currentUser.href){
- $state.go(config.defaultPostLoginState);
- } else {
- $state.go(toState.name,toParams);
- }
- });
- }
- }
- });
- };
-
- function authorizeStateConfig(spStateConfig, authorities){
- var sp = spStateConfig;
- if(sp && sp.authorize && sp.authorize.group) {
- return $user.currentUser.inGroup(sp.authorize.group);
- }else if(authorities){
- // add support for reading from JHipster's data: { authorities: ['ROLE_ADMIN'] }
- // https://github.com/stormpath/stormpath-sdk-angularjs/issues/190
- var roles = authorities.filter(function(authority){
- return $user.currentUser.inGroup(authority);
- });
- return roles.length > 0;
- }else{
- console.error('Unknown authorize configuration for spStateConfig',spStateConfig);
- return false;
+ if ($injector.has('$transitions')) {
+ $transitions = $injector.get('$transitions');
}
+
+ this.uiRouterWrapper = StormpathUIRouter.registerUIRouterInternals(
+ $transitions,
+ $state
+ );
+
+ return this;
}
function routeChangeUnauthenticatedEvent(toRoute) {
@@ -538,7 +398,7 @@ angular.module('stormpath', [
goToRoute(toRoute);
});
} else if ($user.currentUser && sp.authorize) {
- if (!authorizeStateConfig(sp)) {
+ if (!authorizeRouteConfig(sp)) {
event.preventDefault();
routeChangeUnauthorizedEvent(toRoute);
}
@@ -620,10 +480,10 @@ angular.module('stormpath', [
* });
*
*/
- StormpathService.prototype.uiRouter = function uiRouter(config){
+ StormpathService.prototype.uiRouter = function uiRouter(config) {
var self = this;
config = typeof config === 'object' ? config : {};
- this.stateChangeInterceptor(config);
+ this.uiRouterWrapper.registerInterceptor(config);
if(config.loginState){
self.unauthenticatedWather = $rootScope.$on(STORMPATH_CONFIG.STATE_CHANGE_UNAUTHENTICATED,function(e,toState,toParams){
diff --git a/src/stormpath.oauth.js b/src/stormpath.oauth.js
index b0668a4..e9ce965 100644
--- a/src/stormpath.oauth.js
+++ b/src/stormpath.oauth.js
@@ -65,9 +65,15 @@ function StormpathOAuthTokenProvider(STORMPATH_CONFIG) {
* {@link stormpath.oauth.StormpathOAuthToken#setTokenStoreType StormpathOAuthToken.setTokenStoreType}.
*/
this.$get = function $get($q, $normalizeObjectKeys, TokenStoreManager, $injector) {
- function StormpathOAuthToken() {
- this.tokenStore = TokenStoreManager.getTokenStore(self._tokenStoreType);
- }
+ function StormpathOAuthToken() {}
+
+ StormpathOAuthToken.prototype.getTokenStore = function getTokenStore() {
+ if (angular.isUndefined(this.tokenStore)) {
+ this.tokenStore = TokenStoreManager.getTokenStore(self._tokenStoreType);
+ }
+
+ return this.tokenStore;
+ };
/**
* @ngdoc method
@@ -102,7 +108,7 @@ function StormpathOAuthTokenProvider(STORMPATH_CONFIG) {
var canonicalToken = $normalizeObjectKeys(token);
// Store a time at which we should renew the token, subtract off one second to give us some buffer of time
canonicalToken.exp = new Date(new Date().setMilliseconds(0)+((token.expires_in-1)*1000));
- return this.tokenStore.put(STORMPATH_CONFIG.OAUTH_TOKEN_STORAGE_NAME, canonicalToken);
+ return this.getTokenStore().put(STORMPATH_CONFIG.OAUTH_TOKEN_STORAGE_NAME, canonicalToken);
};
/**
@@ -120,7 +126,7 @@ function StormpathOAuthTokenProvider(STORMPATH_CONFIG) {
* {@link stormpath.oauth.StormpathOAuthToken#setTokenResponse StormpathOAuthToken.setTokenResponse}.
*/
StormpathOAuthToken.prototype.getTokenResponse = function getTokenResponse() {
- return this.tokenStore.get(STORMPATH_CONFIG.OAUTH_TOKEN_STORAGE_NAME);
+ return this.getTokenStore().get(STORMPATH_CONFIG.OAUTH_TOKEN_STORAGE_NAME);
};
/**
@@ -136,7 +142,7 @@ function StormpathOAuthTokenProvider(STORMPATH_CONFIG) {
* implementation details.
*/
StormpathOAuthToken.prototype.removeToken = function removeToken() {
- return this.tokenStore.remove(STORMPATH_CONFIG.OAUTH_TOKEN_STORAGE_NAME);
+ return this.getTokenStore().remove(STORMPATH_CONFIG.OAUTH_TOKEN_STORAGE_NAME);
};
/**
diff --git a/src/stormpath.ui-router.js b/src/stormpath.ui-router.js
new file mode 100644
index 0000000..3a953fc
--- /dev/null
+++ b/src/stormpath.ui-router.js
@@ -0,0 +1,544 @@
+'use strict';
+
+/**
+* @ngdoc overview
+*
+* @name stormpath.ui-router
+*
+* @description
+*
+* This module provides the {@link stormpath.ui.router:StormpathUIRouter StormpathUIRouter}
+* service, which handles Stormpath's integration with UIRouter (both 0.*.* and 1.**).
+*/
+angular
+ .module('stormpath.ui-router', ['stormpath.userService'])
+ .service('StormpathUIRouter', StormpathUIRouter);
+
+/**
+ * @ngdoc service
+ * @name stormpath.ui.router:StormpathUIRouterTransition
+ * @description
+ *
+ * Encapsulates UIRouter state transitions for both versions 0.*.* and 1.*.*
+ * Expects UIRouter's `$state` as the first argument, and the transition start
+ * `arguments` as the second argument. It provides normalised access to the
+ * standard state transition manually.
+ *
+ * Should never be instantiated in client code. This class is meant only
+ * for private use in the {@link stormpath.ui.router:StormpathUIRouter StormpathUIRouter}
+ * service.
+ */
+function StormpathUIRouterTransition($state, args) {
+ this.$state = $state;
+ this._parseArguments(args);
+}
+
+StormpathUIRouterTransition.prototype._parseArguments = function _parseArguments(args) {
+ if (args.length >= 5) {
+ this.$event = args[0];
+ this.$toState = args[1];
+ this.$toParams = args[2];
+ this.$fromState = args[3];
+ this.$fromParams = args[4];
+ } else {
+ this.$trans = args[0];
+ }
+};
+
+/**
+ * @ngdoc method
+ * @name isLegacy
+ * @methodOf stormpath.ui.router:StormpathUIRouterTransition
+ *
+ * @returns {Boolean} Whether this is UI Router < 1.0.0
+ *
+ * @description
+ * Checks whether the version of UIRouter being used is 0.*.*
+ */
+StormpathUIRouterTransition.prototype.isLegacy = function isLegacy() {
+ return typeof this.$trans === 'undefined';
+};
+
+/**
+ * @ngdoc method
+ * @name sp
+ * @methodOf stormpath.ui.router:StormpathUIRouterTransition
+ *
+ * @returns {Object}
+ * Stormpath configuration for this state, or empty object if none is defined
+ *
+ * @description
+ * Accesses the Stormpath state configuration (set in the `sp` property) for
+ * the state currently being transitioned to. If no such configuration object
+ * is defined, an empty object is returned instead.
+ */
+StormpathUIRouterTransition.prototype.sp = function sp() {
+ return this.toState().sp || {};
+};
+
+/**
+ * @ngdoc method
+ * @name authorities
+ * @methodOf stormpath.ui.router:StormpathUIRouterTransition
+ *
+ * @returns {Array} List of roles authorized to enter this state
+ *
+ * @description
+ * Retrieves a list of roles that are authorized to enter this state, defined
+ * in the state definition under `data.authorities`. If there is no such array,
+ * an empty array is returned.
+ */
+StormpathUIRouterTransition.prototype.authorities = function authorities() {
+ var toState = this.toState();
+
+ if (toState.data && toState.data.authorities) {
+ return toState.data.authorities;
+ }
+
+ return [];
+};
+
+/**
+ * @ngdoc method
+ * @name fromState
+ * @methodOf stormpath.ui.router:StormpathUIRouterTransition
+ *
+ * @returns {State} State definition for state from which the transition is occurring
+ *
+ * @description
+ * Returns the state definition object for the state currently being transitioned
+ * from. This is a wrapper to provide uniform access to both UI Router 0.*.* and
+ * UI router 1.*.* state.
+ */
+StormpathUIRouterTransition.prototype.fromState = function fromState() {
+ return this.$fromState || this.$trans.from();
+};
+
+/**
+ * @ngdoc method
+ * @name fromParams
+ * @methodOf stormpath.ui.router:StormpathUIRouterTransition
+ *
+ * @returns {Object} Parameters of the state currently being transitioned from
+ *
+ * @description
+ * Returns the state parameters for the state currently being transitioned
+ * from. This is a wrapper to provide uniform access to both UI Router 0.*.* and
+ * UI router 1.*.* state parameters.
+ */
+StormpathUIRouterTransition.prototype.fromParams = function fromParams() {
+ return this.$fromParams || this.$trans.params('from');
+};
+
+/**
+ * @ngdoc method
+ * @name toState
+ * @methodOf stormpath.ui.router:StormpathUIRouterTransition
+ *
+ * @returns {State} State definition for state to which the transition is occurring
+ *
+ * @description
+ * Returns the state definition object for the state currently being transitioned
+ * to. This is a wrapper to provide uniform access to both UI Router 0.*.* and
+ * UI router 1.*.* state.
+ */
+StormpathUIRouterTransition.prototype.toState = function toState() {
+ return this.$toState || this.$trans.to();
+};
+
+/**
+ * @ngdoc method
+ * @name toParams
+ * @methodOf stormpath.ui.router:StormpathUIRouterTransition
+ *
+ * @returns {Object} Parameters of the state currently being transitioned to
+ *
+ * @description
+ * Returns the state parameters for the state currently being transitioned
+ * to. This is a wrapper to provide uniform access to both UI Router 0.*.* and
+ * UI router 1.*.* state parameters.
+ */
+StormpathUIRouterTransition.prototype.toParams = function toParams() {
+ return this.$toParams || this.$trans.params();
+};
+
+/**
+ * @ngdoc method
+ * @name pause
+ * @methodOf stormpath.ui.router:StormpathUIRouterTransition
+ *
+ * @returns {Boolean} Always `false`
+ *
+ * @description
+ * Prevents the current transition from happening. This actually only does so
+ * for UIRouter 0.*.*. For UIRouter 1.*.*, the resolution is promise-based and
+ * will not resolve when a promise is returned until that promise does.
+ *
+ * Returns `false` as a helper for UIRouter 1.*.* - in it, when `false` is returned,
+ * the transition is prevented.
+ */
+StormpathUIRouterTransition.prototype.pause = function pause() {
+ if (this.isLegacy()) {
+ this.$event.preventDefault();
+ }
+
+ return false;
+};
+
+/**
+ * @ngdoc method
+ * @name resume
+ * @methodOf stormpath.ui.router:StormpathUIRouterTransition
+ *
+ * @description
+ * Continues the transition to the original target state, if prevented. Otherwise
+ * a noop.
+ */
+StormpathUIRouterTransition.prototype.resume = function resume() {
+ this.redirect(this.toState(), this.toParams());
+};
+
+/**
+ * @ngdoc method
+ * @name redirect
+ * @methodOf stormpath.ui.router:StormpathUIRouterTransition
+ *
+ * @param {State|String} state State definition or state name
+ * @param {Object} params State parameters
+ *
+ * @description
+ * Performs a transition to a given state with the given parameters. A wrapper
+ * around UIRouter's `$state.go`.
+ */
+StormpathUIRouterTransition.prototype.redirect = function redirect(state, params) {
+ return this.$state.go(state.name || state, params);
+};
+
+
+/**
+ * @ngdoc service
+ * @name stormpath.ui.router:StormpathUIRouter
+ *
+ * @requires $rootScope
+ * @requires stormpath.userService.$user:$user
+ * @requires stormpath.STORMPATH_CONFIG:STORMPATH_CONFIG
+ *
+ * @description
+ * A wrapper around UI Router that allows Stormpath to connect to state transitions
+ * and perform authentication, authorization and redirection.
+ *
+ * Emits {@link stormpath.ui.router:StormpathUIRouter#events_$stateChangeUnauthorized $stateChangeUnauthorized}
+ * or {@link stormpath.ui.router:StormpathUIRouter#events_$stateChangeUnauthenticated $stateChangeUnauthenticated}
+ * in case of authorization or authentication failure, to allow for further client handling.
+ *
+ * When initialized, {@link stormpath.ui.router:StormpathUIRouter#methods_registerUIRouterInternals StormpathUIRouter.registerUIRouterInternals()}
+ * must be called to inject `$state` and `$transitions` (if UIRouter >= 1.0.0) into the service.
+ */
+function StormpathUIRouter($rootScope, $user, STORMPATH_CONFIG) {
+ this.$rootScope = $rootScope;
+ this.$user = $user;
+ this.STORMPATH_CONFIG = STORMPATH_CONFIG;
+}
+
+/**
+ * @ngdoc metod
+ * @name registerUIRouterInternals
+ * @methodOf stormpath.ui.router:StormpathUIRouter
+ *
+ * @param {TransitionsService} $transitions UIRouter 1.*.* `$transitions` service. If undefined, UIRouter 0.*.* is assumed
+ * @param {StateService} $state UIRouter `$state` service
+ * @returns {StormpathUIRouter} This instance, to allow for chaining.
+ *
+ * @description
+ * Anguiar UIRouter services are passed to this method to allow for them to be used
+ * if and only if UIRouter is the routing system being used.
+ *
+ * This method must be called if UIRouter is used (but the client code should not
+ * have to do so), and when called, at least `$state` must be defined.
+ */
+StormpathUIRouter.prototype.registerUIRouterInternals = function registerInternals($transitions, $state) {
+ this.$transitions = $transitions;
+ this.$state = $state;
+ return this;
+};
+
+/**
+ * @ngdoc method
+ * @name isLegacy
+ * @methodOf stormpath.ui.router:StormpathUIRouter
+ *
+ * @returns {Boolean} Whether this is UI Router < 1.0.0
+ *
+ * @description
+ * Checks whether the version of UIRouter being used is 0.*.*
+ */
+StormpathUIRouter.prototype.isLegacy = function isLegacy() {
+ return typeof this.$transitions === 'undefined';
+};
+
+/**
+ * @ngdoc method
+ * @name authorizeStateConfig
+ * @methodOf stormpath.ui.router:StormpathUIRouter
+ *
+ * @param {StormpathUIRouterTransition} transition Current transition
+ * @returns {Boolean} Is the user authorized to make this state transition
+ *
+ * @description
+ * Checks whether the user has the proper authorization to make the current
+ * transition to the target state. Does so by reading the state configuration's
+ * `sp.authorize` or `data.authorities` permitted role arrays.
+ */
+StormpathUIRouter.prototype.authorizeStateConfig = function authorizeStateConfig(transition) {
+ var sp = transition.sp();
+ var authorities = sp.authorities();
+
+ if (sp && sp.authorize && sp.authorize.group) {
+ return this.$user.currentUser.inGroup(sp.authorize.group);
+ } else if (authorities.length) {
+ // add support for reading from JHipster's data: { authorities: ['ROLE_ADMIN'] }
+ // https://github.com/stormpath/stormpath-sdk-angularjs/issues/190
+ var roles = authorities.filter(function(authority) {
+ return this.$user.currentUser.inGroup(authority);
+ });
+ return roles.length > 0;
+ }
+
+ console.error('Unknown authorize configuration for spStateConfig', spStateConfig);
+ return false;
+};
+
+/**
+ * @ngdoc method
+ * @name onStateChange
+ * @methodOf stormpath.ui.router:StormpathUIRouter
+ *
+ * @param {StormpathUIRouterTransition} transition Current state transition
+ *
+ * @description
+ * Performs required checks and possibly redirects when transitioning to a state.
+ * The possible operations are:
+ *
+ *
+ * $rootScope.$on('$stateChangeUnauthorized',function(e,toState,toParams){
+ * // Your custom logic for deciding how the user should be
+ * // notified that they are forbidden from this state
+ * });
+ *
+ */
+ this.$rootScope.$broadcast(
+ this.STORMPATH_CONFIG.STATE_CHANGE_UNAUTHORIZED,
+ transition.toState(),
+ transition.toParams()
+ );
+};
+
+/**
+ * @ngdoc method
+ * @name emitUnauthenticated
+ * @methodOf stormpath.ui.router:StormpathUIRouter
+ *
+ * @param {StormpathUIRouterTransition} transition Current state transition
+ *
+ * @description
+ * Emits the {@link stormpath.ui.router:StormpathUIRouter#$stateChangeUnauthenticated $stateChangeUnauthenticated}
+ * event with the data about the target state to signify that the transition failed due to the user not
+ * being authenticated (i.e. having the permissions to access this route).
+ */
+StormpathUIRouter.prototype.emitUnauthenticated = function emitUnauthenticated(transition) {
+ /**
+ * @ngdoc event
+ *
+ * @name stormpath.ui.router:StormpathUIRouter#$stateChangeUnauthenticated
+ *
+ * @eventOf stormpath.ui.router:StormpathUIRouter
+ *
+ * @eventType broadcast on root scope
+ *
+ * @param {Object} event
+ *
+ * Angular event object.
+ *
+ * @param {Object} toState The state that the user attempted to access.
+ *
+ * @param {Object} toParams The state params of the state that the user
+ * attempted to access.
+ *
+ * @description
+ *
+ * This event is broadcast when a UI state change is prevented,
+ * because the user is not logged in.
+ *
+ * Use this event if you want to implement your own strategy for
+ * presenting the user with a login form.
+ *
+ * To receive this event, you must be using the UI Router integration.
+ *
+ * @example
+ *
+ *
+ * $rootScope.$on('$stateChangeUnauthenticated',function(e,toState,toParams){
+ * // Your custom logic for deciding how the user should login, and
+ * // if you want to redirect them to the desired state afterwards
+ * });
+ *
+ */
+ this.$rootScope.$broadcast(
+ this.STORMPATH_CONFIG.STATE_CHANGE_UNAUTHENTICATED,
+ transition.toState(),
+ transition.toParams()
+ );
+};
+
+StormpathUIRouter.$inject = ['$rootScope', '$user', 'STORMPATH_CONFIG'];