Here's my case scenario:
User is not logged in, and they try to access a /settings page.
My Settings controller recognizes based on $auth.isAuthenticated() != true that they aren't logged in, and redirects them to /login
User fills out their email and password and hits submit.
What I would like to do on this third step is then redirect them to the /settings page, not the home page.
I'm thinking I would be changing this variable:
$authProvider.loginRedirect = '/';
The problem is that I cannot include $authProvider in my loginCtrl.js file without getting an "unknown provider" error in my console: https://docs.angularjs.org/error/$injector/unpr?p0= In other words, Angular does not recognize $authProvider when I try to include it. Here's what my loginCtrl.js file looks like:
/* Everything is blowing up because I'm trying to include $authProvider */
angular.module("PrayerChain")
.controller("loginCtrl", ["$rootScope", "$scope", "$state", "$http", "$auth", "$authProvider", loginCtrl]);
function loginCtrl($rootScope, $scope, $state, $http, $auth, $authProvider) {
$authProvider.loginRedirect = '/settings';
$scope.login = function () {
$auth.login({ Email: $scope.email, Password: $scope.password })
.then(function() {
})
.catch(function (response, status, headers) {
console.log(response);
$scope.error = JSON.parse(response.data);
});
};
}
Is including $authProvider in a controller even possible? If not, what's an alternative solution to changing where people are redirected upon logging in using Satellizer?
Thanks.
Usually provider objects can only be accessed at config time, whereas controllers are created in runtime. If you need to setup the authProvider, try doing:
angular.module('PrayerChain').config(
[ "$authProvider",
function($authProvider) {
$authProvider.loginRedirect = '/settings';
}
]).controller("loginCtrl",
// ...
The new version (0.12.5) are not using this settings anymore. You need to set the url inside your controller
$auth.login({ Email: $scope.email, Password: $scope.password })
.then(function() {
$location.path('your-new-route');
})
.catch(function (response, status, headers) {
console.log(response);
$scope.error = JSON.parse(response.data);
});
I was looking to do this and found that in version 0.13.0 (maybe earlier too?) you can pass an options parameter to login function like this:
$auth
.login(user, {
url: config.api + '/authenticate/customer'
})
Related
I have angular ui-router and I with to restrict access to a view unless a cookie is active.
I set my cookie in a post where i set cookie to users email address.
I know wish to only allow user to a view if they have a cookie username.
How do I do this ?
Setting cookie
FirstModule.controller('LoginController', function ($scope, $http, $cookies, $location) {
$scope.sometext = {};
$scope.LoginForm = function () {
var data = {
LoginEmail: $scope.sometext.LoginEmail
};
$http({
url: 'http://localhost:8080/back-end/controller',
method: "POST",
data: data,
headers: {'Content-Type': 'application/json'}
}).then(function (response) {
if (response.status === 200)
// $scope.sometext = "You are a valid member, please proceed to Mock Up Maker";
$cookies.put('UserName', $scope.sometext.LoginEmail);
$location.path('/download');
// console.log($cookies);
}).catch(function (response) {
if (response.status === 400)
$scope.sometext = "Hmm, it seems you are not registered, try again or register";
else if (response.status === 404)
$scope.sometext = "this is non a valid email address, please check email";
else if (response.status === 500)
$scope.sometext = "No API connection. Server side fail ";
else $scope.sometext = "Server connection error, give it a second to establish connection then try again";
});
}
});
router
FirstModule.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
// route to show our basic form (/form)
.state('form', {
url: '/form',
templateUrl: 'views/form.html',
controller: 'formController'
})
// nested states
// each of these sections will have their own view
// url will be nested (/form/signup)
.state('form.signup', {
url: '/signup',
templateUrl: 'views/form-signup.html'
})
// url will be /form/select
.state('form.select', {
url: '/select',
templateUrl: 'views/form-select.html'
})
// url will be /form/type
.state('form.type', {
url: '/type',
templateUrl: 'views/form-type.html'
})
// catch all route
// send users to the form page
$urlRouterProvider.otherwise('/form/signup');
});
You can use the resolve property from ui-router, just add the field resolve in your state definition and if the condition is not met redirect the user to a different state (eg. the login state)
state('user', {
url: '/',
templateUrl: 'UserView.html',
controller: 'UserViewController',
resolve: {
check: function($q, $cookies) {
if ($cookies.get('UserName')){ //cookie to check
return $q.resolve({});
} else{
return $q.reject({redirectState: 'loginState'});
}
}
}
});
then add an error handler to detect any error
$rootScope.$on('$stateChangeError', function(evt, to, toParams, from, fromParams, error) {
if (error.redirectState) {
$state.go(error.redirectState);
}
})
The answer given by Karim is good and should work well. However, this is like taking action at second step in flow.
The best way to handle this is to provide user a way to navigate to this URL only if he has that cookie set. You can either disable the link or hide it completely. IMO, that is a better approach.
I's rather implement it both ways to make my application robust. That implies, disabling/removing link/button will prevent users from accessing the link at all. And the Router-Resolve will safeguard from smart users who can copy paste the link or hit enter/F5 on browser. :)
I'm not sure if this is a duplicate or not, but I didn't manage to find anything that worked for me, so I'm posting this question.
I have a situation where I need to get values from database before directing user to certain routes, so I could decide what content to show.
If I move e.preventDefault() right before $state.go(..) then it works, but not properly. Problem is that it starts to load default state and when it gets a response from http, only then it redirects to main.home. So let's say, if the db request takes like 2 seconds, then it takes 2 seconds before it redirects to main.home, which means that user sees the content it is not supposed to for approximately 2 seconds.
Is there a way to prevent default at the beginning of state change and redirect user at the end of state change?
Also, if we could prevent default at the beginning of state change, then how could we continue to default state?
(function(){
"use strict";
angular.module('app.routes').run(['$rootScope', '$state', '$http', function($rootScope, $state, $http){
/* State change start */
$rootScope.$on('$stateChangeStart', function(e, to, toParams, from, fromParams){
e.preventDefault();
$http
.get('/url')
.error(function(err){
console.log(err);
})
.then(function(response){
if( response.data === 2 ){
// e.preventDefault()
$state.go('main.home');
}
// direct to default state
})
}
}]);
});
You could add a resolve section to your $stateProviderConfig.
Inside the resolve you can make a request to the databse and check required conditions. If case you don't want user to acces this page you can use $state.go() to redirect him elsewhere.
Sample config:
.state({
name: 'main.home',
template: 'index.html',
resolve: {
accessGranted: ['$http', '$state', '$q',
function($http, $state, $q) {
let deffered = $q.defer();
$http({
method: 'GET',
url: '/url'
}).then(function(data) {
if (data === 2) {
// ok to pass the user
deffered.resolve(true);
} else {
//no access, redirect
$state.go('main.unauthorized');
}
}, function(data) {
console.log(data);
//connection error, redirect
$state.go('main.unauthorized');
});
return deffered.promise;
}
]
}
});
Documentation of the resolve is available here
Note that you could use Promise object instead of $q service in case you don't need to support IE
One way to handle this situation is adding an interceptor as follows.
.config(function ($httpProvider) {
$httpProvider.interceptors.push('stateChangeInterceptor');
}).factory('stateChangeInterceptor', function ($q, $window,$rootScope) {
return {
'response': function(response) {
var isValid = true;//Write your logic here to validate the user/action.
/*
* Here you need to allow all the template urls and ajax urls which doesn't
*/
if(isValid){
return response;
}
else{
$rootScope.$broadcast("notValid",{statusCode : 'INVALID'});
}
},
'responseError': function(rejection) {
return $q.reject(rejection);
}
}
})
Then handle the message 'notValid' as follows
.run(function($state,$rootScope){
$rootScope.$on("notValid",function(event,message){
$state.transitionTo('whereever');
});
})
In my angular application I store the logged in user name in a cookie.
And I want to display this user name in the web page header.
I can retrieve this in my controller as below and display in the page.
$rootScope.fullName = $cookies.get('user');
But instead of doing this in every controller, is it possible to do it in one place and always get this data ?
Update #1:
I don't use ui-view. I use angular route and a sample is as below.
Are there any simple approach please ?
var mdmApp = angular.module('mdmApp');
// Routes
mdmApp.config(function($routeProvider, $httpProvider, $locationProvider) {
$routeProvider
.when('/listScopeAndFrequency/:reportTypeId', {
templateUrl : '3_calendar/listScopeAndFrequencies.html',
controller : "listScopeAndFrequenciesController"
})
.when('/listTemplateFrequencyExceptions/:reportTypeId/:consolidationScopeCode/:frequencyCode', {
templateUrl : '3_calendar/listTemplateFrequencyExceptions.html',
controller : "listTemplateFrequencyExceptionsController"
})
.when('/viewSubmissionDates', {
templateUrl : '3_calendar/viewSubmissionDates.html',
controller : "viewSubmissionDatesController"
})
Update #2
I tried like below but could not get any dynamic values from REST API, I am only able to hard code the value. Not able to read from cookie, localStorage or var all are undefined or give errors.
mdmApp.factory('userService', function($http, $localStorage, $cookies) {
var fullName ;
// Gets user details
$http.get("/mdm/getUser")
.success(function(data, status, headers, config) {
console.log('userService > user : ' +data.fullName);
fullName = data.fullName;
$localStorage.user = data;
$cookies.put('user', data.fullName);
})
.error(function(data, status, headers, config, statusText) {
console.log("Error while retrieving user details.");
});
console.log('fullName : ' +fullName);
console.log('$cookies.get(user) : ' +$cookies.get('user'));
console.log('$localStorage.user : ' +$localStorage.user);
return { name: "hard code only works here"};
});
mdmApp.directive('userTitle', ['userService', function(user) {
console.log('userTitle > user : ' +user);
return {
template: user.name,
};
}]);
Why don't you use a service? Those are specially designed for this task:
Angular services are:
Lazily instantiated – Angular only instantiates a service when an application component depends on it.
Singletons – Each component dependent on a service gets a reference to the single instance generated by the service factory.
How I do it (not necessarily the best way):
setup an independent angular app (non-secured area) for user registration, on login store a token or some identifier in a cookie or the localStorage
when instantiating the full angular app (secured area), create a service which will get back the previous value and inject it whenever you need the user
My User service looks like:
angular.module('app.core')
.service('User', User);
function User($rootScope, localStorageService) {
let user = localStorageService.get('user');
let token = localStorageService.get('userToken');
if(!token) {
//this loads another angular app which has nothing to do with this secured area (on /login)
window.location = '/login'
return;
}
for(let i in user)
this[i] = user[i]
this.token = 'Bearer ' + token;
//note you could reference this to the rootScope so that every scope can have the User object, this can be considered as bad practice!
//$rootScope.user = this;
return this;
}
I would go with a service and a directory for this.
As soyuka explained. Services exists for the purpose of sharing data between parts of an application. I would then use a directive to handle the data in relation to the DOM, that is, to write to the header.
Service/factory:
app.factory('userService', function() {
// do what you have to get your user and save the data.
return { name: 'John'};
});
Directive. Notice the injection of the service. This could be done through a controller if you wish. It depends on whether you want your directives to have a dependency to the service.
app.directive('userTitle', ['userService', function(user) {
// do what you want with the data
return {
template: user.name,
};
}]);
Here the service just returns a dummy name and the directive outputs the name in the DOM.
Here is a running plunker of the setup.
My code of login.js is
var loginModule = angular.module('loginModule', [])
.controller('LoginCtrl', function ($scope, $auth, $location) {
if (!$auth.isAuthenticated()) {
$scope.oneAtATime = true;
$scope.login = function () {
$auth.login({userName: $scope.username, password: $scope.password, isEncript: false})
.then(function () {
console.log($auth.getMessage());
})
.catch(function (response) {
console.log(response);
});
};
}
else {
$location.path('/home');
}
$scope.status = 'true';
});
and unit test code is
describe('login()', function() {
it('should be able to handle login errors', function () {
var user = {
email: 'foo#bar.com',
password: '1234',
isEncript: false
};
this.$httpBackend.expectPOST('app/controller/apicall.php?serviceName=login').respond(200);
this.$auth.login(user).then(angular.noop, function () {
});
this.$httpBackend.flush();
expect(user.isEncript).toBe(false);
});
});
});
......................the error i am getting is below .......................................
$auth login() should be able to handle login errors FAILED
Error: Unexpected request: POST ../../controller/apicall.php?serviceName=login
Expected POST /app/controller/apicall.php?serviceName=login
help me to solve how should i solve this error.
Something is calling
../../controller/apicall.php?serviceName=login
You can work out whats calling that, or just change your
this.$httpBackend.expectPOST('app/controller/apicall.php?serviceName=login').respond(200);
to
this.$httpBackend.expectPOST('../../controller/apicall.php?serviceName=login').respond(200);
and that should do it.
This error say that your $auth service send request to the other URL.
Therefore at first you need check and test what do your $auth service when you call its 'login' method.
The better way in order to test is to have separate suites for $auth service (where you will test how it interact with backend) and for LoginCtrl controller (where you will mock $auth service and test only behavior of the controller).
You can use this plunker http://tinyurl.com/lv3gskf as example. (I use tinyurl.com because stackoverflow not allow to post links to plunker without code)
My basic premise is I want to call back to the server to get the logged in user in case someone comes to the site and is still logged in. On the page I want to call this method. Since I am passing the user service to all my controllers I don't know which controller will be in use since I won't know what page they're landing on.
I have the following User Service
app.factory('userService', function ($window) {
var root = {};
root.get_current_user = function(http){
var config = {
params: {}
};
http.post("/api/user/show", null, config)
.success(function(data, status, headers, config) {
if(data.success == true) {
user = data.user;
show_authenticated();
}
});
};
return root;
});
Here is an empty controller I'm trying to inject the service into
app.controller('myResourcesController', function($scope, $http, userService) {
});
So on the top of my index file I want to have something along the lines of
controller.get_current_user();
This will be called from all the pages though so I'm not sure the syntax here. All examples I found related to calling a specific controller, and usually from within another controller. Perhaps this needs to go into my angularjs somewhere and not simply within a script tag on my index page.
You could run factory initialization in run method of your angular application.
https://docs.angularjs.org/guide/module#module-loading-dependencies
E.g.
app.run(['userService', function(userService) {
userService.get_current_user();
}]);
And userService factory should store authenticated user object internaly.
...
if (data.success == true) {
root.user = data.user;
}
...
Then you will be able to use your factory in any controller
app.controller('myController', ['userService', function(userService) {
//alert(userService.user);
}]);
You need to inject $http through the factory constructor function, for firsts
app.factory('userService', function ($window, $http) {
var root = {};
root.get_current_user = function(){
var config = {
params: {}
};
$http.post("/api/user/show", null, config)
.success(function(data, status, headers, config) {
if(data.success == true) {
user = data.user;
show_authenticated();
}
});
};
return root;
});
in your controller you can say
$scope.get_current_user = UserService.get_current_user();
ng attributes in your html if needed. besides this, i am not sure what you need.