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 values
Questions
- 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?
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
Issue
The
TokenObtainInputSchemaBase.validate_inputsmethod expects the input to be a dictionary, but in the current version of Django Ninja, the input is wrapped in aDjangoGetterobject. This causes the validation to be bypassed, leading to aNoneTypeerror when trying to authenticate.Code
Request
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'
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:
Questions