From c93c143cc82dffc44467f6a71b92c7e8fb3e9521 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 19 Oct 2022 18:55:34 +0200 Subject: [PATCH] add token to user registration and admin email --- ereuse_devicehub/forms.py | 60 +++++--- ereuse_devicehub/inventory/forms.py | 4 - ereuse_devicehub/mail/flask_mail.py | 137 +++++++++++------- .../ereuse_devicehub/user_registration.html | 50 ++++--- .../ereuse_devicehub/user_validation.html | 66 +++++++++ ereuse_devicehub/views.py | 24 ++- examples/app.py | 2 +- 7 files changed, 243 insertions(+), 100 deletions(-) create mode 100644 ereuse_devicehub/templates/ereuse_devicehub/user_validation.html diff --git a/ereuse_devicehub/forms.py b/ereuse_devicehub/forms.py index 2940623d..daa0760f 100644 --- a/ereuse_devicehub/forms.py +++ b/ereuse_devicehub/forms.py @@ -1,5 +1,5 @@ -from flask import g from flask import current_app as app +from flask import g from flask_wtf import FlaskForm from werkzeug.security import generate_password_hash from wtforms import ( @@ -115,17 +115,16 @@ class PasswordForm(FlaskForm): class UserNewRegisterForm(FlaskForm): email = EmailField( - 'Email Address', [ - validators.DataRequired(), - validators.Length(min=6, max=35) - ] + 'Email Address', [validators.DataRequired(), validators.Length(min=6, max=35)] ) password = PasswordField('Password', [validators.DataRequired()]) password2 = PasswordField('Password', [validators.DataRequired()]) name = StringField( 'Name', [validators.DataRequired(), validators.Length(min=3, max=35)] ) - telephone = TelField('Telephone', [validators.DataRequired()]) + telephone = TelField( + 'Telephone', [validators.DataRequired(), validators.Length(min=9, max=35)] + ) error_messages = { 'invalid_login': ( @@ -160,16 +159,19 @@ class UserNewRegisterForm(FlaskForm): return True def save(self, commit=True): - user = User( - email=self.email.data, - password=self.password.data, - active=False - ) + user_validation = self.new_user() + if commit: + db.session.commit() + + self._token = user_validation.token + self.send_mail() + self.send_mail_admin(user_validation.user) + + def new_user(self): + user = User(email=self.email.data, password=self.password.data, active=False) person = Person( - email=self.email.data, - name=self.name.data, - telephone=self.telephone.data + email=self.email.data, name=self.name.data, telephone=self.telephone.data ) user.individuals.add(person) @@ -180,19 +182,33 @@ class UserNewRegisterForm(FlaskForm): ) db.session.add(user_validation) - if commit: - db.session.commit() - - self._token = user_validation.token - self.send_mail() + return user_validation def send_mail(self): host = app.config.get('HOST') - token = self._token - url = f'https://{ host }/validate/{ token }' - message = """Hello, you are register in Usody.com + token = self._token + url = f'https://{ host }/validate_user/{ token }' + message = """ + Hello, you are register in Usody.com Please for activate your account click in the next address: """ message += url subject = "Validate email for register in Usody.com" send_email(subject, [self.email.data], message) + + def send_mail_admin(self, user): + person = next(iter(user.individuals)) + email = person.email + name = person.name + telephone = person.telephone + + message = f"""A new user has been registered. These are your data" + Name: {name} + Telephone: {telephone} + Email: {email} + """ + subject = "New Register" + + email_admin = app.config.get("EMAIL_ADMIN") + if email_admin: + send_email(subject, [email_admin], message) diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index 69f0b4ba..4d934dfb 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -187,10 +187,6 @@ class FilterForm(FlaskForm): if filter_type: self.devices = self.devices.filter(Device.type.in_(filter_type)) - # if self.device_type in STORAGE + ["All DataStorage"]: - # import pdb; pdb.set_trace() - # self.devices = self.devices.filter(Component.parent_id.is_(None)) - return self.devices.order_by(Device.updated.desc()) diff --git a/ereuse_devicehub/mail/flask_mail.py b/ereuse_devicehub/mail/flask_mail.py index 7f2b87b4..44e968af 100644 --- a/ereuse_devicehub/mail/flask_mail.py +++ b/ereuse_devicehub/mail/flask_mail.py @@ -14,21 +14,20 @@ from __future__ import with_statement __version__ = '0.9.1' import re -import blinker import smtplib import sys import time import unicodedata - +from contextlib import contextmanager from email import charset from email.encoders import encode_base64 +from email.header import Header from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from email.header import Header -from email.utils import formatdate, formataddr, make_msgid, parseaddr -from contextlib import contextmanager +from email.utils import formataddr, formatdate, make_msgid, parseaddr +import blinker from flask import current_app PY3 = sys.version_info[0] == 3 @@ -36,12 +35,13 @@ PY3 = sys.version_info[0] == 3 PY34 = PY3 and sys.version_info[1] >= 4 if PY3: - string_types = str, + string_types = (str,) text_type = str from email import policy + message_policy = policy.SMTP else: - string_types = basestring, + string_types = (basestring,) text_type = unicode message_policy = None @@ -85,10 +85,10 @@ def force_text(s, encoding='utf-8', errors='strict'): if not isinstance(s, Exception): raise FlaskMailUnicodeDecodeError(s, *e.args) else: - s = ' '.join([force_text(arg, encoding, strings_only, - errors) for arg in s]) + s = ' '.join([force_text(arg, encoding, strings_only, errors) for arg in s]) return s + def sanitize_subject(subject, encoding='utf-8'): try: subject.encode('ascii') @@ -99,6 +99,7 @@ def sanitize_subject(subject, encoding='utf-8'): subject = Header(subject, 'utf-8').encode() return subject + def sanitize_address(addr, encoding='utf-8'): if isinstance(addr, string_types): addr = parseaddr(force_text(addr)) @@ -131,6 +132,7 @@ def _has_newline(line): return True return False + class Connection(object): """Handles connection to host.""" @@ -175,8 +177,9 @@ class Connection(object): assert message.send_to, "No recipients have been added" assert message.sender, ( - "The message does not specify a sender and a default sender " - "has not been configured") + "The message does not specify a sender and a default sender " + "has not been configured" + ) if message.has_bad_headers(): raise BadHeaderError @@ -185,11 +188,13 @@ class Connection(object): message.date = time.time() if self.host: - self.host.sendmail(sanitize_address(envelope_from or message.sender), - list(sanitize_addresses(message.send_to)), - message.as_bytes() if PY3 else message.as_string(), - message.mail_options, - message.rcpt_options) + self.host.sendmail( + sanitize_address(envelope_from or message.sender), + list(sanitize_addresses(message.send_to)), + message.as_bytes() if PY3 else message.as_string(), + message.mail_options, + message.rcpt_options, + ) email_dispatched.send(message, app=current_app._get_current_object()) @@ -227,8 +232,14 @@ class Attachment(object): :param disposition: content-disposition (if any) """ - def __init__(self, filename=None, content_type=None, data=None, - disposition=None, headers=None): + def __init__( + self, + filename=None, + content_type=None, + data=None, + disposition=None, + headers=None, + ): self.filename = filename self.content_type = content_type self.data = data @@ -255,20 +266,23 @@ class Message(object): :param rcpt_options: A list of ESMTP options to be used in RCPT commands """ - def __init__(self, subject='', - recipients=None, - body=None, - html=None, - sender=None, - cc=None, - bcc=None, - attachments=None, - reply_to=None, - date=None, - charset=None, - extra_headers=None, - mail_options=None, - rcpt_options=None): + def __init__( + self, + subject='', + recipients=None, + body=None, + html=None, + sender=None, + cc=None, + bcc=None, + attachments=None, + reply_to=None, + date=None, + charset=None, + extra_headers=None, + mail_options=None, + rcpt_options=None, + ): sender = sender or current_app.extensions['mail'].default_sender @@ -364,9 +378,9 @@ class Message(object): filename = filename.encode('utf8') filename = ('UTF8', '', filename) - f.add_header('Content-Disposition', - attachment.disposition, - filename=filename) + f.add_header( + 'Content-Disposition', attachment.disposition, filename=filename + ) for key, value in attachment.headers: f.add_header(key, value) @@ -418,7 +432,10 @@ class Message(object): def is_bad_headers(self): from warnings import warn - msg = 'is_bad_headers is deprecated, use the new has_bad_headers method instead.' + + msg = ( + 'is_bad_headers is deprecated, use the new has_bad_headers method instead.' + ) warn(DeprecationWarning(msg), stacklevel=1) return self.has_bad_headers() @@ -435,12 +452,14 @@ class Message(object): self.recipients.append(recipient) - def attach(self, - filename=None, - content_type=None, - data=None, - disposition=None, - headers=None): + def attach( + self, + filename=None, + content_type=None, + data=None, + disposition=None, + headers=None, + ): """Adds an attachment to the message. :param filename: filename of attachment @@ -449,11 +468,11 @@ class Message(object): :param disposition: content-disposition (if any) """ self.attachments.append( - Attachment(filename, content_type, data, disposition, headers)) + Attachment(filename, content_type, data, disposition, headers) + ) class _MailMixin(object): - @contextmanager def record_messages(self): """Records all messages. Use in unit tests for example:: @@ -508,13 +527,26 @@ class _MailMixin(object): try: return Connection(app.extensions['mail']) except KeyError: - raise RuntimeError("The curent application was not configured with Flask-Mail") + raise RuntimeError( + "The curent application was not configured with Flask-Mail" + ) class _Mail(_MailMixin): - def __init__(self, server, username, password, port, use_tls, use_ssl, - default_sender, debug, max_emails, suppress, - ascii_attachments=False): + def __init__( + self, + server, + username, + password, + port, + use_tls, + use_ssl, + default_sender, + debug, + max_emails, + suppress, + ascii_attachments=False, + ): self.server = server self.username = username self.password = password @@ -553,7 +585,7 @@ class Mail(_MailMixin): int(config.get('MAIL_DEBUG', debug)), config.get('MAIL_MAX_EMAILS'), config.get('MAIL_SUPPRESS_SEND', testing), - config.get('MAIL_ASCII_ATTACHMENTS', False) + config.get('MAIL_ASCII_ATTACHMENTS', False), ) def init_app(self, app): @@ -577,7 +609,10 @@ class Mail(_MailMixin): signals = blinker.Namespace() -email_dispatched = signals.signal("email-dispatched", doc=""" +email_dispatched = signals.signal( + "email-dispatched", + doc=""" Signal sent when an email is dispatched. This signal will also be sent in testing mode, even though the email will not actually be sent. -""") +""", +) diff --git a/ereuse_devicehub/templates/ereuse_devicehub/user_registration.html b/ereuse_devicehub/templates/ereuse_devicehub/user_registration.html index 5a1babcc..a2dfafbf 100644 --- a/ereuse_devicehub/templates/ereuse_devicehub/user_registration.html +++ b/ereuse_devicehub/templates/ereuse_devicehub/user_registration.html @@ -41,6 +41,13 @@
Please enter your email.
+ {% if form.email.errors %} +

+ {% for error in form.email.errors %} + {{ error }}
+ {% endfor %} +

+ {% endif %}
@@ -58,27 +65,42 @@
+ {% if form.name.errors %} +

+ {% for error in form.name.errors %} + {{ error }}
+ {% endfor %} +

+ {% endif %}
+ {% if form.telephone.errors %} +

+ {% for error in form.telephone.errors %} + {{ error }}
+ {% endfor %} +

+ {% endif %}
-

Don't have account? Create an account

+

+ You have account? Create an account +

{% else %} -
- We have sent you a validation email. Please check your email. -
-
- {{ form._token }} -
+
+
+ We have sent you a validation email.
+ Please check your email. +
{% endif %} @@ -96,18 +118,4 @@ - - - {% endblock body %} diff --git a/ereuse_devicehub/templates/ereuse_devicehub/user_validation.html b/ereuse_devicehub/templates/ereuse_devicehub/user_validation.html new file mode 100644 index 00000000..dde06b84 --- /dev/null +++ b/ereuse_devicehub/templates/ereuse_devicehub/user_validation.html @@ -0,0 +1,66 @@ +{% extends "ereuse_devicehub/base.html" %} + +{% block page_title %}Login{% endblock %} + +{% block body %} +
+
+ +
+
+
+
+ +
+ +
+ +
+ +
+ + {% if is_valid %} +
+
User is valid
+ +
+ Your new user is activate.
+ Now you can do Login and entry. +
+
+ {% else %} +
+
User is Invalid
+
+
+ Invalid +
+
+ +
+
+ Sorry, your token not exist or is expired. +
+
+
+ {% endif %} + +
+
+ +
+ Designed by BootstrapMade +
+ +
+
+
+ +
+ +
+
+ +{% endblock body %} diff --git a/ereuse_devicehub/views.py b/ereuse_devicehub/views.py index cfd449af..4c80b270 100644 --- a/ereuse_devicehub/views.py +++ b/ereuse_devicehub/views.py @@ -9,7 +9,7 @@ from ereuse_devicehub.db import db from ereuse_devicehub.forms import LoginForm, PasswordForm, UserNewRegisterForm from ereuse_devicehub.resources.action.models import Trade from ereuse_devicehub.resources.lot.models import Lot -from ereuse_devicehub.resources.user.models import User +from ereuse_devicehub.resources.user.models import User, UserValidation from ereuse_devicehub.utils import is_safe_url core = Blueprint('core', __name__) @@ -120,10 +120,32 @@ class UserRegistrationView(View): return flask.render_template(self.template_name, **context) +class UserValidationView(View): + methods = ['GET'] + template_name = 'ereuse_devicehub/user_validation.html' + + def dispatch_request(self, token): + context = {'is_valid': self.is_valid(token), 'version': __version__} + return flask.render_template(self.template_name, **context) + + def is_valid(self, token): + user_valid = UserValidation.query.filter_by(token=token).first() + if not user_valid: + return False + user = user_valid.user + user.active = True + db.session.commit() + return True + + core.add_url_rule('/login/', view_func=LoginView.as_view('login')) core.add_url_rule('/logout/', view_func=LogoutView.as_view('logout')) core.add_url_rule('/profile/', view_func=UserProfileView.as_view('user-profile')) core.add_url_rule( '/new_register/', view_func=UserRegistrationView.as_view('user-registration') ) +core.add_url_rule( + '/validate_user/', + view_func=UserValidationView.as_view('user-validation'), +) core.add_url_rule('/set_password/', view_func=UserPasswordView.as_view('set-password')) diff --git a/examples/app.py b/examples/app.py index 7dcd0792..5cb282c8 100644 --- a/examples/app.py +++ b/examples/app.py @@ -6,12 +6,12 @@ Use this as a starting point. from decouple import config -from ereuse_devicehub.mail.flask_mail import Mail from ereuse_devicehub.api.views import api from ereuse_devicehub.config import DevicehubConfig from ereuse_devicehub.devicehub import Devicehub from ereuse_devicehub.inventory.views import devices from ereuse_devicehub.labels.views import labels +from ereuse_devicehub.mail.flask_mail import Mail from ereuse_devicehub.views import core from ereuse_devicehub.workbench.views import workbench