Angularjs role based routing error - angularjs

I am trying to implement role based authorization in beeman's loopback-angular-admin repo by using this https://github.com/Narzerus/angular-permission
so I am trying to change the users.config.js to like this -
(function () {
'use strict';
angular.module('com.module.users')
.run(function ($rootScope, gettextCatalog, Permission, AppAuth, User) {
$rootScope.addMenu(gettextCatalog.getString('Users'), 'app.users.list', 'fa-user');
Permission.defineRole('anonymous', function (stateParams) {
// If the returned value is *truthy* then the user has the role, otherwise they don't
return !AppAuth.hasOwnProperty('currentUser');
});
Permission.defineRole('student', function (stateParams) {
// If the returned value is *truthy* then the user has the role, otherwise they don't
AppAuth.ensureHasCurrentUser(function () {
//This call also serves to redirect a user to the login screen, via the interceptor in users.auth.js, if they are not authenticated.
console.log(User.getCurrent());
return !!(User.getCurrent().hasOwnProperty('role') && User.getCurrent().role == 'student');
});
}).defineRole('tutor', function (stateParams) {
// If the returned value is *truthy* then the user has the role, otherwise they don't
AppAuth.ensureHasCurrentUser(function () {
//This call also serves to redirect a user to the login screen, via the interceptor in users.auth.js, if they are not authenticated.
console.log(User.getCurrent());
return !!(User.getCurrent().hasOwnProperty('role') && User.getCurrent().role == 'tutor');
});
});
});
})();
So basically I am defining my roles with the help of AppAuth module that has the following code -
(function () {
'use strict';
/*jshint sub:true*/
/*jshint camelcase: false */
angular
.module('com.module.users')
.factory('AppAuth', function ($cookies, User, LoopBackAuth, $http) {
var self = {
login: function (data, cb) {
LoopBackAuth.currentUserId = LoopBackAuth.accessTokenId = null;
$http.post('/api/users/login?include=user', {
email: data.email,
password: data.password
})
.then(function (response) {
if (response.data && response.data.id) {
LoopBackAuth.currentUserId = response.data.userId;
LoopBackAuth.accessTokenId = response.data.id;
}
if (LoopBackAuth.currentUserId === null) {
delete $cookies['accessToken'];
LoopBackAuth.accessTokenId = null;
}
LoopBackAuth.save();
if (LoopBackAuth.currentUserId && response.data && response.data
.user) {
self.currentUser = response.data.user;
cb(self.currentUser);
} else {
cb({});
}
}, function () {
console.log('User.login() err', arguments);
LoopBackAuth.currentUserId = LoopBackAuth.accessTokenId =
null;
LoopBackAuth.save();
cb({});
});
},
logout: function (cb) {
//Destroy the access token.
User.logout({"access_token": LoopBackAuth.accessTokenId}, function () {
//Destory both cookies that get created.
delete $cookies["access_token"];
delete $cookies["accessToken"];
//Perform the Passport Logout
$http.post('/auth/logout');
});
self.currentUser = null;
cb();
},
ensureHasCurrentUser: function (cb) {
if ((!this.currentUser || this.currentUser.id === 'social') && $cookies.accessToken) {
LoopBackAuth.currentUserId = LoopBackAuth.accessTokenId = null;
$http.get('/auth/current')
.then(function (response) {
if (response.data.id) {
LoopBackAuth.currentUserId = response.data.id;
LoopBackAuth.accessTokenId = $cookies.accessToken.substring(
2, 66);
}
if (LoopBackAuth.currentUserId === null) {
delete $cookies['accessToken'];
LoopBackAuth.accessTokenId = null;
}
LoopBackAuth.save();
self.currentUser = response.data;
var profile = self.currentUser && self.currentUser.profiles &&
self.currentUser.profiles.length && self.currentUser.profiles[
0];
if (profile) {
self.currentUser.name = profile.profile.name;
}
cb(self.currentUser);
}, function () {
console.log('User.getCurrent() err', arguments);
LoopBackAuth.currentUserId = LoopBackAuth.accessTokenId =
null;
LoopBackAuth.save();
cb({});
});
} else {
if(self.currentUser){
console.log('Using cached current user.');
}
cb(self.currentUser);
}
}
};
return self;
});
})();
and then in my routes file I am doing -
$stateProvider
.state('router', {
url: '/router',
template: '<div class="lockscreen" style="height: 100%"></div>',
controller: 'RouteCtrl'
})
.state('error', {
url: '/error',
template: '<div class="text-center alert alert-danger" style="margin: 100px">An error occurred.</div>'
})
.state('app', {
abstract: true,
url: '/app',
templateUrl: 'modules/core/views/app.html',
controller: 'MainCtrl',
data: {
permissions: {
only: ['admin', 'student']
}
}
})
.state('app.home', {
url: '',
templateUrl: 'modules/core/views/home.html',
controller: 'HomeCtrl',
data: {
permissions: {
only: ['admin', 'student']
}
}
})
.state('tutor', {
abstract: true,
url: '/tutor',
templateUrl: 'modules/core/views/app.html',
controller: 'MainCtrl',
data: {
permissions: {
only: ['admin', 'tutor']
}
}
})
.state('tutor.home', {
url: '',
templateUrl: 'modules/core/views/home.html',
controller: 'HomeCtrl',
data: {
permissions: {
only: ['admin', 'tutor']
}
}
});
$urlRouterProvider.otherwise('/router');
but now I am getting the following error in the browser console -
Error: undefined role or invalid role validation
at Object.Permission._findMatchingRole (angular-permission.js:170)
at Object.Permission.resolveIfMatch (angular-permission.js:211)
at Object.Permission.authorize (angular-permission.js:239)
at angular-permission.js:45
at Scope.$broadcast (angular-scenario.js:24081)
at Object.transitionTo (angular-ui-router.js:3229)
at Array.<anonymous> (angular-ui-router.js:2346)
at Object.invoke (angular.js:4185)
at handleIfMatch (angular-ui-router.js:1836)
at angular-ui-router.js:1891
so how can I debug this error or implement client side role based authorization in my loopback app

