login/logout as client-side service

This commit is contained in:
Davide Alberani 2016-06-12 16:04:46 +02:00
parent d344deb91c
commit 7549accecd
6 changed files with 142 additions and 120 deletions

View file

@ -59,13 +59,13 @@
<ul class="nav navbar-nav navbar-right">
<li ng-if="info.user.username">
<span class="btn">{{info.user.username}}</span>
<span class="btn btn-link">
<a href="/logout"><span class="fa fa-sign-out vcenter"></span>&nbsp;{{'logout' | translate}}</a>
<span class="btn btn-link">
<a ng-controller="LoginCtrl" ng-click="logout()"><span class="fa fa-sign-out vcenter"></span>&nbsp;{{'logout' | translate}}</a>
</span>
</li>
<li ng-if="!info.user.username">
<span class="btn btn-link">
<a href="/login"><span class="fa fa-sign-in vcenter"></span>&nbsp;{{'login' | translate}}</a>
<span class="btn btn-link">
<a ui-sref="login"><span class="fa fa-sign-in vcenter"></span>&nbsp;{{'login' | translate}}</a>
</span>
</li>
</ul>

18
angular_app/js/app.js vendored
View file

@ -37,9 +37,14 @@ eventManApp.run(['$rootScope', '$state', '$stateParams', '$log', 'Info',
$rootScope.error = {error: false};
Info.get({}, function(data) {
$rootScope.info = data || {};
});
$rootScope.readInfo = function(callback) {
Info.get({}, function(data) {
$rootScope.info = data || {};
if (callback) {
callback();
}
});
};
$rootScope.errorHandler = function(response) {
$log.debug('Handling error message:');
@ -76,6 +81,8 @@ eventManApp.run(['$rootScope', '$state', '$stateParams', '$log', 'Info',
);
return granted;
};
$rootScope.readInfo();
}]
);
@ -155,6 +162,11 @@ eventManApp.config(['$stateProvider', '$urlRouterProvider',
url: '/persons',
templateUrl: 'import-persons.html',
controller: 'FileUploadCtrl'
})
.state('login', {
url: '/login',
templateUrl: 'login.html',
controller: 'LoginCtrl'
});
}
]);

View file

