Python Back-end registration validation I'm working on a personal project (Flask CRUD app) and I am currently building the user service. I am trying to use as less as libraries as possible (that's why I do not use WTF-forms for example, just for learning purposes). I am really not sure about a couple of things.
I am aware that form validation should be handled in both front and back-end (or at the very least on the back-end). In order to validate a new user, I am (currently) checking 3 things:
1) email does not already exist on the db (can only be verified on the back-end)
2) password is strong enough (can be done in both front and back end)
3) password and password_confirm matches (can be done in both front and back end)
My goal is to display a small error message on the registration page if either at least one those errors appears. Currently, I am checking option 1, 2 and 3 and the back-end but only return True if the registration is valid otherwise, False.
Since case 1 and 2 can directly be handle also on the front-end, can I display the error message using JavaScript and not from the back-end? I am still confused on what would be the best return for validate_registration (currently a Boolean).
Also, I am not quite sure my code is over-engineered, I made a RegistrationForm class just to validate a registration form. I did that because I am able to pick validations methods from my validators.py which may be used in other Classes as well.
So this is what I did so for for the registration form:
user/blueprints/routes.py
@user.route('/new-user',methods = ['POST'])
def register_user():
    form_email = request.form.get('email')
    form_password = request.form.get('psw')
    form_password_repeat = request.form.get('psw-repeat')
    registration_form = RegistrationForm(form_email, form_password, form_password_repeat).validate_registration()
    
    if registration_form:
        new_user = UserService().register_user(form_email, form_password)
        user_repository = UserRepository(conn, 'users')
        user_repository.add_user(new_user)
        user_repository.save()
        return &quot;ok&quot; #will probably change return statements later on
    return &quot;not ok&quot; #will probably change return statements later on

user/blueprints/forms.py
from prepsmarter.blueprints.user.validators import email_already_in_use, password_matching

class RegistrationForm():
    def __init__(self, email, pwd_1, pwd_2):
        self.email = email
        self.pwd_1 = pwd_1
        self.pwd_2 = pwd_2

    def validate_registration(self):
        is_valid = email_already_in_use(self.email) and is_strong_password(self.pwd_1) and password_matching(self.pwd_1, self.pwd_2)
        return is_valid

class LoginForm():
 # to do

user/blueprints/validators.py
import re 
from email_validator import validate_email, EmailNotValidError
from prepsmarter.extensions import conn

def is_strong_password(password):
    length_error = len(password) &lt; 8
    digit_error = re.search(r&quot;\d&quot;, password) is None
    uppercase_error = re.search(r&quot;[A-Z]&quot;, password) is None
    return length_error and digit_error and uppercase_error
    
def is_email_formated_correctly(email):
    is_correct = True
    try:
        validate_email(email)
    except EmailNotValidError:
        is_correct = False
    return is_correct

def password_matching(pwd_1, pwd_2):
    return pwd_1 == pwd_2

    
def email_already_in_use(email):
    sql = &quot;SELECT CASE WHEN EXISTS ( SELECT * FROM users WHERE email = (%s)) THEN 1 ELSE 0 END;&quot;
    cursor = conn.cursor()
    cursor.execute(sql, (email))
    res = cursor.fetchall()
    return res == 1
I have not thought carefully about all of your code and questions, but here's
one suggested technique for how to handle validation code more simply. In most
contexts, people find it easier to understand affirmative boolean logic rather
than negated logic. My guess is that the bug in is_strong_password() stems
from that phenomenon (the function tells me hi is strong, for example).
You've created 3 variables to be true when something is wrong but then combined
them under the opposite assumption. Here are two alternatives to consider:
# This alternative uses affirmative variables: True means OK. Also notice
# how the variable names are a bit more intuitive because of the
# affirmative framing. When possible, try to name boolean variables to
# convey their nature: is_X, has_X, contains_X, etc. Your current code has
# variables names implying that they each hold an &quot;error&quot; of some kind --
# but they do not. Finally, the usage of bool() here is not strictly
# necessary, but it does convey intent explicitly.

def is_strong_password(password):
    is_long = len(password) &gt;= 8
    has_digit = bool(re.search(r&quot;\d&quot;, password))
    has_upper = bool(re.search(r&quot;[A-Z]&quot;, password))
    return is_long and has_digit and has_upper
    
# This alternative illustrates a handy code-layout technique
# that often works well when doing validation. This version
# is shorter (sometimes nice) but less explicit. In this
# case, I would tend to favor the short version, because the
# checks are so simple. If the complexity were higher, I would
# prefer the approach that names every check to increase clarity

def is_strong_password(password):
    return (
        len(password) &gt;= 8 and
        re.search(r&quot;\d&quot;, password) and  
        re.search(r&quot;[A-Z]&quot;, password)
    )

The is_email_formated_correctly() can be simplified by doing things more
directly. In addition, if validate_email() returns True/False you could
simplify further by just returning its return value.
def is_email_formated_correctly(email):
    try:
        validate_email(email)
        return True
    except EmailNotValidError:
        return False