Add 2FA support
This commit is contained in:
parent
12669a86fa
commit
f9b092e456
@ -20,15 +20,15 @@
|
|||||||
Tu peux gérer ton compte MAIL et XMPP si les serveurs Mail(SMTP, IMAP) et XMPP sont actifs.
|
Tu peux gérer ton compte MAIL et XMPP si les serveurs Mail(SMTP, IMAP) et XMPP sont actifs.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% include '_flash_msgs.html' %}
|
{% include '_flash_msgs.html' %}
|
||||||
|
|
||||||
|
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
<form method="POST" action="{{ url_for('loginlogout.login') }}">
|
<form method="POST" action="{{ url_for('loginlogout.login') }}">
|
||||||
<input type="text" name="user" id="user" placeholder="Utilisateur" class="form-control" width="200px"><br />
|
<input type="text" name="user" id="user" placeholder="Utilisateur" width="200px"><br />
|
||||||
<input type="password" name="passwd" id="passwd" placeholder="Mot de passe" class="form-control"><br />
|
<input type="password" name="passwd" id="passwd" placeholder="Mot de passe"><br />
|
||||||
<input type="checkbox" id="totpcheckbox">
|
<input type="checkbox" id="totpcheckbox">
|
||||||
<label for="totpcheckbox">J'ai un code d'authentification 2FA</label>
|
<label for="totpcheckbox">J'ai un mot de passe à usage unique</label>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<label for="2FAInput" class="totp">Code TOTP:</label>
|
<label for="2FAInput" class="totp">Code TOTP:</label>
|
||||||
|
|||||||
48
templates/mypassword.html
Normal file
48
templates/mypassword.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{% extends 'up_squelette.html' %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<h3> Changer mon mot de passe </h3>
|
||||||
|
|
||||||
|
<form method="POST" action="" >
|
||||||
|
|
||||||
|
<p> Votre compte sur ce serveur : {{ username }} </p>
|
||||||
|
|
||||||
|
<label> Mot de passe </label>
|
||||||
|
<input type="password" name="password" id="password" placeholder="Votre mot de passe"><br />
|
||||||
|
<input type="password" name="passwd_confirm" id="passwd_confirm" placeholder="Confirmation du mot de passe"><br />
|
||||||
|
<button class="btn btn-default btn-primary" type="submit">Envoyer</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<h3> Mot de passe à usage unique </h3>
|
||||||
|
<p> Le mot de passe à usage unique utilisé par pywallter est un mot de passe composé de chiffres qui change périodiquement (ttes les 30 sec). Cela permet de lutter contres le phishing et le vol de votre de mot de passe. <br/> En cas de vol de votre mot de passe une application génère un code à usage unique et donc le voleur ne peut pas se connecter à votre compte juste en connaissant votre de passe </p>
|
||||||
|
|
||||||
|
<p> Voici quelques exemple de générateur de mot de passe à usage unique (OTP en anglais) pour <a href="https://getaegis.app/">Android</a>, <a href="https://apps.apple.com/us/app/totp-authenticator-fast-2fa/id1404230533">iOS</a> et <a href="https://keepassxc.org/">Linux/BSD et windows</a>
|
||||||
|
|
||||||
|
<p> Afin que le serveur et votre application génère le même code en même temps; il vous faut valider la clef sécrete partager par votre application et le serveur. Pour cela, si ce n'est pas déjà fait, scannez ou entrez la clef secrète dans votre application. Pour finir, cliquez sur valider la clef en entrant le code générer par votre application. Si le test réussi parfait c'est configuré ! </p>
|
||||||
|
|
||||||
|
<p> Pour changer votre clef secrète vous devez d'abord supprimer votre clef actuelle pour en regénérer une nouvelle. </p>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<p> Votre clef secrète : {{ totp_shared_key }} </p>
|
||||||
|
|
||||||
|
{% if shared_key_validate %}
|
||||||
|
<p class="success"> Votre clef secrète est valide et activé </p>
|
||||||
|
{% else %}
|
||||||
|
<p class="alert"> Votre clef secrète n'est pas validé et donc non active </p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="POST" action="{{ url_for('profil.set_totp') }}" >
|
||||||
|
<input type="disabled" class="hidden" name="shared_key" id="shared_key" value="{{ totp_shared_key }}"><br />
|
||||||
|
<fieldset role="group">
|
||||||
|
<input type="text" name="code_totp" id="code_totp" placeholder="Entrez votre code ici"><br />
|
||||||
|
<input type="submit" value="Valider ma clef secrète">
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% if shared_key_validate %}
|
||||||
|
<p> Si vous voulez changer votre clef secrète vous devez d'abord supprimé votre clef actuelle</p>
|
||||||
|
<a href="{{ url_for('profil.del_totp') }}"> <button class="btn-alert"> Supprimer ma clef secrète </button></a>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
@ -4,7 +4,7 @@ from markupsafe import escape
|
|||||||
from flask_bcrypt import Bcrypt
|
from flask_bcrypt import Bcrypt
|
||||||
from socket import gethostname
|
from socket import gethostname
|
||||||
from os import remove, system
|
from os import remove, system
|
||||||
from tools.utils import email_disp, valid_token_register, valid_passwd, valid_username, gen_token
|
from tools.utils import email_disp, valid_token_register, valid_passwd, valid_username, gen_token, totp_is_valid
|
||||||
from tools.mailer import Mailer
|
from tools.mailer import Mailer
|
||||||
|
|
||||||
app = Flask( 'pywallter' )
|
app = Flask( 'pywallter' )
|
||||||
@ -19,8 +19,7 @@ DATAS_USER = app.config['DOSSIER_APP']
|
|||||||
extensionimg = app.config['EXT_IMG']
|
extensionimg = app.config['EXT_IMG']
|
||||||
|
|
||||||
DATABASE = app.config['DATABASE']
|
DATABASE = app.config['DATABASE']
|
||||||
|
BASE_URL = "http://"+app.config['HOST']+app.config['PORT']
|
||||||
BASE_URL = app.config['BASE_URL']
|
|
||||||
SETUID = app.config['SETUID']
|
SETUID = app.config['SETUID']
|
||||||
MAIL_SERVER = app.config['MAIL_SERVER']
|
MAIL_SERVER = app.config['MAIL_SERVER']
|
||||||
XMPP_SERVER = app.config['XMPP_SERVER']
|
XMPP_SERVER = app.config['XMPP_SERVER']
|
||||||
@ -39,17 +38,19 @@ def login() :
|
|||||||
if request.method == 'POST' :
|
if request.method == 'POST' :
|
||||||
user = request.form['user']
|
user = request.form['user']
|
||||||
password = request.form['passwd']
|
password = request.form['passwd']
|
||||||
|
totp = request.form['code_totp']
|
||||||
|
|
||||||
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
|
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
|
||||||
cursor = conn.cursor() # Création de l'objet "curseur"
|
cursor = conn.cursor() # Création de l'objet "curseur"
|
||||||
cursor.execute("""SELECT name, passwd FROM users WHERE name=?""", (user,))
|
cursor.execute("""SELECT name, passwd, totp FROM users WHERE name=?""", (user,))
|
||||||
user_exist = cursor.fetchone()
|
user_exist = cursor.fetchone()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
if user_exist:
|
if user_exist:
|
||||||
user = user_exist[0]
|
user = user_exist[0]
|
||||||
passwd_bcrypt = user_exist[1]
|
passwd_bcrypt = user_exist[1]
|
||||||
|
totp_key = user_exist[2]
|
||||||
if user == request.form['user'] and bcrypt.check_password_hash(passwd_bcrypt, password) is True:
|
if totp_is_valid(totp_key, totp) and user == request.form['user'] and bcrypt.check_password_hash(passwd_bcrypt, password) is True:
|
||||||
session['username'] = request.form['user']
|
session['username'] = request.form['user']
|
||||||
resp = redirect(url_for('profil.profile', _external=True))
|
resp = redirect(url_for('profil.profile', _external=True))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import os
|
|||||||
from shutil import copy
|
from shutil import copy
|
||||||
from socket import gethostname
|
from socket import gethostname
|
||||||
from flask_bcrypt import Bcrypt
|
from flask_bcrypt import Bcrypt
|
||||||
from tools.utils import email_disp, append_to_log, gen_token, valid_passwd, valid_token_register, get_user_by_token
|
from tools.utils import email_disp, append_to_log, gen_token, valid_passwd, valid_token_register, get_user_by_token, totp_is_valid
|
||||||
|
from pyotp import random_base32
|
||||||
|
|
||||||
profil = Blueprint('profil', __name__, template_folder='templates')
|
profil = Blueprint('profil', __name__, template_folder='templates')
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ DATAS_USER = app.config['DOSSIER_APP']
|
|||||||
MAIL_SERVER = app.config['MAIL_SERVER']
|
MAIL_SERVER = app.config['MAIL_SERVER']
|
||||||
XMPP_SERVER = app.config['XMPP_SERVER']
|
XMPP_SERVER = app.config['XMPP_SERVER']
|
||||||
SETUID = app.config['SETUID']
|
SETUID = app.config['SETUID']
|
||||||
BASE_URL = app.config['BASE_URL']
|
BASE_URL= "http://"+app.config['HOST']+":"+app.config['PORT']
|
||||||
BACKUP_TIME = app.config['BACKUP_TIME']
|
BACKUP_TIME = app.config['BACKUP_TIME']
|
||||||
|
|
||||||
##################################################################################################
|
##################################################################################################
|
||||||
@ -135,34 +136,36 @@ def homepage():
|
|||||||
@profil.route('/profil/change-password/', methods=['GET','POST'] )
|
@profil.route('/profil/change-password/', methods=['GET','POST'] )
|
||||||
def change_passwd() :
|
def change_passwd() :
|
||||||
if 'username' in session:
|
if 'username' in session:
|
||||||
UTILISATEUR='%s' % escape(session['username'])
|
user='%s' % escape(session['username'])
|
||||||
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
|
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
|
||||||
cursor = conn.cursor() # Création de l'objet "curseur"
|
cursor = conn.cursor() # Création de l'objet "curseur"
|
||||||
cursor.execute("""SELECT Mail, alias, xmpp FROM users WHERE name=?""", (UTILISATEUR,))
|
cursor.execute("""SELECT Mail, alias, xmpp, totp FROM users WHERE name=?""", (user,))
|
||||||
tmp = cursor.fetchone()
|
tmp = cursor.fetchone()
|
||||||
mailbox = dict()
|
shared_key_validate=True
|
||||||
mailbox['Mail'] = tmp[0]
|
account = dict()
|
||||||
mailbox['alias'] = tmp[1]
|
account['Mail'] = tmp[0]
|
||||||
mailbox['xmpp'] = tmp[2]
|
account['alias'] = tmp[1]
|
||||||
|
account['xmpp'] = tmp[2]
|
||||||
|
account['totp'] = tmp[3]
|
||||||
|
|
||||||
if request.method == 'POST' :
|
if request.method == 'POST' :
|
||||||
|
|
||||||
password = request.form['password']
|
password = request.form['password']
|
||||||
password_confirm = request.form['passwd_confirm']
|
password_confirm = request.form['passwd_confirm']
|
||||||
|
|
||||||
if password == password_confirm and valid_passwd(password):
|
|
||||||
|
if not(password == "") and password == password_confirm and valid_passwd(password):
|
||||||
mail_passwd_change = 0
|
mail_passwd_change = 0
|
||||||
xmpp_passwd_change = 0
|
xmpp_passwd_change = 0
|
||||||
passwd = request.form['password']
|
passwd = request.form['password']
|
||||||
|
|
||||||
if MAIL_SERVER:
|
if MAIL_SERVER:
|
||||||
cmd = SETUID+ ' set_mail_passwd ' + '"'+mailbox['Mail']+'" '+ '"'+passwd+'"'
|
cmd = SETUID+ ' set_mail_passwd ' + '"'+account['Mail']+'" '+ '"'+passwd+'"'
|
||||||
mail_passwd_change = os.system(cmd)
|
mail_passwd_change = os.system(cmd)
|
||||||
|
|
||||||
|
|
||||||
if XMPP_SERVER:
|
if XMPP_SERVER:
|
||||||
tmp = mailbox['Mail'].split('@')
|
tmp = account['Mail'].split('@')
|
||||||
cmd = SETUID+ " prosodyctl register '"+tmp[0]+"' " + "'"+tmp[1]+"' " + "'"+passwd+"'"
|
cmd = SETUID+ " prosodyctl register '"+tmp[0]+"' " + "'"+tmp[1]+"' " + "'"+passwd+"'"
|
||||||
xmpp_passwd_change = os.system(cmd)
|
xmpp_passwd_change = os.system(cmd)
|
||||||
if xmpp_passwd_change != 0:
|
if xmpp_passwd_change != 0:
|
||||||
@ -172,26 +175,35 @@ def change_passwd() :
|
|||||||
if mail_passwd_change == 0:
|
if mail_passwd_change == 0:
|
||||||
passwd_bcrypt = bcrypt.generate_password_hash(passwd)
|
passwd_bcrypt = bcrypt.generate_password_hash(passwd)
|
||||||
cursor.execute("UPDATE users SET passwd=? WHERE name=?",
|
cursor.execute("UPDATE users SET passwd=? WHERE name=?",
|
||||||
(passwd_bcrypt, UTILISATEUR))
|
(passwd_bcrypt, user))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
TIME=time.strftime("%A %d %B %Y %H:%M:%S")
|
TIME=time.strftime("%A %d %B %Y %H:%M:%S")
|
||||||
IP=request.environ['REMOTE_ADDR']
|
IP=request.environ['REMOTE_ADDR']
|
||||||
CLIENT_PLATFORM=request.headers.get('User-Agent')
|
CLIENT_PLATFORM=request.headers.get('User-Agent')
|
||||||
log=TIME + ' - ' + IP + ' - ' + UTILISATEUR + ' - ' + CLIENT_PLATFORM + '\n' + '---> ' + "Changement du mot de passe" + '\n'
|
log=TIME + ' - ' + IP + ' - ' + user + ' - ' + CLIENT_PLATFORM + '\n' + '---> ' + "Changement du mot de passe" + '\n'
|
||||||
append_to_log(log, UTILISATEUR)
|
append_to_log(log, user)
|
||||||
flash(u'Votre mot de passe a été changé', 'succes')
|
flash(u'Votre mot de passe a été changé', 'success')
|
||||||
else:
|
else:
|
||||||
if not( valid_passwd(password) ):
|
if not( valid_passwd(password) ):
|
||||||
flash(u'Le mot de passe ne peut pas contenir les caractères " et &', 'error')
|
flash(u'Le mot de passe ne peut pas contenir les caractères " et &', 'error')
|
||||||
|
elif password == "":
|
||||||
|
flash(u' Vous ne pouvez pas ne pas mettre de mot de passe ou un mot de passe vide', 'error')
|
||||||
else:
|
else:
|
||||||
flash(u'Les mot de passes ne sont pas identique :/ ', 'error')
|
flash(u'Les mot de passes ne sont pas identiques :/ ', 'error')
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
return render_template('mailbox.html',
|
|
||||||
|
if not(account['totp']):
|
||||||
|
account['totp'] = random_base32()
|
||||||
|
shared_key_validate = False
|
||||||
|
|
||||||
|
return render_template('mypassword.html',
|
||||||
section="Profil",
|
section="Profil",
|
||||||
address=mailbox['Mail'],
|
address=account['Mail'],
|
||||||
alias=mailbox['alias'],
|
alias=account['alias'],
|
||||||
username=UTILISATEUR)
|
totp_shared_key=account['totp'],
|
||||||
|
shared_key_validate=shared_key_validate,
|
||||||
|
username=user)
|
||||||
|
|
||||||
else :
|
else :
|
||||||
return redirect(BASE_URL, code=401)
|
return redirect(BASE_URL, code=401)
|
||||||
@ -253,7 +265,8 @@ def change_passwd_lost(token) :
|
|||||||
log=TIME + ' - ' + IP + ' - ' + user + ' - ' + CLIENT_PLATFORM + '\n' + '---> ' + "Changement du mot de passe" + '\n'
|
log=TIME + ' - ' + IP + ' - ' + user + ' - ' + CLIENT_PLATFORM + '\n' + '---> ' + "Changement du mot de passe" + '\n'
|
||||||
append_to_log(log, user)
|
append_to_log(log, user)
|
||||||
flash(u'Votre mot de passe a été changé', 'succes')
|
flash(u'Votre mot de passe a été changé', 'succes')
|
||||||
cursor.execute("""UPDATE users set Lost_password_token='' where name=?""", (user,))
|
cursor.execute("""UPDATE users SET Lost_password_token='' where name=?""", (user,))
|
||||||
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
resp = redirect(url_for('loginlogout.login'))
|
resp = redirect(url_for('loginlogout.login'))
|
||||||
|
|
||||||
@ -273,7 +286,42 @@ def change_passwd_lost(token) :
|
|||||||
|
|
||||||
return redirect(BASE_URL, code=401)
|
return redirect(BASE_URL, code=401)
|
||||||
|
|
||||||
|
@profil.route('/set_totp/', methods=['POST'])
|
||||||
|
def set_totp():
|
||||||
|
if 'username' in session:
|
||||||
|
user='%s' % escape(session['username'])
|
||||||
|
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
|
||||||
|
cursor = conn.cursor() # Création de l'objet "curseur"
|
||||||
|
|
||||||
|
shared_key = request.form['shared_key']
|
||||||
|
code_totp = request.form['code_totp']
|
||||||
|
|
||||||
|
|
||||||
|
if totp_is_valid(shared_key, code_totp) and code_totp !="" and shared_key != "":
|
||||||
|
print("shared_key: " +shared_key)
|
||||||
|
cursor.execute("""UPDATE users SET totp=? WHERE name=?""", (shared_key, user,))
|
||||||
|
conn.commit()
|
||||||
|
flash(u'Votre mot de passe à usage unique est configuré et actif.', 'success')
|
||||||
|
else:
|
||||||
|
flash(u'Le code de validation totp n\'est pas valide.', 'error')
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
return redirect(url_for('profil.change_passwd', _external=True))
|
||||||
|
else:
|
||||||
|
return redirect(BASE_URL, code=401)
|
||||||
|
|
||||||
|
@profil.route('/del_totp/', methods=['GET'])
|
||||||
|
def del_totp():
|
||||||
|
if 'username' in session:
|
||||||
|
user='%s' % escape(session['username'])
|
||||||
|
conn = sqlite3.connect(DATABASE) # Connexion à la base de donnée
|
||||||
|
cursor = conn.cursor() # Création de l'objet "curseur"
|
||||||
|
cursor.execute("""UPDATE users SET totp="" WHERE name=?""", (user,))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return redirect(url_for('profil.change_passwd', _external=True))
|
||||||
|
|
||||||
|
|
||||||
@profil.route('/deltoken-password-lost/<token>', methods=['GET','POST'] )
|
@profil.route('/deltoken-password-lost/<token>', methods=['GET','POST'] )
|
||||||
def deltoken_passwd_lost(token) :
|
def deltoken_passwd_lost(token) :
|
||||||
|
|
||||||
@ -415,6 +463,8 @@ def invitation():
|
|||||||
else:
|
else:
|
||||||
return redirect(BASE_URL, code=401)
|
return redirect(BASE_URL, code=401)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@profil.route('/gen_token/', methods=['GET'])
|
@profil.route('/gen_token/', methods=['GET'])
|
||||||
def generate_token():
|
def generate_token():
|
||||||
if 'username' in session:
|
if 'username' in session:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user