nrbnlulu / strawberry-django-auth

Authentication system for django using strawberry
https://nrbnlulu.github.io/strawberry-django-auth/
MIT License
68 stars 31 forks source link

Custom UserClass without "username" errors out #463

Closed alexandrubese closed 4 months ago

alexandrubese commented 11 months ago

I have a custom Account in my app, when we started the project we didn't needed a "username", we just wanted email addresses.

#Account Model

class Account(AbstractUser, PermissionsMixin):
    username = None
    first_name = None
    last_name = None
    email = models.EmailField("email", unique=True, blank=False)
    ...

USERNAME_FIELD = "email"
REQUIRED_FIELDS = [
        "name",
        "city",
..
]

So with this setting, when I try to start the project with your plugin integrated I always get an error:

django.core.exceptions.FieldError: Unknown field(s) (username) specified for Account This is because you are checking if there is a "username" that is None.

What is a workaround for this? I cannot remove them because then I'll inherit the old ones which I don't have in the DB.

Adding values to them is also not a good idea, because I don't have anything in the DB related to them, so it crashes my app also.

Edit: Just for the fun of it, I've changed them to Char

class Account(AbstractUser, PermissionsMixin):
    username = models.EmailField("username", blank=True)
    first_name = models.EmailField("first_name", blank=True)
    last_name = models.EmailField("last_name", blank=True)
    email = models.EmailField("email", unique=True, blank=False)
    name = models.CharField("name", max_length=30, blank=False)
    ....

    USERNAME_FIELD = "email"
    EMAIL_FIELD = "email"

I've set both username and email fileds to "email" because that is what I actually have in the DB, no username in the DB.

Just to test out how this works.

But now I receive the error: "message": "'ObtainJSONWebTokenInput' object has no attribute 'email'",

I guess it expects a "username" or something there.

I feel like your plugin is linked very much to this "username" field, which actually many apps don't use, and they have custom user/account classes that ignore that "username".

What solution do you propose to this problem?

shmoon-kr commented 7 months ago

I successfully setup gql_auth with the same requirement. (no username, firstname, lastname and email only) It's a configuration issue.

try to add the following to your settings.py.

from gqlauth.settings_type import GqlAuthSettings, username_field, email_field, password_field

GQL_AUTH = GqlAuthSettings(
    ALLOW_LOGIN_NOT_VERIFIED=False,
    LOGIN_FIELDS={email_field, password_field},
    LOGIN_REQUIRE_CAPTCHA=False,
    REGISTER_MUTATION_FIELDS={email_field},
    REGISTER_REQUIRE_CAPTCHA=False,
    CAPTCHA_EXPIRATION_DELTA=timedelta(seconds=120),
    CAPTCHA_MAX_RETRIES=5,
    # CAPTCHA_TEXT_FACTORY=default_text_factory,
    # CAPTCHA_TEXT_VALIDATOR=default_captcha_text_validator,
    FORCE_SHOW_CAPTCHA=False,
    CAPTCHA_SAVE_IMAGE=False,
    UPDATE_MUTATION_FIELDS={},
)
jbdura commented 4 months ago
from gqlauth.settings_type import GqlAuthSettings, email_field, username_field, password_field

AUTH_USER_MODEL = 'users.CustomUser'

GQL_AUTH = GqlAuthSettings(
    LOGIN_REQUIRE_CAPTCHA=False,
    REGISTER_REQUIRE_CAPTCHA=False,
    JWT_TIME_FORMAT="%Y-%m-%dT%H:%M:%S",  # A valid 'strftime' string for the token payload
    JWT_LONG_RUNNING_REFRESH_TOKEN=True,  # Enable refresh tokens
    JWT_EXPIRATION_DELTA=timedelta(minutes=60),  # Token expiration time
    JWT_REFRESH_EXPIRATION_DELTA=timedelta(days=7),
    EXPIRATION_ACTIVATION_TOKEN=timedelta(minutes=60),
    LOGIN_FIELDS={email_field, password_field},
    # LOGIN_FIELDS = {username_field},

)
# users/models.py

from django.contrib.auth.models import AbstractUser
from django.db import models

from gallery.models import Image

# Create your models here.

class CustomUser(AbstractUser):
    email = models.EmailField(unique=True, blank=False, max_length=254, verbose_name="email address")
    profile_image = models.OneToOneField(Image, on_delete=models.SET_NULL, null=True, blank=True,
                                         related_name='user_profile_image')  # Add this line

    USERNAME_FIELD = "username"  # e.g: "username", "email"
    EMAIL_FIELD = "email"  # e.g: "email", "primary_email"

    def __str__(self):
        return self.email
# users/schema.py
import strawberry
from gqlauth.user.queries import UserQueries
from gqlauth.core.middlewares import JwtSchema
from gqlauth.user import arg_mutations as mutations
from django.contrib.auth import get_user_model
from strawberry.extensions import QueryDepthLimiter, AddValidationRules
from gallery.schema import ImageType
from gallery.models import Image
import typing
from graphql.validation import NoSchemaIntrospectionCustomRule

@strawberry.django.type(model=get_user_model())
class CustomUserType:
    id: strawberry.auto
    username: strawberry.auto
    email: strawberry.auto
    first_name: strawberry.auto
    last_name: strawberry.auto
    profile_image: typing.Optional[ImageType]

@strawberry.type
class Query(UserQueries):
    @strawberry.field
    def whoami(self, info) -> CustomUserType:
        user = info.context.request.user
        if user.is_anonymous:
            raise Exception("Authentication Failure: You must be signed in")
        return user

    @strawberry.field
    def users(self, info) -> list[CustomUserType]:
        user = info.context.request.user
        if not user.is_authenticated:
            raise Exception("Authentication Failure: You must be signed in")
        return get_user_model().objects.all()

@strawberry.type
class Mutation:
    register = mutations.Register.field
    verify_token = mutations.VerifyToken.field
    update_account = mutations.UpdateAccount.field
    archive_account = mutations.ArchiveAccount.field
    delete_account = mutations.DeleteAccount.field
    password_change = mutations.PasswordChange.field
    token_auth = mutations.ObtainJSONWebToken.field
    verify_account = mutations.VerifyAccount.field
    resend_activation_email = mutations.ResendActivationEmail.field
    send_password_reset_email = mutations.SendPasswordResetEmail.field
    password_reset = mutations.PasswordReset.field
    password_set = mutations.PasswordSet.field
    refresh_token = mutations.RefreshToken.field
    revoke_token = mutations.RevokeToken.field

    @strawberry.mutation
    def set_profile_image(self, info, image_id: int) -> CustomUserType:
        user = info.context.request.user
        if user.is_anonymous:
            raise Exception("Authentication Failure: You must be signed in")

        try:
            image = Image.objects.get(id=image_id)
            user.profile_image = image
            user.save()
            return user
        except Image.DoesNotExist:
            raise Exception("Image not found")

schema = JwtSchema(
    query=Query,
    mutation=Mutation,
    extensions=[
        QueryDepthLimiter(max_depth=7),
        # AddValidationRules([NoSchemaIntrospectionCustomRule]),
    ]
)

this is the error I am getting: Screenshot from 2024-07-08 11-43-18

nrbnlulu commented 4 months ago

stale