This commit is contained in:
Doug Le Tough 2018-02-27 19:51:43 +01:00
parent 7332af1dc0
commit 738596a13f
9 changed files with 461 additions and 65 deletions

View File

@ -41,6 +41,7 @@ class Tetawebapp_users(db.Model):
name = db.Column(db.Text, nullable=True)
phone = db.Column(db.Text, nullable=True)
diet = db.Column(db.Text, nullable=True)
is_admin = db.Column(db.Integer, nullable=False, default=0)
class Tetawebapp_roles(db.Model):
__tablename__ = 'participer_thsf_roles'
@ -48,6 +49,15 @@ class Tetawebapp_roles(db.Model):
role = db.Column(db.Text, nullable=False)
description = db.Column(db.Text, nullable=False)
class Tetawebapp_turns(db.Model):
__tablename__ = 'participer_thsf_turns'
id = db.Column(db.Integer, primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('participer_thsf_roles.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('participer_thsf_users.id'), nullable=True)
wday = db.Column(db.Enum('J', 'V', 'S', 'D'), nullable=False)
start_time = db.Column(db.Time, nullable=False)
end_time = db.Column(db.Time, nullable=False)
########################################################################
# Menu and navigation management
########################################################################
@ -62,14 +72,17 @@ def get_menu(page):
- The int 0 is used to determine which menu entry is actally called.
The value MUST be 0."""
menu = [[u'Accueil', {u'/': [u'/']}, 0],
[u'Mon compte', {u'/account': [u'/account', u'/account/<ID>']}, 0],
[u'Mes quarts', {u'/turn': [u'/turn']}, 0],
[u'Feuille de staff', {u'/staff_sheet': [u'/staff_sheet']}, 0],
[u'Mon compte', {u'/account': [u'/account', u'/account/update']}, 0],
[u'Mes tours de staff', {u'/turn': [u'/turn']}, 0],
[u'Feuilles de staff', {u'/staff_sheets': [u'/staff_sheet']}, 0],
[u'Déconnexion', {u'/logout': [u'/logout']}, 0],
]
if session['is_admin']:
menu = [[u'Accueil', {u'/': [u'/']}, 0],
[u'Tours de staff', {u'/staff': [u'/staff']}, 0],
[u'Feuilles de staff', {u'/staff_sheets': [u'/staff_sheet']}, 0],
[u'Liste des staffers', {u'/users': [u'/users', u'/account/<ID>']}, 0],
[u'Déconnexion', {u'/logout': [u'/logout']}, 0],
[u'Inputs', {u'/inputs': [u'/inputs']}, 0],
[u'Ajax', {u'/ajax': [u'/ajax']}, 0],
[u'Database', {u'/database': [u'/database']}, 0],
[u'Todo', {u'/todo': [u'/todo']}, 0],
]
for item in menu:
for url in item[1]:
@ -122,29 +135,153 @@ def check_session(func):
def check(*args, **kwargs):
try:
if session['token'] == request.cookies['token'] and len(session['token']) > 0:
# User is logged in and identified
return func(*args, **kwargs)
else:
# User is not logged in or session expired
session['token'] = ''
response = app.make_response(render_template('login_or_register.html', message=''))
sync_cookies(response, session)
return response
except KeyError:
# User is not logged in
return render_template('login_or_register.html', message='')
return check
def check_login(login, password):
""" Puts the login verification code here """
password = password.encode('utf-8')
hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
stored_hash = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.password).first()
is_admin = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.is_admin).first()
if stored_hash:
if bcrypt.checkpw(password, stored_hash[0].encode('utf-8')):
session['is_admin'] = is_admin[0]
return True
return False
def register_user(login, password, confirm):
""" Register new user """
if password != confirm:
# Password does not match confirmation
print "[+] Password mismatch confirmation"
return False
check_user = Tetawebapp_users.query.filter_by(mail=login).count()
if check_user != 0:
# User already exists
print "[+] User already exists"
return False
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
user = Tetawebapp_users(mail=login.encode('utf8'), password=hashed_password)
try:
db.session.add(user)
commit = db.session.commit()
except Exception as e:
db.session.rollback()
print "[+] Error at register_user:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
if commit != None:
return False
return True
def update_user(login, password, confirm, name, phone, diet):
""" Update user infos with provided data """
if password != confirm:
# Password does not match confirmation
print "[+] Password mismatch confirmation"
return False
check_user = Tetawebapp_users.query.filter_by(mail=login).count()
if check_user == 0:
# User does not exist
print "[+] User does not exist"
return False
user = Tetawebapp_users.query.filter_by(mail=login).first()
if len(password) > 0:
# User requested password modification
hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
setattr(user, 'password', hashed_password)
# Password has been updated if necessary
# Now let's update other data
setattr(user, 'name', name)
setattr(user, 'phone', phone)
setattr(user, 'diet', diet)
try:
db.session.add(user)
commit = db.session.commit()
except Exception as e:
db.session.rollback()
print "[+] Error at update_user:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
if commit != None:
return False
return True
def update_user_by_id(user_id, login, password, confirm, name, phone, diet):
""" Update user infos with provided data """
if password != confirm:
# Password does not match confirmation
print "[+] Password mismatch confirmation"
return False
check_user = Tetawebapp_users.query.filter_by(id=user_id).count()
if check_user == 0:
# User does not exist
print "[+] User does not exist"
return False
user = Tetawebapp_users.query.filter_by(id=user_id).first()
if len(password) > 0:
# User requested password modification
hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
setattr(user, 'password', hashed_password)
# Password has been updated if necessary
# Now let's update other data
setattr(user, 'name', name)
setattr(user, 'phone', phone)
setattr(user, 'diet', diet)
try:
db.session.add(user)
commit = db.session.commit()
except Exception as e:
db.session.rollback()
print "[+] Error at update_user:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
if commit != None:
return False
return True
def delete_user(user_id):
""" Delete user """
try:
Tetawebapp_users.query.filter_by(id=int(user_id)).delete()
db.session.commit()
return True
except ValueError as e:
return False
except Exception as e:
db.session.rollback()
print "[+] Error at delete_user:"
print "------------------------------"
print "%s" % e.message
print "------------------------------"
return False
def check_user_info():
""" Check user info and send appropriate message if info are not complete"""
message = "Vos informations personnelles ne sont pas totalement renseignées. N'oubliez pas de remplir votre fiche située dans la section 'Mon compte'"
message = ''
user = Tetawebapp_users.query.filter_by(mail=session['login']).first()
name = user.name
phone = user.phone
diet = user.diet
if name == None or phone == None or diet == None or \
len(name) == 0 or len(phone) == 0 or len(diet) == 0:
message = "Vos informations personnelles ne sont pas complètement renseignées. N'oubliez pas de remplir votre fiche située dans la section 'Mon compte'"
return message.decode('utf-8')
def gen_token():
@ -165,44 +302,55 @@ def page_not_found(e):
@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
session['token'] = gen_token()
# Return user to index page
page = '/'
menu = get_menu(page)
message = check_user_info()
response = app.make_response(render_template('index.html', menu=menu, message=message))
# Push token to cookie
try:
login = request.form.get('login').encode('utf-8')
password = request.form.get('password').encode('utf-8')
if check_login(login, password):
# Generate and store a token in session
session['token'] = gen_token()
session['login'] = login
# Return user to index page
page = '/'
menu = get_menu(page)
message = check_user_info()
response = app.make_response(render_template('index.html', menu=menu, message=message, login=login))
# Push token to cookie
sync_cookies(response, session)
return response
# Credentials are not valid
response = app.make_response(render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide"))
session['token'] = ''
sync_cookies(response, session)
return response
# Credentials are not valid
response = app.make_response(render_template('login_or_register.html', message='Invalid user or password'))
session['token'] = ''
sync_cookies(response, session)
return response
except AttributeError:
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
@app.route("/register", methods=['GET', 'POST'])
def register():
login = request.form.get('login')
password = request.form.get('password')
if check_login(login, password):
# Generate and store a token in session
session['token'] = gen_token()
# Return user to index page
page = '/'
menu = get_menu(page)
response = app.make_response(render_template('index.html', menu=menu))
# Push token to cookie
try:
login = request.form.get('login').encode('utf-8')
password = request.form.get('password').encode('utf-8')
confirm = request.form.get('confirm').encode('utf-8')
if register_user(login, password, confirm):
# Generate and store a token in session
session['token'] = gen_token()
session['login'] = login
# Return user to index page
page = '/'
menu = get_menu(page)
message = check_user_info()
response = app.make_response(render_template('index.html', menu=menu, login=login, message=message))
# Push token to cookie
sync_cookies(response, session)
return response
# Error while registering user
message = "Erreur lors de l'enregsitrement: L'utilisateur existe t-il déjà ?".decode('utf-8')
response = app.make_response(render_template('login_or_register.html', message=message))
session['token'] = ''
sync_cookies(response, session)
return response
# Credentials are not valid
response = app.make_response(render_template('login_or_register.html', message='Invalid user or password'))
session['token'] = ''
sync_cookies(response, session)
return response
except AttributeError:
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
@app.route("/", methods=['GET', 'POST'])
@check_session
@ -211,25 +359,122 @@ def index():
page = str(request.url_rule)
menu = get_menu(page)
message = check_user_info()
return render_template('index.html', menu=menu, message=message)
return render_template('index.html', menu=menu, message=message, login=session['login'])
@app.route("/account", methods=['GET', 'POST'])
@check_session
def account():
""" Arcticles page """
""" Account page """
page = str(request.url_rule)
menu = get_menu(page)
return render_template('account.html', menu=menu)
user = Tetawebapp_users.query.filter_by(mail=session['login']).first()
mail = '' if user.mail == None else user.mail
name = '' if user.name == None else user.name
phone = '' if user.phone == None else user.phone
diet = '' if user.diet == None else user.diet
message = check_user_info()
return render_template('account.html', menu=menu, mail=mail, name=name, phone=phone, diet=diet, message=message)
@app.route("/account/update", methods=['GET', 'POST'])
@check_session
def update_account():
""" Update current account """
try:
page = str(request.url_rule)
menu = get_menu(page)
login = session['login']
password = request.form.get('password').encode('utf-8')
confirm = request.form.get('confirm').encode('utf-8')
name = request.form.get('name').encode('utf-8')
phone = request.form.get('phone').encode('utf-8')
diet = request.form.get('diet').encode('utf-8')
if update_user(login, password, confirm, name, phone, diet):
message = check_user_info()
else:
message = "Erreur lors de l'enregistrement des données."
return render_template('account.html',
menu=menu,
mail=login.decode('utf-8'),
name=name.decode('utf-8'),
phone=phone.decode('utf-8'),
diet=diet.decode('utf-8'),
message=message)
except AttributeError:
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
@app.route("/users", methods=['GET', 'POST'])
@check_session
def list_users():
""" Users list """
page = str(request.url_rule)
menu = get_menu(page)
message = check_user_info()
staffers = Tetawebapp_users.query.filter_by(is_admin=0).order_by(Tetawebapp_users.name).all()
return render_template('list_users.html', menu=menu, staffers=staffers, message=message)
@app.route("/account/<ID>", methods=['GET', 'POST'])
@check_session
def account_by_id(ID):
""" Arcticles page """
page = str(request.url_rule)
menu = get_menu(page)
selected = page.replace('<ID>', ID)
navbar = get_navbar(page, selected)
return render_template('account_by_id.html', menu=menu, navbar=navbar, ID=ID)
try:
if session['is_admin']:
page = str(request.url_rule)
menu = get_menu(page)
user = Tetawebapp_users.query.filter_by(id=ID).first()
return render_template('account_by_id.html', menu=menu, user=user)
# User is not admin
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except AttributeError:
# User is not logged in
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
@app.route("/account/update/<ID>", methods=['GET', 'POST'])
@check_session
def update_account_by_id(ID):
""" Update given account """
try:
if session['is_admin']:
page = str(request.url_rule)
menu = get_menu(page)
login = session['login']
password = request.form.get('password').encode('utf-8')
confirm = request.form.get('confirm').encode('utf-8')
name = request.form.get('name').encode('utf-8')
phone = request.form.get('phone').encode('utf-8')
diet = request.form.get('diet').encode('utf-8')
if update_user_by_id(ID, login, password, confirm, name, phone, diet):
user = Tetawebapp_users.query.filter_by(id=ID).first()
message = check_user_info()
else:
message = "Erreur lors de l'enregistrement des données."
return render_template('account_by_id.html',
menu=menu,
user=user,
message=message)
# User is not admin
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except AttributeError:
# User is not logged in
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
@app.route("/account/delete/<ID>", methods=['GET', 'POST'])
@check_session
def delete_account(ID):
""" Delete given account """
try:
if session['is_admin']:
message = "Erreur lors de la suppression.".decode('utf-8')
if delete_user(ID):
message = ''
page = str(request.url_rule)
menu = get_menu(page)
staffers = Tetawebapp_users.query.filter_by(is_admin=0).order_by(Tetawebapp_users.name).all()
return render_template('list_users.html', menu=menu, staffers=staffers, message=message)
# User is not admin
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
except AttributeError:
# User is not logged in
return render_template('login_or_register.html', message="Utilisateur ou mot de passe invalide")
@app.route("/logout", methods=['GET', 'POST'])
@check_session
@ -237,6 +482,7 @@ def logout():
""" Logout user """
# Remove session token
session['token'] = None
session['login'] = None
# Return user to index page
response = app.make_response(render_template('login_or_register.html', message=''))
# Push token to cookie

View File

@ -36,25 +36,64 @@ CREATE TABLE participer_thsf_users (
password text not NULL,
name text,
phone text,
diet text
diet text,
is_admin integer not NULL
);
\echo *********************************
\echo * Creating participer_thsf_roles table
\echo *********************************
CREATE TABLE participer_thsf_roles (
id serial primary key,
role text not NULL,
description text not NULL
);
\echo *********************************
\echo * Creating participer_thsf_turns table
\echo *********************************
CREATE TYPE dow AS ENUM ('J', 'V', 'S', 'D');
CREATE TABLE participer_thsf_turns (
id serial primary key,
role_id integer not NULL,
user_id integer,
wday dow not NULL,
start_time time not NULL,
end_time time not NULL,
constraint fk_turns_role
foreign key (role_id)
REFERENCES participer_thsf_roles (id),
constraint fk_turns_user
foreign key (user_id)
REFERENCES participer_thsf_users (id)
);
\echo *************************************************
\echo * Giving participer_thsf_users ownership to participer_thsf
\echo *************************************************
alter table participer_thsf_users owner to participer_thsf;
\echo *************************************************
\echo * Giving participer_thsf_roles ownership to participer_thsf
\echo *************************************************
alter table participer_thsf_roles owner to participer_thsf;
\echo *************************************************
\echo * Giving participer_thsf_turns ownership to participer_thsf
\echo *************************************************
alter table participer_thsf_turns owner to participer_thsf;
\echo *********************************************************************
\echo * Inserting user demo identified by password demo to participer_thsf_users
\echo *********************************************************************
insert into participer_thsf_users (mail, password, name) values ('demo', '$2b$12$yjv4QMctGJFj2HmmbF6u5uDq9ATIl/Y9Z96MbaqRrcG6AE0CGHKSS', 'demo');
insert into participer_thsf_users (mail, password, name, phone, diet, is_admin) values ('bofh@tetalab.org',
'$2b$12$wm7PQ9IE7TYGYk1XB11mPusq7HsjYLYxt5G4v5Wz.jZbh5iWDHP5q',
'BOFH',
'0000000000',
'Omnivore',
1);
\echo *********************************************************************
\echo * Inserting roles to participer_thsf_roles

View File

@ -17,3 +17,23 @@ function register() {
}
return true;
}
function update_account() {
var password = document.getElementById('password').value;
var confirm = document.getElementById('confirm').value;
if (password != confirm){
alert("Confirmation mot de passe incohérente");
return false;
}
if (password.length > 0 && password.length < 8){
alert("Le mot de passe doit avoir une longueur d'au moins 8 caractères");
return false;
}
return true;
}
function delete_account(id) {
if (confirm("La suppression d'un compte est définitive.\n\nConfirmer ?")) {
document.location='/account/delete/'+id;
}
}

View File

@ -370,7 +370,7 @@ input.upload {
}
form {
width: 350px;
width: 400px;
text-align: center;
line-height: 40px;
}
@ -382,3 +382,49 @@ form > label {
form > input[type='text'], form > input[type='password'] {
float: right;
}
div.table_header {
background-color: var(--coloured-bg);
text-align: left;
width: 910px;
}
div.table_row {
text-align: left;
width: 910px;
}
div.table_header > div, div.table_row > div {
display: inline-block;
text-align: center;
font-weight: bold;
width: 200px;
}
div.table_row > div {
border-color: var(--coloured-bg);
border-width: 1px;
font-weight: normal;
}
div.even {
background-color: var(--light-coloured-bg);
border-color: var(--coloured-bg);
border-width: 1px;
border-style: none none solid none;
}
div.odd {
background-color: var(--white);
border-color: var(--coloured-bg);
border-width: 1px;
border-style: none none solid none;
}
div.table_row > div.border_left {
border-style: none none none solid;
}
div.table_row > div.border_right {
border-style: none solid none none;
}

View File

@ -16,13 +16,13 @@
<li>Aucune des données que vous nous transmettrez ne sera fournie à un tiers</li>
</ul>
</p>
<form method='POST' action='/account'>
<label>Adresse email: </label><input id='login' name='login' type='text' /><br/>
<label>Prénom ou pseudo: </label><input id='pseudo' name='pseudo' type='text' /><br/>
<label>Nouveau mot de passe: </label><input id='password' name='password' type='password' /><br/>
<label>Répetez mot de passe: </label><input id='confirm' name='confirm' type='password' /><br/>
<label>Numéro de téléphone: </label><input id='phone' name='phone' type='text' /><br/>
<label>Régime alimentaire: </label><input id='diet' name='diet' type='text' /><br/>
<form method='POST' action='/account/update'>
<label>Adresse email: </label><input id='login' name='login' type='text' value='{{ mail }}' disabled='disabled'/><br/>
<label>Prénom ou pseudo: </label><input id='name' name='name' type='text' value='{{ name }}'/><br/>
<label>Nouveau mot de passe: </label><input id='password' name='password' type='password'/><br/>
<label>Confirmation mot de passe: </label><input id='confirm' name='confirm' type='password'/><br/>
<label>Numéro de téléphone: </label><input id='phone' name='phone' type='text' value='{{ phone }}'/><br/>
<label>Régime alimentaire et remarques: </label><input id='diet' name='diet' type='text' value='{{ diet }}'/><br/>
<input type='submit' value='Update' onclick='javascript:return update_account();'>
</article>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends "index.html" %}
{% block title %}Articles{% endblock %}
{% block main %}
<article>
<h3>Informations du compte</h3>
<form method='POST' action='/account/update/{{ user.id }}'>
<label>Adresse email: </label><input id='login' name='login' type='text' value='{{ user.mail }}' disabled='disabled'/><br/>
<label>Prénom ou pseudo: </label><input id='name' name='name' type='text' value='{{ user.name }}'/><br/>
<label>Nouveau mot de passe: </label><input id='password' name='password' type='password'/><br/>
<label>Confirmation mot de passe: </label><input id='confirm' name='confirm' type='password'/><br/>
<label>Numéro de téléphone: </label><input id='phone' name='phone' type='text' value='{{ user.phone }}'/><br/>
<label>Régime alimentaire et remarques: </label><input id='diet' name='diet' type='text' value='{{ user.diet }}'/><br/>
<input type='submit' value='Update' onclick='javascript:return update_account();'>
</article>
{% endblock %}

View File

@ -46,12 +46,13 @@
</ul>
</div>
{% endif %}
{% if message and message != '' %}
<pre>{{ message }}</pre>
{% endif %}
{% block main %}
<article class='right'>
{% if message != '' %}
<pre>{{ message }}</pre>
{% endif %}
<h3>We Make THSF</h3>
<p>Bonjour {{ login }},</p>
<p>
Comme chaque année le <a href='https://www.thsf.net>'>Toulouse Hacker Space Factory</a> aura lieu à
<a href='http://mixart-myrys.org'>Mix'Art Myrys</a>.
@ -71,7 +72,7 @@
de sélectionner le poste et les créneaux horaires pendant lesquels vous souhaitez vous rendre disponible.
</li>
<li>
d'être tenu informé des évolutions de l'organisation et du déroulement du THSF via la mailing list du staff.
de poser vos questions, obtenir des réponses et être tenu informé des évolutions de l'organisation et du déroulement du THSF via la mailing list du staff.
</li>
</ul>
</p>

29
templates/list_users.html Normal file
View File

@ -0,0 +1,29 @@
{% extends "index.html" %}
{% block title %}Articles{% endblock %}
{% block main %}
<article>
<h3>Liste des staffers enregistrés</h3>
<div class='table_header'>
<div class='border_right' style='width: 40px;'>ID</div>
<div class='border_right'>Mail</div>
<div class='border_right'>Nom</div>
<div class='border_right'>Téléphone</div>
<div class='border_right'>Régime / Remarques</div>
<div style='width: 50px;'>Action</div>
</div>
{% set row_class = cycler('odd', 'even') %}
{% for staffer in staffers %}
<div class='table_row {{ row_class.next() }}'>
<div class='border_right' style='width: 40px;'>{{ staffer.id }}</div>
<div class='border_right'>{{ staffer.mail }}</div>
<div class='border_right'>{{ staffer.name }}</div>
<div class='border_right'>{{ staffer.phone }}</div>
<div class='border_right'>{{ staffer.diet }}</div>
<div style='width: 50px;'>
<input class='edit' value='' onclick='javascript:document.location="/account/{{ staffer.id }}"' title='Éditer'/>
<input class='trash' value='' onclick='javascript:delete_account({{ staffer.id }});' title='Supprimer'/>
</div>
</div>
{% endfor %}
</article>
{% endblock %}

View File

@ -40,9 +40,6 @@
<p class='note'>Aucun électron n'a été maltraité lors de la mise au point de ce site.
Par ailleurs ce site n'utilise ni ressources hébergées par des tiers, ni bullshitwares, ni trackers.</p>
</article>
{% if message != '' %}
<pre>{{ message }}</pre>
{% endif %}
<hr/>
<article class='login'>
<h3>Connexion</h3>
@ -53,6 +50,9 @@
<label>Mot de passe: </label><input id='password' name='password' type='password' /><br/>
<input type='submit' value='Log me in' onclick='javascript:return verify_login();'>
</form>
<p class='note'>
Mot de passe oublié ? Envoyez une <a href='https://bofh.tetalab.org/?do=newtask&project=2'>demande de réinitialisation de votre mot de passe</a>.
</p>
</article>
<hr/>
<article class='login'>