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:
- 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?]
- 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}}
- 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.
- 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!
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.) \$\endgroup\$