safer check for existing user in tutorial

Co-authored-by: David Lord <davidism@gmail.com>
This commit is contained in:
Angeline 2021-06-07 22:09:58 +08:00 committed by David Lord
parent 50b7dcbab3
commit 5119657547
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
2 changed files with 46 additions and 42 deletions

View File

@ -91,18 +91,18 @@ write templates to generate the HTML form.
error = 'Username is required.' error = 'Username is required.'
elif not password: elif not password:
error = 'Password is required.' error = 'Password is required.'
elif db.execute(
'SELECT id FROM user WHERE username = ?', (username,)
).fetchone() is not None:
error = f"User {username} is already registered."
if error is None: if error is None:
db.execute( try:
'INSERT INTO user (username, password) VALUES (?, ?)', db.execute(
(username, generate_password_hash(password)) "INSERT INTO user (username, password) VALUES (?, ?)",
) (username, generate_password_hash(password)),
db.commit() )
return redirect(url_for('auth.login')) db.commit()
except db.IntegrityError:
error = f"User {username} is already registered."
else:
return redirect(url_for("auth.login"))
flash(error) flash(error)
@ -125,26 +125,25 @@ Here's what the ``register`` view function is doing:
#. Validate that ``username`` and ``password`` are not empty. #. Validate that ``username`` and ``password`` are not empty.
#. Validate that ``username`` is not already registered by querying the
database and checking if a result is returned.
:meth:`db.execute <sqlite3.Connection.execute>` takes a SQL query
with ``?`` placeholders for any user input, and a tuple of values
to replace the placeholders with. The database library will take
care of escaping the values so you are not vulnerable to a
*SQL injection attack*.
:meth:`~sqlite3.Cursor.fetchone` returns one row from the query.
If the query returned no results, it returns ``None``. Later,
:meth:`~sqlite3.Cursor.fetchall` is used, which returns a list of
all results.
#. If validation succeeds, insert the new user data into the database. #. If validation succeeds, insert the new user data into the database.
For security, passwords should never be stored in the database
directly. Instead, - :meth:`db.execute <sqlite3.Connection.execute>` takes a SQL
:func:`~werkzeug.security.generate_password_hash` is used to query with ``?`` placeholders for any user input, and a tuple of
securely hash the password, and that hash is stored. Since this values to replace the placeholders with. The database library
query modifies data, :meth:`db.commit() <sqlite3.Connection.commit>` will take care of escaping the values so you are not vulnerable
needs to be called afterwards to save the changes. to a *SQL injection attack*.
- For security, passwords should never be stored in the database
directly. Instead,
:func:`~werkzeug.security.generate_password_hash` is used to
securely hash the password, and that hash is stored. Since this
query modifies data,
:meth:`db.commit() <sqlite3.Connection.commit>` needs to be
called afterwards to save the changes.
- An :exc:`sqlite3.IntegrityError` will occur if the username
already exists, which should be shown to the user as another
validation error.
#. After storing the user, they are redirected to the login page. #. After storing the user, they are redirected to the login page.
:func:`url_for` generates the URL for the login view based on its :func:`url_for` generates the URL for the login view based on its
@ -200,6 +199,11 @@ There are a few differences from the ``register`` view:
#. The user is queried first and stored in a variable for later use. #. The user is queried first and stored in a variable for later use.
:meth:`~sqlite3.Cursor.fetchone` returns one row from the query.
If the query returned no results, it returns ``None``. Later,
:meth:`~sqlite3.Cursor.fetchall` will be used, which returns a list
of all results.
#. :func:`~werkzeug.security.check_password_hash` hashes the submitted #. :func:`~werkzeug.security.check_password_hash` hashes the submitted
password in the same way as the stored hash and securely compares password in the same way as the stored hash and securely compares
them. If they match, the password is valid. them. If they match, the password is valid.

View File

@ -60,21 +60,21 @@ def register():
error = "Username is required." error = "Username is required."
elif not password: elif not password:
error = "Password is required." error = "Password is required."
elif (
db.execute("SELECT id FROM user WHERE username = ?", (username,)).fetchone()
is not None
):
error = f"User {username} is already registered."
if error is None: if error is None:
# the name is available, store it in the database and go to try:
# the login page db.execute(
db.execute( "INSERT INTO user (username, password) VALUES (?, ?)",
"INSERT INTO user (username, password) VALUES (?, ?)", (username, generate_password_hash(password)),
(username, generate_password_hash(password)), )
) db.commit()
db.commit() except db.IntegrityError:
return redirect(url_for("auth.login")) # The username was already taken, which caused the
# commit to fail. Show a validation error.
error = f"User {username} is already registered."
else:
# Success, go to the login page.
return redirect(url_for("auth.login"))
flash(error) flash(error)