diff --git a/.dockerignore b/.dockerignore index d259ec7..6ea5688 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ *.pyc .git/ -Dockerfile \ No newline at end of file +Dockerfile +__pycache__/* \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..cf00692 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,10 @@ +from flask import Flask, render_template +from . import reset + +def create_app(): + app = Flask(__name__, instance_relative_config=True, template_folder="ui/templates", static_folder="ui/static") + app.config.from_object("instance.config.DebugConfig") + + app.register_blueprint(reset.bp) + + return app \ No newline at end of file diff --git a/app/reset.py b/app/reset.py new file mode 100644 index 0000000..204c2e1 --- /dev/null +++ b/app/reset.py @@ -0,0 +1,39 @@ +import functools + +from flask import ( + Blueprint, flash, g, redirect, render_template, request, session, url_for +) +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField, BooleanField, \ + SubmitField +from wtforms.validators import ValidationError, DataRequired, \ + Email, EqualTo, Length +from werkzeug.security import check_password_hash, generate_password_hash + + +bp = Blueprint('reset', __name__, url_prefix='/reset') + +class ResetPasswordForm(FlaskForm): + username = StringField(label=('Username'), + validators=[DataRequired(), + Length(max=64)]) + currentpassword = PasswordField(label=('Current password'), + validators=[DataRequired()]) + newpassword = PasswordField(label=('New password'), + validators=[DataRequired(), + Length(min=8, message='Password should be at least %(min)d characters long')], + render_kw={"onkeyup": "validate_form()"}) + confirm_password = PasswordField( + label=('Confirm Password'), + validators=[DataRequired(message='* Required'), + EqualTo('newpassword', message='Both password fields must be equal!')], + render_kw={"onkeyup": "validate_confirm()"}) + + submit = SubmitField(label=('Change my password'), render_kw={"onclick": "validate_form()"}) + +@bp.route('/', methods=('GET', 'POST')) +def reset(): + form = ResetPasswordForm() + if form.validate_on_submit(): + return f'''

Welcome {form.username.data}

''' + return render_template('reset.html', form=form) \ No newline at end of file diff --git a/app/ui/static/css/main.css b/app/ui/static/css/main.css new file mode 100644 index 0000000..0e5b919 --- /dev/null +++ b/app/ui/static/css/main.css @@ -0,0 +1,84 @@ +html, +body { + margin: 0; + height: 100%; + min-width: 310px; +} + +body { + min-height: 100%; + font-family: 'Roboto Mono', monospace; + color: white; + background: rgb(0, 0, 0); + background: linear-gradient(8deg, rgb(35, 155, 21) 0%, rgba(14, 47, 11, 1) 40%, rgba(0, 0, 0, 1) 70%); + background-repeat: no-repeat; + background-attachment: fixed; +} + +.vcenter { + position: absolute; + left: 50%; + top: 50%; + -webkit-transform: translateX(-50%) translateY(-50%); + transform: translateX(-50%) translateY(-50%); +} + +.container { + animation: fadein ease 3s; + animation-iteration-count: 1; + animation-fill-mode: forwards; +} + +a { + text-decoration: none; +} + +a:link>span { + color: rgb(216, 216, 216); +} + +a:visited>span { + color: rgb(216, 216, 216); +} + +a:hover>span { + color: rgb(255, 255, 255); +} + +.icon2x { + height: 32px; + width: 32px; + font-size: 32px; +} + +.errorinput { + border-color: #f44246 !important; +} + +#confirm-msg, +#password-msg { + color: #f44246; + opacity: 0; + transition: opacity 225ms cubic-bezier(0.25, 0.46, 0.45, 0.94); +} + +.errorinput:focus { + box-shadow: 0 0 0 .10rem rgba(244, 66, 70, 0.50) !important; + -webkit-box-shadow: 0 0 0 .10rem rgba(244, 66, 70, 0.50) !important; +} + +.form-control:focus { + 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); +} + +#reset-form { + background: #4e4e4e; + border-radius: .50rem; +} + +.fade { + opacity: 1 !important; + transition: opacity 300ms cubic-bezier(0.55, 0.085, 0.68, 0.53); +} \ No newline at end of file diff --git a/app/ui/static/js/validate.js b/app/ui/static/js/validate.js new file mode 100644 index 0000000..3f90df1 --- /dev/null +++ b/app/ui/static/js/validate.js @@ -0,0 +1,37 @@ +function validate_form() { + var pass = validate_password(); + + return validate_confirm() && pass; +} + +function validate_confirm() { + var password = document.getElementById("newpassword"); + var confirm = document.getElementById("confirm_password"); + + if (password.value != confirm.value) { + confirm.classList.add("errorinput"); + document.getElementById("confirm-msg").classList.add("fade"); + return false; + } + + confirm.classList.remove("errorinput"); + document.getElementById("confirm-msg").classList.remove("fade"); + + return true; +} + +function validate_password() { + var password = document.getElementById("newpassword"); + var reg = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}/; + + if (reg.test(password.value) != true) { + password.classList.add("errorinput"); + document.getElementById("password-msg").classList.add("fade"); + return false; + } + + password.classList.remove("errorinput"); + document.getElementById("password-msg").classList.remove("fade"); + + return true; +} \ No newline at end of file diff --git a/app/ui/templates/base.html b/app/ui/templates/base.html new file mode 100644 index 0000000..b640b1b --- /dev/null +++ b/app/ui/templates/base.html @@ -0,0 +1,26 @@ + + + + + + + + AlxCzl - LDAP Interface + + + + + + + + + {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} +
+ {% block main_block %}{% endblock main_block %} +
+ {% block script_block %}{% endblock script_block %} + + + diff --git a/app/ui/templates/reset.html b/app/ui/templates/reset.html new file mode 100644 index 0000000..dcd0031 --- /dev/null +++ b/app/ui/templates/reset.html @@ -0,0 +1,42 @@ +{% extends 'base.html' %} + +{% block main_block %} +
+ {% for field, errors in form.errors.items() %} + {{ ', '.join(errors) }} + {% endfor %} +
+ {{ form.csrf_token() }} +
+ {{ form.username.label }} + {{ form.username(class="form-control") }} +
+
+ {{ form.currentpassword.label }} + {{ form.currentpassword(class="form-control") }} +
+
+ {{ form.newpassword.label }} + + The new password should contain at least : 8 characters, one numeric digit, one lowercase and one uppercase characters. + + {{ form.newpassword(class="form-control") }} +
+
+ {{ form.confirm_password.label }} + + Passwords should match + + {{ form.confirm_password(class="form-control") }} +
+
+
+ {{ form.submit(class="btn btn-primary")}} +
+
+
+{% endblock main_block %} + +{% block script_block %} + +{% endblock script_block %} \ No newline at end of file diff --git a/app/entrypoint.sh b/entrypoint.sh similarity index 100% rename from app/entrypoint.sh rename to entrypoint.sh diff --git a/requirements.txt b/requirements.txt index 9c6a7e4..03fa1b2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,5 @@ MarkupSafe==2.0.1 typing_extensions==4.0.0 Werkzeug==2.0.2 zipp==3.6.0 -python-ldap \ No newline at end of file +python-ldap +Flask-WTF==1.0.0 \ No newline at end of file diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..3f0abc1 --- /dev/null +++ b/wsgi.py @@ -0,0 +1,6 @@ +import os, sys +app_path = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, app_path) + +from app import create_app +application = create_app()