diff --git a/tetawebapp/static/scripts/tetawebapp.js b/tetawebapp/static/scripts/tetawebapp.js index ff1bc63..6176345 100644 --- a/tetawebapp/static/scripts/tetawebapp.js +++ b/tetawebapp/static/scripts/tetawebapp.js @@ -48,6 +48,16 @@ function valid_input(obj) { , 2000); } +function verify_login() { + // Verify login inputs + login = document.getElementById('login').value; + password = document.getElementById('password').value; + if (login.length > 0 && password.length > 0) { + return true; + } + return false; +} + /* ************************************************************************************** * AJAX * **************************************************************************************/ diff --git a/tetawebapp/static/styles/tetawebapp.css b/tetawebapp/static/styles/tetawebapp.css index fb31acd..c91896a 100644 --- a/tetawebapp/static/styles/tetawebapp.css +++ b/tetawebapp/static/styles/tetawebapp.css @@ -85,14 +85,16 @@ main > article { display: block; } -main > article.error, main > article.error > p { +main > article.error, +main > article.error > p { padding: 10px; color: var(--text-color); display: block; text-align: center; } -main > article > h3, main > section.inline > article > h3 { +main > article > h3, +main > section.inline > article > h3 { font-size: 30px; color: var(--text-color); } @@ -161,12 +163,17 @@ footer { border-top-width: 1px; } -header, footer { +header, +footer { background-color: var(--coloured-bg); color: var(--white); } -input[type="text"], textarea, select, pre { +input[type="text"], +input[type="password"], +textarea, +select, +pre { border-color: var(--dark-border); border-style: solid; border-width: 1px; @@ -181,7 +188,9 @@ pre { border-color: var(--coloured-bg); } -button, input[type="button"], input[type="submit"] { +button, +input[type="button"], +input[type="submit"] { border-color: var(--dark-border); border-style: solid; border-width: 1px; @@ -194,7 +203,9 @@ button, input[type="button"], input[type="submit"] { border-radius: 4px; } -button:hover, input[type="button"]:hover, input[type="submit"]:hover { +button:hover, +input[type="button"]:hover, +input[type="submit"]:hover { background-color: var(--light-coloured-bg); color: var(--text-color); cursor: pointer; diff --git a/tetawebapp/templates/login.html b/tetawebapp/templates/login.html new file mode 100644 index 0000000..00406f9 --- /dev/null +++ b/tetawebapp/templates/login.html @@ -0,0 +1,23 @@ +{% extends "index.html" %} +{% block title %}Login{% endblock %} +{% block nav %}{% endblock %} +{% block main %} +
+

Login

+

The demo login is:

+ +
+ {% if message != '' %} +
{{ message }}
+ {% endif %} +
+
+ Login: + Password: + +
+
+{% endblock %} diff --git a/tetawebapp/templates/todo.html b/tetawebapp/templates/todo.html index e66b8ff..12b4774 100644 --- a/tetawebapp/templates/todo.html +++ b/tetawebapp/templates/todo.html @@ -7,7 +7,7 @@
  • Installation wizard
  • Back office for basic content management
  • Basic Ajax support
  • -
  • Session management
  • +
  • Session management
  • Basic documentation
  • Horizontal navbar
  • License
  • diff --git a/tetawebapp/tetawebapp.py b/tetawebapp/tetawebapp.py index 9214c7b..0763cc2 100755 --- a/tetawebapp/tetawebapp.py +++ b/tetawebapp/tetawebapp.py @@ -2,7 +2,10 @@ # -*- coding: utf-8 # Required modules +import os import inspect +import random +import binascii from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash from functools import wraps @@ -27,17 +30,15 @@ db = SQLAlchemy(app) ######################################################################## # Menu management: -# ---------------- -# -# The main menu is a list of lists in the followin format -# [string caption, string URL endpoint, int 0] -# - The URL end point MUST match the function called by the corresponding -# route. -# - The int 0 is used to determine which menu entry is actally called. -# The value MUST be 0. ######################################################################## def get_menu(page): + """ The main menu is a list of lists in the followin format: + [string caption, string URL endpoint, int 0] + - The URL end point MUST match the function called by the corresponding + route. + - The int 0 is used to determine which menu entry is actally called. + The value MUST be 0.""" menu = [['Accueil', '/', 0], ['Articles', '/articles', 0], ['Basics', '/basics', 0], @@ -60,11 +61,47 @@ def get_menu(page): # This should never happen return menu +######################################################################## +# Session management +######################################################################## + +def sync_session(request, session): + """ Synchronize cookies with session """ + for key in request.cookies: + session[key] = request.cookies[key].encode('utf8') + +def sync_cookies(response, session): + """ Synchronize session with cookies """ + for key in session: + response.set_cookie(key, value=str(session[key])) + +def check_session(func): + """ Check if the session has required token cookie set. + If not, redirects to the login page. """ + @wraps(func) + def check(*args, **kwargs): + try: + if session['token'] == request.cookies['token']: + return func(*args, **kwargs) + except KeyError: + return render_template('login.html', message='') + return check + +def check_login(login, password): + """ Puts the login verification code here """ + if login == 'demo' and password == 'demo': + return True + return False + +def gen_token(): + """ Generate a random token to be stored in session and cookie """ + token = binascii.hexlify(os.urandom(42)) + return token + ######################################################################## # Routes: # ------- -# -# Except for the index fonction, the function name MUST have the same +# Except for the index function, the function name MUST have the same # name than the URL endpoint to make the menu work properly ######################################################################## @app.errorhandler(404) @@ -72,66 +109,101 @@ def page_not_found(e): """ 404 not found """ return render_template('error.html'), 404 +@app.route("/login", methods=['GET', 'POST']) +def login(): + login = request.form.get('login') + password = request.form.get('password') + if check_login(login, password): + # Generate and store a token in session + token = gen_token() + session['token'] = token + # Return user to index page + page = 'index' + menu = get_menu(page) + response = app.make_response(render_template('index.html', menu=menu)) + # Push token to cookie + sync_cookies(response, session) + return response + # Credentials are not valid + return render_template('login.html', message='Invalid user or password') @app.route("/", methods=['GET', 'POST']) +@check_session def index(): + """ Index page """ page = inspect.currentframe().f_code.co_name menu = get_menu(page) return render_template('index.html', menu=menu) @app.route("/articles", methods=['GET', 'POST']) +@check_session def articles(): + """ Arcticles page """ page = inspect.currentframe().f_code.co_name menu = get_menu(page) return render_template('articles.html', menu=menu) @app.route("/basics", methods=['GET', 'POST']) +@check_session def basics(): + """ Basics page """ page = inspect.currentframe().f_code.co_name menu = get_menu(page) return render_template('basics.html', menu=menu) @app.route("/inputs", methods=['GET', 'POST']) +@check_session def inputs(): + """ Show the input collection """ page = inspect.currentframe().f_code.co_name menu = get_menu(page) return render_template('inputs.html', menu=menu) @app.route("/ajax", methods=['GET', 'POST']) +@check_session def ajax(): + """ Propose various AJAX tests """ page = inspect.currentframe().f_code.co_name menu = get_menu(page) return render_template('ajax.html', menu=menu) @app.route("/database", methods=['GET', 'POST']) +@check_session def database(): + """ A blah on using databases """ page = inspect.currentframe().f_code.co_name menu = get_menu(page) return render_template('database.html', menu=menu) @app.route("/todo", methods=['GET', 'POST']) +@check_session def todo(): + """ The famous TODO list """ page = inspect.currentframe().f_code.co_name menu = get_menu(page) return render_template('todo.html', menu=menu) ######################################################################## -# AJAX +# AJAX routes ######################################################################## @app.route("/get_html_from_ajax", methods=['GET', 'POST']) +@check_session def get_html_from_ajax(): - import random + """ Return HTML code to an AJAX request + It may generate a 404 http error for testing purpose """ if int(random.random()*10) % 2: # Randomlu generate 404 HTTP response return render_template('error.html'), 404 return render_template('ajax_html.html') @app.route("/get_value_from_ajax", methods=['GET', 'POST']) +@check_session def get_value_from_ajax(): + """ Return a randomly generated value to an AJAX request + It may return an error code for testing purpose """ err_code = 'TETA_ERR' - import random RND = int(random.random()*10) if RND % 2: # Randomlu generate error @@ -139,7 +211,10 @@ def get_value_from_ajax(): return str(RND) @app.route("/set_value_from_ajax/", methods=['GET', 'POST']) +@check_session def set_value_from_ajax(value): + """ Accept a value from an AJAX request + It may return an error code for testing purpose """ err_code = 'TETA_ERR' if value != 'We Make Porn': return 'True'