nrbnlulu / strawberry-django-auth

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

get_user fails on channels requests #414

Closed jaydensmith closed 1 year ago

jaydensmith commented 1 year ago

The ChannelsRequest class has no attribute user. Fixed in my own project by changing to:

def get_user(info: Info) -> USER_UNION:
    try:
        return info.context.request.user  # type: ignore
    except AttributeError:
        try:
            return info.context["request"].user
        except AttributeError:
            return info.context["request"].consumer.scope["user"]
nrbnlulu commented 1 year ago

Do you use strawberry channels consumer? What is your setup?

jaydensmith commented 1 year ago

@nrbnlulu yes I am using the GraphQLWSConsumer and GraphQLHTTPConsumer.

import os

from django.urls import re_path
from django.core.asgi import get_asgi_application

from gqlauth.core.middlewares import channels_jwt_middleware
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from strawberry.channels import GraphQLWSConsumer, GraphQLHTTPConsumer

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'franchiseportal.settings')
django_asgi_app = get_asgi_application()

from .schema import schema

application = ProtocolTypeRouter({
    'http': URLRouter([
        re_path('^graphql', AuthMiddlewareStack(channels_jwt_middleware(GraphQLHTTPConsumer.as_asgi(schema=schema)))),
        re_path('^', django_asgi_app),
    ]),
    'websocket': URLRouter([
        re_path('^graphql', AuthMiddlewareStack(channels_jwt_middleware(GraphQLWSConsumer.as_asgi(schema=schema)))),
    ]),
})
nrbnlulu commented 1 year ago

Do you use JwtSchema?

jaydensmith commented 1 year ago

@nrbnlulu I wasn't, thanks for the pointer. Still not working, though, as ChannelsRequest doesn't have a scope attribute.

Note the line user_or_error: UserOrError = request.scope[USER_OR_ERROR_KEY].

@staticmethod
    def _inject_user_and_errors(kwargs: dict) -> UserOrError:
        context = kwargs.get("context_value")
        # channels compat
        if isinstance(context, dict):
            request = context["request"]
            user_or_error: UserOrError = request.scope[USER_OR_ERROR_KEY]
            request.user = user_or_error.user  # type: ignore
        else:
            user_or_error: UserOrError = getattr(context.request, USER_OR_ERROR_KEY)  # type: ignore
            context.request.user = user_or_error.user  # type: ignore
        return user_or_error
nrbnlulu commented 1 year ago

Weird... I have tests for channels... I'll dig into it later.

nrbnlulu commented 1 year ago

BTW what version of strawberry are you running?

jaydensmith commented 1 year ago

@nrbnlulu the latest version, 0.195.2. It seems sometimes the request variable is an instance of ChannelsRequest, and sometimes it is the consumer. I haven't looked too deep but possibly the difference between a websocket connection and HTTP request.

nrbnlulu commented 1 year ago

Why would you use channels for http?

jaydensmith commented 1 year ago

@nrbnlulu it's pretty typical to route traditional HTTP requests through ASGI? https://channels.readthedocs.io/en/stable/topics/routing.html

nrbnlulu commented 1 year ago

Yeah though you can just use django asgi app and have the grahpql view there, that middleware was created for channels ws requests only. django has it's own middleware. AFAIK this is the recommended way of doing this.

ElMahdiAboulmanadel commented 1 year ago

Hello, I have the same issue, I tried to do as in the examples but it doesn't work. When I do run it and visit /graphql, the GraphiQL keeps trying to fetch the schema and at the end it says error fetching schema and when I try to run any query or mutation whether it was mine or from gqlauth, the get_user fails and I get an error on USER_OR_ERROR...

This is asgi.py


from django.urls import re_path
from django.core.asgi import get_asgi_application

from gqlauth.core.middlewares import channels_jwt_middleware
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from strawberry.channels import GraphQLWSConsumer, GraphQLHTTPConsumer

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blajobers.settings')
django_asgi_app = get_asgi_application()

from .schema import arg_schema as schema

application = ProtocolTypeRouter({
'http': URLRouter([
re_path('^graphql', AuthMiddlewareStack(GraphQLHTTPConsumer.as_asgi(schema=schema))),
re_path('^', django_asgi_app),
]),
'websocket': URLRouter([
re_path('^graphql', AuthMiddlewareStack(channels_jwt_middleware(GraphQLWSConsumer.as_asgi(schema=schema)))),
]),
})

schema.py

import strawberry
from gqlauth.core.middlewares import JwtSchema

from gqlauth.user.queries import UserQueries
from users.schema import Query as UsersQueries

from gqlauth.user import arg_mutations as mutations

from services.schema import Query as ServiceQueries, Mutation as ServiceMutations
from tickets.schema import Query as TicketQueries, Mutation as TicketMutations

@strawberry.type
class Query(
UserQueries,
UsersQueries,
ServiceQueries,
TicketQueries
):
pass

@strawberry.type
class Mutation(
ServiceMutations,
TicketMutations,
):
# include what-ever mutations you want.
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
# swap_emails = mutations.SwapEmails.field
# captcha = Captcha.field
token_auth = mutations.ObtainJSONWebToken.field
register = mutations.Register.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
# verify_secondary_email = mutations.VerifySecondaryEmail.field

arg_schema = JwtSchema(
query=Query,
mutation=Mutation
)

urls.py

from django.urls import path, include
from strawberry.django.views import AsyncGraphQLView, GraphQLView
from django.views.decorators.csrf import csrf_exempt

from .schema import arg_schema

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
path("admin/", admin.site.urls),
path("graphql/", csrf_exempt(GraphQLView.as_view(schema=arg_schema))),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)