[..] implement client side role based authorization in my loopback app
This doesn't mean anything, loopback is your server application.
Client-side authorization should be prohibited, since clients can be tampered with.
Authorization should take place server-side exclusively. Client-side you should just adapt the content in function of the role. That way if someone tampers with your client code at least its http requests to the API will fail and your data is safe

Related

angular resolve with numerous routes

I'm working on application which is part of family apps. Every app works with the same token but with different settings, I have wrote a piece of code which allows checks configs before loading controller and page template, but the problem is that I have around 50 routes and I don't want to duplicate it for every route. So I wonder if angular has some interceptor allows resolve my check function before loading route and don't duplicate this piece of code
P.S. of course the simple way is write factory for this and add simple line of "resolve return factory" for every route, but I'm curious if there any other way?
Next is piece of my resolve function
page: 'Home Page',
templateUrl: Config.baseURL + '/views/welcome.html',
controller: 'welcome',
resolve: {
getConfig: function (lovServices) {
var config = App.storage('App_config');
if (config == null){
return lovServices.appConfig()
.then(function (response) {
App.storage('App_config', response);
})
} else {
return false
}
},
getPermissions: function (lovServices) {
var permissions = App.storage('App_access');
if (permissions == null){
return lovServices.permissions()
.then(function (response) {
function checkAvailability(arr, val) {
return arr.some(function(arrVal) {
return val === arrVal.allowed;
});
}
if (checkAvailability(response, true)){
var permissions = JSON.stringify(response);
App.storage('App_access', permissions);
} else {
localStorage.removeItem('App_token');
localStorage.removeItem('App_config');
localStorage.removeItem('App_access');
return false
}
});
}
}
}
})
So based on this answer Inside of my route provider before route definitions I have put this code:
function ($routeProvider, $locationProvider) {
var originalWhen = $routeProvider.when;
$routeProvider.when = function(path, route) {
route.resolve || (route.resolve = {});
angular.extend(route.resolve, {
getConfig: function (lovServices) {
var config = App.storage('App_config');
if (config == null && path !== '/login'){
return lovServices.AppConfig()
.then(function (response) {
App.storage('App_config', response);
})
} else {
return false
}
},
getPermissions: function (lovServices) {
var permissions = App.storage('App_access');
if (permissions == null && path !== '/login'){
return lovServices.permissions()
.then(function (response) {
function checkAvailability(arr, val) {
return arr.some(function(arrVal) {
return val === arrVal.allowed;
});
}
if (checkAvailability(response, true)){
var permissions = JSON.stringify(response);
App.storage('App_access', permissions);
} else {
localStorage.removeItem('App_token');
localStorage.removeItem('App_config');
localStorage.removeItem('App_access');
return false
}
});
}
}
});
return originalWhen.call($routeProvider, path, route);
};
$routeProvider.when('/', { ....
This code works as I expected.

Different tabs for different user roles using $rootScope in .state

What Im aiming is that I will have different tabs layout for different users
like patient and doctors
in my controller, I store the logged user in $rootScope.currentUser like this
$auth.login(credentials).then(function(data) {
return $http.get(CONSTANTS.LINK+'/authenticate/user');
}, function(data) {
var alertPopup = $ionicPopup.alert({
title: 'Error Logging in',
template: 'Invalid Credentials'
});
alertPopup.then(function(res) {
});
}).then(function(response) {
var user = JSON.stringify(response.data.user);
localStorage.setItem('user', user);
$rootScope.authenticated = true;
$rootScope.currentUser = response.data.user;
$state.go('tabs.home');
});;
Now this is my app.js. Nothing happens. am i Doing it right?
The $rootscope.currentUser is also returning undefined when i try console.log($rootScope.currentUser);
.state('tabs', {
url: '/tab',
templateUrl: function ($rootScope) {
if($rootScope.currentUser.role == 'Patient') {
return 'templates/tabs.html';
} else if ($rootScope.currentUser.role == 'Doctor') {
return 'templates/tabs-doctors.html';
}
},
abstract: true,
})
Since I was storing the user data in local storage when he logs in, I used that data to check the role of the user.
.state('tabs', {
url: '/tab',
templateUrl: function () {
var user = JSON.parse(localStorage.getItem('user'));
role = user.role;
if(role == 'Patient') {
return 'templates/tabs.html';
} else if (role == 'Doctor') {
return 'templates/tabs2.html';
} else {
return 'templates/tabs.html';
}
},
abstract: true,
})

Sending data from a service to a factory

Hey guys I am trying to send a variable from a service from auth.service.js to a factory which is in app.js. I am kind of new to angular so I appreciate any help.
(function() {
'use strict';
angular
.module('app')
.service('authService', authService);
authService.$inject = ['$rootScope', 'lock', 'authManager', 'jwtHelper'];
function authService($rootScope, lock, authManager, jwtHelper) {
var userProfile = JSON.parse(localStorage.getItem('profile')) || {};
function login() {
lock.show();
}
// Logging out just requires removing the user's
// id_token and profile
function logout() {
localStorage.removeItem('id_token');
localStorage.removeItem('profile');
authManager.unauthenticate();
userProfile = {};
}
// Set up the logic for when a user authenticates
// This method is called from app.run.js
function registerAuthenticationListener() {
lock.on('authenticated', function(authResult) {
localStorage.setItem('id_token', authResult.idToken);
authManager.authenticate();
var pubkey = KEYUTIL.getKey('-----BEGIN CERTIFICATE-----MIIC8DCCAdigAwIBAgIJVUYCZUQdreDfMA0GCSqGSIb3DQEBBQUAMB8xHTAbBgNVBAMTFHN3aXRjaC1hcHAuYXV0aDAuY29tMB4XDTE2MTAwNTE5MzczM1oXDTMwMDYxNDE5MzczM1owHzEdMBsGA1UEAxMUc3dpdGNoLWFwcC5hdXRoMC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTG7oTPof81dqHqDKT8Mi/umpKgALMHJRwXSVIPBtPZGrgyOubi1oPsWZQrYOwla/3fMhstV/g6ZclVLGg9YHwNZl7uZYaOhAX1CjaTnDUe85R0lvFMRO42N5ZdbhXQASPPrMNZL7gov3eBQcj2n+Jb2k7OWYpN2mevw1fd6iah0eKAeoUcoWGjYwkB9DLmN7HizRsMHeVRyx3BJisI1PmFMkR+Ewbdu+HtOf7yavaVS6KmJti/U/192mXDgakRBLeODrZ+AxwedYcaF4CtGyS52SKkkHsbi6KsjDjfc9CbRRM+51VffVNzTsMTHYtADG34KHigGry/x/5QfsCAEXnAgMBAAGjLzAtMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFOUsGT48yyeHc6a/RdMAlasaM3p7MA0GCSqGSIb3DQEBBQUAA4IBAQAeqkTdeYqE1gSauYr/h30OSjxvHgskUOueWvFBnFveiEQWV3eVyu/psn2YG/2QgCeNSWUMsd8SXCAOBilFy6GL27bUmGKoZEDYsm0dUFTxZiTHgJZNMMQIpPtCLw48Ly1BVQQvi21DZnS9G5ZdWbTEwjNK4M+Fil5zgaiJaObRH4+oAXpgwngT+ZoEO3Z38urbs/Gcp1VKvHjEdY18JxyDChQfIDQNb6bc2zoOR62JTx75fC7khQesJ2jcxJhE1VLsvMRr1bVaVgBeEAdq+fC6WQsJA08209JmJfO4/OYscSe9RxnDEXa6UOQpNO34x5Tr8AImQTLy3jdFoNg1/fSL-----END CERTIFICATE-----');
var isValid = KJUR.jws.JWS.verifyJWT(authResult.idToken, pubkey, {alg: ['RS256']});
console.log(isValid);
// Used for decoding the message returned form auth0
var decoded = jwt_decode(authResult.idToken);
// The encoded message returned from auth0
console.log(authResult.idToken);
//The decoded message returned from auth0
console.log(decoded);
lock.hide();
// Redirect to default page
location.hash = '#/app/home';
lock.getProfile(authResult.idToken, function(error, profile) {
if (error) {
console.log(error);
}
localStorage.setItem('profile', JSON.stringify(profile));
});
});
}
function checkAuthOnRefresh() {
var token = localStorage.getItem('id_token');
if (token) {
if (!jwtHelper.isTokenExpired(token)) {
if (!$rootScope.isAuthenticated) {
authManager.authenticate();
}
}
}
}
return {
userProfile: userProfile,
login: login,
logout: logout,
registerAuthenticationListener: registerAuthenticationListener,
checkAuthOnRefresh: checkAuthOnRefresh
}
}
})();
I want to send from the function registeredAuthentificationListener() the authResult.idToken to a factory in
app.js
(function () {
var iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
if (iOS) {
var isApple = 'isApple';
}else{
var isApple = 'notApple';
}
'use strict';
var app = angular
.module('app', ['ionic', 'auth0.lock', 'angular-jwt'])
.config(config);
config.$inject = ['$stateProvider', '$urlRouterProvider', 'lockProvider', 'jwtOptionsProvider','$httpProvider'];
function factory() {
return {
request : function (config) {
config.headers['X-switch-using'] = isApple;
return config;
}
}
}
function config($stateProvider, $urlRouterProvider, lockProvider, jwtOptionsProvider,$httpProvider) {
$stateProvider
// setup an abstract state for the tabs directive
.state('app', {
url: '/app',
abstract: true,
templateUrl: 'components/menu/menu.html',
})
.state('app.home', {
url: '/home',
views: {
'menuContent': {
templateUrl: 'components/home/home.html'
}
}
})
.state('app.dashboard', {
url: '/dashboard',
views: {
'menuContent': {
templateUrl: 'components/template/template.html'
}
}
})
.state('app.signin', {
url: '/login',
views: {
'menuContent': {
templateUrl: 'components/login/login.html'
}
}
});
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/app/home');
$httpProvider.interceptors.push(factory);
lockProvider.init({
clientID: AUTH0_CLIENT_ID,
domain: AUTH0_DOMAIN,
options: {
auth: {
redirect: false,
params: {
scope: 'openid',
device: 'Mobile device'
}
}
}
});
// Configuration for angular-jwt
jwtOptionsProvider.config({
tokenGetter: function() {
return localStorage.getItem('id_token');
},
whiteListedDomains: ['localhost'],
unauthenticatedRedirectPath: '/login'
});
}
})();
Thank you for your help!
It's my bad, you can send the variable using localStorage.getItem('id_token') and use that straight in the app.js

Prevent a state change in ui-router based on HTTP query

How do I prevent a state change for a specific "to" state in ui-router (is it using onEnter?) assuming I have this route:
.state('auth.confirm', {
url: '/confirm/:resetToken',
template: '<confirm-page></confirm-page>',
data: { pageTitle: 'Confirm Reset', specialClass: 'gray-bg' }
})
and this service with this promise-based function:
validateResetToken: function(resetToken) {
var self = this;
var deferred = $q.defer();
$http.post(AppConstants.hostRootUrl + '/auth/reset/validate', { resetToken: resetToken })
.then(function(response) {
if(response.data && response.data.success) {
// if we got a 200 return and it indicates success in the response, resolve
self.message = 'Success';
deferred.resolve(self.message);
}
else if (response.data && !response.data.success && response.data.error) {
// if we got a 200 return, but success is falsey and there's an error message, reject with that message
self.message = response.data.error;
deferred.reject(self.message);
}
else {
// error with generic message
self.message = 'Unknown response. Contact administrator.';
deferred.reject(self.message);
}
}, function(errPost) {
if (errPost.data && errPost.data.error) {
self.message = errPost.data.error;
deferred.reject(self.message);
}
else {
self.message = 'Could not connect.';
deferred.reject(self.message);
}
});
return deferred.promise;
},
For posterity (and Googlers) sake, Alon Eitan made me take a second look at my resolve approach, and I realized that my addition of the catch() was causing the rejected promise to not percolate up. This final code works:
.state('auth.confirm', {
url: '/confirm/:resetToken',
template: '<confirm-page></confirm-page>',
data: { pageTitle: 'Confirm Reset', specialClass: 'gray-bg' },
resolve: {
validated: function($q, $stateParams, AuthService, toastr) {
//$log.log('auth.confirm resolve $stateParams',$stateParams);
return AuthService.validateResetToken($stateParams.resetToken).catch(function(validateErr) {
toastr.error(validateErr, 'Blocked', {closeButton: true});
return $q.reject(validateErr);
});
}
}
})
You can create a rule as in https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-create-rules-to-prevent-access-to-a-state
Adapt their example:
app.config(function($stateProvider) {
$stateProvider.state('privatePage', {
data: {
rule: function(token) {
return validateResetToken(token)
}
});
});
app.run(function($rootScope, $state, $stateParams) {
$rootScope.$on('$stateChangeStart', function(e, to) {
if (!angular.isFunction(to.data.rule)) return;
var result = to.data.rule($stateParams.resetToken);
if (result && result.to) {
e.preventDefault();
// Optionally set option.notify to false if you don't want
// to retrigger another $stateChangeStart event
$state.go(result.to, result.params, {notify: false});
}
});
});

Angular-UI and PassportJS Authentication

So I'm trying to get local-auth working with passport and angular-ui but I think i've confused myself. I believe local-signup and local-login work because the redirects are working as expected.
I have a small Auth service that I want to inject and use to store the user information. it looks like this:
myApp.factory('Auth', function() {
var user;
return {
setUser: function(aUser) {
user = aUser;
},
isLoggedIn: function() {
return (user) ? user : false;
}
}
});
My routes look like this:
app.post('/login', function(req, res, next) {
passport.authenticate('local-login', function(err, user, info) {
if (err) {
console.log(err);
return next(err);
}
if (!user) {
console.log(user);
var param = 'There was an error with your login';
return res.redirect('/#/login?valid=' + param);
}
req.logIn(user, function(err) {
if (err) {
console.log(err);
return next(err);
}
//console.log(req);
console.log(user);
return res.redirect('/#/news');
});
})(req, res, next);
});
My states look like this:
myApp.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/news");
$stateProvider
.state('news', {
url: "/news",
templateUrl: "templates/news.html",
params: {
message: null
}
})
.state('people', {
url: "/people",
templateUrl: "templates/people.html",
resolve: {
authenticate: authenticate
},
})
.state('login', {
url: "/login",
templateUrl: "templates/login.html"
})
.state('signup', {
url: "/signup",
templateUrl: "templates/signup.html"
})
.state('preLogin', {
url: "/preLogin",
templateUrl: "templates/preLogin.html"
});
function authenticate($q, Auth, $state, $timeout) {
if (Auth.isLoggedIn()) {
return $q.when()
} else {
$timeout(function() {
$state.go('login')
})
return $q.reject()
}
}
});
I don't know how or when to store the user information in my Auth service. I assume I need to do this in the loginCtrl but I don't know how to tell when its a successful login besides the URL redirects.
Any help is appreciated.

Resources