3
\$\begingroup\$

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!

\$\endgroup\$
  • 1
    \$\begingroup\$ 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.) \$\endgroup\$ – Gloweye Oct 28 '19 at 15:59
  • \$\begingroup\$ @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! \$\endgroup\$ – Daniel A. Oct 29 '19 at 16:01
1
\$\begingroup\$

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
user_pwd = 'password' # 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:
        q1 = input('Username:> ')
        if q1 == user_db: # if q1 in user_db
          q2 = input('Password:> ')
          # 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:
            print('Incorrect password')
        else:
            print('Username invalid')
            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']
Username:> test
Password:> password
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
Username invalid
Username:> f
Username invalid
Username:> test
Password:> s
Incorrect password
Username:> j
Username invalid
Username:> wksd
Username invalid
Too many failed attempts
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Awesome, thank you so much! Also... I edited the question... I meant saving each user object. That question is, essentially, if I should insert only information like username and password, or if I should store things like the attempt count. Also, same question, I should store the hashed password in the database, correct? One other question, regarding the encryption... just so this does not get super long, do you have any recommendations on links to understand that type of data encryption (and if it’s customizable - depends on environment, etc). Thank you again! \$\endgroup\$ – Daniel A. Oct 27 '19 at 1:40
  • \$\begingroup\$ en.wikipedia.org/wiki/HMAC for the encryption. You want to read up on SQL lite for database's, they are really helpful. You can use your db to store anything you want. \$\endgroup\$ – Barb Oct 27 '19 at 2:02
  • \$\begingroup\$ Thank you so much! \$\endgroup\$ – Daniel A. Oct 27 '19 at 2:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.