Dziugas IoT
Dziugas IoT

Reputation: 61

Django: Authentication system errors and bugs. (value too long for type character varying(80))

I'm trying to code the login/register system on Django, but I'm getting errors. So my main task is to make a custom auth system that the user can easily login/logout/register. I don't want to use the in-built User model, I want to use my main from flask. So the thing is that I'm trying to move from flask to Django and set-up the models and stuff. So I have this models.py file which concludes:

import datetime
import uuid

from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.db import models
from werkzeug.security import generate_password_hash, check_password_hash


class UserManager(BaseUserManager):
    def create_user(self, username, password, primary_telephone_number, secondary_telephone_number, primary_email_address, first_name, last_name, registration_ip):
        if not username or not password or not primary_telephone_number or not secondary_telephone_number or not primary_email_address or not first_name or not last_name or not registration_ip:
            raise ValueError('Not enough values')

        user_obj = self.model(username=username,
                              password=generate_password_hash(password),
                              primary_telephone_number=primary_telephone_number,
                              secondary_telephone_number=secondary_telephone_number,
                              primary_email_address=primary_email_address,
                              first_name=first_name,
                              last_name=last_name,
                              registration_ip=last_name)
        user_obj.save(using=self._db)

        return user_obj

class Users(AbstractBaseUser):
    """Model for Winteka.IOT Users."""

    public_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, blank=False, null=False, max_length=36)
    username = models.CharField(max_length=50, unique=True, blank=False, null=False)
    password = models.CharField(max_length=80, blank=False, null=False)

    # Third-party social (Emails, Phone numbers, Verifications)
    primary_telephone_number = models.CharField(max_length=15, unique=False, null=True, blank=False)
    secondary_telephone_number = models.CharField(max_length=15, unique=False, null=True, blank=True)

    primary_telephone_number_verified = models.BooleanField(default=False, null=False, blank=False) #Default
    secondary_telephone_number_verified = models.BooleanField(default=False, null=False, blank=False) #Default

    primary_email_address = models.EmailField(max_length=320, unique=True, blank=False, null=True)
    recovery_email_address = models.EmailField(max_length=320, null=True, blank=True, unique=True, default=None) #Default

    primary_email_address_verified = models.BooleanField(null=False, blank=False, default=False) #Default
    recovery_email_address_verified = models.BooleanField(null=False, blank=False, default=False) #Default

    # User Profile Stuff...
    first_name = models.CharField(max_length=26, null=True, blank=True)
    last_name = models.CharField(max_length=35, null=True, blank=True)

    profile_bio = models.CharField(max_length=200, default="This user wants to keep a secret about himself.",
                                   null=False, blank=False) #Default
    profile_picture = models.CharField(max_length=20, default="profile_picture", null=False, blank=False) #Default
    profile_cover_picture = models.CharField(max_length=20, default="profile_cover_picture", null=True, blank=False) #Default
    profile_qr_code = models.CharField(max_length=20, default="qr_code", null=False, blank=False) #Default

    # Account administration
    role = models.CharField(max_length=20, blank=False, null=False, default='user') #Default

    reports = models.IntegerField(blank=False, null=False, default=0) #Default
    warnings = models.IntegerField(blank=False, null=False, default=0) #Default

    frozen = models.BooleanField(blank=False, null=False, default=False) #Default
    suspended = models.BooleanField(blank=False, null=False, default=False) #Default

    # Purchases * Balance Tracking
    balance = models.DecimalField(max_digits=11, decimal_places=2, blank=False, null=False, default=0.00) #Default

    subscriptions_bought = models.IntegerField(null=False, blank=False, default=0) #Default
    total_purchases = models.IntegerField(blank=False, null=False, default=0) #Default

    total_deposit = models.DecimalField(max_digits=11, decimal_places=2, blank=False, null=False, default=0.00) #Default
    total_withdraw = models.DecimalField(max_digits=11, decimal_places=2, blank=False, null=False, default=0.00) #Default

    lastAccessedTime = models.CharField(max_length=36, blank=False, null=False,
                                        default=datetime.datetime.now().strftime('%Y-%m-%d %H:%M.%S')) #Default

    # First Registration.
    first_login = models.CharField(max_length=36, null=False, blank=False,
                                   default=datetime.datetime.now().strftime('%Y-%m-%d %H:%M.%S')) #Default
    default_language = models.CharField(max_length=3, null=False, blank=False, default="EN") #Default
    registration_ip = models.CharField(max_length=32, blank=False, null=False)

    # Subscriptions & Purchases
    devices = models.IntegerField(blank=False, null=False, default=0) #Default

    # Third-Party Login System (Unfinished)
    google_authorized = models.BooleanField(blank=False, null=False, default=False) #Default
    google_client_id = models.CharField(max_length=36, blank=True, null=True, default=None) #Default

    facebook_authorized = models.BooleanField(blank=False, null=False, default=False) #Default
    facebook_client_id = models.CharField(max_length=36, blank=True, null=True, default=None) #Default

    # Location services. Street address & more.
    primary_street_address = models.CharField(max_length=95, blank=True, null=True, default=None) #Default
    secondary_street_address = models.CharField(max_length=95, blank=True, null=True, default=None) #Default
    zip_code = models.CharField(max_length=6, blank=True, null=True, default=None) #Default

    region = models.CharField(max_length=6, blank=True, null=True, default=None) #Default
    country = models.CharField(max_length=63, blank=True, null=True, default=None) #Default
    city = models.CharField(max_length=28, blank=True, null=True, default=None) #Default

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['password', 'primary_telephone_number', 'secondary_telephone_number', 'primary_email_address', 'first_name', 'last_name', 'registration_ip']

    objects = UserManager()

    def create_password(self, password):
        """Create hashed password."""
        return generate_password_hash(self.password, method="sha256")

    def check_password(self, password):
        """Check hashed password."""
        return check_password_hash(self.password, password)

    def __str__(self):
        return "User {}".format(self.id)

    class Meta:
        verbose_name_plural = "Users"

