From 4988092706053e08a0cd91959058bade781517dd Mon Sep 17 00:00:00 2001 From: Meewan Date: Fri, 16 Aug 2024 15:40:49 +0200 Subject: [PATCH] first commit --- README.md | 12 +++ __init__.py | 0 etc/contact-form/config.ini | 9 +++ etc/contact-form/contact-form.service | 13 +++ etc/contact-form/uwsgi.ini | 5 ++ requirements.txt | 3 + templates/form.html | 58 ++++++++++++++ templates/success.html | 12 +++ webapp.py | 109 ++++++++++++++++++++++++++ 9 files changed, 221 insertions(+) create mode 100644 __init__.py create mode 100644 etc/contact-form/config.ini create mode 100644 etc/contact-form/contact-form.service create mode 100644 etc/contact-form/uwsgi.ini create mode 100644 requirements.txt create mode 100644 templates/form.html create mode 100644 templates/success.html create mode 100644 webapp.py diff --git a/README.md b/README.md index a74e168..345fe57 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ # contact-form +Formulaire de contacte pour le site de katzei + +# installation + +To install you need to: + +* install flask, jinja2 an sqlalchemy +* clone the repo in the /site directory +* copy the etc content in /etc +* link /etc/contact-form/contact-form.service to /etc/systemd/system/contact-form.service + +By default it will serve on port 9090 \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/etc/contact-form/config.ini b/etc/contact-form/config.ini new file mode 100644 index 0000000..b6504ee --- /dev/null +++ b/etc/contact-form/config.ini @@ -0,0 +1,9 @@ +[app] +path = /dyn/contact +db_path = /tmp/contact-form.db +# time to keep the captacha valid in seconds +captcha_timout = 3600 + +[email] +reciever = email@example.com +server = localhost \ No newline at end of file diff --git a/etc/contact-form/contact-form.service b/etc/contact-form/contact-form.service new file mode 100644 index 0000000..fb269f8 --- /dev/null +++ b/etc/contact-form/contact-form.service @@ -0,0 +1,13 @@ +[Unit] +Description=Nextcloudregister a service to register in nextcloud +After=local-fs.target +Wants=network-online.target + +[Service] +Environment=PYTHONPATH=/usr/lib/python3/dist-packages/:/sites/ +ExecStart=/usr/bin/uwsgi-core --ini /etc/contact-form/uwsgi.ini +User=contactform +Group=contactform + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/etc/contact-form/uwsgi.ini b/etc/contact-form/uwsgi.ini new file mode 100644 index 0000000..0db92c7 --- /dev/null +++ b/etc/contact-form/uwsgi.ini @@ -0,0 +1,5 @@ +[uwsgi] +socket = :9090 +protocol = http +wsgi-file = /sites/contact-form/webapp.py +plugin = python3 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9b9921a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +flask +jinja2 +sqlalchemy \ No newline at end of file diff --git a/templates/form.html b/templates/form.html new file mode 100644 index 0000000..12ca206 --- /dev/null +++ b/templates/form.html @@ -0,0 +1,58 @@ + + + + + + + + + + + +
+ +
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+ {% if captcha_error %} +
Le captcha est invalide veuillez réessayer
+ {% endif %} +
+ +
+
+ + + \ No newline at end of file diff --git a/templates/success.html b/templates/success.html new file mode 100644 index 0000000..983cb85 --- /dev/null +++ b/templates/success.html @@ -0,0 +1,12 @@ + + + + + + + + + + Votre demande a bien été transmise. Nous vous recontacterons dès que possible. + + \ No newline at end of file diff --git a/webapp.py b/webapp.py new file mode 100644 index 0000000..40c98d7 --- /dev/null +++ b/webapp.py @@ -0,0 +1,109 @@ +from configparser import ConfigParser +from email.message import EmailMessage +import random +import operator +from os.path import exists +from secrets import token_urlsafe +from smtplib import SMTP +from time import time + +import flask +import sqlalchemy +import sqlalchemy.orm + +CONFIG_PATH = "/etc/contact-form/config.ini" +OPERATORS = ( + ('+', operator.add), + ('×', operator.mul), + ('-', operator.sub), +) + +config = ConfigParser() +config.read(CONFIG_PATH) +app = flask.Flask(__name__) +Base = sqlalchemy.orm.declarative_base() + + + +class Captcha(Base): + __tablename__ = "captcha" + + token = sqlalchemy.Column(sqlalchemy.String(90), primary_key=True) + answer = sqlalchemy.Column(sqlalchemy.String(3)) + expiration = sqlalchemy.Column(sqlalchemy.Integer()) + + +def get_session(): + db_path = config.get("app", "db_path", fallback="") + engine = sqlalchemy.create_engine(f"sqlite:///{db_path}") + if not exists(db_path): + Base.metadata.create_all(engine) + session_factory = sqlalchemy.orm.sessionmaker(engine) + return session_factory() + + +def clean_db(session): + session.query(Captcha).filter(Captcha.expiration < int(time())).delete() + + +def generate_captacha(): + session = get_session() + clean_db(session) + first_number = random.randrange(10) + second_number = random.randrange(10) + op_text, op_func = random.choice(OPERATORS) + result = op_func(first_number, second_number) + captcha = Captcha( + token=token_urlsafe(67), + answer=str(result), + expiration=int(time()) + config.getint("app", "captcha_timout", fallback=3600), + ) + session.add(captcha) + session.commit() + return { + "first_number": first_number, + "second_number": second_number, + "op_text": op_text, + "token": captcha.token, + } + + +def validate_captcha(token, value): + result = False + session = get_session() + clean_db(session) + captcha = session.query(Captcha).filter(Captcha.token == token).one_or_none() + if captcha: + result = value == captcha.answer + session.query(Captcha).filter(Captcha.token == token).delete() + session.commit() + return result + + +def send_email(sender, message): + email = EmailMessage() + email.set_content(message) + email["From"] = sender + email["To"] = config.get("email", "reciever") + smtp = SMTP(config.get("email", "server")) + smtp.send_message(email) + smtp.quit() + +@app.route(config.get("app", "path"), methods=["GET"]) +def get_form(context=None): + context = (context or {}) | generate_captacha() + return flask.render_template("form.html", **context) + + +@app.route(config.get("app", "path"), methods=["POST"]) +def validate_form(): + form = flask.request.form + if not all(form.get(f, False) for f in ("email", "message", "captcha", "token")): + return "Erreur", 400 + if not validate_captcha(form["token"], form["captcha"]): + context = dict(**form) + context["captcha_error"] = True + context.pop("captcha", None) + return get_form(context=context) + send_email(sender=form["email"], message=form["message"]) + return flask.render_template("success.html")