sei-ec-remote / project-4-issues

Open an issue to receive help on project 4
0 stars 0 forks source link

Trying to convert token to session based auth & add User fields for sign up (DR stack) #210

Closed estebbins closed 1 year ago

estebbins commented 1 year ago

What stack are you using?

(ex: MERN(mongoose + react), DR(django + react), PEN, etc.)

DR

What's the problem you're trying to solve?

Trying to change to session-based authentication & trying to add fields for sign-up

Post any code you think might be relevant (one fenced block per file)

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.conf import settings
from rest_framework.authtoken.models import Token

class UserManager(BaseUserManager):
    """Manager for user profiles"""

    # The create_user method is passed:
    # self:      All methods in Python receive the class as the first argument
    # username:  We want username
    # phone_number: users must have a phone number
    # email:     Because we want to be able to log users in with email
    #            instead of username (Django's default behavior)
    # password:  The password has a default of None for validation purposes.
    #            This ensures the proper error is thrown if a password is
    #            not provided.
    # **extra_fields:  Just in case there are extra arguments passed.
    def create_user(self, username, email, phone_number, password=None, **extra_fields):
        """Create a new user profile"""
        # Add a custom validation error
        if not email:
            raise ValueError('User must have an email address')

        if not phone_number:
            raise ValueError('User must have a phone number')

        if not username:
            raise ValueError('User must have a username')

        # Create a user from the UserModel
        # Use the normalize_email method from the BaseUserManager to
        # normalize the domain of the email
        # We'll also unwind the extra fields.  Remember that two asterisk (**)
        # in Python refers to the extra keyword arguments that are passed into
        # a function (meaning these are key=value pairs).
        user = self.model(email=self.normalize_email(email), phone_number=self.phone_number, username=self.username, **extra_fields)

        # Use the set_password method to hash the password
        user.set_password(password)
        # Call save to save the user to the database
        user.save()

        # Always return the user!
        return user

    def create_superuser(self, username, phone_number, email, password):
        """Create and save a new superuser with given details"""

        # Use the custom create_user method above to create
        # the user.
        user = self.create_user(username, email, phone_number, password)

        # Add the required is_superuser and is_staff properties
        # which must be set to True for superusers
        user.is_superuser = True
        user.is_staff = True
        # Save the user to the database with the new properties
        user.save()

        # Always return the user!
        return user

# Inherit from AbstractBaseUser and PermissionsMixin:
class User(AbstractBaseUser, PermissionsMixin):
    """Database model for users"""
    # As with any Django models, we need to define the fields
    # for the model with the type and options:
    username = models.CharField(max_length=20, unique=True)
    email = models.EmailField(max_length=255, unique=True)
    phone_number = models.TextField(max_length=20, blank=False)
    # name = models.CharField(max_length=255)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    # Any time we call User.objects (such as in objects.all() or objects.filter())
    # make sure to use the custom user manager we created.
    objects = UserManager()

    # Tell Django to use the email field as the unique identifier for the
    # user account instead of its built in behavior of using the username.
    USERNAME_FIELD = 'email'
    # This doesn't mean the field is required (that's defined above in the field options)
    # This refers to the fields that are prompted for when creating a superuser.
    # https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#django.contrib.auth.models.CustomUser.REQUIRED_FIELDS
    REQUIRED_FIELDS = ['username', 'phone_number']

    # Standard Python: We'll create a string representation so when
    # the class is output we'll get something meaningful.
    def __str__(self):
        """Return string representation of the user"""
        return self.email

    def get_auth_token(self):
        Token.objects.filter(user=self).delete()
        token = Token.objects.create(user=self)
        self.token = token.key
        self.save()
        return token.key

    def delete_token(self):
        Token.objects.filter(user=self).delete()
        self.token = None
        self.save()
        return self
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # 'rest_framework.authentication.TokenAuthentication'
        'rest_framework.authentication.SessionAuthentication'
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated'
    ]
}

If you see an error message, post it here. If you don't, what unexpected behavior are you seeing?

No error messages but in admin portal, not being asked for Username & Phone number. Also trying to unpack the boilerplate user customizations that already took place and build around token auth when trying to convert to session based authentication.

What is your best guess as to the source of the problem?

Custom boilerplate set-up

What things have you already tried to solve the problem?

See code above.

Paste a link to your repository here https://github.com/estebbins/FareIsFair-API (view the authversion1 branch)

asands94 commented 1 year ago

Here are the docs about authentication: https://www.django-rest-framework.org/api-guide/authentication/

Take a look at this (it specifically goes over sessions) let us know if you still need help!

estebbins commented 1 year ago

I did review this already and still need help.

timmshinbone commented 1 year ago

See if you can get anything from this medium article, don't follow the code verbatim or anything, but check out their settings etc to see what packages they use. Let me know how it goes

estebbins commented 1 year ago

I think I was able to get it working with using the examples in here. If it works, then it boils down to the changes below for the boilerplates. I am hesitant to say that this issue is fully resolved as I haven't been able to test further with my own models/routes, and there's some customization in the Django API boilerplate related to users & tokens I am not sure if they can be just deleted out yet or not. I will update this issue when I have been able to do more testing with the final resolution.

If you see anything that doesn't look right below, please let me know!

Django API

# settings.py
# Add this above CORS_ORIGIN_WHITELIST (in both if & else)
CORS_ALLOW_CREDENTIALS = True

# Modify REST_FRAMEWORK to the below
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication'
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated'
    ]
}

React Client

// under other imports in Index.js put ->
import axios from 'axios'

axios.defaults.withCredentials = true; 
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'x-csrftoken'
timmshinbone commented 1 year ago

Looks good to me, test it out and let me know how it goes. It's possible you might have to use the token for some functionality and the session for others, but I'm not sure as I haven't tried it myself. Keep me posted.

estebbins commented 1 year ago

Auth is working, but when I refresh, it still logs me out. I'm not sure how to access the session information as our prior example was with express in the single resource app. If we could take a look together, that would be great!

timmshinbone commented 1 year ago

Gotcha, we'll take a look after django deployment today.

estebbins commented 1 year ago

Added the below to Home.js in react in addition to the above in order to get this working!

    const clearUser = () => {
        console.log('clear user ran')
        setUser(null)
        localStorage.clear()
    }

    useEffect(() => {
        const loggedInUser = localStorage.getItem("user")
        console.log('ul', user, loggedInUser)
        if (loggedInUser) {
            const foundUser = JSON.parse(loggedInUser)
            setUser(foundUser.user)
            setUpdated(prev => !prev)
        }
    }, [])