My register logic is this:

def register(request):
    if request.user.is_authenticated:
        return HttpResponseRedirect(settings.MAIN_REDIRECT_URL)

    if request.method == 'GET':
        if request.user.is_authenticated:
            return HttpResponseRedirect(settings.MAIN_REDIRECT_URL)

        return render(request, 'authn/register.html')

    elif request.method == 'POST':
        if request.user.is_authenticated:
            return HttpResponseRedirect(settings.MAIN_REDIRECT_URL)

        email_address = request.POST.get('acc_primary_email_address', None)
        password = request.POST.get('acc_password', None)
        username = request.POST.get('acc_username', None)
        primary_phone_number = request.POST.get('acc_primary_phone_number', None)

        print(email_address, password, username, primary_phone_number)

        _ip_address_ = get_client_ip(request)

        if _ip_address_ in settings.ADMINISTRATOR_IPS:
            # TODO Create log system and log when administrator or mod appears online.
            pass

        if _ip_address_ is None:
            messages.success(request, f'Our systems have temporarily blocked traffic from this IP Address.')
            return render(request, 'authn/register.html')

        # Check for user...
        existing_user = Users.objects.filter(primary_email_address=email_address).first()

        if existing_user:
            messages.error(request, 'User with this email address exists!')
            return render(request, 'authn/login.html')

        user = Users.objects.create_user(
            username=username,
            password=password,
            primary_telephone_number='4124124151255',
            secondary_telephone_number='41244124',
            primary_email_address=email_address,
            first_name='fname',
            last_name='lname',
            registration_ip='5.20.236.256'
        )
        user.save()
        messages.success(request, 'new_acc test')
        return render(request, 'authn/login.html')

And I would like to ask is this is a good structure of everything? Because as I saw some tutorials that people are using some in-built functions as user.set_password(), do I need this? I have my own sha256 hash, but I still don't understand the functioning principle of Django auth. So as I made this module and tried to launch it, I had many errors, but now I'm stuck on this one. I have my registration_ip max char length set to 32, but the error says that It's set to 80. So the main question is if I'm doing everything as I should?

Also, I have my templates and static files, so I just use render function to return a template, so I don't need to use the views or as_view functions from Django, or Should I?

Upvotes: 1

Views: 201

Answers (2)

user1600649
user1600649

Reputation:

The built-in things in Django, especially authentication are tried and tested and follow industry standards (for example, Django's default hasher changes its number of iterations every few releases). The difficulty is knowing when to deviate and to choose the right path to do so.

I think you're on the wrong path here and the Django documentation on authentication and custom users is actually an excellent resource on how to customize it. I would highly suggest to take small detour, create a test project and follow the guide. Pay attention to:

  • Keeping a lean user model that should only deal with what is required for authentication and the minimal requirements for sign up.
  • Add extra personal details to a profile model
  • Try to use the built-in password hashers which consist of 3 moving parts:
    • Hashing algorithms, each with different CPU, memory and storage requirements resulting in different security grades. Change in settings.
    • A password policy framework that can put restrictions on the passwords that people use, such as length, dictionary words and character variation. Change in settings.
    • Managing the password during sign-up and authentication. This is the part you want to touch the least: Even if I have very different requirements for the main user, I always delegate to UserManager._create_user() in the final step or I use a long living active library like Django allauth, so I can benefit from any fixes or improvements that Django puts in.

To stress the last point: millions of users are using the Django authentication (without knowing it) and while authentication is not hard to do, it also not hard do it wrong and it's better to trust the protocol that many others in the industry keep a close eye on.

Upvotes: 1

Dziugas IoT
Dziugas IoT

Reputation: 61

The main reason was that I was basically using the wrong max_length value, I didn't saw that I have some fields that were had max_length=20, but they contain more than 20 char.

This value had wrong settings.

profile_cover_picture = models.CharField(max_length=20, default="profile_cover_picture", null=True, blank=False) #Default

Change to this:

profile_cover_picture = models.CharField(max_length=30, default="profile_cover_picture", null=True, blank=False) #Default

Upvotes: 0

Related Questions