@ -538,26 +538,51 @@ eventManControllers.controller('PersonDetailsCtrl', ['$scope', '$stateParams', '
}]
);
eventManControllers.controller('LoginCtrl', ['$scope', '$rootScope', '$state', '$log', 'User',
function ($scope, $rootScope, $state, $log, User) {
$scope.loginData = {};
eventManControllers.controller('FileUploadCtrl', ['$scope', '$log', '$upload', 'Event',
function ($scope, $log, $upload, Event) {
$scope.file = null;
$scope.reply = {};
$scope.events = Event.all();
$scope.upload = function(file, url) {
$log.debug("FileUploadCtrl.upload");
$upload.upload({
url: url,
file: file,
fields: {targetEvent: $scope.targetEvent}
}).progress(function(evt) {
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
$log.debug('progress: ' + progressPercentage + '%');
}).success(function(data, status, headers, config) {
$scope.file = null;
$scope.reply = angular.fromJson(data);
});
};
$scope.login = function() {
User.login($scope.loginData, function(data) {
if (!data.error) {
$rootScope.readInfo(function() {
$state.go('events');
});
}
});
};
$scope.logout = function() {
User.logout({}, function(data) {
if (!data.error) {
$rootScope.readInfo(function() {
$state.go('events');
});
}
});
};
}]
);
eventManControllers.controller('FileUploadCtrl', ['$scope', '$log', '$upload', 'Event',
function ($scope, $log, $upload, Event) {
$scope.file = null;
$scope.reply = {};
$scope.events = Event.all();
$scope.upload = function(file, url) {
$log.debug("FileUploadCtrl.upload");
$upload.upload({
url: url,
file: file,
fields: {targetEvent: $scope.targetEvent}
}).progress(function(evt) {
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
$log.debug('progress: ' + progressPercentage + '%');
}).success(function(data, status, headers, config) {
$scope.file = null;
$scope.reply = angular.fromJson(data);
});
};
}]
);

View file

@ -238,6 +238,37 @@ eventManServices.factory('Info', ['$resource', '$rootScope',
);
eventManServices.factory('User', ['$resource', '$rootScope',
function($resource, $rootScope) {
return $resource('users/:id', {id: '@_id'}, {
get: {
method: 'GET',
interceptor : {responseError: $rootScope.errorHandler},
transformResponse: function(data, headers) {
data = angular.fromJson(data);
if (data.error) {
return data;
}
return data.user || {};
}
},
login: {
method: 'POST',
url: '/login',
interceptor : {responseError: $rootScope.errorHandler}
},
logout: {
method: 'GET',
url: '/logout',
interceptor : {responseError: $rootScope.errorHandler}
}
});
}]
);
/* WebSocket collection used to update the list of persons of an Event. */
eventManApp.factory('EventUpdates', ['$websocket', '$location', '$log',
function($websocket, $location, $log) {

View file

@ -1,75 +1,22 @@
<!doctype html>
<html ng-app="eventManApp">
<head>
<title>EventMan{ager} - Login</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<script src="/static/js/jquery-2.1.3.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<link href="/static/css/normalize.css" rel="stylesheet">
<link href="/static/css/bootstrap.css" rel="stylesheet">
<link href="/static/css/bootstrap-theme.css" rel="stylesheet">
<link href="/static/css/font-awesome-4.3.0/css/font-awesome.min.css" rel="stylesheet">
<link href="/static/css/eventman.css" rel="stylesheet">
</head>
<!--
Copyright 2015 Davide Alberani <da@erlug.linux.it>
RaspiBO <info@raspibo.org>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<body>
<div class="container">
<div class="panel panel-primary table-striped top5">
<div class="panel-heading">Login</div>
<div class="panel-body">
<div id="wronglogin" class="alert alert-danger alert-dismissible hidden" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<strong>Error!</strong> Wrong username and password.
</div>
<form method="POST">
<div class="input-group input-group-lg">
<span class="input-group-addon min150">Username</span>
<input type="text" id="username" name="username" class="form-control" placeholder="admin">
</div>
<div class="input-group input-group-lg top10">
<span class="input-group-addon min150">Password</span>
<input type="password" id="password" name="password" class="form-control" placeholder="eventman">
</div>
<button type="submit" class="btn btn-success top10">login</button>
</form>
</div>
<div class="container">
<div class="panel panel-primary table-striped top5">
<div class="panel-heading">{{'Login' | translate}}</div>
<div class="panel-body">
<div id="wronglogin" class="alert alert-danger alert-dismissible hidden" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<strong>{{'Error!' | translate}}</strong> {{'Wrong username and password.' | translate}}
</div>
<form method="POST">
<div class="input-group input-group-lg">
<span class="input-group-addon min150">{{'Username' | translate}}</span>
<input type="text" id="username" name="username" ng-model="loginData.username" class="form-control" placeholder="admin">
</div>
<div class="input-group input-group-lg top10">
<span class="input-group-addon min150">{{'Password' | translate}}</span>
<input type="password" id="password" name="password" ng-model="loginData.password" class="form-control" placeholder="eventman">
</div>
<button type="submit" ng-click="login()" class="btn btn-success top10">{{'login' | translate}}</button>
</form>
</div>
</body>
<script>
function getUrlParameter(sParam) {
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++) {
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == sParam) {
return sParameterName[1];
}
}
}
$(document).ready(function() {
if (getUrlParameter('failed')) {
$('#wronglogin').removeClass('hidden');
}
});
</script>
</html>
</div>
</div>

View file

@ -56,7 +56,7 @@ def authenticated(method):
if not self.authentication:
return method(self, *args, **kwargs)
# unauthenticated API calls gets redirected to /v1.0/[...]
if self.is_api() and not self.get_.current_user():
if self.is_api() and not self.current_user:
self.redirect('/v%s%s' % (API_VERSION, self.get_login_url()))
return
return original_wrapper(self, *args, **kwargs)
@ -483,7 +483,6 @@ class PersonsHandler(CollectionHandler):
"""Handle requests for Persons."""
document = 'person'
collection = 'persons'
object_id = 'person_id'
def handle_get_events(self, id_, resource_id=None, **kwargs):
# Get a list of events attended by this person.
@ -520,7 +519,6 @@ class EventsHandler(CollectionHandler):
"""Handle requests for Events."""
document = 'event'
collection = 'events'
object_id = 'event_id'
def filter_get(self, output):
if not self.has_permission('persons-all|read'):
@ -664,6 +662,12 @@ class EventsHandler(CollectionHandler):
handle_delete_tickets = handle_delete_persons
class UsersHandler(CollectionHandler):
"""Handle requests for Users."""
document = 'user'
collection = 'users'
class EbCSVImportPersonsHandler(BaseHandler):
"""Importer for CSV files exported from eventbrite."""
csvRemap = {
@ -794,7 +798,7 @@ class WebSocketEventUpdatesHandler(tornado.websocket.WebSocketHandler):
logging.warn('WebSocketEventUpdatesHandler.on_close error closing websocket: %s', str(e))
class LoginHandler(RootHandler):
class LoginHandler(BaseHandler):
"""Handle user authentication requests."""
re_split_salt = re.compile(r'\$(?P<salt>.+)\$(?P<hash>.+)')
@ -827,37 +831,37 @@ class LoginHandler(RootHandler):
return False
@gen.coroutine
def post(self):
def post(self, *args, **kwargs):
# authenticate a user
username = self.get_body_argument('username')
password = self.get_body_argument('password')
try:
password = self.get_body_argument('password')
username = self.get_body_argument('username')
except tornado.web.MissingArgumentError:
data = escape.json_decode(self.request.body or '{}')
username = data.get('username')
password = data.get('password')
if not (username and password):
self.set_status(401)
self.write({'error': True, 'message': 'missing username or password'})
return
if self._authorize(username, password):
logging.info('successful login for user %s' % username)
self.set_secure_cookie("user", username)
if self.is_api():
self.write({'error': False, 'message': 'successful login'})
else:
self.redirect('/')
self.write({'error': False, 'message': 'successful login'})
return
logging.info('login failed for user %s' % username)
if self.is_api():
self.set_status(401)
self.write({'error': True, 'message': 'wrong username and password'})
else:
self.redirect('/login?failed=1')
self.set_status(401)
self.write({'error': True, 'message': 'wrong username and password'})
class LogoutHandler(RootHandler):
class LogoutHandler(BaseHandler):
"""Handle user logout requests."""
@gen.coroutine
def get(self, **kwds):
# log the user out
logging.info('logout')
self.logout()
if self.is_api():
self.redirect('/v%s/login' % API_VERSION)
else:
self.redirect('/login')
self.write({'error': False, 'message': 'logged out'})
def run():
@ -911,11 +915,14 @@ def run():
_ws_handler = (r"/ws/+event/+(?P<event_id>[\w\d_-]+)/+updates/?", WebSocketEventUpdatesHandler)
_persons_path = r"/persons/?(?P<id_>[\w\d_-]+)?/?(?P<resource>[\w\d_-]+)?/?(?P<resource_id>[\w\d_-]+)?"
_events_path = r"/events/?(?P<id_>[\w\d_-]+)?/?(?P<resource>[\w\d_-]+)?/?(?P<resource_id>[\w\d_-]+)?"
_users_path = r"/users/?(?P<id_>[\w\d_-]+)?/?(?P<resource>[\w\d_-]+)?/?(?P<resource_id>[\w\d_-]+)?"
application = tornado.web.Application([
(_persons_path, PersonsHandler, init_params),
(r'/v%s%s' % (API_VERSION, _persons_path), PersonsHandler, init_params),
(_events_path, EventsHandler, init_params),
(r'/v%s%s' % (API_VERSION, _events_path), EventsHandler, init_params),
(_users_path, UsersHandler, init_params),
(r'/v%s%s' % (API_VERSION, _users_path), UsersHandler, init_params),
(r"/(?:index.html)?", RootHandler, init_params),
(r"/ebcsvpersons", EbCSVImportPersonsHandler, init_params),
(r"/settings", SettingsHandler, init_params),