From d5a8445df353402939a74b4c93c85c7daddb4016 Mon Sep 17 00:00:00 2001 From: Alexandre CHAZAL Date: Sun, 5 Dec 2021 14:08:03 +0100 Subject: [PATCH 1/3] feat(archi): renamed reset part as password --- app/__init__.py | 4 ++-- app/{reset.py => password.py} | 13 +++++++------ app/ui/static/css/main.css | 2 +- app/ui/templates/base.html | 2 +- app/ui/templates/{reset.html => change.html} | 4 ++-- 5 files changed, 13 insertions(+), 12 deletions(-) rename app/{reset.py => password.py} (88%) rename app/ui/templates/{reset.html => change.html} (93%) diff --git a/app/__init__.py b/app/__init__.py index ebedb5f..33a09db 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,10 +1,10 @@ from flask import Flask -from . import reset, config +from . import password, config def create_app(): app = Flask(__name__, template_folder="ui/templates", static_folder="ui/static") app.config.from_object(config.ProductionConfig()) - app.register_blueprint(reset.bp) + app.register_blueprint(password.bp) return app diff --git a/app/reset.py b/app/password.py similarity index 88% rename from app/reset.py rename to app/password.py index f6036fd..d8afd66 100644 --- a/app/reset.py +++ b/app/password.py @@ -14,9 +14,9 @@ from wtforms.validators import ( EqualTo, Length, Regexp ) -bp = Blueprint('reset', __name__, url_prefix='/reset') +bp = Blueprint('password', __name__, url_prefix='/password') -class ResetPasswordForm(FlaskForm): +class ChangePasswordForm(FlaskForm): # Minimal password length minlength = 9 @@ -54,11 +54,12 @@ class ResetPasswordForm(FlaskForm): raise ValidationError( f"Character {char} is not allowed in an username.") -@bp.route('/', methods=('GET', 'POST')) -def reset(): - form = ResetPasswordForm() +@bp.route('/change', methods=('GET', 'POST')) +def change(): + form = ChangePasswordForm() if form.validate_on_submit(): client = ldap_client.Client(address=current_app.config["LDAP_ADDR"], port=current_app.config["LDAP_PORT"], base_dn=current_app.config["BASE_DN"], tls=current_app.config["LDAP_TLS"]) + bind_status = client.bind(form.username._value(), form.currentpassword._value()) if bind_status[0] == False: flash(f"Connection failed, are you sure that your login and password are correct ? ({client.link.last_error})") @@ -69,4 +70,4 @@ def reset(): flash('Your password has been changed !') client.unbind() - return render_template('reset.html', form=form) \ No newline at end of file + return render_template('change.html', form=form) diff --git a/app/ui/static/css/main.css b/app/ui/static/css/main.css index b0faa5f..9e4542f 100644 --- a/app/ui/static/css/main.css +++ b/app/ui/static/css/main.css @@ -93,7 +93,7 @@ li.errormsg::before { -webkit-box-shadow: 0 0 0 .10rem rgba(92, 184, 92, 0.50); } -#reset-form { +#change-form { background: #4e4e4e; border-radius: .50rem; } diff --git a/app/ui/templates/base.html b/app/ui/templates/base.html index 70d2ffb..4ad51cb 100644 --- a/app/ui/templates/base.html +++ b/app/ui/templates/base.html @@ -11,7 +11,7 @@ - + {% for message in get_flashed_messages() %} diff --git a/app/ui/templates/reset.html b/app/ui/templates/change.html similarity index 93% rename from app/ui/templates/reset.html rename to app/ui/templates/change.html index b593e02..529d1bd 100644 --- a/app/ui/templates/reset.html +++ b/app/ui/templates/change.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% block main_block %} -
+
{{ form.csrf_token() }}
@@ -45,4 +45,4 @@ {% block script_block %} -{% endblock script_block %} \ No newline at end of file +{% endblock script_block %} From bcf50ff111fcff4769562fdc075adc532e56de19 Mon Sep 17 00:00:00 2001 From: Alexandre CHAZAL Date: Mon, 6 Dec 2021 17:47:05 +0100 Subject: [PATCH 2/3] feat(password): added a password reset page (and unused form), corrected the validation of the password changer, modified styles --- app/password.py | 25 +++++++++++++++----- app/ui/static/css/main.css | 22 +++++++++++------- app/ui/static/js/validate.js | 41 +++++++++++++++++++++++++------- app/ui/templates/base.html | 10 ++++---- app/ui/templates/change.html | 45 ++++++++++++++++++++---------------- app/ui/templates/reset.html | 20 ++++++++++++++++ requirements.txt | 3 ++- 7 files changed, 118 insertions(+), 48 deletions(-) create mode 100644 app/ui/templates/reset.html diff --git a/app/password.py b/app/password.py index d8afd66..5153c8f 100644 --- a/app/password.py +++ b/app/password.py @@ -7,11 +7,11 @@ from flask import ( from flask_wtf import FlaskForm from wtforms import ( StringField, PasswordField, - SubmitField + SubmitField, EmailField ) from wtforms.validators import ( ValidationError, DataRequired, - EqualTo, Length, Regexp + EqualTo, Length, Regexp, Email ) bp = Blueprint('password', __name__, url_prefix='/password') @@ -37,14 +37,15 @@ class ChangePasswordForm(FlaskForm): # "(?=.*[@$!%*#?&])", message="Password must contain a special character" #),], ], - render_kw={"onkeyup": f"validate_form({minlength})"}) + render_kw={"onkeyup": f"validate_username_form({minlength})"}) confirm_password = PasswordField( label=('Confirm Password'), validators=[DataRequired(message='* Required'), EqualTo('newpassword')], - render_kw={"onkeyup": f"validate_confirm({minlength})"}) + render_kw={"onkeyup": f"validate_username_form({minlength})"}) - submit = SubmitField(label=('Change my password'), render_kw={"onclick": f"validate_form({minlength})"}) + submit = SubmitField(label=('Change my password'), render_kw={"disabled": "true", + "onclick": f"validate_username_form({minlength})"}) # Validators def validate_username(self, username): @@ -54,7 +55,15 @@ class ChangePasswordForm(FlaskForm): raise ValidationError( f"Character {char} is not allowed in an username.") -@bp.route('/change', methods=('GET', 'POST')) +class ResetPasswordForm(FlaskForm): + email = EmailField(label=('Email address'), + validators=[DataRequired(), Email()], + render_kw={"onkeyup": f"validate_email()"}) + + submit = SubmitField(label=('Change my password'), render_kw={"disabled": "true", + "onclick": f"validate_email()"}) + +@bp.route('/change', methods=["GET", "POST"]) def change(): form = ChangePasswordForm() if form.validate_on_submit(): @@ -71,3 +80,7 @@ def change(): client.unbind() return render_template('change.html', form=form) + +@bp.route('/reset', methods=["GET"]) +def reset(): + return render_template('reset.html') \ No newline at end of file diff --git a/app/ui/static/css/main.css b/app/ui/static/css/main.css index 9e4542f..c69a498 100644 --- a/app/ui/static/css/main.css +++ b/app/ui/static/css/main.css @@ -15,6 +15,17 @@ body { background-attachment: fixed; } +#main-block > div { + box-shadow: 1px 1px 10px black; + border-radius: .50rem; + background: #4e4e4e; + margin: 1em; +} + +#main-block > div > *:first-child { + margin: 1em; +} + .vcenter { position: absolute; left: 50%; @@ -65,7 +76,7 @@ a:hover>span { } #password-msg li::before { - content: "☑ "; + content: "OK - "; } .errorinput { @@ -78,7 +89,7 @@ a:hover>span { } li.errormsg::before { - content: "☒ " !important; + content: "KO - " !important; } @@ -91,9 +102,4 @@ li.errormsg::before { border-color: #5cb85c; box-shadow: 0 0 0 .10rem rgba(92, 184, 92, 0.50); -webkit-box-shadow: 0 0 0 .10rem rgba(92, 184, 92, 0.50); -} - -#change-form { - background: #4e4e4e; - border-radius: .50rem; -} +} \ No newline at end of file diff --git a/app/ui/static/js/validate.js b/app/ui/static/js/validate.js index 8d7feeb..cf18633 100644 --- a/app/ui/static/js/validate.js +++ b/app/ui/static/js/validate.js @@ -1,8 +1,34 @@ -function validate_form(minlength) { +function validate_username_form(minlength) { var user = validate_username(); var pass = validate_password(minlength); - return validate_confirm() && pass && user; + if (validate_confirm() && pass && user) { + disable_submit(false); + return true; + } + + disable_submit(true); + return false; +} + +function disable_submit(status) { + document.getElementById("submit").disabled = status; +} + +function validate_email() { + var email = document.getElementById("email"); + var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + + if (re.test(email.value) != true) { + disable_submit(true); + email.classList.add("errorinput"); + return false; + } + + + disable_submit(false); + email.classList.remove("errorinput"); + return true; } function validate_confirm() { @@ -25,15 +51,16 @@ function validate_username() { var username = document.getElementById("username"); var forbidden = /[*?!'\^+%\&/()=}{\$#;,\\"]+/; - if (username.value.length > 64 || forbidden.test(username.value) == true) - { + if (username.value.length > 64 || forbidden.test(username.value) == true) { document.getElementById("username-msg").classList.add("errormsg"); username.classList.add("errorinput"); + disable_submit(true); return false; } document.getElementById("username-msg").classList.remove("errormsg"); username.classList.remove("errorinput"); + disable_submit(false); return true; } @@ -43,8 +70,7 @@ function validate_password(minlength) { // Target element var password = document.getElementById("newpassword"); // Check the length - if (password.value.length < minlength) - { + if (password.value.length < minlength) { status = false; document.getElementById("minlen").classList.add("errormsg"); } @@ -71,11 +97,10 @@ function validate_password(minlength) { } else document.getElementById("upper").classList.remove("errormsg"); + // Change the color of the inputbox if (status == false) - { password.classList.add("errorinput"); - } else password.classList.remove("errorinput"); diff --git a/app/ui/templates/base.html b/app/ui/templates/base.html index 4ad51cb..649fb1b 100644 --- a/app/ui/templates/base.html +++ b/app/ui/templates/base.html @@ -5,20 +5,20 @@ - AlxCzl - LDAP Interface + AlxCzl - {% block title %}{% endblock title %} - + - + {% for message in get_flashed_messages() %}
{{ message }}
{% endfor %} -
- {% block main_block %}{% endblock main_block %} +
+ {% block main_block %}{% endblock main_block %}
{% block script_block %}{% endblock script_block %} diff --git a/app/ui/templates/change.html b/app/ui/templates/change.html index 529d1bd..198d6e1 100644 --- a/app/ui/templates/change.html +++ b/app/ui/templates/change.html @@ -1,48 +1,53 @@ {% extends 'base.html' %} +{% block title %}Password change{% endblock %} + {% block main_block %} -
+
+
+

Password change

+
{{ form.csrf_token() }} -
+
{{ form.username.label }} -
- The username can contain at most 64 characters and cannot contain one of the following characters : [*?!'^+%&/()=}{$#;,\" -
{{ form.username(class="form-control") }} + + The username can contain at most 64 characters and cannot contain one of the following characters : + [*?!'^+%&/()=}{$#;,\" +
-
+
{{ form.currentpassword.label }} {{ form.currentpassword(class="form-control") }}
-
+
{{ form.newpassword.label }} -
- The new password should contain at least : + {{ form.newpassword(class="form-control") }} + + The new password should at least contain the following:
  • {{ form.minlength }} characters
  • 1 numeric digit [0-9]
  • 1 lowercase character [a-z]
  • 1 uppercase character [A-Z]
-
- {{ form.newpassword(class="form-control") }} +
-
+
{{ form.confirm_password.label }} -
- Passwords must match -
{{ form.confirm_password(class="form-control") }} + + Passwords must match +
-
-
- {{ form.submit(class="btn btn-primary")}} -
+ {{ form.submit(class="btn btn-primary")}} + I forgot my password
+
{% endblock main_block %} {% block script_block %} -{% endblock script_block %} +{% endblock script_block %} \ No newline at end of file diff --git a/app/ui/templates/reset.html b/app/ui/templates/reset.html new file mode 100644 index 0000000..e4c8e25 --- /dev/null +++ b/app/ui/templates/reset.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} + +{% block title %}Password reset{% endblock %} + +{% block main_block %} +
+
+

Password reset

+
+
Sorry, password self-reset isn't available on my intranet for security reasons.
+ Just contact me and I'll send you a message containing a new password. +
+ I just need to change it +
+
+{% endblock main_block %} + +{% block script_block %} + +{% endblock script_block %} diff --git a/requirements.txt b/requirements.txt index 89e7f5c..071afd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ typing_extensions==4.0.0 Werkzeug==2.0.2 zipp==3.6.0 ldap3 -Flask-WTF==1.0.0 \ No newline at end of file +Flask-WTF==1.0.0 +email-validator \ No newline at end of file From 79ef9f1be8461f3eb4010166bf4c3d5fbfec7b1f Mon Sep 17 00:00:00 2001 From: Alexandre CHAZAL Date: Mon, 6 Dec 2021 17:48:42 +0100 Subject: [PATCH 3/3] feat(reset): changed button text --- app/ui/templates/reset.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ui/templates/reset.html b/app/ui/templates/reset.html index e4c8e25..4bbee74 100644 --- a/app/ui/templates/reset.html +++ b/app/ui/templates/reset.html @@ -10,7 +10,7 @@
Sorry, password self-reset isn't available on my intranet for security reasons.
Just contact me and I'll send you a message containing a new password.
- I just need to change it + I just remembered it !
{% endblock main_block %}