- 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 30
 
Description
Description
When using Django Ninja JWT with a custom token obtain pair schema, the validation is being bypassed due to input type mismatch, leading to authentication errors.
Environment
- Python: 3.12
 - Django: 5.1.6
 - django-ninja: 1.3.0
 - django-ninja-jwt: 5.3.5
 - pydantic: 2.9.2
 
Issue
The TokenObtainInputSchemaBase.validate_inputs method expects the input to be a dictionary, but in the current version of Django Ninja, the input is wrapped in a DjangoGetter object. This causes the validation to be bypassed, leading to a NoneType error when trying to authenticate.
Code
class TokenObtainPairInputSchema(TokenObtainInputSchemaBase):
      """Custom schema for token obtain pair."""
      @pyd.model_validator(mode="before")
      def validate_inputs(cls, values: DjangoGetter) -> DjangoGetter:
          input_values = values.obj
          request = values.context.get("request")
          # This condition is never true because input_values is a DjangoGetter
          if isinstance(input_values, dict):  # <--
              values.obj.update(
                  cls.validate_values(request=request, values=input_values)
              )
              return values
          return values
    @classmethod
    def get_response_schema(cls) -> type[Schema]:
        return TokenObtainPairOutputSchema
    @classmethod
    def get_token(cls, user: AbstractUser) -> dict[str, t.Any]:
        values = {}
        refresh = RefreshToken.for_user(user)
        values["refresh"] = str(refresh)
        values["access"] = str(refresh.access_token)
        values.update(
            user=UserSchema.from_orm(user)
        )  # this will be needed when creating output schema
        return values
Request
curl -X 'POST' \
'http://localhost:8001/api/v1/auth/token/pair' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"username": "string",
"password": "string"
}'Error Log
[debug ] Input validation - values type: <class 'ninja.schema.DjangoGetter'>
[debug ] Input validation - input_values type: <class 'ninja.schema.DjangoGetter'>
[debug ] Input validation - input_values: <DjangoGetter: {'password': 'string', 'username': 'string'}>
[error ] 'NoneType' object has no attribute 'id'
Traceback (most recent call last):
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja/operation.py", line 119, in run
    values = self._get_values(request, kw, temporal_response)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja/operation.py", line 288, in _get_values
    data = model.resolve(request, self.api, path_params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja/params/models.py", line 57, in resolve
    return cls.model_validate(data, context={"request": request})
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/pydantic/main.py", line 641, in model_validate
    return cls.__pydantic_validator__.validate_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja/schema.py", line 228, in _run_root_validator
    return handler(values)
           ^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja_jwt/schema.py", line 117, in post_validate
    return cls.post_validate_schema(values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja_jwt/schema.py", line 128, in post_validate_schema
    data = cls.get_token(cls._user)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/app/app/api/auth/token_obtain.py", line 93, in get_token
    refresh = RefreshToken.for_user(user)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ubuntu/.cache/pypoetry/virtualenvs/app-VA82Wl8V-py3.12/lib/python3.12/site-packages/ninja_jwt/tokens.py", line 189, in for_user
    user_id = getattr(user, api_settings.USER_ID_FIELD)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'id'
Expected Behavior
The validation should handle both dictionary and DjangoGetter inputs, ensuring proper validation before authentication attempts.
Current Workaround
We've implemented a workaround by explicitly handling the DjangoGetter case:
@pyd.model_validator(mode="before")
def validate_inputs(cls, values: DjangoGetter) -> DjangoGetter:
    input_values = values.obj
    request = values.context.get("request")
    # Handle both dict and DjangoGetter inputs
    if isinstance(input_values, dict):
        values_dict = input_values
    else:
        # Convert DjangoGetter to dict
        values_dict = input_values._obj
    validated_values = cls.validate_values(request=request, values=values_dict)
    values.obj = validated_values
    return valuesQuestions
- Is this the intended behavior of the input validation?
 - Should the base implementation be updated to handle DjangoGetter inputs?
 - Is there a better way to handle this validation in custom schemas?