first commit
This commit is contained in:
		
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,2 +1,14 @@ | |||||||
| # contact-form | # 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 | ||||||
							
								
								
									
										0
									
								
								__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										9
									
								
								etc/contact-form/config.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								etc/contact-form/config.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										13
									
								
								etc/contact-form/contact-form.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								etc/contact-form/contact-form.service
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										5
									
								
								etc/contact-form/uwsgi.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								etc/contact-form/uwsgi.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | [uwsgi] | ||||||
|  | socket = :9090 | ||||||
|  | protocol = http | ||||||
|  | wsgi-file = /sites/contact-form/webapp.py | ||||||
|  | plugin = python3 | ||||||
							
								
								
									
										3
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | flask | ||||||
|  | jinja2 | ||||||
|  | sqlalchemy | ||||||
							
								
								
									
										58
									
								
								templates/form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								templates/form.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | <!doctype html> | ||||||
|  |  | ||||||
|  | <html lang="fr"> | ||||||
|  |   <head> | ||||||
|  |     <meta charset="utf-8"> | ||||||
|  |     <link rel="stylesheet" href="/css/frombulma.css"> | ||||||
|  |     <link rel="stylesheet" href="/css/styles.css"> | ||||||
|  |     <style type="text/css"> | ||||||
|  |       .form-line>.input, .form-line>.textarea{ | ||||||
|  |         border-color: #aaaaaa; | ||||||
|  |         width:100%; | ||||||
|  |         margin-bottom: 1em; | ||||||
|  |       } | ||||||
|  |       .form-line>.textarea{ | ||||||
|  |         min-height: 25em; | ||||||
|  |       } | ||||||
|  |       .form-error{ | ||||||
|  |         width: 100%; | ||||||
|  |         border-radius: 4px; | ||||||
|  |         margin-bottom: 1em; | ||||||
|  |       } | ||||||
|  |       .form-alert{ | ||||||
|  |         background-color: rgba(255, 0, 0, 0.2); | ||||||
|  |       } | ||||||
|  |     </style> | ||||||
|  |   </head> | ||||||
|  |   <body> | ||||||
|  |      | ||||||
|  |     <form method="post"> | ||||||
|  |       <input type="hidden" name="token" value={{token}}> | ||||||
|  |       <div class="form-line field"> | ||||||
|  |           <label class="label" for=email>Adresse de courriel</label> | ||||||
|  |         <div class="form-line control"> | ||||||
|  |           <input class="input" id="email" type="email" name="email" value="{{email}}" required/> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="form-line field"> | ||||||
|  |         <label class="label" for="message">Votre message</label> | ||||||
|  |       </div> | ||||||
|  |       <div class="form-line field"> | ||||||
|  |         <textarea class="textarea" id="message" name="message" rows="20" minlength="100" maxlength="10000" required>{{message}}</textarea> | ||||||
|  |       </div> | ||||||
|  |       <div class="form-line field"> | ||||||
|  |         <label class="label" for="captcha">Vérification d’humanité: combien font {{first_number}} {{op_text}} {{second_number}} ?</label> | ||||||
|  |       </div> | ||||||
|  |       <div class="form-line field"> | ||||||
|  |         <input class="input {% if captcha_error %}form-alert{% endif %}" id="captcha" type="number" name="captcha" min="-10" max="100" required /> | ||||||
|  |       </div> | ||||||
|  |       {% if captcha_error %} | ||||||
|  |       <div class="form-error">Le captcha est invalide veuillez réessayer</div> | ||||||
|  |       {% endif %} | ||||||
|  |       <div class="form-line field"> | ||||||
|  |         <input class="button is-link" type="submit" value="Envoyer" /> | ||||||
|  |       </div> | ||||||
|  |     </form> | ||||||
|  |    | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										12
									
								
								templates/success.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								templates/success.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | <!doctype html> | ||||||
|  |  | ||||||
|  | <html lang="fr"> | ||||||
|  |   <head> | ||||||
|  |     <meta charset="utf-8"> | ||||||
|  |     <link rel="stylesheet" href="/css/frombulma.css"> | ||||||
|  |     <link rel="stylesheet" href="/css/styles.css"> | ||||||
|  |   </head> | ||||||
|  |   <body> | ||||||
|  |    Votre demande a bien été transmise. Nous vous recontacterons dès que possible. | ||||||
|  |   </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										109
									
								
								webapp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								webapp.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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") | ||||||
		Reference in New Issue
	
	Block a user
	 Meewan
					Meewan