# Simple User and Database Relationship

Hello and thanks for clicking!

I'm currently studying the relationship between a database and user objects. This is a very simple program and the "database" itself is a dictionary. This will be used in a website (after studying a lot, of course, as I am fairly new to the "programming world", if you will), but I've abstracted a lot of details (e.g. adding the inputs directly into Python). The code became a bit... unnecessary at times, because of that - e.g. constantly doing checks to see if the inputs are correctly formatted.

It also has a password generator... probably not the best, but works for me right now.

Here is the code:

from random import randint

lower_char = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
upper_char = [let.upper() for let in lower_char]
special_char = ['!', '@', '#', '$', "%", "&", "*", "(", ")", "-"] number_char = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'] all_char = [lower_char, upper_char, special_char, number_char] user_db = {} class User(): def __init__(self, username, password, random): # Check if user already exists - self.username becomes None if so self.username = username if username not in user_db else print("Username already exists!") # Case random = true if random and not password and self.username != None: while True: try: num_of_let = int(input("Please type the number of characters in your password: [has to be between 8 and 40 characters]\n")) if num_of_let >= 8 and num_of_let <= 40: break else: print("Password has to be between 8 and 40 characters!") except ValueError: print("Integers only!") self.password = self.createRPassword(num_of_let) # Case random = false else: self.password = password if self.username != None: user_db.update({self.username.lower(): self.password}) def createRPassword(self, num_of_let): """Creates (pseudo) random password with given number of letters""" pw = [] for _ in range(num_of_let): current_list = all_char[randint(0, 3)] pw.append(current_list[randint(0, len(current_list)-1)]) pw = "".join(str(let) for let in pw) print(f"Okay! Your new password is\n{pw}") return hash(pw) def validateCredentials(self, username, password): if username in user_db: if password == user_db[username]: print("Success!") return ("Success!") else: print("Wrong Password!") return ("Wrong Password!") else: print("Username does not exist!") return ("Username does not exist!") if __name__ == "__main__": while True: create_or_login = input("[Create] a new user or [login]?\n").lower() if create_or_login == "create": new_username = input("Please type a username to create it: [has to be between 3 and 12 characters]\n") is_random = False if input("Random password? [y] [n]\n") == 'n' else True new_password = hash(input("Please type a new password: [has to be between 8 and 40 characters]\n")) if not is_random else False user = User(new_username, new_password, is_random) if len(new_username) >= 3 and len(new_username) <= 12 else print("Username is too short") elif create_or_login == "login": username = input("Username:\n").lower() password = hash(input("Password:\n")) user.validateCredentials(username, password) wanna = input("Wanna stop? [y] [n]\n").lower() if wanna == 'y': break elif wanna == 'n': print(f"Current db: {user_db}") pass else: print("Really?")  I have a few questions, though: 1. How to securely handle user information? For the security, I've read about 'salting' the hash and, of course, using different hashing algorithms, but I'll use the pre-built one from Python, for testing purposes. I can imagine that securing the username is not AS important as the password... but how do I store these credentials in the system at all? Should it be stored as it currently is - user and hashed password? Also, do I hash the password BEFORE sending it to the script that will handle it (in this case, Python) and, then, send it to the database? Or does the password go directly to the script and then gets hashed? (Maybe we can double hash it :) ) [As an example for the above, let's say there is a login website. Should the password be hashed before the POST request or after?] 1. How should the User object be saved in the database? Should it have things like blocked status that gets activated if user tried to login many times with the wrong password? Other suggestions are welcomed, but I guess it depends on the usage of the user 'object'. A simple implementation would be, in the case of this example, to have something like this: user_db = {example_user: {password: hashedpw, blocked: False, tried: 0}}  1. Should I be doing all this logic inside __init__? Pretty straight-forward question... should I only initialize the variables inside init and do the rest of the logic elsewhere? It does work as it is, but not sure if it is the best way to do it. 1. Should I use more functions for better readability? An example of this would be saving the user object in the database... should that be a function? I do know there are, most likely, a lot of code improvements to be made. I came back very recently to Python and do not remember some of the best practices (and I was never a professional as well, as you probably can see). I will happily accept any code improvements, but please focus on the main logic, instead of the small little improvements. I've seen that Python 3.8 gave us the awesome print(f'{var=}') functionality, which technically could be used in my program, but not that important ;) Also, sorry if this is not the right place to ask this question or if there are any issues with the question at all. I will modify it as needed :) Thank you for your attention! • How important is security? Because if this is production software to protect consumer data, you don't want to use python's build in hash algorithm, but instead grab a module offering cryptographically secure hash functions. You'll also want to use a salt first. Also, python's random module is NOT random enough for secure applications. Use secrets instead. And take your code to security when you're done here. (They'll probably just tell you to use a reliable library, though.) – Gloweye Oct 28 '19 at 15:59 • @Gloweye In this case, this is a simulation (hence why I'm using the hash algorithm and randint from random module). But security will be really important, once I start to make and deploy my project. I did read about salt, but will have to dig into encryption and hashing, first, then I'll check about salting (as I am unsure on how to properly do it, right now). Thanks for the library recommendation, did not know about it until now :) I'll definitely check and study it! Also... did not know there was a security area in SE... it will be super helpful! Thank you so much for your help! – Daniel A. Oct 29 '19 at 16:01 ## 1 Answer A few notes... • How to securely handle user information? You should hash after you encrypt however you may be better using HMAC which are hash MACS. • How should the User class be saved in the database? You save the variable/user info in the DB not the class itself. • Should I be doing all this logic inside __init__? You can but it may be easier to compare your conditions in a function to see if the condition from a user input. • Should I use more functions for better readability? Don't make your code longer unnecessarily, The code below is an example of a simple version of what your trying to achieve from the main logic aspect. I have put things such as the attempt effort and db search into 1 function. import string lower_char = string.ascii_lowercase upper_char = string.ascii_uppercase special_char = ['!', '@', '#', '$', "%", "&", "*", "(", ")", "-"]
number_char = string.digits
all_char = [lower_char, upper_char, special_char, number_char]
user_db = 'test' # Your DB in here
print(all_char)

#class User():

class User:
def __init__(self, name, pwd):
self.name = name
self.pwd = pwd

user = User

def run():
while True:
attempts = 3
while attempts >= 0:
try:
if q1 == user_db: # if q1 in user_db
# search for value against q1 key in user_db
if q2 == user_pwd: # id q2 in user_db
print('DO SOMETHING ELSE') # do something else. Other function
exit()
else:
else:
attempts -= 1
except ValueError:
pass
else:
print('Too many failed attempts')
break

run()


This is the output. At the top I printed out the string.ascii variables so you don't have to type out each letter and number:

['abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', ['
!', '@', '#', '\$', '%', '&', '*', '(', ')', '-'], '0123456789']
DO SOMETHING ELSE


and when you put in the wrong user or password. You will need to add arguments to check your db keys as names and values assigned to them for the password:

Username:> d