diff --git a/graphql_auth/mixins.py b/graphql_auth/mixins.py index ba2d184..56a885e 100644 --- a/graphql_auth/mixins.py +++ b/graphql_auth/mixins.py @@ -41,7 +41,13 @@ else: async_email_func = None +def add_dynamic_fields(cls): + setattr(cls, "token", graphene.Field(graphene.String)) + if using_refresh_tokens(): + setattr(cls, "refresh_token", graphene.Field(graphene.String)) + return cls +@add_dynamic_fields class RegisterMixin(Output): """ Register user with fields defined in the settings. @@ -70,14 +76,6 @@ class RegisterMixin(Output): else RegisterForm ) - @classmethod - def Field(cls, *args, **kwargs): - if app_settings.ALLOW_LOGIN_NOT_VERIFIED: - if using_refresh_tokens(): - cls._meta.fields["refresh_token"] = graphene.Field(graphene.String) - cls._meta.fields["token"] = graphene.Field(graphene.String) - return super().Field(*args, **kwargs) - @classmethod @token_auth def login_on_register(cls, root, info, **kwargs): @@ -477,7 +475,7 @@ def resolve_action(cls, user, *args, **kwargs): user.save(update_fields=["is_active"]) revoke_user_refresh_token(user=user) - +@add_dynamic_fields class PasswordChangeMixin(Output): """ Change account password when user knows the old password. @@ -487,13 +485,6 @@ class PasswordChangeMixin(Output): form = PasswordChangeForm - @classmethod - def Field(cls, *args, **kwargs): - if using_refresh_tokens(): - cls._meta.fields["refresh_token"] = graphene.Field(graphene.String) - cls._meta.fields["token"] = graphene.Field(graphene.String) - return super().Field(*args, **kwargs) - @classmethod @token_auth def login_on_password_change(cls, root, info, **kwargs): diff --git a/graphql_auth/mutations.py b/graphql_auth/mutations.py index 71ac4b3..780a8df 100644 --- a/graphql_auth/mutations.py +++ b/graphql_auth/mutations.py @@ -1,5 +1,10 @@ import graphene import graphql_jwt +from django.contrib.auth import get_user_model +from graphql_jwt.decorators import token_auth +from graphene.types.generic import GenericScalar +from graphql_jwt.settings import jwt_settings +from graphql_jwt.mixins import RefreshTokenMixin, KeepAliveRefreshMixin from .bases import MutationMixin, DynamicArgsMixin from .mixins import ( @@ -24,6 +29,70 @@ from .settings import graphql_auth_settings as app_settings from .schema import UserNode +class JSONWebTokenMixin: + payload = GenericScalar() + refresh_expires_in = graphene.Int() + + @classmethod + def Field(cls, *args, **kwargs): + if not jwt_settings.JWT_HIDE_TOKEN_FIELDS: + cls._meta.fields["token"] = graphene.Field(graphene.String) + + if jwt_settings.JWT_LONG_RUNNING_REFRESH_TOKEN: + cls._meta.fields["refresh_token"] = graphene.Field( + graphene.String, + ) + + return super().Field(*args, **kwargs) + +class CustomObtainJSONWebTokenMixin(JSONWebTokenMixin): + @classmethod + def __init_subclass_with_meta__(cls, name=None, **options): + assert getattr(cls, "resolve", None), ( + f"{name or cls.__name__}.resolve " + "method is required in a JSONWebTokenMutation." + ) + + super().__init_subclass_with_meta__(name=name, **options) + +class JSONWebTokenMutation(CustomObtainJSONWebTokenMixin, graphene.Mutation): + class Meta: + abstract = True + + @classmethod + def Field(cls, *args, **kwargs): + cls._meta.arguments.update( + { + get_user_model().USERNAME_FIELD: graphene.String(required=True), + "password": graphene.String(required=True), + }, + ) + return super().Field(*args, **kwargs) + + @classmethod + @token_auth + def mutate(cls, root, info, **kwargs): + return cls.resolve(root, info, **kwargs) + +class RefreshMixin( + ( + RefreshTokenMixin + if jwt_settings.JWT_LONG_RUNNING_REFRESH_TOKEN + else KeepAliveRefreshMixin + ), + JSONWebTokenMixin, +): + """RefreshMixin""" + + +class CustomRefresh(RefreshMixin, graphene.Mutation): + class Arguments(RefreshMixin.Fields): + """Refresh Arguments""" + + @classmethod + def mutate(cls, *arg, **kwargs): + return cls.refresh(*arg, **kwargs) + class Register(MutationMixin, DynamicArgsMixin, RegisterMixin, graphene.Mutation): @@ -103,19 +172,12 @@ class PasswordReset( class ObtainJSONWebToken( - MutationMixin, ObtainJSONWebTokenMixin, graphql_jwt.JSONWebTokenMutation + MutationMixin, ObtainJSONWebTokenMixin, JSONWebTokenMutation ): __doc__ = ObtainJSONWebTokenMixin.__doc__ user = graphene.Field(UserNode) unarchiving = graphene.Boolean(default_value=False) - @classmethod - def Field(cls, *args, **kwargs): - cls._meta.arguments.update({"password": graphene.String(required=True)}) - for field in app_settings.LOGIN_ALLOWED_FIELDS: - cls._meta.arguments.update({field: graphene.String()}) - return super(graphql_jwt.JSONWebTokenMutation, cls).Field(*args, **kwargs) - class ArchiveAccount( MutationMixin, ArchiveAccountMixin, DynamicArgsMixin, graphene.Mutation @@ -150,7 +212,7 @@ class VerifyToken(MutationMixin, VerifyOrRefreshOrRevokeTokenMixin, graphql_jwt. class RefreshToken( - MutationMixin, VerifyOrRefreshOrRevokeTokenMixin, graphql_jwt.Refresh + MutationMixin, VerifyOrRefreshOrRevokeTokenMixin, CustomRefresh ): __doc__ = VerifyOrRefreshOrRevokeTokenMixin.__doc__ diff --git a/setup.py b/setup.py index 8ec02de..ac17f79 100644 --- a/setup.py +++ b/setup.py @@ -42,12 +42,8 @@ def get_version(package): ), packages=find_packages(exclude=["tests*"]), install_requires=[ - "Django>=2.2.0", - "django-graphql-jwt==0.3.0", - "django-filter>=2.2.0", - "graphene_django>=2.1.8", - "graphene>=2.1.8", - "PyJWT<2.0.0", + "django-graphql-jwt==0.4.0", + "PyJWT==2.8.0", ], tests_require=tests_require, classifiers=[