From 76bfb287b51bd97c9ca0bfc4e7760b7ee8e15b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Smodi=C5=A1?= Date: Thu, 8 Oct 2015 18:56:48 +0200 Subject: Reworked session handling. * All requests have a session ID, except for the initial create_session system messages. * User session can be in an authenticated or anonymous state. * In anonymous state it is not possible to perform user actions. * Logout has been implemented. * Sessions timeout and are cleared after a period of inactivity (1 hour). * Bugfixed the lang setting handling. * Renamed get_problem -> get_current_solution, return only the user's current solution, not the whole problem data. --- web/main.js | 172 +++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 118 insertions(+), 54 deletions(-) (limited to 'web') diff --git a/web/main.js b/web/main.js index 37729b9..b3c096f 100644 --- a/web/main.js +++ b/web/main.js @@ -32,12 +32,29 @@ var server = new engine.Server({ }); http_server.on('request', function (request, response) { - var uriParts = request.url.split('/'); // uriParts[0] will be an empty string, because uri must start with a / + var uriParts = request.url.split('/'), // uriParts[0] will be an empty string, because uri must start with a / + params, session; logger.debug('HTTP server request, URL: ' + request.url); if ((uriParts.length <= 1) || (uriParts[1] === 'ws')) { - server.handleRequest(request, response); + if ((uriParts.length == 3) && (uriParts[2].substring(0, 7) === 'logout?')) { + // special logout service + params = uriParts[2].substring(7).split('&')[0].split('='); + if ((params.length == 2) && (params[0] === 'sid')) { + session = sessions[params[1]]; + if (session) { + logger.debug('Logging out via AJAX, sid=' + params[1]); + guiHandlers.logout(session, {'sid': params[1], 'action': 'logout'}); + } + } + response.writeHead(200, {'Content-Type': 'text/plain'}); + response.write('OK'); + response.end(); + } + else { + server.handleRequest(request, response); + } } else { response.writeHead(404, {'Content-Type': 'text/plain'}); @@ -59,69 +76,83 @@ var sessions = { // GUI action handlers, keyed by the action name, values are functions that take the session and the message, or truthy values var guiHandlers = { // special-case action handlers - 'login': function actionLogin(session, message) { - // first-time connect: login - var tid = message['tid']; // remember any TID that is set - delete message['tid']; // delete any TID, we must use an internal one - logger.debug('Received a login request from GUI'); - sendDataToPython(message).then( - function loginRequestOK(response) { + 'logout': function actionLogout(session, message) { + // logout, the user quit the app + logger.debug('Logout GUI'); + sendDataToPython(message).finally(function () { + session.end({'type': 'reset', 'code': 9999, 'message': 'Bye.'}); + }).done(); + }, + + // actions to use default handling should define truthy values that are not functions + // (this is to filter out unnecessary traffic before it hits Python) + 'login': true, + 'signup': true, + 'change_password': true, + 'activity': true, + 'query': true, + 'python_exec': true, + 'python_push': true, + 'python_stop': true, + 'hint': true, + 'test': true, + 'get_current_solution': true, + 'update_settings': true, + 'load_problem': true, + 'end_problem': true, + 'system': true +}; + +var system_handlers = { + 'create_session': function (session, message) { + sendDataToPython({'action': 'create_session'}) + .then(function (response) { var sid, existingSession; - if ((typeof tid !== 'undefined') && (tid !== null)) response['tid'] = tid; - else delete response['tid']; if (response.code !== 0) { - logger.debug('Python rejected login request from GUI'); - session.end(response); + logger.debug('Python rejected create_session request from GUI'); + session.send({'type': 'reset', 'message': response.message}); } else { - logger.debug('Python accepted login request from GUI'); + logger.debug('Python accepted create_session request from GUI'); session.sid = sid = response.sid; existingSession = sessions[sid]; sessions[sid] = session; - session.send(response); - if (existingSession) { - existingSession.end({'code': -40, 'message': 'Supplanted with a new connection for the same session', 'sid': sid}); + if (existingSession && (existingSession !== session)) { + existingSession.end({'type': 'reset', 'message': 'Supplanted with a new connection for the same session', 'sid': sid}); } + session.send({'type': 'sid', 'sid': sid}); } - }, - function loginRequestFailed(err) { - var response = { - 'code': -30 - }, - reason; - logger.debug('Failed to request login from Python'); - if ((typeof tid !== 'undefined') && (tid !== null)) response['tid'] = tid; - if ((typeof err === 'object') && (err !== null)) reason = err.toString(); - else reason = '' + err; - response['message'] = 'Login request failed: ' + reason; - session.end(response); - } - ).done(); - }, - - 'logout': function actionLogout(session, message) { - // logout, the user quit the app - logger.debug('Logout GUI'); - sendDataToPython(message).finally(function () { - session.end({'code': 9999, 'message': 'Bye.'}); - }).done(); + }) + .catch(function (error) { + session.send({'type': 'reset', 'message': 'Could not create a new session: ' + (error || 'unknown error')}); + }) + .done(); }, - // actions to use default handling should define truthy values that are not functions - // (this is to filter out unnecessary traffic before it hits Python) - 'signup': true, - 'change_password': true, - 'activity': true, - 'query': true, - 'python_exec': true, - 'python_push': true, - 'python_stop': true, - 'hint': true, - 'test': true, - 'get_problem': true, - 'settings': true, - 'load_problem': true, - 'end_problem': true + 'connect_session': function (session, message) { + sendDataToPython({'action': 'login', 'sid': session.sid, 'username': message.username, 'password': message.password}) + .then(function (response) { + var sid, existingSession; + if (response.code !== 0) { + logger.debug('Python rejected connect_session request from GUI'); + session.send({'type': 'reset', 'message': response.message}); + } + else { + logger.debug('Python accepted connect_session request from GUI'); + session.sid = sid = response.sid; + existingSession = sessions[sid]; + sessions[sid] = session; + if (existingSession && (existingSession !== session)) { + existingSession.end({'type': 'reset', 'message': 'Supplanted with a new connection for the same session', 'sid': sid}); + } + session.send({'type': 'sid', 'sid': sid}); + } + }) + .catch(function (error) { + session.send({'type': 'reset', 'message': 'Could not connect the existing session: ' + (error || 'unknown error')}); + }) + .done(); + } }; server.on('connection', function (socket) { @@ -205,6 +236,14 @@ server.on('connection', function (socket) { return; } + if (typeof m.type === 'string') { + // a system (meta) protocol message + handler = system_handlers[m.type]; + if (!handler) logger.error('Received an unknown system message from client: ' + m.type); + else handler(session, m); + return; + } + tid = m['tid']; // transaction ID if (typeof tid !== 'number') { fatal({"code": -3, "message": "Transaction ID is missing or is not an integer."}); @@ -266,6 +305,10 @@ server.on('connection', function (socket) { }); }); +//var sessionActivityTimeout = setInterval(function () { +// var sid; +//}, 4200000); // once every 70 minutes + // ========== Python server connection ========== @@ -304,6 +347,27 @@ var processPacketFromPython = function processPacketFromPythonFunc(packetString) return; } + if (typeof m.type === 'string') { + // system (meta) protocol message + switch (m.type) { + case 'session_expire': + if (typeof m.sid === 'string') { + session = sessions[m.sid]; + if (session) session.end({'type': 'reset', 'message': 'Session expired'}); // send a system message to force the client to reset + else logger.warn('Received the session_expire system message from Python for a sid not handled by me: ' + m.sid); + } + else { + logger.error('Received the session_expire system message from Python, but no sid was included'); + } + break; + + default: + logger.warn('An unknown system message from Python: ' + m.type); + break; + } + return; + } + if ((typeof m.tid !== 'undefined') && (m.tid !== null)) { if ((typeof m.tid === 'string') && (m.tid.charAt(0) === 'i')) { // internal TID: only one way to key this one -- cgit v1.2.1