-
-
Notifications
You must be signed in to change notification settings - Fork 27
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 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?