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:
+
+ - Login: demo
+ - Password: demo
+
+
+ {% if message != '' %}
+
{{ message }}
+ {% endif %}
+
+
+
+{% 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'