├── .coveragerc ├── .flake8 ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── publish.yml │ ├── test.yml │ └── test_full.yml ├── .gitignore ├── .isort.cfg ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.rst ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.md ├── codecov.yml ├── docs ├── auth_integration.md ├── blacklist_app.md ├── creating_tokens_manually.md ├── customizing_token_claims.md ├── development_and_contributing.md ├── getting_started.md ├── img │ └── token_customize.gif ├── index.md ├── settings.md └── token_types.md ├── licenses └── LICENSE-SimpleJWT.txt ├── mkdocs.yml ├── mypy.ini ├── ninja_jwt ├── __init__.py ├── authentication.py ├── backends.py ├── compat.py ├── controller.py ├── exceptions.py ├── locale │ ├── cs │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── de_CH │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── es │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── es_AR │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── es_CL │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── fa_IR │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── fr │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── id_ID │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── it_IT │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── ko_KR │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── nl_NL │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pl_PL │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pt_BR │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── ro │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── ru_RU │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── sv │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── tr │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── uk_UA │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ └── zh_Hans │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── models.py ├── py.typed ├── routers │ ├── __init__.py │ ├── blacklist.py │ ├── obtain.py │ └── verify.py ├── schema.py ├── schema_control.py ├── settings.py ├── state.py ├── token_blacklist │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── flushexpiredtokens.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_outstandingtoken_jti_hex.py │ │ ├── 0003_auto_20171017_2007.py │ │ ├── 0004_auto_20171017_2013.py │ │ ├── 0005_remove_outstandingtoken_jti.py │ │ ├── 0006_auto_20171017_2113.py │ │ ├── 0007_auto_20171017_2214.py │ │ ├── 0008_migrate_to_bigautofield.py │ │ ├── 0010_fix_migrate_to_bigautofield.py │ │ ├── 0011_linearizes_history.py │ │ ├── 0012_alter_outstandingtoken_user.py │ │ └── __init__.py │ └── models.py ├── tokens.py └── utils.py ├── pyproject.toml ├── pytest.ini ├── requirements-docs.txt ├── requirements-tests.txt ├── requirements.txt ├── scripts └── i18n_updater.py └── tests ├── __init__.py ├── conftest.py ├── keys.py ├── models.py ├── test_authentication.py ├── test_backends.py ├── test_custom_schema.py ├── test_integration.py ├── test_models.py ├── test_routers.py ├── test_token_blacklist.py ├── test_tokens.py ├── test_utils.py ├── test_views.py ├── urls.py ├── utils.py └── views.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | ninja_jwt/token_blacklist/admin.py 4 | ninja_jwt/token_blacklist/apps.py 5 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | ignore = E203, E241, E501, W503 4 | exclude = 5 | .git, 6 | __pycache__ 7 | .history 8 | tests -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [eadwinCode] 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" 9 | directory: "/" 10 | schedule: 11 | interval: "monthly" 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: monthly 16 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Set up Python 14 | uses: actions/setup-python@v5 15 | with: 16 | python-version: 3.8 17 | - name: Install Flit 18 | run: pip install flit 19 | - name: Install Dependencies 20 | run: make install 21 | - name: Install build dependencies 22 | run: pip install build 23 | - name: Build distribution 24 | run: python -m build 25 | - name: Publish 26 | uses: pypa/gh-action-pypi-publish@v1.12.4 27 | with: 28 | password: ${{ secrets.PYPI_API_TOKEN }} 29 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | types: [assigned, opened, synchronize, reopened] 7 | 8 | jobs: 9 | test_coverage: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up Python 15 | uses: actions/setup-python@v5 16 | with: 17 | python-version: 3.8 18 | - name: Install Flit 19 | run: pip install flit 20 | - name: Install Dependencies 21 | run: make install 22 | - name: Test 23 | run: pytest --cov=ninja_jwt --cov-report=xml tests 24 | - name: Coverage 25 | uses: codecov/codecov-action@v5 26 | -------------------------------------------------------------------------------- /.github/workflows/test_full.yml: -------------------------------------------------------------------------------- 1 | name: Full Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | types: [assigned, opened, synchronize, reopened] 7 | 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] 15 | django-version: ['<3.2', '<3.3', '<4.1', '<4.2'] 16 | exclude: 17 | - python-version: '3.8' 18 | django-version: '<5.1' 19 | - python-version: '3.9' 20 | django-version: '<5.1' 21 | - python-version: '3.12' 22 | django-version: '<3.2' 23 | - python-version: '3.12' 24 | django-version: '<3.3' 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Set up Python 29 | uses: actions/setup-python@v5 30 | with: 31 | python-version: ${{ matrix.python-version }} 32 | - name: Install core 33 | run: pip install "Django${{ matrix.django-version }}" 34 | - name: Install tests 35 | run: pip install pytest pytest-asyncio pytest-django django-ninja-extra 'python-jose==3.3.0' 'pyjwt[crypto]' freezegun 36 | - name: Test 37 | run: pytest 38 | codestyle: 39 | runs-on: ubuntu-latest 40 | 41 | steps: 42 | - uses: actions/checkout@v4 43 | - name: Set up Python 44 | uses: actions/setup-python@v5 45 | with: 46 | python-version: 3.9 47 | - name: Install Flit 48 | run: pip install flit 49 | - name: Install Dependencies 50 | run: make install 51 | - name: Ruff Linting Check 52 | run: ruff check ninja_jwt tests 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | # *.mo Needs to come with the package 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 100 | __pypackages__/ 101 | 102 | # Celery stuff 103 | celerybeat-schedule 104 | celerybeat.pid 105 | 106 | # SageMath parsed files 107 | *.sage.py 108 | 109 | # Environments 110 | .env 111 | .venv 112 | .vscode 113 | .mypy_cache 114 | .coverage 115 | htmlcov 116 | 117 | dist 118 | test.py 119 | 120 | docs/site 121 | 122 | .DS_Store 123 | .idea 124 | local_install.sh -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | profile = black 3 | combine_as_imports = true 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v2.3.0 4 | hooks: 5 | - id: check-merge-conflict 6 | - repo: https://github.com/asottile/yesqa 7 | rev: v1.3.0 8 | hooks: 9 | - id: yesqa 10 | - repo: local 11 | hooks: 12 | - id: code_formatting 13 | args: [] 14 | name: Code Formatting 15 | entry: "make fmt" 16 | types: [python] 17 | language_version: python3.8 18 | language: python 19 | - id: code_linting 20 | args: [ ] 21 | name: Code Linting 22 | entry: "make lint" 23 | types: [ python ] 24 | language_version: python3.8 25 | language: python 26 | - repo: https://github.com/pre-commit/pre-commit-hooks 27 | rev: v2.3.0 28 | hooks: 29 | - id: end-of-file-fixer 30 | exclude: >- 31 | ^examples/[^/]*\.svg$ 32 | - id: requirements-txt-fixer 33 | - id: trailing-whitespace 34 | types: [python] 35 | - id: check-case-conflict 36 | - id: check-json 37 | - id: check-xml 38 | - id: check-executables-have-shebangs 39 | - id: check-toml 40 | - id: check-xml 41 | - id: check-yaml 42 | - id: debug-statements 43 | - id: check-added-large-files 44 | - id: check-symlinks 45 | - id: debug-statements 46 | exclude: ^tests/ 47 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | As contributors and maintainers of the Jazzband projects, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating documentation, 6 | submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in the Jazzband a harassment-free experience 9 | for everyone, regardless of the level of experience, gender, gender identity and 10 | expression, sexual orientation, disability, personal appearance, body size, race, 11 | ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | - The use of sexualized language or imagery 16 | - Personal attacks 17 | - Trolling or insulting/derogatory comments 18 | - Public or private harassment 19 | - Publishing other's private information, such as physical or electronic addresses, 20 | without explicit permission 21 | - Other unethical or unprofessional conduct 22 | 23 | The Jazzband roadies have the right and responsibility to remove, edit, or reject 24 | comments, commits, code, wiki edits, issues, and other contributions that are not 25 | aligned to this Code of Conduct, or to ban temporarily or permanently any contributor 26 | for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 27 | 28 | By adopting this Code of Conduct, the roadies commit themselves to fairly and 29 | consistently applying these principles to every aspect of managing the jazzband 30 | projects. Roadies who do not follow or enforce the Code of Conduct may be permanently 31 | removed from the Jazzband roadies. 32 | 33 | This code of conduct applies both within project spaces and in public spaces when an 34 | individual is representing the project or its community. 35 | 36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 37 | contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and 38 | investigated and will result in a response that is deemed necessary and appropriate to 39 | the circumstances. Roadies are obligated to maintain confidentiality with regard to the 40 | reporter of an incident. 41 | 42 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 43 | 1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version] 44 | 45 | [homepage]: https://contributor-covenant.org 46 | [version]: https://contributor-covenant.org/version/1/3/0/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://jazzband.co/static/img/jazzband.svg 2 | :target: https://jazzband.co/ 3 | :alt: Jazzband 4 | 5 | This is a `Jazzband `_ project. By contributing you agree to abide by the `Contributor Code of Conduct `_ and follow the `guidelines `_. 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2021 Ezeudoh Tochukwu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE.txt 3 | recursive-include ninja_jwt/locale *.mo 4 | recursive-include ninja_jwt/locale *.po 5 | recursive-exclude * __pycache__ 6 | recursive-exclude * *.py[co] 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help docs 2 | .DEFAULT_GOAL := help 3 | 4 | help: 5 | @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 6 | 7 | clean: ## Removing cached python compiled files 8 | find . -name \*pyc | xargs rm -fv 9 | find . -name \*pyo | xargs rm -fv 10 | find . -name \*~ | xargs rm -fv 11 | find . -name __pycache__ | xargs rm -rfv 12 | find . -name .ruff_cache | xargs rm -rfv 13 | 14 | install:clean ## Install dependencies 15 | pip install -r requirements.txt 16 | flit install --symlink 17 | 18 | install-full:install ## Install dependencies 19 | pre-commit install -f 20 | 21 | lint:fmt ## Run code linters 22 | ruff check ninja_jwt tests 23 | # mypy ninja_jwt 24 | 25 | fmt format:clean ## Run code formatters 26 | ruff format ninja_jwt tests 27 | ruff check --fix ninja_jwt tests 28 | 29 | test:clean ## Run tests 30 | pytest . 31 | 32 | test-cov:clean ## Run tests with coverage 33 | pytest --cov=ninja_jwt --cov-report term-missing tests 34 | 35 | doc-deploy:clean ## Run Deploy Documentation 36 | mkdocs gh-deploy --force 37 | 38 | doc-serve:clean ## Run Deploy Documentation 39 | mkdocs serve 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ninja JWT 2 | ![Test](https://github.com/eadwinCode/django-ninja-jwt/workflows/Test/badge.svg) 3 | [![PyPI version](https://badge.fury.io/py/django-ninja-jwt.svg)](https://badge.fury.io/py/django-ninja-jwt) 4 | [![PyPI version](https://img.shields.io/pypi/v/django-ninja-jwt.svg)](https://pypi.python.org/pypi/django-ninja-jwt) 5 | [![PyPI version](https://img.shields.io/pypi/pyversions/django-ninja-jwt.svg)](https://pypi.python.org/pypi/django-ninja-jwt) 6 | [![PyPI version](https://img.shields.io/pypi/djversions/django-ninja-jwt.svg)](https://pypi.python.org/pypi/django-ninja-jwt) 7 | [![Codecov](https://img.shields.io/codecov/c/gh/eadwinCode/django-ninja-jwt)](https://codecov.io/gh/eadwinCode/django-ninja-jwt) 8 | [![Downloads](https://static.pepy.tech/badge/django-ninja-jwt)](https://pepy.tech/project/django-ninja-jwt) 9 | 10 | 11 | ## Abstract 12 | 13 | Ninja JWT is a JSON Web Token (JWT) plugin for Django-Ninja. 14 | This library is a fork of [Simple JWT](https://github.com/jazzband/djangorestframework-simplejwt) by Jazzband, 15 | a widely-used JWT plugin for the [Django REST Framework](http://www.django-rest-framework.org). 16 | 17 | #### Notice 18 | 19 | This library does not address any issues present in the original SIMPLE JWT. 20 | It only adds support for Django-Ninja and removes dependencies on DRF. 21 | Subsequent updates from SIMPLE JWT will be reflected here over time. 22 | 23 | For full documentation, [visit this page](https://eadwincode.github.io/django-ninja-jwt/). 24 | #### Requirements 25 | - Python >= 3.6 26 | - Django >= 2.1 27 | - Django-Ninja >= 0.16.1 28 | - Django-Ninja-Extra >= 0.14.2 29 | 30 | ## Example 31 | Checkout this sample project: https://github.com/eadwinCode/bookstoreapi 32 | 33 | 34 | ## Installation 35 | 36 | Ninja JWT can be installed with pip: 37 | 38 | ```shell 39 | pip install django-ninja-jwt 40 | ``` 41 | 42 | You also need to register the `NinjaJWTDefaultController` controller to your Django-Ninja API: 43 | 44 | ```python 45 | from ninja_jwt.controller import NinjaJWTDefaultController 46 | from ninja_extra import NinjaExtraAPI 47 | 48 | api = NinjaExtraAPI() 49 | api.register_controllers(NinjaJWTDefaultController) 50 | ``` 51 | 52 | The `NinjaJWTDefaultController` includes three routes: `obtain_token`, `refresh_token`, and `verify_token`. 53 | It combines two subclasses, `TokenVerificationController` and `TokenObtainPairController`. 54 | If you want to customize these routes, you can inherit from these controllers and modify their implementation: 55 | 56 | ```python 57 | from ninja_extra import api_controller 58 | from ninja_jwt.controller import TokenObtainPairController 59 | 60 | @api_controller('token', tags=['Auth']) 61 | class MyCustomController(TokenObtainPairController): 62 | """obtain_token and refresh_token only""" 63 | ... 64 | api.register_controllers(MyCustomController) 65 | ``` 66 | 67 | To use localizations/translations, add `ninja_jwt` to your `INSTALLED_APPS`: 68 | 69 | ```python 70 | INSTALLED_APPS = [ 71 | ... 72 | 'ninja_jwt', 73 | ... 74 | ] 75 | ``` 76 | 77 | ## Using Ninja Router 78 | 79 | If you prefer not to follow the NinjaExtra methodology, 80 | refer to this [documentation](https://eadwincode.github.io/django-ninja-jwt/customizing_token_claims/#use-django-ninja-router) 81 | on how to use `Ninja-JWT` with `Django-Ninja Router`. 82 | 83 | ## Usage 84 | 85 | To verify that Ninja JWT is working, you can use curl to issue a couple 86 | of test requests: 87 | 88 | ``` {.sourceCode .bash} 89 | curl \ 90 | -X POST \ 91 | -H "Content-Type: application/json" \ 92 | -d '{"username": "davidattenborough", "password": "boatymcboatface"}' \ 93 | http://localhost:8000/api/token/pair 94 | 95 | ... 96 | { 97 | "access":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNDU2LCJqdGkiOiJmZDJmOWQ1ZTFhN2M0MmU4OTQ5MzVlMzYyYmNhOGJjYSJ9.NHlztMGER7UADHZJlxNG0WSi22a2KaYSfd1S-AuT7lU", 98 | "refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImNvbGRfc3R1ZmYiOiLimIMiLCJleHAiOjIzNDU2NywianRpIjoiZGUxMmY0ZTY3MDY4NDI3ODg5ZjE1YWMyNzcwZGEwNTEifQ.aEoAYkSJjoWH1boshQAaTkf8G3yn0kapko6HFRt7Rh4" 99 | } 100 | ``` 101 | 102 | You can use the returned access token to prove authentication for a 103 | protected view: 104 | 105 | ``` {.sourceCode .bash} 106 | curl \ 107 | -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNDU2LCJqdGkiOiJmZDJmOWQ1ZTFhN2M0MmU4OTQ5MzVlMzYyYmNhOGJjYSJ9.NHlztMGER7UADHZJlxNG0WSi22a2KaYSfd1S-AuT7lU" \ 108 | http://localhost:8000/api/some-protected-view/ 109 | ``` 110 | 111 | When this short-lived access token expires, you can use the longer-lived 112 | refresh token to obtain another access token: 113 | 114 | ``` {.sourceCode .bash} 115 | curl \ 116 | -X POST \ 117 | -H "Content-Type: application/json" \ 118 | -d '{"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImNvbGRfc3R1ZmYiOiLimIMiLCJleHAiOjIzNDU2NywianRpIjoiZGUxMmY0ZTY3MDY4NDI3ODg5ZjE1YWMyNzcwZGEwNTEifQ.aEoAYkSJjoWH1boshQAaTkf8G3yn0kapko6HFRt7Rh4"}' \ 119 | http://localhost:8000/api/token/refresh 120 | 121 | ... 122 | {"access":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNTY3LCJqdGkiOiJjNzE4ZTVkNjgzZWQ0NTQyYTU0NWJkM2VmMGI0ZGQ0ZSJ9.ekxRxgb9OKmHkfy-zs1Ro_xs1eMLXiR17dIDBVxeT-w"} 123 | ``` 124 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | informational: true 6 | patch: 7 | default: 8 | informational: true 9 | changes: false 10 | 11 | comment: off 12 | -------------------------------------------------------------------------------- /docs/auth_integration.md: -------------------------------------------------------------------------------- 1 | 2 | Ninja JWT uses Django Ninja `HttpBearer` as a way to authenticate users reaching your api endpoint. 3 | Authenticated user can be found in `request.user` or `request.auth` 4 | 5 | ### Route Authentication—Class Based 6 | 7 | ```python 8 | from ninja_extra import api_controller, route 9 | from ninja_jwt.authentication import JWTAuth 10 | 11 | @api_controller 12 | class MyController: 13 | @route.get('/some-endpoint', auth=JWTAuth()) 14 | def some_endpoint(self): 15 | ... 16 | ``` 17 | 18 | ### Route Authentication—Function Based 19 | 20 | ```python 21 | from ninja import router 22 | from ninja_jwt.authentication import JWTAuth 23 | 24 | router = router('') 25 | 26 | @router.get('/some-endpoint', auth=JWTAuth()) 27 | def some_endpoint(request): 28 | ... 29 | ``` 30 | 31 | ## Custom Auth Implementation 32 | If you wish to use a different implementation of `JWTAuth`, then you need to inherit from `JWTBaseAuthentication`. 33 | Please read more on [Django Ninja - Authentication](https://django-ninja.rest-framework.com/tutorial/authentication/), if you want to use a different approach that is not `bearer`. 34 | 35 | example: 36 | ```python 37 | from ninja.security import APIKeyHeader 38 | from ninja_jwt.authentication import JWTBaseAuthentication 39 | from ninja import router 40 | 41 | class ApiKey(APIKeyHeader, JWTBaseAuthentication): 42 | param_name = "X-API-Key" 43 | 44 | def authenticate(self, request, key): 45 | return self.jwt_authenticate(request, token=key) 46 | 47 | 48 | header_key = ApiKey() 49 | router = router('') 50 | 51 | @router.get("/headerkey", auth=header_key) 52 | def apikey(request): 53 | return f"Token = {request.auth}" 54 | 55 | ``` 56 | 57 | ### Asynchronous Route Authentication 58 | If you are interested in the Asynchronous Route Authentication, there is `AsyncJWTAuth` class 59 | 60 | ```python 61 | from ninja_extra import api_controller, route 62 | from ninja_jwt.authentication import AsyncJWTAuth 63 | 64 | @api_controller 65 | class MyController: 66 | @route.get('/some-endpoint', auth=AsyncJWTAuth()) 67 | async def some_endpoint(self): 68 | ... 69 | ``` 70 | N:B `some_endpoint` must be asynchronous. Any endpoint function marked with `AsyncJWTAuth` must be asynchronous. 71 | 72 | !!! warning 73 | Asynchronous feature is only available for django version > 3.0 74 | -------------------------------------------------------------------------------- /docs/blacklist_app.md: -------------------------------------------------------------------------------- 1 | 2 | Ninja JWT includes an app that provides token blacklist functionality. 3 | To use this app, include it in your list of installed apps in 4 | `settings.py`: 5 | 6 | ```python 7 | # Django project settings.py 8 | 9 | ... 10 | 11 | INSTALLED_APPS = ( 12 | ... 13 | 'ninja_jwt.token_blacklist', 14 | ... 15 | ) 16 | ``` 17 | 18 | Also, make sure to run `python manage.py migrate` to run the app's 19 | migrations. 20 | 21 | If the blacklist app is detected in `INSTALLED_APPS`, Ninja JWT will 22 | add any generated refresh or sliding tokens to a list of outstanding 23 | tokens. It will also check that any refresh or sliding token does not 24 | appear in a blacklist of tokens before it considers it as valid. 25 | 26 | The Ninja JWT blacklist app implements its outstanding and blacklisted 27 | token lists using two models: `OutstandingToken` and `BlacklistedToken`. 28 | Model admins are defined for both of these models. To add a token to the 29 | blacklist, find its corresponding `OutstandingToken` record in the admin 30 | and use the admin again to create a `BlacklistedToken` record that 31 | points to the `OutstandingToken` record. 32 | 33 | Alternatively, you can blacklist a token by creating a `BlacklistMixin` 34 | subclass instance and calling the instance's `blacklist` method: 35 | 36 | ```python 37 | from ninja_jwt.tokens import RefreshToken 38 | 39 | token = RefreshToken(base64_encoded_token_string) 40 | token.blacklist() 41 | ``` 42 | 43 | This will create unique outstanding token and blacklist records for the 44 | token's `jti` claim or whichever claim is specified by the 45 | `JTI_CLAIM` setting. 46 | 47 | The blacklist app also provides a management command, 48 | `flushexpiredtokens`, which will delete any tokens from the outstanding 49 | list and blacklist that have expired. You should set up a cron job on 50 | your server or hosting platform which runs this command daily. 51 | -------------------------------------------------------------------------------- /docs/creating_tokens_manually.md: -------------------------------------------------------------------------------- 1 | 2 | Sometimes, you may wish to manually create a token for a user. This 3 | could be done as follows: 4 | 5 | ```python 6 | from ninja_jwt.tokens import RefreshToken 7 | 8 | def get_tokens_for_user(user): 9 | refresh = RefreshToken.for_user(user) 10 | 11 | return { 12 | 'refresh': str(refresh), 13 | 'access': str(refresh.access_token), 14 | } 15 | ``` 16 | 17 | The above function `get_tokens_for_user` will return the serialized 18 | representations of new refresh and access tokens for the given user. In 19 | general, a token for any subclass of `ninja_jwt.tokens.Token` can be 20 | created in this way. 21 | -------------------------------------------------------------------------------- /docs/customizing_token_claims.md: -------------------------------------------------------------------------------- 1 | If you wish to customize the claims contained in web tokens which are 2 | generated by the `NinjaJWTDefaultController` and `NinjaJWTSlidingController` 3 | views, create a subclass for the desired controller as well as a subclass for 4 | its corresponding serializer. Here\'s an example : 5 | 6 | !!! info 7 | if you are interested in an Asynchronous version of the class, use `AsyncNinjaJWTDefaultController` and `AsyncNinjaJWTSlidingController`. 8 | Also note, it's only available for Django versions that support asynchronous actions. 9 | 10 | ```python 11 | from ninja_jwt.schema import TokenObtainPairInputSchema 12 | from ninja_jwt.controller import TokenObtainPairController 13 | from ninja_extra import api_controller, route 14 | from ninja import Schema 15 | 16 | 17 | class UserSchema(Schema): 18 | first_name: str 19 | email: str 20 | 21 | 22 | class MyTokenObtainPairOutSchema(Schema): 23 | refresh: str 24 | access: str 25 | user: UserSchema 26 | 27 | 28 | class MyTokenObtainPairSchema(TokenObtainPairInputSchema): 29 | def to_response_schema(self): 30 | out_dict = self.get_response_schema_init_kwargs() 31 | out_dict.update(user=UserSchema.from_orm(self._user)) 32 | return MyTokenObtainPairOutSchema(**out_dict) 33 | 34 | 35 | @api_controller('/token', tags=['Auth']) 36 | class MyTokenObtainPairController(TokenObtainPairController): 37 | @route.post( 38 | "/pair", response=MyTokenObtainPairOutSchema, url_name="token_obtain_pair" 39 | ) 40 | def obtain_token(self, user_token: MyTokenObtainPairSchema): 41 | return user_token.to_response_schema() 42 | 43 | ``` 44 | 45 | As with the standard controller, you\'ll also need to include register the controller as shown in `getting_started` 46 | 47 | #### Use Django Ninja Router 48 | 49 | If you are interested in using functions rather than classes, then you are also covered. 50 | Here is an example 51 | 52 | ```python 53 | from ninja_jwt.routers.blacklist import blacklist_router 54 | from ninja_jwt.routers.obtain import obtain_pair_router, sliding_router 55 | from ninja_jwt.routers.verify import verify_router 56 | ``` 57 | 58 | Register the `router` to the django-ninja `api` like so: 59 | 60 | ```python 61 | from ninja import NinjaAPI 62 | 63 | api = NinjaAPI() 64 | api.add_router('/token', tags=['Auth'], router=obtain_pair_router) 65 | ... 66 | ``` 67 | 68 | If you are interested in customize the token claims, you can do so by creating a subclass of `TokenObtainPairInputSchema` and `TokenObtainPairController`. See [Controller Schema Swapping](#controller-schema-swapping) 69 | 70 | Also, its important to note that `NinjaExtra` registers a handler for `APIException` class which is not available in `NinjaAPI` instance. 71 | To fix that, you need the extra code below: 72 | 73 | ```python 74 | from ninja import NinjaAPI 75 | from ninja_extra import exceptions 76 | 77 | api = NinjaAPI() 78 | api.add_router('', tags=['Auth'], router=router) 79 | 80 | def api_exception_handler(request, exc): 81 | headers = {} 82 | 83 | if isinstance(exc.detail, (list, dict)): 84 | data = exc.detail 85 | else: 86 | data = {"detail": exc.detail} 87 | 88 | response = api.create_response(request, data, status=exc.status_code) 89 | for k, v in headers.items(): 90 | response.setdefault(k, v) 91 | 92 | return response 93 | 94 | api.exception_handler(exceptions.APIException)(api_exception_handler) 95 | ``` 96 | 97 | ### Controller Schema Swapping 98 | 99 | You can now swap controller schema in `NINJA_JWT` settings without having to inherit or override Ninja JWT controller function. 100 | 101 | All controller input schema must inherit from `ninja_jwt.schema.InputSchemaMixin` and token generating schema should inherit 102 | from `ninja_jwt.schema.TokenObtainInputSchemaBase` or `ninja_jwt.schema.TokenInputSchemaMixin` if you want to have more control. 103 | 104 | Using the example above: 105 | 106 | ```python 107 | # project/schema.py 108 | from typing import Type, Dict 109 | from ninja_jwt.schema import TokenObtainInputSchemaBase 110 | from ninja import Schema 111 | from ninja_jwt.tokens import RefreshToken 112 | 113 | class UserSchema(Schema): 114 | first_name: str 115 | email: str 116 | 117 | 118 | class MyTokenObtainPairOutSchema(Schema): 119 | refresh: str 120 | access: str 121 | user: UserSchema 122 | 123 | 124 | class MyTokenObtainPairInputSchema(TokenObtainInputSchemaBase): 125 | @classmethod 126 | def get_response_schema(cls) -> Type[Schema]: 127 | return MyTokenObtainPairOutSchema 128 | 129 | @classmethod 130 | def get_token(cls, user) -> Dict: 131 | values = {} 132 | refresh = RefreshToken.for_user(user) 133 | values["refresh"] = str(refresh) 134 | values["access"] = str(refresh.access_token) 135 | values.update(user=UserSchema.from_orm(user)) # this will be needed when creating output schema 136 | return values 137 | ``` 138 | 139 | In the `MyTokenObtainPairInputSchema` we override `get_token` to define our token and some data needed for our output schema. 140 | We also override `get_response_schema` to define our output schema `MyTokenObtainPairOutSchema`. 141 | 142 | Next, we apply the `MyTokenObtainPairInputSchema` schema to controller. This is simply done in `NINJA_JWT` settings. 143 | 144 | ```python 145 | # project/settings.py 146 | 147 | NINJA_JWT = { 148 | 'TOKEN_OBTAIN_PAIR_INPUT_SCHEMA': 'project.schema.MyTokenObtainPairInputSchema', 149 | } 150 | ``` 151 | Other swappable schemas can be follow as shown below: 152 | ```python 153 | # project/settings.py 154 | 155 | NINJA_JWT = { 156 | # FOR OBTAIN PAIR 157 | 'TOKEN_OBTAIN_PAIR_INPUT_SCHEMA': "project.schema.MyTokenObtainPairInputSchema", 158 | 'TOKEN_OBTAIN_PAIR_REFRESH_INPUT_SCHEMA': "for.obtain_pair.refresh_input.schema", 159 | # FOR SLIDING TOKEN 160 | 'TOKEN_OBTAIN_SLIDING_INPUT_SCHEMA': "for.obtain_sliding.input.schema", 161 | 'TOKEN_OBTAIN_SLIDING_REFRESH_INPUT_SCHEMA': "for.obtain_pair.refresh_input.schema", 162 | 163 | 'TOKEN_BLACKLIST_INPUT_SCHEMA': "for.blacklist_input.schema", 164 | 'TOKEN_VERIFY_INPUT_SCHEMA': "for.verify_input.schema", 165 | } 166 | ``` 167 | 168 | ![token_customization_git](./img/token_customize.gif) 169 | 170 | !!! Note 171 | `Controller Schema Swapping` is only available from **v5.2.4** 172 | -------------------------------------------------------------------------------- /docs/development_and_contributing.md: -------------------------------------------------------------------------------- 1 | 2 | To do development work for Ninja JWT, make your own fork on Github, 3 | clone it locally, make and activate a virtualenv for it, then from 4 | within the project directory: 5 | 6 | After that, install flit 7 | 8 | ```shell 9 | $(venv) pip install flit 10 | ``` 11 | 12 | Install development libraries and pre-commit hooks for code linting and styles 13 | 14 | ```shell 15 | $(venv) make install 16 | ``` 17 | 18 | To run the tests: 19 | 20 | ```shell 21 | $(venv) make test 22 | ``` 23 | 24 | To run the tests with coverage: 25 | 26 | ```shell 27 | $(venv) make test-cov 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/getting_started.md: -------------------------------------------------------------------------------- 1 | 2 | #### Requirements 3 | - Python >= 3.6 4 | - Django >= 2.1 5 | - Django-Ninja >= 0.16.1 6 | - Django-Ninja-Extra >= 0.11.0 7 | 8 | These are the officially supported python and package versions. Other 9 | versions will probably work. You 're free to modify the tox config and 10 | see what is possible. 11 | 12 | Installation 13 | ============ 14 | Ninja JWT can be installed with pip: 15 | 16 | pip install django-ninja-jwt 17 | 18 | Also, you need to register `NinjaJWTDefaultController` controller to your Django-Ninja api. 19 | The `NinjaJWTDefaultController` comes with three routes `obtain_token`, `refresh_token` and `verify_token` 20 | 21 | ```python 22 | from ninja_jwt.controller import NinjaJWTDefaultController 23 | from ninja_extra import NinjaExtraAPI 24 | 25 | api = NinjaExtraAPI() 26 | api.register_controllers(NinjaJWTDefaultController) 27 | 28 | ``` 29 | 30 | The `NinjaJWTDefaultController` comes with three routes `obtain_token`, `refresh_token` and `verify_token`. 31 | It is a combination of two subclass `TokenVerificationController` and `TokenObtainPairController`. 32 | If you wish to customize these routes, you can inherit from these controllers and change its implementation 33 | 34 | ```python 35 | from ninja_extra import api_controller 36 | from ninja_jwt.controller import TokenObtainPairController 37 | 38 | @api_controller('token', tags=['Auth']) 39 | class MyCustomController(TokenObtainPairController): 40 | """obtain_token and refresh_token only""" 41 | ... 42 | api.register_controllers(MyCustomController) 43 | ``` 44 | 45 | If you wish to use localizations/translations, simply add `ninja_jwt` to 46 | `INSTALLED_APPS`. 47 | 48 | ```python 49 | INSTALLED_APPS = [ 50 | ... 51 | 'ninja_jwt', 52 | ... 53 | ] 54 | ``` 55 | 56 | ## Using Ninja Router 57 | 58 | If you prefer not to follow the NinjaExtra methodology, 59 | refer to this [documentation](https://eadwincode.github.io/django-ninja-jwt/customizing_token_claims/#use-django-ninja-router) 60 | on how to use `Ninja-JWT` with `Django-Ninja Router`. 61 | 62 | ## Usage 63 | 64 | To verify that Ninja JWT is working, you can use curl to issue a couple 65 | of test requests: 66 | 67 | ``` {.sourceCode .bash} 68 | curl \ 69 | -X POST \ 70 | -H "Content-Type: application/json" \ 71 | -d '{"username": "davidattenborough", "password": "boatymcboatface"}' \ 72 | http://localhost:8000/api/token/pair 73 | 74 | ... 75 | { 76 | "access":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNDU2LCJqdGkiOiJmZDJmOWQ1ZTFhN2M0MmU4OTQ5MzVlMzYyYmNhOGJjYSJ9.NHlztMGER7UADHZJlxNG0WSi22a2KaYSfd1S-AuT7lU", 77 | "refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImNvbGRfc3R1ZmYiOiLimIMiLCJleHAiOjIzNDU2NywianRpIjoiZGUxMmY0ZTY3MDY4NDI3ODg5ZjE1YWMyNzcwZGEwNTEifQ.aEoAYkSJjoWH1boshQAaTkf8G3yn0kapko6HFRt7Rh4" 78 | } 79 | ``` 80 | 81 | You can use the returned access token to prove authentication for a 82 | protected view: 83 | 84 | ``` {.sourceCode .bash} 85 | curl \ 86 | -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNDU2LCJqdGkiOiJmZDJmOWQ1ZTFhN2M0MmU4OTQ5MzVlMzYyYmNhOGJjYSJ9.NHlztMGER7UADHZJlxNG0WSi22a2KaYSfd1S-AuT7lU" \ 87 | http://localhost:8000/api/some-protected-view/ 88 | ``` 89 | 90 | When this short-lived access token expires, you can use the longer-lived 91 | refresh token to obtain another access token: 92 | 93 | ``` {.sourceCode .bash} 94 | curl \ 95 | -X POST \ 96 | -H "Content-Type: application/json" \ 97 | -d '{"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImNvbGRfc3R1ZmYiOiLimIMiLCJleHAiOjIzNDU2NywianRpIjoiZGUxMmY0ZTY3MDY4NDI3ODg5ZjE1YWMyNzcwZGEwNTEifQ.aEoAYkSJjoWH1boshQAaTkf8G3yn0kapko6HFRt7Rh4"}' \ 98 | http://localhost:8000/api/token/refresh/ 99 | 100 | ... 101 | {"access":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZX...", "refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoicmVm..."} 102 | ``` 103 | 104 | ## Cryptographic Dependencies (Optional) 105 | 106 | If you are planning on encoding or decoding tokens using certain digital 107 | signature algorithms (i.e. RSA and ECDSA; visit PyJWT for other algorithms), you will need to install the 108 | cryptography_ library. This can be installed explicitly, or as a required 109 | extra in the `django-ninja-jwt` requirement: 110 | 111 | pip install django-ninja-jwt[crypto] 112 | 113 | 114 | The `django-ninja-jwt[crypto]` format is recommended in requirement 115 | files in projects using `Ninja JWT`, as a separate `cryptography` requirement 116 | line may later be mistaken for an unused requirement and removed. 117 | [cryptography](https://cryptography.io) 118 | -------------------------------------------------------------------------------- /docs/img/token_customize.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/docs/img/token_customize.gif -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | 2 | ![Test](https://github.com/eadwinCode/django-ninja-jwt/workflows/Test/badge.svg) 3 | [![PyPI version](https://badge.fury.io/py/django-ninja-jwt.svg)](https://badge.fury.io/py/django-ninja-jwt) 4 | [![PyPI version](https://img.shields.io/pypi/v/django-ninja-jwt.svg)](https://pypi.python.org/pypi/django-ninja-jwt) 5 | [![PyPI version](https://img.shields.io/pypi/pyversions/django-ninja-jwt.svg)](https://pypi.python.org/pypi/django-ninja-jwt) 6 | [![PyPI version](https://img.shields.io/pypi/djversions/django-ninja-jwt.svg)](https://pypi.python.org/pypi/django-ninja-jwt) 7 | [![Downloads](https://static.pepy.tech/badge/django-ninja-jwt)](https://pepy.tech/project/django-ninja-jwt) 8 | 9 | ## Introduction 10 | 11 | The Ninja JWT plugin offers JSON Web Token (JWT) 12 | authentication for [Django Ninja](https://github.com/vitalik/django-ninja). 13 | Designed to handle common JWT use cases, 14 | it provides a robust authentication backend with a practical set of default features. 15 | Additionally, Ninja JWT is highly extensible, allowing developers to add custom features as needed. 16 | 17 | ## Acknowledgments 18 | 19 | This project utilizes code from [SIMPLE JWT](https://github.com/jazzband/djangorestframework-simplejwt) 20 | to implement JSON Web Token 21 | (JWT) for the Django Ninja REST Framework. 22 | The SIMPLE JWT license is included in the `license` folder. 23 | -------------------------------------------------------------------------------- /docs/token_types.md: -------------------------------------------------------------------------------- 1 | 2 | Ninja JWT provides two different token types that can be used to prove 3 | authentication. In a token's payload, its type can be identified by the 4 | value of its token type claim, which is `token_type` by default. This 5 | may have a value of `access`, `sliding`, or `refresh` however 6 | refresh tokens are not considered valid for authentication at this time. 7 | The claim name used to store the type can be customized by changing the 8 | `TOKEN_TYPE_CLAIM` setting. 9 | 10 | By default, Ninja JWT expects an `access` token to prove 11 | authentication. The allowed auth token types are determined by the value 12 | of the `AUTH_TOKEN_CLASSES` setting. This setting contains a list of dot 13 | paths to token classes. It includes the `'ninja_jwt.tokens.AccessToken'` 14 | dot path by default but may also include the 15 | `'ninja_jwt.tokens.SlidingToken'` dot path. Either or both of those dot 16 | paths may be present in the list of auth token classes. If they are both 17 | present, then both of those token types may be used to prove 18 | authentication. 19 | 20 | ## Sliding tokens 21 | 22 | Sliding tokens offer a more convenient experience to users of tokens 23 | with the trade-offs of being less secure and, in the case that the 24 | blacklist app is being used, less performant. A sliding token is one 25 | that contains both an expiration claim and a refresh expiration claim. 26 | As long as the timestamp in a sliding token\'s expiration claim has not 27 | passed, it can be used to prove authentication. Additionally, as long as 28 | the timestamp in its refresh expiration claim has not passed, it may 29 | also be submitted to a refresh view to get another copy of itself with a 30 | renewed expiration claim. 31 | 32 | If you want to use sliding tokens, change the `AUTH_TOKEN_CLASSES` 33 | setting to `('ninja_jwt.tokens.SlidingToken',)`. (Alternatively, the 34 | `AUTH_TOKEN_CLASSES` setting may include dot paths to both the 35 | `AccessToken` and `SlidingToken` token classes in the `ninja_jwt.tokens` 36 | module if you want to allow both token types to be used for 37 | authentication.) 38 | 39 | Also, register `NinjaJWTSlidingController` to the `api`: 40 | ```python 41 | from ninja_jwt.controller import NinjaJWTSlidingController 42 | from ninja_extra import NinjaExtraAPI 43 | 44 | api = NinjaExtraAPI() 45 | api.register_controllers(NinjaJWTSlidingController) 46 | 47 | ``` 48 | 49 | Be aware that, if you are using the blacklist app, Ninja JWT will 50 | validate all sliding tokens against the blacklist for each authenticated 51 | request. This will reduce the performance of authenticated API views. 52 | -------------------------------------------------------------------------------- /licenses/LICENSE-SimpleJWT.txt: -------------------------------------------------------------------------------- 1 | Copyright 2017 David Sanders 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Django Ninja JWT 2 | site_description: Django Ninja JWT - A Simple JWT plugin for Django-Ninja. 3 | site_url: https://eadwincode.github.io/django-ninja-jwt/ 4 | repo_name: eadwinCode/django-ninja-jwt 5 | repo_url: https://github.com/eadwinCode/django-ninja-jwt 6 | edit_uri: 'https://github.com/eadwinCode/django-ninja-jwt/docs' 7 | 8 | theme: 9 | name: material 10 | palette: 11 | - media: "(prefers-color-scheme: light)" 12 | scheme: default 13 | primary: deeppurple 14 | accent: deeppurple 15 | toggle: 16 | icon: material/toggle-switch 17 | name: Switch to dark mode 18 | 19 | - media: "(prefers-color-scheme: dark)" 20 | scheme: slate 21 | primary: blue 22 | accent: blue 23 | toggle: 24 | icon: material/toggle-switch-off-outline 25 | name: Switch to light mode 26 | 27 | language: en 28 | icon: 29 | repo: fontawesome/brands/git-alt 30 | 31 | plugins: 32 | - search 33 | - mkdocstrings 34 | 35 | nav: 36 | - Ninja JWT: index.md 37 | - Getting Started: getting_started.md 38 | - Route Authentication: auth_integration.md 39 | - Settings: settings.md 40 | - Customizing Token Claims: customizing_token_claims.md 41 | - Creating Token Manually: creating_tokens_manually.md 42 | - Token Types: token_types.md 43 | - Blacklist App: blacklist_app.md 44 | - Development and Contributing: development_and_contributing.md 45 | - Experimental Feature: experimental_features.md 46 | #- ninja_jwt package: index.md 47 | 48 | markdown_extensions: 49 | - codehilite 50 | - admonition 51 | - pymdownx.superfences 52 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | python_version = 3.8 3 | 4 | show_column_numbers = True 5 | 6 | follow_imports = normal 7 | ignore_missing_imports = True 8 | 9 | # be strict 10 | disallow_untyped_calls = True 11 | warn_return_any = True 12 | strict_optional = True 13 | warn_no_return = True 14 | warn_redundant_casts = True 15 | warn_unused_ignores = True 16 | 17 | disallow_untyped_defs = True 18 | check_untyped_defs = True 19 | no_implicit_reexport = True 20 | 21 | [mypy-ninja_jwt.token_blacklist.*] 22 | ignore_errors = True 23 | [mypy-ninja_jwt.controller] 24 | ignore_errors = True 25 | -------------------------------------------------------------------------------- /ninja_jwt/__init__.py: -------------------------------------------------------------------------------- 1 | """Django Ninja JWT - JSON Web Token for Django-Ninja""" 2 | 3 | __version__ = "5.3.7" 4 | -------------------------------------------------------------------------------- /ninja_jwt/authentication.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Type 2 | 3 | import django 4 | from django.contrib.auth import get_user_model 5 | from django.contrib.auth.models import AbstractBaseUser, AnonymousUser 6 | from django.http import HttpRequest 7 | from django.utils.translation import gettext_lazy as _ 8 | from ninja_extra.security import AsyncHttpBearer, HttpBearer 9 | 10 | from .exceptions import AuthenticationFailed, InvalidToken, TokenError 11 | from .settings import api_settings 12 | from .tokens import Token 13 | 14 | 15 | class JWTBaseAuthentication: 16 | def __init__(self) -> None: 17 | super().__init__() 18 | self.user_model = get_user_model() 19 | 20 | @classmethod 21 | def get_validated_token(cls, raw_token) -> Type[Token]: 22 | """ 23 | Validates an encoded JSON web token and returns a validated token 24 | wrapper object. 25 | """ 26 | messages = [] 27 | for AuthToken in api_settings.AUTH_TOKEN_CLASSES: 28 | try: 29 | return AuthToken(raw_token) 30 | except TokenError as e: 31 | messages.append( 32 | { 33 | "token_class": AuthToken.__name__, 34 | "token_type": AuthToken.token_type, 35 | "message": e.args[0], 36 | } 37 | ) 38 | 39 | raise InvalidToken( 40 | { 41 | "detail": _("Given token not valid for any token type"), 42 | "messages": messages, 43 | } 44 | ) 45 | 46 | def get_user(self, validated_token) -> AbstractBaseUser: 47 | """ 48 | Attempts to find and return a user using the given validated token. 49 | """ 50 | try: 51 | user_id = validated_token[api_settings.USER_ID_CLAIM] 52 | except KeyError as e: 53 | raise InvalidToken( 54 | _("Token contained no recognizable user identification") 55 | ) from e 56 | 57 | try: 58 | user = self.user_model.objects.get(**{api_settings.USER_ID_FIELD: user_id}) 59 | except self.user_model.DoesNotExist as e: 60 | raise AuthenticationFailed(_("User not found")) from e 61 | 62 | if not user.is_active: 63 | raise AuthenticationFailed(_("User is inactive")) 64 | 65 | return user 66 | 67 | def jwt_authenticate(self, request: HttpRequest, token: str) -> AbstractBaseUser: 68 | request.user = AnonymousUser() 69 | validated_token = self.get_validated_token(token) 70 | user = self.get_user(validated_token) 71 | request.user = user 72 | return user 73 | 74 | 75 | class JWTAuth(JWTBaseAuthentication, HttpBearer): 76 | def authenticate(self, request: HttpRequest, token: str) -> Any: 77 | return self.jwt_authenticate(request, token) 78 | 79 | 80 | class JWTStatelessUserAuthentication(JWTBaseAuthentication, HttpBearer): 81 | """ 82 | An authentication plugin that authenticates requests through a JSON web 83 | token provided in a request header without performing a database lookup to obtain a user instance. 84 | """ 85 | 86 | def authenticate(self, request: HttpRequest, token: str) -> Any: 87 | return self.jwt_authenticate(request, token) 88 | 89 | def get_user(self, validated_token: Any) -> Type[AbstractBaseUser]: 90 | """ 91 | Returns a stateless user object which is backed by the given validated 92 | token. 93 | """ 94 | if api_settings.USER_ID_CLAIM not in validated_token: 95 | # The TokenUser class assumes tokens will have a recognizable user 96 | # identifier claim. 97 | raise InvalidToken(_("Token contained no recognizable user identification")) 98 | 99 | return api_settings.TOKEN_USER_CLASS(validated_token) 100 | 101 | 102 | JWTTokenUserAuth = JWTStatelessUserAuthentication 103 | 104 | 105 | def default_user_authentication_rule(user) -> bool: 106 | # Prior to Django 1.10, inactive users could be authenticated with the 107 | # default `ModelBackend`. As of Django 1.10, the `ModelBackend` 108 | # prevents inactive users from authenticating. App designers can still 109 | # allow inactive users to authenticate by opting for the new 110 | # `AllowAllUsersModelBackend`. However, we explicitly prevent inactive 111 | # users from authenticating to enforce a reasonable policy and provide 112 | # sensible backwards compatibility with older Django versions. 113 | return user is not None and user.is_active 114 | 115 | 116 | if not django.VERSION < (3, 1): 117 | from asgiref.sync import sync_to_async 118 | 119 | class AsyncJWTBaseAuthentication(JWTBaseAuthentication): 120 | async def async_jwt_authenticate( 121 | self, request: HttpRequest, token: str 122 | ) -> Type[AbstractBaseUser]: 123 | request.user = AnonymousUser() 124 | get_validated_token = sync_to_async(self.get_validated_token) 125 | validated_token = await get_validated_token(token) 126 | get_user = sync_to_async(self.get_user) 127 | user = await get_user(validated_token) 128 | request.user = user 129 | return user 130 | 131 | class AsyncJWTAuth(AsyncJWTBaseAuthentication, JWTAuth, AsyncHttpBearer): 132 | async def authenticate(self, request: HttpRequest, token: str) -> Any: 133 | return await self.async_jwt_authenticate(request, token) 134 | 135 | class AsyncJWTTokenUserAuth( 136 | AsyncJWTBaseAuthentication, JWTTokenUserAuth, AsyncHttpBearer 137 | ): 138 | async def authenticate(self, request: HttpRequest, token: str) -> Any: 139 | return await self.async_jwt_authenticate(request, token) 140 | -------------------------------------------------------------------------------- /ninja_jwt/backends.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import timedelta 3 | from typing import Any, Dict, Optional, Type, Union 4 | 5 | import jwt 6 | from django.utils.translation import gettext_lazy as _ 7 | from jwt import InvalidAlgorithmError, InvalidTokenError, algorithms 8 | 9 | from .exceptions import TokenBackendError 10 | from .utils import format_lazy 11 | 12 | try: 13 | from jwt import PyJWKClient, PyJWKClientError 14 | 15 | JWK_CLIENT_AVAILABLE = True 16 | except ImportError: 17 | JWK_CLIENT_AVAILABLE = False 18 | 19 | ALLOWED_ALGORITHMS = { 20 | "HS256", 21 | "HS384", 22 | "HS512", 23 | "RS256", 24 | "RS384", 25 | "RS512", 26 | "ES256", 27 | "ES384", 28 | "ES512", 29 | } 30 | 31 | 32 | class TokenBackend: 33 | def __init__( 34 | self, 35 | algorithm, 36 | signing_key=None, 37 | verifying_key="", 38 | audience=None, 39 | issuer=None, 40 | jwk_url: str = None, 41 | leeway: Union[float, int, timedelta] = None, 42 | json_encoder: Optional[Type[json.JSONEncoder]] = None, 43 | ) -> None: 44 | self._validate_algorithm(algorithm) 45 | 46 | self.algorithm = algorithm 47 | self.signing_key = signing_key 48 | self.verifying_key = verifying_key 49 | self.audience = audience 50 | self.issuer = issuer 51 | 52 | if JWK_CLIENT_AVAILABLE: 53 | self.jwks_client = PyJWKClient(jwk_url) if jwk_url else None 54 | else: 55 | self.jwks_client = None 56 | self.leeway = leeway 57 | self.json_encoder = json_encoder 58 | 59 | def _validate_algorithm(self, algorithm) -> None: 60 | """ 61 | Ensure that the nominated algorithm is recognized, and that cryptography is installed for those 62 | algorithms that require it 63 | """ 64 | if algorithm not in ALLOWED_ALGORITHMS: 65 | raise TokenBackendError( 66 | format_lazy(_("Unrecognized algorithm type '{}'"), algorithm) 67 | ) 68 | 69 | if algorithm in algorithms.requires_cryptography and not algorithms.has_crypto: 70 | raise TokenBackendError( 71 | format_lazy( 72 | _("You must have cryptography installed to use {}."), algorithm 73 | ) 74 | ) 75 | 76 | def get_leeway(self) -> timedelta: 77 | if self.leeway is None: 78 | return timedelta(seconds=0) 79 | elif isinstance(self.leeway, (int, float)): 80 | return timedelta(seconds=self.leeway) 81 | elif isinstance(self.leeway, timedelta): 82 | return self.leeway 83 | else: 84 | raise TokenBackendError( 85 | format_lazy( 86 | _( 87 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 88 | ), 89 | type(self.leeway), 90 | ) 91 | ) 92 | 93 | def get_verifying_key(self, token) -> bytes: 94 | if self.algorithm.startswith("HS"): 95 | return self.signing_key 96 | 97 | if self.jwks_client: 98 | try: 99 | return self.jwks_client.get_signing_key_from_jwt(token).key 100 | except PyJWKClientError as ex: 101 | raise TokenBackendError(_("Token is invalid or expired")) from ex 102 | 103 | return self.verifying_key 104 | 105 | def encode(self, payload: dict) -> str: 106 | """ 107 | Returns an encoded token for the given payload dictionary. 108 | """ 109 | jwt_payload = payload.copy() 110 | if self.audience is not None: 111 | jwt_payload["aud"] = self.audience 112 | if self.issuer is not None: 113 | jwt_payload["iss"] = self.issuer 114 | 115 | token = jwt.encode( 116 | jwt_payload, 117 | self.signing_key, 118 | algorithm=self.algorithm, 119 | json_encoder=self.json_encoder, 120 | ) 121 | if isinstance(token, bytes): 122 | # For PyJWT <= 1.7.1 123 | return token.decode("utf-8") 124 | # For PyJWT >= 2.0.0a1 125 | return token 126 | 127 | def decode(self, token, verify=True) -> Dict[str, Any]: 128 | """ 129 | Performs a validation of the given token and returns its payload 130 | dictionary. 131 | 132 | Raises a `TokenBackendError` if the token is malformed, if its 133 | signature check fails, or if its 'exp' claim indicates it has expired. 134 | """ 135 | try: 136 | return jwt.decode( 137 | token, 138 | self.get_verifying_key(token), 139 | algorithms=[self.algorithm], 140 | audience=self.audience, 141 | issuer=self.issuer, 142 | leeway=self.get_leeway(), 143 | options={ 144 | "verify_aud": self.audience is not None, 145 | "verify_signature": verify, 146 | }, 147 | ) 148 | except InvalidAlgorithmError as ex: 149 | raise TokenBackendError(_("Invalid algorithm specified")) from ex 150 | except InvalidTokenError as ex: 151 | raise TokenBackendError(_("Token is invalid or expired")) from ex 152 | -------------------------------------------------------------------------------- /ninja_jwt/compat.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from typing import Any 3 | 4 | try: 5 | from django.urls import reverse, reverse_lazy 6 | except ImportError: 7 | from django.core.urlresolvers import reverse, reverse_lazy # NOQA 8 | 9 | 10 | class RemovedInDjango20Warning(DeprecationWarning): 11 | pass 12 | 13 | 14 | class CallableBool: # pragma: no cover 15 | """ 16 | An boolean-like object that is also callable for backwards compatibility. 17 | """ 18 | 19 | do_not_call_in_templates = True 20 | 21 | def __init__(self, value) -> None: 22 | self.value = value 23 | 24 | def __bool__(self) -> bool: 25 | return self.value 26 | 27 | def __call__(self) -> Any: 28 | warnings.warn( 29 | "Using user.is_authenticated() and user.is_anonymous() as a method " 30 | "is deprecated. Remove the parentheses to use it as an attribute.", 31 | RemovedInDjango20Warning, 32 | stacklevel=2, 33 | ) 34 | return self.value 35 | 36 | def __nonzero__(self): # Python 2 compatibility 37 | return self.value 38 | 39 | def __repr__(self) -> str: 40 | return "CallableBool(%r)" % self.value 41 | 42 | def __eq__(self, other) -> bool: 43 | return self.value == other 44 | 45 | def __ne__(self, other) -> bool: 46 | return self.value != other 47 | 48 | def __or__(self, other) -> bool: 49 | return bool(self.value or other) 50 | 51 | def __hash__(self) -> Any: 52 | return hash(self.value) 53 | 54 | 55 | CallableFalse = CallableBool(False) 56 | CallableTrue = CallableBool(True) 57 | -------------------------------------------------------------------------------- /ninja_jwt/controller.py: -------------------------------------------------------------------------------- 1 | from asgiref.sync import sync_to_async 2 | from ninja_extra import ControllerBase, api_controller, http_post 3 | from ninja_extra.permissions import AllowAny 4 | 5 | from ninja_jwt.schema_control import SchemaControl 6 | from ninja_jwt.settings import api_settings 7 | 8 | __all__ = [ 9 | "TokenVerificationController", 10 | "TokenBlackListController", 11 | "TokenObtainPairController", 12 | "TokenObtainSlidingController", 13 | "TokenObtainSlidingController", 14 | "NinjaJWTDefaultController", 15 | "NinjaJWTSlidingController", 16 | "AsyncTokenVerificationController", 17 | "AsyncTokenBlackListController", 18 | "AsyncTokenObtainPairController", 19 | "AsyncTokenObtainSlidingController", 20 | "AsyncTokenObtainSlidingController", 21 | "AsyncNinjaJWTDefaultController", 22 | "AsyncNinjaJWTSlidingController", 23 | ] 24 | 25 | schema = SchemaControl(api_settings) 26 | 27 | 28 | class TokenVerificationController: 29 | auto_import = False 30 | 31 | @http_post( 32 | "/verify", 33 | response={200: schema.verify_schema.get_response_schema()}, 34 | url_name="token_verify", 35 | operation_id="token_verify", 36 | ) 37 | def verify_token(self, token: schema.verify_schema): 38 | return token.to_response_schema() 39 | 40 | 41 | class TokenBlackListController: 42 | auto_import = False 43 | 44 | @http_post( 45 | "/blacklist", 46 | response={200: schema.blacklist_schema.get_response_schema()}, 47 | url_name="token_blacklist", 48 | operation_id="token_blacklist", 49 | ) 50 | def blacklist_token(self, refresh: schema.blacklist_schema): 51 | return refresh.to_response_schema() 52 | 53 | 54 | class TokenObtainPairController: 55 | auto_import = False 56 | 57 | @http_post( 58 | "/pair", 59 | response=schema.obtain_pair_schema.get_response_schema(), 60 | url_name="token_obtain_pair", 61 | operation_id="token_obtain_pair", 62 | ) 63 | def obtain_token(self, user_token: schema.obtain_pair_schema): 64 | user_token.check_user_authentication_rule() 65 | return user_token.to_response_schema() 66 | 67 | @http_post( 68 | "/refresh", 69 | response=schema.obtain_pair_refresh_schema.get_response_schema(), 70 | url_name="token_refresh", 71 | operation_id="token_refresh", 72 | ) 73 | def refresh_token(self, refresh_token: schema.obtain_pair_refresh_schema): 74 | return refresh_token.to_response_schema() 75 | 76 | 77 | class TokenObtainSlidingController(TokenObtainPairController): 78 | auto_import = False 79 | 80 | @http_post( 81 | "/sliding", 82 | response=schema.obtain_sliding_schema.get_response_schema(), 83 | url_name="token_obtain_sliding", 84 | operation_id="token_obtain_sliding", 85 | ) 86 | def obtain_token(self, user_token: schema.obtain_sliding_schema): 87 | user_token.check_user_authentication_rule() 88 | return user_token.to_response_schema() 89 | 90 | @http_post( 91 | "/sliding/refresh", 92 | response=schema.obtain_sliding_refresh_schema.get_response_schema(), 93 | url_name="token_refresh_sliding", 94 | operation_id="token_refresh_sliding", 95 | ) 96 | def refresh_token(self, refresh_token: schema.obtain_sliding_refresh_schema): 97 | return refresh_token.to_response_schema() 98 | 99 | 100 | @api_controller("/token", permissions=[AllowAny], tags=["token"], auth=None) 101 | class NinjaJWTDefaultController( 102 | ControllerBase, TokenVerificationController, TokenObtainPairController 103 | ): 104 | """NinjaJWT Default controller for obtaining and refreshing tokens""" 105 | 106 | auto_import = False 107 | 108 | 109 | @api_controller("/token", permissions=[AllowAny], tags=["token"], auth=None) 110 | class NinjaJWTSlidingController( 111 | ControllerBase, TokenVerificationController, TokenObtainSlidingController 112 | ): 113 | """ 114 | NinjaJWT Sliding controller for obtaining and refreshing tokens 115 | Add 'ninja_jwt.tokens.SlidingToken' in AUTH_TOKEN_CLASSES in Settings 116 | """ 117 | 118 | auto_import = False 119 | 120 | 121 | class AsyncTokenVerificationController(TokenVerificationController): 122 | @http_post( 123 | "/verify", 124 | response={200: schema.verify_schema.get_response_schema()}, 125 | url_name="token_verify", 126 | operation_id="token_verify_async", 127 | ) 128 | async def verify_token(self, token: schema.verify_schema): 129 | return token.to_response_schema() 130 | 131 | 132 | class AsyncTokenBlackListController(TokenBlackListController): 133 | auto_import = False 134 | 135 | @http_post( 136 | "/blacklist", 137 | response={200: schema.blacklist_schema.get_response_schema()}, 138 | url_name="token_blacklist", 139 | operation_id="token_blacklist_async", 140 | ) 141 | async def blacklist_token(self, refresh: schema.blacklist_schema): 142 | return refresh.to_response_schema() 143 | 144 | 145 | class AsyncTokenObtainPairController(TokenObtainPairController): 146 | @http_post( 147 | "/pair", 148 | response=schema.obtain_pair_schema.get_response_schema(), 149 | url_name="token_obtain_pair", 150 | operation_id="token_obtain_pair_async", 151 | ) 152 | async def obtain_token(self, user_token: schema.obtain_pair_schema): 153 | await sync_to_async(user_token.check_user_authentication_rule)() 154 | return user_token.to_response_schema() 155 | 156 | @http_post( 157 | "/refresh", 158 | response=schema.obtain_pair_refresh_schema.get_response_schema(), 159 | url_name="token_refresh", 160 | operation_id="token_refresh_async", 161 | ) 162 | async def refresh_token(self, refresh_token: schema.obtain_pair_refresh_schema): 163 | refresh = await sync_to_async(refresh_token.to_response_schema)() 164 | return refresh 165 | 166 | 167 | class AsyncTokenObtainSlidingController(TokenObtainSlidingController): 168 | @http_post( 169 | "/sliding", 170 | response=schema.obtain_sliding_schema.get_response_schema(), 171 | url_name="token_obtain_sliding", 172 | operation_id="token_obtain_sliding_async", 173 | ) 174 | async def obtain_token(self, user_token: schema.obtain_sliding_schema): 175 | await sync_to_async(user_token.check_user_authentication_rule)() 176 | return user_token.to_response_schema() 177 | 178 | @http_post( 179 | "/sliding/refresh", 180 | response=schema.obtain_sliding_refresh_schema.get_response_schema(), 181 | url_name="token_refresh_sliding", 182 | operation_id="token_refresh_sliding_async", 183 | ) 184 | async def refresh_token(self, refresh_token: schema.obtain_sliding_refresh_schema): 185 | refresh = await sync_to_async(refresh_token.to_response_schema)() 186 | return refresh 187 | 188 | 189 | @api_controller("/token", permissions=[AllowAny], tags=["token"], auth=None) 190 | class AsyncNinjaJWTDefaultController( 191 | ControllerBase, AsyncTokenVerificationController, AsyncTokenObtainPairController 192 | ): 193 | """NinjaJWT Async Default controller for obtaining and refreshing tokens""" 194 | 195 | auto_import = False 196 | 197 | 198 | @api_controller("/token", permissions=[AllowAny], tags=["token"], auth=None) 199 | class AsyncNinjaJWTSlidingController( 200 | ControllerBase, AsyncTokenVerificationController, AsyncTokenObtainSlidingController 201 | ): 202 | """ 203 | NinjaJWT Async Sliding controller for obtaining and refreshing tokens 204 | Add 'ninja_jwt.tokens.SlidingToken' in AUTH_TOKEN_CLASSES in Settings 205 | """ 206 | 207 | auto_import = False 208 | -------------------------------------------------------------------------------- /ninja_jwt/exceptions.py: -------------------------------------------------------------------------------- 1 | from django.utils.translation import gettext_lazy as _ 2 | from ninja_extra import exceptions, status 3 | 4 | 5 | class DetailDictMixin: 6 | default_detail: str 7 | default_code: str 8 | 9 | def __init__(self, detail=None, code=None) -> None: 10 | """ 11 | Builds a detail dictionary for the error to give more information to API 12 | users. 13 | """ 14 | detail_dict = {"detail": self.default_detail, "code": self.default_code} 15 | 16 | if isinstance(detail, dict): 17 | detail_dict.update(detail) 18 | elif detail is not None: 19 | detail_dict["detail"] = detail 20 | 21 | if code is not None: 22 | detail_dict["code"] = code 23 | 24 | super().__init__(detail_dict) 25 | 26 | 27 | class TokenError(Exception): 28 | pass 29 | 30 | 31 | class TokenBackendError(Exception): 32 | pass 33 | 34 | 35 | class AuthenticationFailed(DetailDictMixin, exceptions.AuthenticationFailed): 36 | pass 37 | 38 | 39 | class InvalidToken(AuthenticationFailed): 40 | status_code = status.HTTP_401_UNAUTHORIZED 41 | default_detail = _("Token is invalid or expired") 42 | default_code = "token_not_valid" 43 | 44 | 45 | class ValidationError(DetailDictMixin, exceptions.APIException): 46 | status_code = status.HTTP_400_BAD_REQUEST 47 | default_detail = _("Invalid input.") 48 | default_code = "invalid" 49 | -------------------------------------------------------------------------------- /ninja_jwt/locale/cs/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/cs/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/cs/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR , 2019. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-ninja-jwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2021-02-22 17:30+0100\n" 8 | "Last-Translator: Lukáš Rod \n" 9 | "Language: cs\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | 14 | #: authentication.py:78 15 | msgid "Authorization header must contain two space-delimited values" 16 | msgstr "Autorizační hlavička musí obsahovat dvě hodnoty oddělené mezerou" 17 | 18 | #: authentication.py:104 19 | msgid "Given token not valid for any token type" 20 | msgstr "Daný token není validní pro žádný typ tokenu" 21 | 22 | #: authentication.py:116 authentication.py:143 23 | msgid "Token contained no recognizable user identification" 24 | msgstr "Token neobsahoval žádnou rozpoznatelnou identifikaci uživatele" 25 | 26 | #: authentication.py:121 27 | msgid "User not found" 28 | msgstr "Uživatel nenalezen" 29 | 30 | #: authentication.py:124 31 | msgid "User is inactive" 32 | msgstr "Uživatel není aktivní" 33 | 34 | #: backends.py:67 35 | msgid "Unrecognized algorithm type '{}'" 36 | msgstr "Nerozpoznaný typ algoritmu '{}'" 37 | 38 | #: backends.py:73 39 | msgid "You must have cryptography installed to use {}." 40 | msgstr "" 41 | 42 | #: backends.py:88 43 | msgid "" 44 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 45 | msgstr "" 46 | 47 | #: backends.py:147 48 | msgid "Invalid algorithm specified" 49 | msgstr "" 50 | 51 | #: backends.py:149 exceptions.py:38 tokens.py:44 52 | msgid "Token is invalid or expired" 53 | msgstr "Token není validní nebo vypršela jeho platnost" 54 | 55 | #: serializers.py:30 56 | msgid "No active account found with the given credentials" 57 | msgstr "Žádný aktivní účet s danými údaji nebyl nalezen" 58 | 59 | #: settings.py:70 60 | msgid "" 61 | "The '{}' setting has been removed. Please refer to '{}' for available " 62 | "settings." 63 | msgstr "Nastavení '{}' bylo odstraněno. Dostupná nastavení jsou v '{}'" 64 | 65 | #: token_blacklist/admin.py:68 66 | msgid "jti" 67 | msgstr "jti" 68 | 69 | #: token_blacklist/admin.py:74 70 | msgid "user" 71 | msgstr "uživatel" 72 | 73 | #: token_blacklist/admin.py:80 74 | msgid "created at" 75 | msgstr "vytvořený v" 76 | 77 | #: token_blacklist/admin.py:86 78 | msgid "expires at" 79 | msgstr "platí do" 80 | 81 | #: token_blacklist/apps.py:7 82 | msgid "Token Blacklist" 83 | msgstr "Token Blacklist" 84 | 85 | #: tokens.py:30 86 | msgid "Cannot create token with no type or lifetime" 87 | msgstr "Nelze vytvořit token bez zadaného typu nebo životnosti" 88 | 89 | #: tokens.py:102 90 | msgid "Token has no id" 91 | msgstr "Token nemá žádný identifikátor" 92 | 93 | #: tokens.py:115 94 | msgid "Token has no type" 95 | msgstr "Token nemá žádný typ" 96 | 97 | #: tokens.py:118 98 | msgid "Token has wrong type" 99 | msgstr "Token má špatný typ" 100 | 101 | #: tokens.py:170 102 | msgid "Token has no '{}' claim" 103 | msgstr "Token nemá žádnou hodnotu '{}'" 104 | 105 | #: tokens.py:175 106 | msgid "Token '{}' claim has expired" 107 | msgstr "Hodnota tokenu '{}' vypršela" 108 | 109 | #: tokens.py:230 110 | msgid "Token is blacklisted" 111 | msgstr "Token je na černé listině" 112 | -------------------------------------------------------------------------------- /ninja_jwt/locale/de_CH/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/de_CH/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/de_CH/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR , 2020. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-ninja-jwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2021-02-22 17:30+0100\n" 8 | "Last-Translator: rene \n" 9 | "Language: de_CH\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 14 | 15 | #: authentication.py:78 16 | msgid "Authorization header must contain two space-delimited values" 17 | msgstr "" 18 | "Der Authorizationheader muss zwei leerzeichen-getrennte Werte enthalten" 19 | 20 | #: authentication.py:104 21 | msgid "Given token not valid for any token type" 22 | msgstr "Der Token ist für keinen Tokentyp gültig" 23 | 24 | #: authentication.py:116 authentication.py:143 25 | msgid "Token contained no recognizable user identification" 26 | msgstr "Token enthält keine erkennbare Benutzeridentifikation" 27 | 28 | #: authentication.py:121 29 | msgid "User not found" 30 | msgstr "Benutzer nicht gefunden" 31 | 32 | #: authentication.py:124 33 | msgid "User is inactive" 34 | msgstr "Inaktiver Benutzer" 35 | 36 | #: backends.py:67 37 | msgid "Unrecognized algorithm type '{}'" 38 | msgstr "Unerkannter Algorithmustyp '{}'" 39 | 40 | #: backends.py:73 41 | msgid "You must have cryptography installed to use {}." 42 | msgstr "" 43 | 44 | #: backends.py:88 45 | msgid "" 46 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 47 | msgstr "" 48 | 49 | #: backends.py:147 50 | msgid "Invalid algorithm specified" 51 | msgstr "" 52 | 53 | #: backends.py:149 exceptions.py:38 tokens.py:44 54 | msgid "Token is invalid or expired" 55 | msgstr "Ungültiger oder abgelaufener Token" 56 | 57 | #: serializers.py:30 58 | msgid "No active account found with the given credentials" 59 | msgstr "Kein aktiver Account mit diesen Zugangsdaten gefunden" 60 | 61 | #: settings.py:70 62 | msgid "" 63 | "The '{}' setting has been removed. Please refer to '{}' for available " 64 | "settings." 65 | msgstr "" 66 | "Die Einstellung '{}' wurde gelöscht. Bitte beachte '{}' für verfügbare " 67 | "Einstellungen." 68 | 69 | #: token_blacklist/admin.py:68 70 | msgid "jti" 71 | msgstr "jti" 72 | 73 | #: token_blacklist/admin.py:74 74 | msgid "user" 75 | msgstr "Benutzer" 76 | 77 | #: token_blacklist/admin.py:80 78 | msgid "created at" 79 | msgstr "erstellt am" 80 | 81 | #: token_blacklist/admin.py:86 82 | msgid "expires at" 83 | msgstr "läuft ab am" 84 | 85 | #: token_blacklist/apps.py:7 86 | msgid "Token Blacklist" 87 | msgstr "Token Blacklist" 88 | 89 | #: tokens.py:30 90 | msgid "Cannot create token with no type or lifetime" 91 | msgstr "Ein Token ohne Typ oder Lebensdauer kann nicht erstellt werden" 92 | 93 | #: tokens.py:102 94 | msgid "Token has no id" 95 | msgstr "Token hat keine Id" 96 | 97 | #: tokens.py:115 98 | msgid "Token has no type" 99 | msgstr "Token hat keinen Typ" 100 | 101 | #: tokens.py:118 102 | msgid "Token has wrong type" 103 | msgstr "Token hat den falschen Typ" 104 | 105 | #: tokens.py:170 106 | msgid "Token has no '{}' claim" 107 | msgstr "Token hat kein '{}' Recht" 108 | 109 | #: tokens.py:175 110 | msgid "Token '{}' claim has expired" 111 | msgstr "Das Tokenrecht '{}' ist abgelaufen" 112 | 113 | #: tokens.py:230 114 | msgid "Token is blacklisted" 115 | msgstr "Token steht auf der Blacklist" 116 | -------------------------------------------------------------------------------- /ninja_jwt/locale/es/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/es/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/es/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR , 2020. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-ninja-jwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2021-02-22 17:30+0100\n" 8 | "Last-Translator: zeack \n" 9 | "Language: es\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 14 | 15 | #: authentication.py:78 16 | msgid "Authorization header must contain two space-delimited values" 17 | msgstr "" 18 | "El encabezado 'Authorization' debe contener valores delimitados por espacios" 19 | 20 | #: authentication.py:104 21 | msgid "Given token not valid for any token type" 22 | msgstr "El token dado no es valido para ningun tipo de token" 23 | 24 | #: authentication.py:116 authentication.py:143 25 | msgid "Token contained no recognizable user identification" 26 | msgstr "El token no contenía identificación de usuario reconocible" 27 | 28 | #: authentication.py:121 29 | msgid "User not found" 30 | msgstr "Usuario no encontrado" 31 | 32 | #: authentication.py:124 33 | msgid "User is inactive" 34 | msgstr "El usuario está inactivo" 35 | 36 | #: backends.py:67 37 | msgid "Unrecognized algorithm type '{}'" 38 | msgstr "Tipo de algoritmo no reconocido '{}'" 39 | 40 | #: backends.py:73 41 | msgid "You must have cryptography installed to use {}." 42 | msgstr "Debe tener criptografía instalada para usar {}." 43 | 44 | #: backends.py:88 45 | msgid "" 46 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 47 | msgstr "" 48 | 49 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 50 | msgid "Token is invalid or expired" 51 | msgstr "El token es inválido o ha expirado" 52 | 53 | #: backends.py:150 54 | msgid "Invalid algorithm specified" 55 | msgstr "Algoritmo especificado no válido" 56 | 57 | #: serializers.py:30 58 | msgid "No active account found with the given credentials" 59 | msgstr "La combinación de credenciales no tiene una cuenta activa" 60 | 61 | #: settings.py:70 62 | msgid "" 63 | "The '{}' setting has been removed. Please refer to '{}' for available " 64 | "settings." 65 | msgstr "" 66 | "La configuración '{}' fue removida. Por favor, refiérase a '{}' para " 67 | "consultar las disponibles." 68 | 69 | #: token_blacklist/admin.py:68 70 | msgid "jti" 71 | msgstr "jti" 72 | 73 | #: token_blacklist/admin.py:74 74 | msgid "user" 75 | msgstr "usuario" 76 | 77 | #: token_blacklist/admin.py:80 78 | msgid "created at" 79 | msgstr "creado en" 80 | 81 | #: token_blacklist/admin.py:86 82 | msgid "expires at" 83 | msgstr "expira en" 84 | 85 | #: token_blacklist/apps.py:7 86 | msgid "Token Blacklist" 87 | msgstr "Lista negra de Tokens" 88 | 89 | #: tokens.py:30 90 | msgid "Cannot create token with no type or lifetime" 91 | msgstr "No se puede crear un token sin tipo o de tan larga vida" 92 | 93 | #: tokens.py:102 94 | msgid "Token has no id" 95 | msgstr "El token no tiene id" 96 | 97 | #: tokens.py:115 98 | msgid "Token has no type" 99 | msgstr "El token no tiene tipo" 100 | 101 | #: tokens.py:118 102 | msgid "Token has wrong type" 103 | msgstr "El token tiene un tipo incorrecto" 104 | 105 | #: tokens.py:170 106 | msgid "Token has no '{}' claim" 107 | msgstr "El token no tiene el privilegio '{}'" 108 | 109 | #: tokens.py:175 110 | msgid "Token '{}' claim has expired" 111 | msgstr "El privilegio '{}' del token ha expirado" 112 | 113 | #: tokens.py:230 114 | msgid "Token is blacklisted" 115 | msgstr "El token está en lista negra" 116 | -------------------------------------------------------------------------------- /ninja_jwt/locale/es_AR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/es_AR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/es_AR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 2 | # This file is distributed under the same license as the PACKAGE package. 3 | # 4 | # Translators: 5 | # Ariel Torti , 2020. 6 | # 7 | #, fuzzy 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: django-ninja-jwt\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2021-02-22 17:30+0100\n" 13 | "Last-Translator: Ariel Torti \n" 14 | "Language: es_AR\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | 20 | #: authentication.py:78 21 | msgid "Authorization header must contain two space-delimited values" 22 | msgstr "" 23 | "El header de autorización debe contener dos valores delimitados por espacio" 24 | 25 | #: authentication.py:104 26 | msgid "Given token not valid for any token type" 27 | msgstr "El token dado no es válido para ningún tipo de token" 28 | 29 | #: authentication.py:116 authentication.py:143 30 | msgid "Token contained no recognizable user identification" 31 | msgstr "El token no contiene ninguna identificación de usuario" 32 | 33 | #: authentication.py:121 34 | msgid "User not found" 35 | msgstr "Usuario no encontrado" 36 | 37 | #: authentication.py:124 38 | msgid "User is inactive" 39 | msgstr "El usuario está inactivo" 40 | 41 | #: backends.py:67 42 | msgid "Unrecognized algorithm type '{}'" 43 | msgstr "Tipo de algoritmo no reconocido '{}'" 44 | 45 | #: backends.py:73 46 | msgid "You must have cryptography installed to use {}." 47 | msgstr "" 48 | 49 | #: backends.py:88 50 | msgid "" 51 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 52 | 53 | msgstr "" 54 | 55 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 56 | msgid "Token is invalid or expired" 57 | msgstr "El token es inválido o ha expirado" 58 | 59 | #: backends.py:150 60 | msgid "Invalid algorithm specified" 61 | msgstr "" 62 | 63 | #: serializers.py:30 64 | msgid "No active account found with the given credentials" 65 | msgstr "" 66 | "No se encontró una cuenta de usuario activa para las credenciales dadas" 67 | 68 | #: settings.py:70 69 | msgid "" 70 | "The '{}' setting has been removed. Please refer to '{}' for available " 71 | "settings." 72 | msgstr "" 73 | "La configuración '{}' fue removida. Por favor, refiérase a '{}' para " 74 | "consultar las configuraciones disponibles." 75 | 76 | #: token_blacklist/admin.py:68 77 | msgid "jti" 78 | msgstr "jti" 79 | 80 | #: token_blacklist/admin.py:74 81 | msgid "user" 82 | msgstr "usuario" 83 | 84 | #: token_blacklist/admin.py:80 85 | msgid "created at" 86 | msgstr "creado en" 87 | 88 | #: token_blacklist/admin.py:86 89 | msgid "expires at" 90 | msgstr "expira en" 91 | 92 | #: token_blacklist/apps.py:7 93 | msgid "Token Blacklist" 94 | msgstr "Lista negra de Tokens" 95 | 96 | #: tokens.py:30 97 | msgid "Cannot create token with no type or lifetime" 98 | msgstr "No es posible crear un token sin tipo o tiempo de vida" 99 | 100 | #: tokens.py:102 101 | msgid "Token has no id" 102 | msgstr "El token no tiene id" 103 | 104 | #: tokens.py:115 105 | msgid "Token has no type" 106 | msgstr "El token no tiene tipo" 107 | 108 | #: tokens.py:118 109 | msgid "Token has wrong type" 110 | msgstr "El token tiene un tipo incorrecto" 111 | 112 | #: tokens.py:170 113 | msgid "Token has no '{}' claim" 114 | msgstr "El token no tiene el privilegio '{}'" 115 | 116 | #: tokens.py:175 117 | msgid "Token '{}' claim has expired" 118 | msgstr "El privilegio '{}' del token ha expirado" 119 | 120 | #: tokens.py:230 121 | msgid "Token is blacklisted" 122 | msgstr "El token está en la lista negra" 123 | -------------------------------------------------------------------------------- /ninja_jwt/locale/es_CL/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/es_CL/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/es_CL/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR , 2019. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-ninja-jwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2021-02-22 17:30+0100\n" 8 | "Last-Translator: Alfonso Pola \n" 9 | "Language: es_CL\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 14 | 15 | #: authentication.py:78 16 | msgid "Authorization header must contain two space-delimited values" 17 | msgstr "" 18 | "El header de autorización debe contener dos valores delimitados por espacio" 19 | 20 | #: authentication.py:104 21 | msgid "Given token not valid for any token type" 22 | msgstr "El token provisto no es válido para ningún tipo de token" 23 | 24 | #: authentication.py:116 authentication.py:143 25 | msgid "Token contained no recognizable user identification" 26 | msgstr "El token no contiene identificación de usuario reconocible" 27 | 28 | #: authentication.py:121 29 | msgid "User not found" 30 | msgstr "Usuario no encontrado" 31 | 32 | #: authentication.py:124 33 | msgid "User is inactive" 34 | msgstr "El usuario está inactivo" 35 | 36 | #: backends.py:67 37 | msgid "Unrecognized algorithm type '{}'" 38 | msgstr "Tipo de algoritmo no reconocido '{}'" 39 | 40 | #: backends.py:73 41 | msgid "You must have cryptography installed to use {}." 42 | msgstr "" 43 | 44 | #: backends.py:88 45 | msgid "" 46 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 47 | msgstr "" 48 | 49 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 50 | msgid "Token is invalid or expired" 51 | msgstr "Token inválido o expirado" 52 | 53 | #: backends.py:150 54 | msgid "Invalid algorithm specified" 55 | msgstr "" 56 | 57 | #: serializers.py:30 58 | msgid "No active account found with the given credentials" 59 | msgstr "" 60 | "No se encontró una cuenta de usuario activa para las credenciales provistas" 61 | 62 | #: settings.py:70 63 | msgid "" 64 | "The '{}' setting has been removed. Please refer to '{}' for available " 65 | "settings." 66 | msgstr "" 67 | "La configuración '{}' fue removida. Por favor, refiérase a '{}' para " 68 | "configuraciones disponibles." 69 | 70 | #: token_blacklist/admin.py:68 71 | msgid "jti" 72 | msgstr "jti" 73 | 74 | #: token_blacklist/admin.py:74 75 | msgid "user" 76 | msgstr "Usuario" 77 | 78 | #: token_blacklist/admin.py:80 79 | msgid "created at" 80 | msgstr "creado en" 81 | 82 | #: token_blacklist/admin.py:86 83 | msgid "expires at" 84 | msgstr "expira en" 85 | 86 | #: token_blacklist/apps.py:7 87 | msgid "Token Blacklist" 88 | msgstr "Token Blacklist" 89 | 90 | #: tokens.py:30 91 | msgid "Cannot create token with no type or lifetime" 92 | msgstr "No es posible crear un token sin tipo o tiempo de vida" 93 | 94 | #: tokens.py:102 95 | msgid "Token has no id" 96 | msgstr "Token no tiene id" 97 | 98 | #: tokens.py:115 99 | msgid "Token has no type" 100 | msgstr "Token no tiene tipo" 101 | 102 | #: tokens.py:118 103 | msgid "Token has wrong type" 104 | msgstr "Token tiene tipo erróneo" 105 | 106 | #: tokens.py:170 107 | msgid "Token has no '{}' claim" 108 | msgstr "Token no tiene privilegio '{}'" 109 | 110 | #: tokens.py:175 111 | msgid "Token '{}' claim has expired" 112 | msgstr "El provilegio '{}' del token está expirado" 113 | 114 | #: tokens.py:230 115 | msgid "Token is blacklisted" 116 | msgstr "Token está en la blacklist" 117 | -------------------------------------------------------------------------------- /ninja_jwt/locale/fa_IR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/fa_IR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/fa_IR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR , 2020. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-ninja-jwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2021-02-22 17:30+0100\n" 8 | "Last-Translator: Hirad Daneshvar \n" 9 | "Language: fa_IR\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | 14 | #: authentication.py:78 15 | msgid "Authorization header must contain two space-delimited values" 16 | msgstr "هدر اعتبارسنجی باید شامل دو مقدار جدا شده با فاصله باشد" 17 | 18 | #: authentication.py:104 19 | msgid "Given token not valid for any token type" 20 | msgstr "توکن داده شده برای هیچ نوع توکنی معتبر نمی‌باشد" 21 | 22 | #: authentication.py:116 authentication.py:143 23 | msgid "Token contained no recognizable user identification" 24 | msgstr "توکن شامل هیچ شناسه قابل تشخیصی از کاربر نیست" 25 | 26 | #: authentication.py:121 27 | msgid "User not found" 28 | msgstr "کاربر یافت نشد" 29 | 30 | #: authentication.py:124 31 | msgid "User is inactive" 32 | msgstr "کاربر غیرفعال است" 33 | 34 | #: backends.py:67 35 | msgid "Unrecognized algorithm type '{}'" 36 | msgstr "نوع الگوریتم ناشناخته '{}'" 37 | 38 | #: backends.py:73 39 | msgid "You must have cryptography installed to use {}." 40 | msgstr "" 41 | 42 | #: backends.py:88 43 | msgid "" 44 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 45 | 46 | msgstr "" 47 | 48 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 49 | msgid "Token is invalid or expired" 50 | msgstr "توکن نامعتبر است یا منقضی شده است" 51 | 52 | #: backends.py:150 53 | msgid "Invalid algorithm specified" 54 | msgstr "" 55 | 56 | #: serializers.py:30 57 | msgid "No active account found with the given credentials" 58 | msgstr "هیچ اکانت فعالی برای اطلاعات داده شده یافت نشد" 59 | 60 | #: settings.py:70 61 | msgid "" 62 | "The '{}' setting has been removed. Please refer to '{}' for available " 63 | "settings." 64 | msgstr "تنظیمات '{}' حذف شده است. لطفا به '{}' برای تنظیمات موجود مراجعه کنید." 65 | 66 | #: token_blacklist/admin.py:68 67 | msgid "jti" 68 | msgstr "jti" 69 | 70 | #: token_blacklist/admin.py:74 71 | msgid "user" 72 | msgstr "کاربر" 73 | 74 | #: token_blacklist/admin.py:80 75 | msgid "created at" 76 | msgstr "زمان ایجاد" 77 | 78 | #: token_blacklist/admin.py:86 79 | msgid "expires at" 80 | msgstr "زمان انقضا" 81 | 82 | #: token_blacklist/apps.py:7 83 | msgid "Token Blacklist" 84 | msgstr "لیست سیاه توکن" 85 | 86 | #: tokens.py:30 87 | msgid "Cannot create token with no type or lifetime" 88 | msgstr "توکن بدون هیچ نوع و طول عمر قابل ساخت نیست" 89 | 90 | #: tokens.py:102 91 | msgid "Token has no id" 92 | msgstr "توکن id ندارد" 93 | 94 | #: tokens.py:115 95 | msgid "Token has no type" 96 | msgstr "توکن نوع ندارد" 97 | 98 | #: tokens.py:118 99 | msgid "Token has wrong type" 100 | msgstr "توکن نوع اشتباهی دارد" 101 | 102 | #: tokens.py:170 103 | msgid "Token has no '{}' claim" 104 | msgstr "توکن دارای '{}' claim نمی‌باشد" 105 | 106 | #: tokens.py:175 107 | msgid "Token '{}' claim has expired" 108 | msgstr "'{}' claim توکن منقضی شده" 109 | 110 | #: tokens.py:230 111 | msgid "Token is blacklisted" 112 | msgstr "توکن به لیست سیاه رفته است" 113 | -------------------------------------------------------------------------------- /ninja_jwt/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/fr/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR , 2020. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-ninja-jwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2021-02-22 17:30+0100\n" 8 | "Last-Translator: Stéphane Malta e Sousa \n" 9 | "Language: fr\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | 14 | #: authentication.py:78 15 | msgid "Authorization header must contain two space-delimited values" 16 | msgstr "" 17 | "L'en-tête 'Authorization' doit contenir deux valeurs séparées par des espaces" 18 | 19 | #: authentication.py:104 20 | msgid "Given token not valid for any token type" 21 | msgstr "Le type de jeton fourni n'est pas valide" 22 | 23 | #: authentication.py:116 authentication.py:143 24 | msgid "Token contained no recognizable user identification" 25 | msgstr "" 26 | "Le jeton ne contient aucune information permettant d'identifier l'utilisateur" 27 | 28 | #: authentication.py:121 29 | msgid "User not found" 30 | msgstr "L'utilisateur n'a pas été trouvé" 31 | 32 | #: authentication.py:124 33 | msgid "User is inactive" 34 | msgstr "L'utilisateur est désactivé" 35 | 36 | #: backends.py:67 37 | msgid "Unrecognized algorithm type '{}'" 38 | msgstr "Type d'algorithme non reconnu '{}'" 39 | 40 | #: backends.py:73 41 | msgid "You must have cryptography installed to use {}." 42 | msgstr "Vous devez installer cryptography afin d'utiliser {}." 43 | 44 | #: backends.py:88 45 | msgid "" 46 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 47 | msgstr "" 48 | 49 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 50 | msgid "Token is invalid or expired" 51 | msgstr "Le jeton est invalide ou expiré" 52 | 53 | #: backends.py:150 54 | msgid "Invalid algorithm specified" 55 | msgstr "L'algorithme spécifié est invalide" 56 | 57 | #: serializers.py:30 58 | msgid "No active account found with the given credentials" 59 | msgstr "Aucun compte actif n'a été trouvé avec les identifiants fournis" 60 | 61 | #: settings.py:70 62 | msgid "" 63 | "The '{}' setting has been removed. Please refer to '{}' for available " 64 | "settings." 65 | msgstr "" 66 | "Le paramètre '{}' a été supprimé. Voir '{}' pour la liste des paramètres " 67 | "disponibles." 68 | 69 | #: token_blacklist/admin.py:68 70 | msgid "jti" 71 | msgstr "jti" 72 | 73 | #: token_blacklist/admin.py:74 74 | msgid "user" 75 | msgstr "Utilisateur" 76 | 77 | #: token_blacklist/admin.py:80 78 | msgid "created at" 79 | msgstr "Créé le" 80 | 81 | #: token_blacklist/admin.py:86 82 | msgid "expires at" 83 | msgstr "Expire le" 84 | 85 | #: token_blacklist/apps.py:7 86 | msgid "Token Blacklist" 87 | msgstr "Liste des jetons bannis" 88 | 89 | #: tokens.py:30 90 | msgid "Cannot create token with no type or lifetime" 91 | msgstr "Ne peut pas créer de jeton sans type ni durée de vie" 92 | 93 | #: tokens.py:102 94 | msgid "Token has no id" 95 | msgstr "Le jeton n'a pas d'id" 96 | 97 | #: tokens.py:115 98 | msgid "Token has no type" 99 | msgstr "Le jeton n'a pas de type" 100 | 101 | #: tokens.py:118 102 | msgid "Token has wrong type" 103 | msgstr "Le jeton a un type erroné" 104 | 105 | #: tokens.py:170 106 | msgid "Token has no '{}' claim" 107 | msgstr "Le jeton n'a pas le privilège '{}'" 108 | 109 | #: tokens.py:175 110 | msgid "Token '{}' claim has expired" 111 | msgstr "Le privilège '{}' du jeton a expiré" 112 | 113 | #: tokens.py:230 114 | msgid "Token is blacklisted" 115 | msgstr "Le jeton a été banni" 116 | -------------------------------------------------------------------------------- /ninja_jwt/locale/id_ID/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/id_ID/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/id_ID/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR , 2020. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-ninja-jwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2021-02-22 17:30+0100\n" 8 | "Last-Translator: oon arfiandwi \n" 9 | "Language: id_ID\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 14 | 15 | #: authentication.py:78 16 | msgid "Authorization header must contain two space-delimited values" 17 | msgstr "Header otorisasi harus berisi dua nilai yang dipisahkan spasi" 18 | 19 | #: authentication.py:104 20 | msgid "Given token not valid for any token type" 21 | msgstr "Token yang diberikan tidak valid untuk semua jenis token" 22 | 23 | #: authentication.py:116 authentication.py:143 24 | msgid "Token contained no recognizable user identification" 25 | msgstr "Token tidak mengandung identifikasi pengguna yang dapat dikenali" 26 | 27 | #: authentication.py:121 28 | msgid "User not found" 29 | msgstr "Pengguna tidak ditemukan" 30 | 31 | #: authentication.py:124 32 | msgid "User is inactive" 33 | msgstr "Pengguna tidak aktif" 34 | 35 | #: backends.py:67 36 | msgid "Unrecognized algorithm type '{}'" 37 | msgstr "Jenis algoritma tidak dikenal '{}'" 38 | 39 | #: backends.py:73 40 | msgid "You must have cryptography installed to use {}." 41 | msgstr "Anda harus memasang kriptografi untuk menggunakan {}." 42 | 43 | #: backends.py:88 44 | msgid "" 45 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 46 | msgstr "" 47 | 48 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 49 | msgid "Token is invalid or expired" 50 | msgstr "Token tidak valid atau kedaluwarsa" 51 | 52 | #: backends.py:150 53 | msgid "Invalid algorithm specified" 54 | msgstr "Algoritma yang ditentukan tidak valid" 55 | 56 | #: serializers.py:30 57 | msgid "No active account found with the given credentials" 58 | msgstr "Tidak ada akun aktif yang ditemukan dengan kredensial yang diberikan" 59 | 60 | #: settings.py:70 61 | msgid "" 62 | "The '{}' setting has been removed. Please refer to '{}' for available " 63 | "settings." 64 | msgstr "" 65 | "Setelan '{}' telah dihapus. Silakan merujuk ke '{}' untuk pengaturan yang " 66 | "tersedia." 67 | 68 | #: token_blacklist/admin.py:68 69 | msgid "jti" 70 | msgstr "jti" 71 | 72 | #: token_blacklist/admin.py:74 73 | msgid "user" 74 | msgstr "pengguna" 75 | 76 | #: token_blacklist/admin.py:80 77 | msgid "created at" 78 | msgstr "created at" 79 | 80 | #: token_blacklist/admin.py:86 81 | msgid "expires at" 82 | msgstr "kedaluwarsa pada" 83 | 84 | #: token_blacklist/apps.py:7 85 | msgid "Token Blacklist" 86 | msgstr "Daftar Hitam Token" 87 | 88 | #: tokens.py:30 89 | msgid "Cannot create token with no type or lifetime" 90 | msgstr "Tidak dapat membuat token tanpa tipe atau masa pakai" 91 | 92 | #: tokens.py:102 93 | msgid "Token has no id" 94 | msgstr "Token tidak memiliki id" 95 | 96 | #: tokens.py:115 97 | msgid "Token has no type" 98 | msgstr "Token tidak memiliki tipe" 99 | 100 | #: tokens.py:118 101 | msgid "Token has wrong type" 102 | msgstr "Jenis token salah" 103 | 104 | #: tokens.py:170 105 | msgid "Token has no '{}' claim" 106 | msgstr "Token tidak memiliki klaim '{}'" 107 | 108 | #: tokens.py:175 109 | msgid "Token '{}' claim has expired" 110 | msgstr "Klaim token '{}' telah kedaluwarsa" 111 | 112 | #: tokens.py:230 113 | msgid "Token is blacklisted" 114 | msgstr "Token masuk daftar hitam" 115 | -------------------------------------------------------------------------------- /ninja_jwt/locale/it_IT/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/it_IT/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/it_IT/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR <95adriano@gmail.com>, 2020. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-ninja-jwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2021-02-22 17:30+0100\n" 8 | "PO-Revision-Date: \n" 9 | "Last-Translator: Adriano Di Dio <95adriano@gmail.com>\n" 10 | "Language-Team: \n" 11 | "Language: it_IT\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "X-Generator: Poedit 2.0.6\n" 16 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 17 | 18 | #: authentication.py:78 19 | msgid "Authorization header must contain two space-delimited values" 20 | msgstr "" 21 | "L'header di autorizzazione deve contenere due valori delimitati da uno spazio" 22 | 23 | #: authentication.py:104 24 | msgid "Given token not valid for any token type" 25 | msgstr "Il token dato non è valido per qualsiasi tipo di token" 26 | 27 | #: authentication.py:116 authentication.py:143 28 | msgid "Token contained no recognizable user identification" 29 | msgstr "Il token non conteneva nessuna informazione riconoscibile dell'utente" 30 | 31 | #: authentication.py:121 32 | msgid "User not found" 33 | msgstr "Utente non trovato" 34 | 35 | #: authentication.py:124 36 | msgid "User is inactive" 37 | msgstr "Utente non attivo" 38 | 39 | #: backends.py:67 40 | msgid "Unrecognized algorithm type '{}'" 41 | msgstr "Algoritmo di tipo '{}' non riconosciuto" 42 | 43 | #: backends.py:73 44 | msgid "You must have cryptography installed to use {}." 45 | msgstr "Devi avere installato cryptography per usare '{}'." 46 | 47 | #: backends.py:88 48 | msgid "" 49 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 50 | msgstr "" 51 | 52 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 53 | msgid "Token is invalid or expired" 54 | msgstr "Il token non è valido o è scaduto" 55 | 56 | #: backends.py:150 57 | msgid "Invalid algorithm specified" 58 | msgstr "L'algoritmo specificato non è valido" 59 | 60 | #: serializers.py:30 61 | msgid "No active account found with the given credentials" 62 | msgstr "Nessun account attivo trovato con queste credenziali" 63 | 64 | #: settings.py:70 65 | msgid "" 66 | "The '{}' setting has been removed. Please refer to '{}' for available " 67 | "settings." 68 | msgstr "" 69 | "L'impostazione '{}' è stata rimossa. Per favore utilizza '{}' per " 70 | "visualizzare le impostazioni valide." 71 | 72 | #: token_blacklist/admin.py:68 73 | msgid "jti" 74 | msgstr "jti" 75 | 76 | #: token_blacklist/admin.py:74 77 | msgid "user" 78 | msgstr "utente" 79 | 80 | #: token_blacklist/admin.py:80 81 | msgid "created at" 82 | msgstr "creato il" 83 | 84 | #: token_blacklist/admin.py:86 85 | msgid "expires at" 86 | msgstr "scade il" 87 | 88 | #: token_blacklist/apps.py:7 89 | msgid "Token Blacklist" 90 | msgstr "Blacklist dei token" 91 | 92 | #: tokens.py:30 93 | msgid "Cannot create token with no type or lifetime" 94 | msgstr "Impossibile creare un token senza tipo o durata" 95 | 96 | #: tokens.py:102 97 | msgid "Token has no id" 98 | msgstr "Il token non ha un id" 99 | 100 | #: tokens.py:115 101 | msgid "Token has no type" 102 | msgstr "Il token non ha un tipo" 103 | 104 | #: tokens.py:118 105 | msgid "Token has wrong type" 106 | msgstr "Il token ha un tipo sbagliato" 107 | 108 | #: tokens.py:170 109 | msgid "Token has no '{}' claim" 110 | msgstr "Il token non contiene il parametro '{}'" 111 | 112 | #: tokens.py:175 113 | msgid "Token '{}' claim has expired" 114 | msgstr "Il parametro '{}' del token è scaduto" 115 | 116 | #: tokens.py:230 117 | msgid "Token is blacklisted" 118 | msgstr "Il token è stato inserito nella blacklist" 119 | -------------------------------------------------------------------------------- /ninja_jwt/locale/ko_KR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/ko_KR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/ko_KR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR 양영광 , 2022. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: djangorestframework_simplejwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2022-01-05 06:48+0900\n" 8 | "Last-Translator: 양영광 \n" 9 | "Language: ko_KR\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=1; plural=0;\n" 14 | 15 | #: authentication.py:78 16 | msgid "Authorization header must contain two space-delimited values" 17 | msgstr "인증 헤더에는 공백으로 구분 된 두 개의 값이 포함되어야 합니다" 18 | 19 | #: authentication.py:104 20 | msgid "Given token not valid for any token type" 21 | msgstr "이 토큰은 모든 타입의 토큰에 대해 유효하지 않습니다" 22 | 23 | #: authentication.py:116 authentication.py:143 24 | msgid "Token contained no recognizable user identification" 25 | msgstr "토큰에 사용자 식별자가 포함되어 있지 않습니다" 26 | 27 | #: authentication.py:121 28 | msgid "User not found" 29 | msgstr "찾을 수 없는 사용자" 30 | 31 | #: authentication.py:124 32 | msgid "User is inactive" 33 | msgstr "비활성화된 사용자" 34 | 35 | #: backends.py:67 36 | msgid "Unrecognized algorithm type '{}'" 37 | msgstr "알 수 없는 알고리즘 유형 '{}'" 38 | 39 | #: backends.py:73 40 | msgid "You must have cryptography installed to use {}." 41 | msgstr "{}를 사용하려면 암호화가 설치되어 있어야 합니다." 42 | 43 | #: backends.py:88 44 | msgid "" 45 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 46 | msgstr "" 47 | "알 수 없는 타입 '{}', 'leeway' 값은 반드시 int, float 또는 timedelta 타입이어" 48 | "야 합니다." 49 | 50 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 51 | msgid "Token is invalid or expired" 52 | msgstr "유효하지 않거나 만료된 토큰" 53 | 54 | #: backends.py:150 55 | msgid "Invalid algorithm specified" 56 | msgstr "잘못된 알고리즘이 지정되었습니다" 57 | 58 | #: serializers.py:30 59 | msgid "No active account found with the given credentials" 60 | msgstr "지정된 자격 증명에 해당하는 활성화된 사용자를 찾을 수 없습니다" 61 | 62 | #: settings.py:70 63 | msgid "" 64 | "The '{}' setting has been removed. Please refer to '{}' for available " 65 | "settings." 66 | msgstr "'{}' 설정이 제거되었습니다. 사용 가능한 설정은 '{}'을 참조하십시오." 67 | 68 | #: token_blacklist/admin.py:68 69 | msgid "jti" 70 | msgstr "jti" 71 | 72 | #: token_blacklist/admin.py:74 73 | msgid "user" 74 | msgstr "사용자" 75 | 76 | #: token_blacklist/admin.py:80 77 | msgid "created at" 78 | msgstr "생성 시간" 79 | 80 | #: token_blacklist/admin.py:86 81 | msgid "expires at" 82 | msgstr "만료 시간" 83 | 84 | #: token_blacklist/apps.py:7 85 | msgid "Token Blacklist" 86 | msgstr "토큰 블랙리스트" 87 | 88 | #: tokens.py:30 89 | msgid "Cannot create token with no type or lifetime" 90 | msgstr "타입 또는 수명이 없는 토큰을 생성할 수 없습니다" 91 | 92 | #: tokens.py:102 93 | msgid "Token has no id" 94 | msgstr "토큰에 식별자가 주어지지 않음" 95 | 96 | #: tokens.py:115 97 | msgid "Token has no type" 98 | msgstr "토큰 타입이 주어지지 않음" 99 | 100 | #: tokens.py:118 101 | msgid "Token has wrong type" 102 | msgstr "잘못된 토큰 타입" 103 | 104 | #: tokens.py:170 105 | msgid "Token has no '{}' claim" 106 | msgstr "토큰에 '{}' 클레임이 없음" 107 | 108 | #: tokens.py:175 109 | msgid "Token '{}' claim has expired" 110 | msgstr "토큰 '{}' 클레임이 만료되었습니다" 111 | 112 | #: tokens.py:230 113 | msgid "Token is blacklisted" 114 | msgstr "블랙리스트에 추가된 토큰" 115 | -------------------------------------------------------------------------------- /ninja_jwt/locale/nl_NL/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/nl_NL/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/nl_NL/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR , 2020. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-ninja-jwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2021-06-17 11:06+0200\n" 8 | "Last-Translator: rene \n" 9 | "Language: nl_NL\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | 14 | #: authentication.py:78 15 | msgid "Authorization header must contain two space-delimited values" 16 | msgstr "" 17 | "Authorisatie header moet twee waarden bevatten, gescheiden door een spatie" 18 | 19 | #: authentication.py:104 20 | msgid "Given token not valid for any token type" 21 | msgstr "Het token is voor geen enkel token-type geldig" 22 | 23 | #: authentication.py:116 authentication.py:143 24 | msgid "Token contained no recognizable user identification" 25 | msgstr "Token bevat geen herkenbare gebruikersidentificatie" 26 | 27 | #: authentication.py:121 28 | msgid "User not found" 29 | msgstr "Gebruiker niet gevonden" 30 | 31 | #: authentication.py:124 32 | msgid "User is inactive" 33 | msgstr "Gebruiker is inactief" 34 | 35 | #: backends.py:67 36 | msgid "Unrecognized algorithm type '{}'" 37 | msgstr "Niet herkend algoritme type '{}" 38 | 39 | #: backends.py:73 40 | msgid "You must have cryptography installed to use {}." 41 | msgstr "" 42 | 43 | #: backends.py:88 44 | msgid "" 45 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 46 | 47 | msgstr "" 48 | 49 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 50 | msgid "Token is invalid or expired" 51 | msgstr "Token is niet geldig of verlopen" 52 | 53 | #: backends.py:150 54 | msgid "Invalid algorithm specified" 55 | msgstr "" 56 | 57 | #: serializers.py:30 58 | msgid "No active account found with the given credentials" 59 | msgstr "Geen actief account gevonden voor deze gegevens" 60 | 61 | #: settings.py:70 62 | msgid "" 63 | "The '{}' setting has been removed. Please refer to '{}' for available " 64 | "settings." 65 | msgstr "" 66 | "De '{}' instelling bestaat niet meer. Zie '{}' for beschikbareinstellingen." 67 | 68 | #: token_blacklist/admin.py:68 69 | msgid "jti" 70 | msgstr "jti" 71 | 72 | #: token_blacklist/admin.py:74 73 | msgid "user" 74 | msgstr "gebruiker" 75 | 76 | #: token_blacklist/admin.py:80 77 | msgid "created at" 78 | msgstr "aangemaakt op" 79 | 80 | #: token_blacklist/admin.py:86 81 | msgid "expires at" 82 | msgstr "verloopt op" 83 | 84 | #: token_blacklist/apps.py:7 85 | msgid "Token Blacklist" 86 | msgstr "Token Blacklist" 87 | 88 | #: tokens.py:30 89 | msgid "Cannot create token with no type or lifetime" 90 | msgstr "Kan geen token maken zonder type of levensduur" 91 | 92 | #: tokens.py:102 93 | msgid "Token has no id" 94 | msgstr "Token heeft geen id" 95 | 96 | #: tokens.py:115 97 | msgid "Token has no type" 98 | msgstr "Token heeft geen type" 99 | 100 | #: tokens.py:118 101 | msgid "Token has wrong type" 102 | msgstr "Token heeft het verkeerde type" 103 | 104 | #: tokens.py:170 105 | msgid "Token has no '{}' claim" 106 | msgstr "Token heeft geen '{}' recht" 107 | 108 | #: tokens.py:175 109 | msgid "Token '{}' claim has expired" 110 | msgstr "Token '{}' recht is verlopen" 111 | 112 | #: tokens.py:230 113 | msgid "Token is blacklisted" 114 | msgstr "Token is ge-blacklist" 115 | -------------------------------------------------------------------------------- /ninja_jwt/locale/pl_PL/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/pl_PL/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/pl_PL/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR , 2019. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-ninja-jwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2021-02-22 17:30+0100\n" 8 | "Last-Translator: Mateusz Slisz \n" 9 | "Language: pl_PL\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | 14 | #: authentication.py:78 15 | msgid "Authorization header must contain two space-delimited values" 16 | msgstr "Nagłówek autoryzacji musi zawierać dwie wartości rodzielone spacjami" 17 | 18 | #: authentication.py:104 19 | msgid "Given token not valid for any token type" 20 | msgstr "Podany token jest błędny dla każdego typu tokena" 21 | 22 | #: authentication.py:116 authentication.py:143 23 | msgid "Token contained no recognizable user identification" 24 | msgstr "Token nie zawierał rozpoznawalnej identyfikacji użytkownika" 25 | 26 | #: authentication.py:121 27 | msgid "User not found" 28 | msgstr "Użytkownik nie znaleziony" 29 | 30 | #: authentication.py:124 31 | msgid "User is inactive" 32 | msgstr "Użytkownik jest nieaktywny" 33 | 34 | #: backends.py:67 35 | msgid "Unrecognized algorithm type '{}'" 36 | msgstr "Nierozpoznany typ algorytmu '{}'" 37 | 38 | #: backends.py:73 39 | msgid "You must have cryptography installed to use {}." 40 | msgstr "" 41 | 42 | #: backends.py:88 43 | msgid "" 44 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 45 | 46 | msgstr "" 47 | 48 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 49 | msgid "Token is invalid or expired" 50 | msgstr "Token jest niepoprawny lub wygasł" 51 | 52 | #: backends.py:150 53 | msgid "Invalid algorithm specified" 54 | msgstr "" 55 | 56 | #: serializers.py:30 57 | msgid "No active account found with the given credentials" 58 | msgstr "Nie znaleziono aktywnego konta dla podanych danych uwierzytelniających" 59 | 60 | #: settings.py:70 61 | msgid "" 62 | "The '{}' setting has been removed. Please refer to '{}' for available " 63 | "settings." 64 | msgstr "" 65 | "Ustawienie '{}' zostało usunięte. Dostępne ustawienia znajdują sie w '{}'" 66 | 67 | #: token_blacklist/admin.py:68 68 | msgid "jti" 69 | msgstr "jti" 70 | 71 | #: token_blacklist/admin.py:74 72 | msgid "user" 73 | msgstr "użytkownik" 74 | 75 | #: token_blacklist/admin.py:80 76 | msgid "created at" 77 | msgstr "stworzony w" 78 | 79 | #: token_blacklist/admin.py:86 80 | msgid "expires at" 81 | msgstr "wygasa o" 82 | 83 | #: token_blacklist/apps.py:7 84 | msgid "Token Blacklist" 85 | msgstr "Token Blacklist" 86 | 87 | #: tokens.py:30 88 | msgid "Cannot create token with no type or lifetime" 89 | msgstr "Nie można utworzyć tokena bez podanego typu lub żywotności" 90 | 91 | #: tokens.py:102 92 | msgid "Token has no id" 93 | msgstr "Token nie posiada numeru identyfikacyjnego" 94 | 95 | #: tokens.py:115 96 | msgid "Token has no type" 97 | msgstr "Token nie posiada typu" 98 | 99 | #: tokens.py:118 100 | msgid "Token has wrong type" 101 | msgstr "Token posiada zły typ" 102 | 103 | #: tokens.py:170 104 | msgid "Token has no '{}' claim" 105 | msgstr "Token nie posiada upoważnienia '{}'" 106 | 107 | #: tokens.py:175 108 | msgid "Token '{}' claim has expired" 109 | msgstr "Upoważnienie tokena '{}' wygasło" 110 | 111 | #: tokens.py:230 112 | msgid "Token is blacklisted" 113 | msgstr "Token znajduję się na czarnej liście" 114 | -------------------------------------------------------------------------------- /ninja_jwt/locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/pt_BR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR , 2019. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-ninja-jwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2021-02-22 17:30+0100\n" 8 | "Last-Translator: Bruno Ducraux \n" 9 | "Language: pt_BR\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 14 | 15 | #: authentication.py:78 16 | msgid "Authorization header must contain two space-delimited values" 17 | msgstr "" 18 | "Cabeçalho de autorização deve conter dois valores delimitados por espaço" 19 | 20 | #: authentication.py:104 21 | msgid "Given token not valid for any token type" 22 | msgstr "O token informado não é válido para qualquer tipo de token" 23 | 24 | #: authentication.py:116 authentication.py:143 25 | msgid "Token contained no recognizable user identification" 26 | msgstr "O token não continha nenhuma identificação reconhecível do usuário" 27 | 28 | #: authentication.py:121 29 | msgid "User not found" 30 | msgstr "Usuário não encontrado" 31 | 32 | #: authentication.py:124 33 | msgid "User is inactive" 34 | msgstr "Usuário está inativo" 35 | 36 | #: backends.py:67 37 | msgid "Unrecognized algorithm type '{}'" 38 | msgstr "Tipo de algoritmo '{}' não reconhecido" 39 | 40 | #: backends.py:73 41 | msgid "You must have cryptography installed to use {}." 42 | msgstr "Você deve ter criptografia instalada para usar {}." 43 | 44 | #: backends.py:88 45 | msgid "" 46 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 47 | msgstr "" 48 | 49 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 50 | msgid "Token is invalid or expired" 51 | msgstr "O token é inválido ou expirado" 52 | 53 | #: backends.py:150 54 | msgid "Invalid algorithm specified" 55 | msgstr "Algoritmo inválido especificado" 56 | 57 | #: serializers.py:30 58 | msgid "No active account found with the given credentials" 59 | msgstr "Usuário e/ou senha incorreto(s)" 60 | 61 | #: settings.py:70 62 | msgid "" 63 | "The '{}' setting has been removed. Please refer to '{}' for available " 64 | "settings." 65 | msgstr "" 66 | "A configuração '{}' foi removida. Por favor, consulte '{}' para disponível " 67 | "definições." 68 | 69 | #: token_blacklist/admin.py:68 70 | msgid "jti" 71 | msgstr "jti" 72 | 73 | #: token_blacklist/admin.py:74 74 | msgid "user" 75 | msgstr "usuário" 76 | 77 | #: token_blacklist/admin.py:80 78 | msgid "created at" 79 | msgstr "criado em" 80 | 81 | #: token_blacklist/admin.py:86 82 | msgid "expires at" 83 | msgstr "expira em" 84 | 85 | #: token_blacklist/apps.py:7 86 | msgid "Token Blacklist" 87 | msgstr "Lista negra de Tokens" 88 | 89 | #: tokens.py:30 90 | msgid "Cannot create token with no type or lifetime" 91 | msgstr "Não é possível criar token sem tipo ou tempo de vida" 92 | 93 | #: tokens.py:102 94 | msgid "Token has no id" 95 | msgstr "Token não tem id" 96 | 97 | #: tokens.py:115 98 | msgid "Token has no type" 99 | msgstr "Token não tem nenhum tipo" 100 | 101 | #: tokens.py:118 102 | msgid "Token has wrong type" 103 | msgstr "Token tem tipo errado" 104 | 105 | #: tokens.py:170 106 | msgid "Token has no '{}' claim" 107 | msgstr "Token não tem '{}' privilégio" 108 | 109 | #: tokens.py:175 110 | msgid "Token '{}' claim has expired" 111 | msgstr "O privilégio '{}' do token expirou" 112 | 113 | #: tokens.py:230 114 | msgid "Token is blacklisted" 115 | msgstr "Token está na blacklist" 116 | -------------------------------------------------------------------------------- /ninja_jwt/locale/ro/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/ro/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/ro/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR Daniel Cuznetov , 2022. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: djangorestframework_simplejwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2022-07-13 10:45+0100\n" 8 | "Last-Translator: Daniel Cuznetov \n" 9 | "Language: ro\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=1; plural=0;\n" 14 | 15 | #: authentication.py:78 16 | msgid "Authorization header must contain two space-delimited values" 17 | msgstr "" 18 | "Header-ul(antetul) de autorizare trebuie să conțină două valori separate " 19 | "prin spațiu" 20 | 21 | #: authentication.py:104 22 | msgid "Given token not valid for any token type" 23 | msgstr "Tokenul dat nu este valid pentru niciun tip de token" 24 | 25 | #: authentication.py:116 authentication.py:143 26 | msgid "Token contained no recognizable user identification" 27 | msgstr "Tokenul nu conține date de identificare a utilizatorului" 28 | 29 | #: authentication.py:121 30 | msgid "User not found" 31 | msgstr "Utilizatorul nu a fost găsit" 32 | 33 | #: authentication.py:124 34 | msgid "User is inactive" 35 | msgstr "Utilizatorul este inactiv" 36 | 37 | #: backends.py:67 38 | msgid "Unrecognized algorithm type '{}'" 39 | msgstr "Tipul de algoritm '{}' nu este recunoscut" 40 | 41 | #: backends.py:73 42 | msgid "You must have cryptography installed to use {}." 43 | msgstr "Trebuie să aveți instalată criptografia pentru a utiliza {}." 44 | 45 | #: backends.py:88 46 | msgid "" 47 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 48 | msgstr "" 49 | "Tipul '{}' nu este recunoscut, 'leeway' trebuie să fie de tip int, float sau " 50 | "timedelta." 51 | 52 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 53 | msgid "Token is invalid or expired" 54 | msgstr "Token nu este valid sau a expirat" 55 | 56 | #: backends.py:150 57 | msgid "Invalid algorithm specified" 58 | msgstr "Algoritm nevalid specificat" 59 | 60 | #: serializers.py:30 61 | msgid "No active account found with the given credentials" 62 | msgstr "Nu a fost găsit cont activ cu aceste date de autentificare" 63 | 64 | #: settings.py:70 65 | msgid "" 66 | "The '{}' setting has been removed. Please refer to '{}' for available " 67 | "settings." 68 | msgstr "" 69 | "Setarea '{}' a fost ștearsă. Referați la '{}' pentru setări disponibile." 70 | 71 | #: token_blacklist/admin.py:68 72 | msgid "jti" 73 | msgstr "jti" 74 | 75 | #: token_blacklist/admin.py:74 76 | msgid "user" 77 | msgstr "utilizator" 78 | 79 | #: token_blacklist/admin.py:80 80 | msgid "created at" 81 | msgstr "creat la" 82 | 83 | #: token_blacklist/admin.py:86 84 | msgid "expires at" 85 | msgstr "expiră la" 86 | 87 | #: token_blacklist/apps.py:7 88 | msgid "Token Blacklist" 89 | msgstr "Listă de token-uri blocate" 90 | 91 | #: tokens.py:30 92 | msgid "Cannot create token with no type or lifetime" 93 | msgstr "Nu se poate crea token fără tip sau durată de viață" 94 | 95 | #: tokens.py:102 96 | msgid "Token has no id" 97 | msgstr "Tokenul nu are id" 98 | 99 | #: tokens.py:115 100 | msgid "Token has no type" 101 | msgstr "Tokenul nu are tip" 102 | 103 | #: tokens.py:118 104 | msgid "Token has wrong type" 105 | msgstr "Tokenul are tipul greșit" 106 | 107 | #: tokens.py:170 108 | msgid "Token has no '{}' claim" 109 | msgstr "Tokenul nu are reclamația '{}'" 110 | 111 | #: tokens.py:175 112 | msgid "Token '{}' claim has expired" 113 | msgstr "Reclamația tokenului '{}' a expirat" 114 | 115 | #: tokens.py:230 116 | msgid "Token is blacklisted" 117 | msgstr "Tokenul este în listă de tokenuri blocate" 118 | -------------------------------------------------------------------------------- /ninja_jwt/locale/ru_RU/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/ru_RU/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/ru_RU/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR , 2019. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: django-ninja-jwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2021-02-22 17:30+0100\n" 8 | "PO-Revision-Date: \n" 9 | "Last-Translator: Sergey Ozeranskiy \n" 10 | "Language-Team: \n" 11 | "Language: ru_RU\n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 16 | "%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" 17 | "X-Generator: Poedit 2.2.1\n" 18 | 19 | #: authentication.py:78 20 | msgid "Authorization header must contain two space-delimited values" 21 | msgstr "" 22 | "Заголовок авторизации должен содержать два значения, разделенных пробелом" 23 | 24 | #: authentication.py:104 25 | msgid "Given token not valid for any token type" 26 | msgstr "Данный токен недействителен для любого типа токена" 27 | 28 | #: authentication.py:116 authentication.py:143 29 | msgid "Token contained no recognizable user identification" 30 | msgstr "Токен не содержит идентификатор пользователя" 31 | 32 | #: authentication.py:121 33 | msgid "User not found" 34 | msgstr "Пользователь не найден" 35 | 36 | #: authentication.py:124 37 | msgid "User is inactive" 38 | msgstr "Пользователь неактивен" 39 | 40 | #: backends.py:67 41 | msgid "Unrecognized algorithm type '{}'" 42 | msgstr "Нераспознанный тип алгоритма '{}'" 43 | 44 | #: backends.py:73 45 | msgid "You must have cryptography installed to use {}." 46 | msgstr "" 47 | 48 | #: backends.py:88 49 | msgid "" 50 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 51 | msgstr "" 52 | 53 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 54 | msgid "Token is invalid or expired" 55 | msgstr "Токен недействителен или просрочен" 56 | 57 | #: backends.py:150 58 | msgid "Invalid algorithm specified" 59 | msgstr "" 60 | 61 | #: serializers.py:30 62 | msgid "No active account found with the given credentials" 63 | msgstr "Не найдено активной учетной записи с указанными данными" 64 | 65 | #: settings.py:70 66 | msgid "" 67 | "The '{}' setting has been removed. Please refer to '{}' for available " 68 | "settings." 69 | msgstr "" 70 | "Параметр '{}' был удален. Пожалуйста, обратитесь к '{}' для просмотра " 71 | "доступных настроек." 72 | 73 | #: token_blacklist/admin.py:68 74 | msgid "jti" 75 | msgstr "jti" 76 | 77 | #: token_blacklist/admin.py:74 78 | msgid "user" 79 | msgstr "пользователь" 80 | 81 | #: token_blacklist/admin.py:80 82 | msgid "created at" 83 | msgstr "создан" 84 | 85 | #: token_blacklist/admin.py:86 86 | msgid "expires at" 87 | msgstr "истекает" 88 | 89 | #: token_blacklist/apps.py:7 90 | msgid "Token Blacklist" 91 | msgstr "Token Blacklist" 92 | 93 | #: tokens.py:30 94 | msgid "Cannot create token with no type or lifetime" 95 | msgstr "Невозможно создать токен без типа или времени жизни" 96 | 97 | #: tokens.py:102 98 | msgid "Token has no id" 99 | msgstr "У токена нет идентификатора" 100 | 101 | #: tokens.py:115 102 | msgid "Token has no type" 103 | msgstr "Токен не имеет типа" 104 | 105 | #: tokens.py:118 106 | msgid "Token has wrong type" 107 | msgstr "Токен имеет неправильный тип" 108 | 109 | #: tokens.py:170 110 | msgid "Token has no '{}' claim" 111 | msgstr "Токен не содержит '{}'" 112 | 113 | #: tokens.py:175 114 | msgid "Token '{}' claim has expired" 115 | msgstr "Токен имеет просроченное значение '{}'" 116 | 117 | #: tokens.py:230 118 | msgid "Token is blacklisted" 119 | msgstr "Токен занесен в черный список" 120 | -------------------------------------------------------------------------------- /ninja_jwt/locale/sv/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/sv/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/sv/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR Pasindu , 2022. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: djangorestframework_simplejwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2022-05-29 17:30+0100\n" 8 | "Last-Translator: Pasindu \n" 9 | "Language: sv\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 14 | 15 | #: authentication.py:78 16 | msgid "Authorization header must contain two space-delimited values" 17 | msgstr "Auktoriseringshuvudet måste innehålla två mellanslagsavgränsade värden" 18 | 19 | #: authentication.py:104 20 | msgid "Given token not valid for any token type" 21 | msgstr "Givet token är inte giltigt för någon tokentyp" 22 | 23 | #: authentication.py:116 authentication.py:143 24 | msgid "Token contained no recognizable user identification" 25 | msgstr "Token innehöll ingen identifiering av användaren" 26 | 27 | #: authentication.py:121 28 | msgid "User not found" 29 | msgstr "Användaren hittades inte" 30 | 31 | #: authentication.py:124 32 | msgid "User is inactive" 33 | msgstr "Användaren är inaktiv" 34 | 35 | #: backends.py:67 36 | msgid "Unrecognized algorithm type '{}'" 37 | msgstr "Okänd algoritmtyp '{}'" 38 | 39 | #: backends.py:73 40 | msgid "You must have cryptography installed to use {}." 41 | msgstr "Du måste ha kryptografi installerad för att kunna använda {}." 42 | 43 | #: backends.py:88 44 | msgid "" 45 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 46 | msgstr "" 47 | 48 | #: backends.py:102 backends.py:152 exceptions.py:38 tokens.py:44 49 | msgid "Token is invalid or expired" 50 | msgstr "Token är ogiltig eller har löpt ut" 51 | 52 | #: backends.py:150 53 | msgid "Invalid algorithm specified" 54 | msgstr "Ogiltig algoritm har angetts" 55 | 56 | #: serializers.py:30 57 | msgid "No active account found with the given credentials" 58 | msgstr "Inget aktivt konto hittades med de angivna användaruppgifterna" 59 | 60 | #: settings.py:70 61 | msgid "" 62 | "The '{}' setting has been removed. Please refer to '{}' for available " 63 | "settings." 64 | msgstr "" 65 | "Inställningen '{}' har tagits bort. Se '{}' för tillgängliga inställningar" 66 | 67 | #: token_blacklist/admin.py:68 68 | msgid "jti" 69 | msgstr "jti" 70 | 71 | #: token_blacklist/admin.py:74 72 | msgid "user" 73 | msgstr "användare" 74 | 75 | #: token_blacklist/admin.py:80 76 | msgid "created at" 77 | msgstr "skapad vid" 78 | 79 | #: token_blacklist/admin.py:86 80 | msgid "expires at" 81 | msgstr "går ut kl" 82 | 83 | #: token_blacklist/apps.py:7 84 | msgid "Token Blacklist" 85 | msgstr "Token svartlist" 86 | 87 | #: tokens.py:30 88 | msgid "Cannot create token with no type or lifetime" 89 | msgstr "Kan inte skapa token utan typ eller livslängd" 90 | 91 | #: tokens.py:102 92 | msgid "Token has no id" 93 | msgstr "Token har inget id" 94 | 95 | #: tokens.py:115 96 | msgid "Token has no type" 97 | msgstr "Token har ingen typ" 98 | 99 | #: tokens.py:118 100 | msgid "Token has wrong type" 101 | msgstr "Token har fel typ" 102 | 103 | #: tokens.py:170 104 | msgid "Token has no '{}' claim" 105 | msgstr "Token har inget '{}'-anspråk" 106 | 107 | #: tokens.py:175 108 | msgid "Token '{}' claim has expired" 109 | msgstr "Token '{}'-anspråket har löpt ut" 110 | 111 | #: tokens.py:230 112 | msgid "Token is blacklisted" 113 | msgstr "Token är svartlistad" 114 | -------------------------------------------------------------------------------- /ninja_jwt/locale/tr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/tr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/tr/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # FIRST AUTHOR , 2022. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: djangorestframework_simplejwt\n" 6 | "Report-Msgid-Bugs-To: \n" 7 | "POT-Creation-Date: 2022-01-13 23:05+0300\n" 8 | "Last-Translator: Şuayip Üzülmez \n" 9 | "Language: tr\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 14 | 15 | #: authentication.py:78 16 | msgid "Authorization header must contain two space-delimited values" 17 | msgstr "" 18 | "Yetkilendirme header'i boşlukla sınırlandırılmış iki değer bulundurmak " 19 | "zorunda" 20 | 21 | #: authentication.py:104 22 | msgid "Given token not valid for any token type" 23 | msgstr "Verilen token hiçbir token tipi için geçerli değil" 24 | 25 | #: authentication.py:116 authentication.py:143 26 | msgid "Token contained no recognizable user identification" 27 | msgstr "Token tanınabilir bir kullanıcı kimliği içermiyor" 28 | 29 | #: authentication.py:121 30 | msgid "User not found" 31 | msgstr "Kullanıcı bulunamadı" 32 | 33 | #: authentication.py:124 34 | msgid "User is inactive" 35 | msgstr "Kullanıcı etkin değil" 36 | 37 | #: backends.py:67 38 | msgid "Unrecognized algorithm type '{}'" 39 | msgstr "Tanınmayan algortima tipi '{}'" 40 | 41 | #: backends.py:73 42 | msgid "You must have cryptography installed to use {}." 43 | msgstr "{} kullanmak için cryptography yüklemeniz gerekiyor." 44 | 45 | #: backends.py:88 46 | msgid "" 47 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 48 | msgstr "" 49 | 50 | #: backends.py:147 51 | msgid "Invalid algorithm specified" 52 | msgstr "Geçersiz algoritma belirtildi" 53 | 54 | #: backends.py:149 exceptions.py:38 tokens.py:44 55 | msgid "Token is invalid or expired" 56 | msgstr "Token geçersiz veya süresi geçmiş" 57 | 58 | #: serializers.py:30 59 | msgid "No active account found with the given credentials" 60 | msgstr "Verilen kimlik bilgileriyle aktif bir hesap bulunamadı" 61 | 62 | #: settings.py:70 63 | msgid "" 64 | "The '{}' setting has been removed. Please refer to '{}' for available " 65 | "settings." 66 | msgstr "'{}' ayarı kaldırıldı. Mevcut ayarlar için '{}' adresini ziyaret edin." 67 | 68 | #: token_blacklist/admin.py:68 69 | msgid "jti" 70 | msgstr "jti" 71 | 72 | #: token_blacklist/admin.py:74 73 | msgid "user" 74 | msgstr "kullanıcı" 75 | 76 | #: token_blacklist/admin.py:80 77 | msgid "created at" 78 | msgstr "oluşturulma tarihi" 79 | 80 | #: token_blacklist/admin.py:86 81 | msgid "expires at" 82 | msgstr "sona erme tarihi" 83 | 84 | #: token_blacklist/apps.py:7 85 | msgid "Token Blacklist" 86 | msgstr "Token Kara Listesi" 87 | 88 | #: tokens.py:30 89 | msgid "Cannot create token with no type or lifetime" 90 | msgstr "Tipi veya geçerlilik süresi olmayan token oluşturulamaz" 91 | 92 | #: tokens.py:102 93 | msgid "Token has no id" 94 | msgstr "Token'in id'si yok" 95 | 96 | #: tokens.py:115 97 | msgid "Token has no type" 98 | msgstr "Token'in tipi yok" 99 | 100 | #: tokens.py:118 101 | msgid "Token has wrong type" 102 | msgstr "Token'in tipi yanlış" 103 | 104 | #: tokens.py:170 105 | msgid "Token has no '{}' claim" 106 | msgstr "Token'in '{}' claim'i yok" 107 | 108 | #: tokens.py:175 109 | msgid "Token '{}' claim has expired" 110 | msgstr "Token'in '{}' claim'i sona erdi" 111 | 112 | #: tokens.py:230 113 | msgid "Token is blacklisted" 114 | msgstr "Token kara listeye alınmış" 115 | -------------------------------------------------------------------------------- /ninja_jwt/locale/uk_UA/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/uk_UA/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/uk_UA/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # This file is distributed under the same license as the PACKAGE package. 2 | # Artiukhov Artem , 2021. 3 | # 4 | #, fuzzy 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: django-ninja-jwt\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2021-06-17 12:32+0300\n" 10 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 11 | "Last-Translator: Artiukhov Artem \n" 12 | "Language-Team: LANGUAGE \n" 13 | "Language: uk_UA\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | 18 | #: ninja_jwt/authentication.py:79 19 | msgid "Authorization header must contain two space-delimited values" 20 | msgstr "Авторизаційний заголовок має містити два значення розділені пробілом" 21 | 22 | #: ninja_jwt/authentication.py:100 23 | msgid "Given token not valid for any token type" 24 | msgstr "Наданий токен не відповідає жодному типу ключа" 25 | 26 | #: ninja_jwt/authentication.py:111 27 | #: ninja_jwt/authentication.py:133 28 | msgid "Token contained no recognizable user identification" 29 | msgstr "Наданий токен не мітить жодної ідентифікаційної інформації" 30 | 31 | #: ninja_jwt/authentication.py:116 32 | msgid "User not found" 33 | msgstr "Користувач не знайдений" 34 | 35 | #: ninja_jwt/authentication.py:119 36 | msgid "User is inactive" 37 | msgstr "Користувач неактивний" 38 | 39 | #: ninja_jwt/backends.py:37 40 | msgid "Unrecognized algorithm type '{}'" 41 | msgstr "Тип алгоритму '{}' не розпізнаний" 42 | 43 | #: ninja_jwt/backends.py:40 44 | msgid "You must have cryptography installed to use {}." 45 | msgstr "Встановіть модуль cryptography щоб використовувати {}" 46 | 47 | #: ninja_jwt/backends.py:74 48 | msgid "Invalid algorithm specified" 49 | msgstr "Вказаний невірний алгоритм" 50 | 51 | #: ninja_jwt/backends.py:76 52 | #: ninja_jwt/exceptions.py:38 53 | #: ninja_jwt/tokens.py:44 54 | msgid "Token is invalid or expired" 55 | msgstr "Токен некоректний або термін його дії вичерпаний" 56 | 57 | #: ninja_jwt/serializers.py:24 58 | msgid "No active account found with the given credentials" 59 | msgstr "Не знайдено жодного облікового запису по наданих облікових даних" 60 | 61 | #: ninja_jwt/settings.py:63 62 | msgid "" 63 | "The '{}' setting has been removed. Please refer to '{}' for available " 64 | "settings." 65 | msgstr "Налаштування '{}' видалене. Подивіться у '{}' для інших доступних" 66 | 67 | #: ninja_jwt/token_blacklist/admin.py:72 68 | msgid "jti" 69 | msgstr "jti" 70 | 71 | #: ninja_jwt/token_blacklist/admin.py:77 72 | msgid "user" 73 | msgstr "користувач" 74 | 75 | #: ninja_jwt/token_blacklist/admin.py:82 76 | msgid "created at" 77 | msgstr "створений о" 78 | 79 | #: ninja_jwt/token_blacklist/admin.py:87 80 | msgid "expires at" 81 | msgstr "дійстний по" 82 | 83 | #: ninja_jwt/token_blacklist/apps.py:7 84 | msgid "Token Blacklist" 85 | msgstr "Чорний список токенів" 86 | 87 | #: ninja_jwt/tokens.py:30 88 | msgid "Cannot create token with no type or lifetime" 89 | msgstr "Неможливо створити токен без типу або строку дії" 90 | 91 | #: ninja_jwt/tokens.py:98 92 | msgid "Token has no id" 93 | msgstr "У ключі доступу не міститься id" 94 | 95 | #: ninja_jwt/tokens.py:109 96 | msgid "Token has no type" 97 | msgstr "У ключі доступу не міститься тип" 98 | 99 | #: ninja_jwt/tokens.py:112 100 | msgid "Token has wrong type" 101 | msgstr "токен позначений невірним типом" 102 | 103 | #: ninja_jwt/tokens.py:149 104 | msgid "Token has no '{}' claim" 105 | msgstr "У токені не міститься '{}' заголовку" 106 | 107 | #: ninja_jwt/tokens.py:153 108 | msgid "Token '{}' claim has expired" 109 | msgstr "Заголовок '{}' токена не дійсний" 110 | 111 | #: ninja_jwt/tokens.py:192 112 | 113 | msgid "Token is blacklisted" 114 | msgstr "Токен занесений у чорний список" 115 | -------------------------------------------------------------------------------- /ninja_jwt/locale/zh_Hans/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/locale/zh_Hans/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /ninja_jwt/locale/zh_Hans/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) 2021 3 | # This file is distributed under the same license as the Simple JWT package. 4 | # zengqiu , 2021. 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: django-ninja-jwt\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2021-06-23 13:29+0800\n" 10 | "PO-Revision-Date: 2021-06-23 13:29+080\n" 11 | "Last-Translator: zengqiu \n" 12 | "Language: zh_Hans\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Plural-Forms: nplurals=1; plural=0;\n" 17 | 18 | #: authentication.py:78 19 | msgid "Authorization header must contain two space-delimited values" 20 | msgstr "授权头必须包含两个用空格分隔的值" 21 | 22 | #: authentication.py:104 23 | msgid "Given token not valid for any token type" 24 | msgstr "此令牌对任何类型的令牌无效" 25 | 26 | #: authentication.py:116 authentication.py:143 27 | msgid "Token contained no recognizable user identification" 28 | msgstr "令牌未包含用户标识符" 29 | 30 | #: authentication.py:121 31 | msgid "User not found" 32 | msgstr "未找到该用户" 33 | 34 | #: authentication.py:124 35 | msgid "User is inactive" 36 | msgstr "该用户已禁用" 37 | 38 | #: backends.py:67 39 | msgid "Unrecognized algorithm type '{}'" 40 | msgstr "未知算法类型 '{}'" 41 | 42 | #: backends.py:73 43 | msgid "You must have cryptography installed to use {}." 44 | msgstr "你必须安装 cryptography 才能使用 {}。" 45 | 46 | #: backends.py:88 47 | msgid "" 48 | "Unrecognized type '{}', 'leeway' must be of type int, float or timedelta." 49 | msgstr "" 50 | 51 | #: backends.py:147 52 | msgid "Invalid algorithm specified" 53 | msgstr "指定的算法无效" 54 | 55 | #: backends.py:149 exceptions.py:38 tokens.py:44 56 | msgid "Token is invalid or expired" 57 | msgstr "令牌无效或已过期" 58 | 59 | #: serializers.py:30 60 | msgid "No active account found with the given credentials" 61 | msgstr "找不到指定凭据对应的有效用户" 62 | 63 | #: settings.py:70 64 | msgid "" 65 | "The '{}' setting has been removed. Please refer to '{}' for available " 66 | "settings." 67 | msgstr "'{}' 配置已被移除。 请参阅 '{}' 获取可用的配置。" 68 | 69 | #: token_blacklist/admin.py:68 70 | msgid "jti" 71 | msgstr "jti" 72 | 73 | #: token_blacklist/admin.py:74 74 | msgid "user" 75 | msgstr "用户" 76 | 77 | #: token_blacklist/admin.py:80 78 | msgid "created at" 79 | msgstr "创建时间" 80 | 81 | #: token_blacklist/admin.py:86 82 | msgid "expires at" 83 | msgstr "过期时间" 84 | 85 | #: token_blacklist/apps.py:7 86 | msgid "Token Blacklist" 87 | msgstr "令牌黑名单" 88 | 89 | #: tokens.py:30 90 | msgid "Cannot create token with no type or lifetime" 91 | msgstr "无法创建没有类型或生存期的令牌" 92 | 93 | #: tokens.py:102 94 | msgid "Token has no id" 95 | msgstr "令牌没有标识符" 96 | 97 | #: tokens.py:115 98 | msgid "Token has no type" 99 | msgstr "令牌没有类型" 100 | 101 | #: tokens.py:118 102 | msgid "Token has wrong type" 103 | msgstr "令牌类型错误" 104 | 105 | #: tokens.py:170 106 | msgid "Token has no '{}' claim" 107 | msgstr "令牌没有 '{}' 声明" 108 | 109 | #: tokens.py:175 110 | msgid "Token '{}' claim has expired" 111 | msgstr "令牌 '{}' 声明已过期" 112 | 113 | #: tokens.py:230 114 | msgid "Token is blacklisted" 115 | msgstr "令牌已被加入黑名单" 116 | -------------------------------------------------------------------------------- /ninja_jwt/models.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from django.contrib.auth import models as auth_models 4 | from django.db.models.manager import EmptyManager 5 | from django.utils.functional import cached_property 6 | 7 | from .compat import CallableFalse, CallableTrue 8 | from .settings import api_settings 9 | 10 | 11 | class TokenUser: 12 | """ 13 | A dummy user class modeled after django.contrib.auth.models.AnonymousUser. 14 | Used in conjunction with the `JWTStatelessUserAuthentication` backend to 15 | implement single sign-on functionality across services which share the same 16 | secret key. `JWTStatelessUserAuthentication` will return an instance of this 17 | class instead of a `User` model instance. Instances of this class act as 18 | stateless user objects which are backed by validated tokens. 19 | """ 20 | 21 | # User is always active since Simple JWT will never issue a token for an 22 | # inactive user 23 | is_active = True 24 | 25 | _groups = EmptyManager(auth_models.Group) 26 | _user_permissions = EmptyManager(auth_models.Permission) 27 | 28 | def __init__(self, token: Any) -> None: 29 | self.token = token 30 | 31 | def __str__(self) -> str: 32 | return "TokenUser {}".format(self.id) 33 | 34 | @cached_property 35 | def id(self) -> Any: 36 | return self.token[api_settings.USER_ID_CLAIM] 37 | 38 | @cached_property 39 | def pk(self) -> Any: 40 | return self.id 41 | 42 | @cached_property 43 | def username(self) -> str: 44 | return self.token.get("username", "") 45 | 46 | @cached_property 47 | def is_staff(self) -> bool: 48 | return self.token.get("is_staff", False) 49 | 50 | @cached_property 51 | def is_superuser(self) -> bool: 52 | return self.token.get("is_superuser", False) 53 | 54 | def __eq__(self, other) -> bool: 55 | return self.id == other.id 56 | 57 | def __ne__(self, other) -> bool: 58 | return not self.__eq__(other) 59 | 60 | def __hash__(self) -> int: 61 | return hash(self.id) 62 | 63 | def save(self) -> None: 64 | raise NotImplementedError("Token users have no DB representation") 65 | 66 | def delete(self) -> None: 67 | raise NotImplementedError("Token users have no DB representation") 68 | 69 | def set_password(self, raw_password: str) -> None: 70 | raise NotImplementedError("Token users have no DB representation") 71 | 72 | def check_password(self, raw_password: str) -> None: 73 | raise NotImplementedError("Token users have no DB representation") 74 | 75 | @property 76 | def groups(self) -> EmptyManager: 77 | return self._groups 78 | 79 | @property 80 | def user_permissions(self) -> EmptyManager: 81 | return self._user_permissions 82 | 83 | def get_group_permissions(self, obj=None) -> set: 84 | return set() 85 | 86 | def get_all_permissions(self, obj=None) -> set: 87 | return set() 88 | 89 | def has_perm(self, perm, obj=None) -> bool: 90 | return False 91 | 92 | def has_perms(self, perm_list, obj=None) -> bool: 93 | return False 94 | 95 | def has_module_perms(self, module) -> bool: 96 | return False 97 | 98 | @property 99 | def is_anonymous(self) -> bool: 100 | return CallableFalse 101 | 102 | @property 103 | def is_authenticated(self) -> bool: 104 | return CallableTrue 105 | 106 | def get_username(self) -> str: 107 | return self.username 108 | 109 | def __getattr__(self, attr): 110 | """This acts as a backup attribute getter for custom claims defined in Token serializers.""" 111 | return self.token.get(attr, None) 112 | -------------------------------------------------------------------------------- /ninja_jwt/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/py.typed -------------------------------------------------------------------------------- /ninja_jwt/routers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/routers/__init__.py -------------------------------------------------------------------------------- /ninja_jwt/routers/blacklist.py: -------------------------------------------------------------------------------- 1 | from ninja.router import Router 2 | 3 | from ninja_jwt.schema_control import SchemaControl 4 | from ninja_jwt.settings import api_settings 5 | 6 | blacklist_router = Router() 7 | 8 | schema = SchemaControl(api_settings) 9 | 10 | 11 | @blacklist_router.post( 12 | "/blacklist", 13 | response={200: schema.blacklist_schema.get_response_schema()}, 14 | url_name="token_blacklist", 15 | operation_id="token_blacklist", 16 | auth=None, 17 | ) 18 | def blacklist_token(request, refresh: schema.blacklist_schema): 19 | return refresh.to_response_schema() 20 | -------------------------------------------------------------------------------- /ninja_jwt/routers/obtain.py: -------------------------------------------------------------------------------- 1 | from ninja_extra.router import Router 2 | 3 | from ninja_jwt.schema_control import SchemaControl 4 | from ninja_jwt.settings import api_settings 5 | 6 | schema = SchemaControl(api_settings) 7 | 8 | obtain_pair_router = Router() 9 | sliding_router = Router() 10 | 11 | 12 | @obtain_pair_router.post( 13 | "/pair", 14 | response=schema.obtain_pair_schema.get_response_schema(), 15 | url_name="token_obtain_pair", 16 | operation_id="token_obtain_pair", 17 | auth=None, 18 | ) 19 | def obtain_token(request, user_token: schema.obtain_pair_schema): 20 | user_token.check_user_authentication_rule() 21 | return user_token.to_response_schema() 22 | 23 | 24 | @obtain_pair_router.post( 25 | "/refresh", 26 | response=schema.obtain_pair_refresh_schema.get_response_schema(), 27 | url_name="token_refresh", 28 | operation_id="token_refresh", 29 | auth=None, 30 | ) 31 | def refresh_token(request, refresh_token: schema.obtain_pair_refresh_schema): 32 | return refresh_token.to_response_schema() 33 | 34 | 35 | @sliding_router.post( 36 | "/sliding", 37 | response=schema.obtain_sliding_schema.get_response_schema(), 38 | url_name="token_obtain_sliding", 39 | operation_id="token_obtain_sliding", 40 | auth=None, 41 | ) 42 | def obtain_token_sliding_token(request, user_token: schema.obtain_sliding_schema): 43 | user_token.check_user_authentication_rule() 44 | return user_token.to_response_schema() 45 | 46 | 47 | @sliding_router.post( 48 | "/sliding/refresh", 49 | response=schema.obtain_sliding_refresh_schema.get_response_schema(), 50 | url_name="token_refresh_sliding", 51 | operation_id="token_refresh_sliding", 52 | auth=None, 53 | ) 54 | def refresh_token_sliding(request, refresh_token: schema.obtain_sliding_refresh_schema): 55 | return refresh_token.to_response_schema() 56 | -------------------------------------------------------------------------------- /ninja_jwt/routers/verify.py: -------------------------------------------------------------------------------- 1 | from ninja.router import Router 2 | 3 | from ninja_jwt.schema_control import SchemaControl 4 | from ninja_jwt.settings import api_settings 5 | 6 | schema = SchemaControl(api_settings) 7 | 8 | verify_router = Router() 9 | 10 | 11 | @verify_router.post( 12 | "/verify", 13 | response={200: schema.verify_schema.get_response_schema()}, 14 | url_name="token_verify", 15 | operation_id="token_verify", 16 | auth=None, 17 | ) 18 | def verify_token(request, token: schema.verify_schema): 19 | return token.to_response_schema() 20 | -------------------------------------------------------------------------------- /ninja_jwt/schema_control.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Type 2 | 3 | from django.utils.module_loading import import_string 4 | 5 | from ninja_jwt.schema import InputSchemaMixin, TokenInputSchemaMixin 6 | 7 | if TYPE_CHECKING: # pragma: no cover 8 | from ninja_jwt.settings import NinjaJWTSettings 9 | 10 | 11 | class SchemaControl: 12 | """ 13 | A Schema Helper Class that imports Schema from configurations 14 | """ 15 | 16 | def __init__(self, api_settings: "NinjaJWTSettings") -> None: 17 | self._verify_schema = import_string(api_settings.TOKEN_VERIFY_INPUT_SCHEMA) 18 | self.validate_type( 19 | self._verify_schema, InputSchemaMixin, "TOKEN_VERIFY_INPUT_SCHEMA" 20 | ) 21 | 22 | self._blacklist_schema = import_string( 23 | api_settings.TOKEN_BLACKLIST_INPUT_SCHEMA 24 | ) 25 | self.validate_type( 26 | self._blacklist_schema, InputSchemaMixin, "TOKEN_BLACKLIST_INPUT_SCHEMA" 27 | ) 28 | 29 | self._obtain_pair_schema = import_string( 30 | api_settings.TOKEN_OBTAIN_PAIR_INPUT_SCHEMA 31 | ) 32 | self.validate_type( 33 | self._obtain_pair_schema, 34 | TokenInputSchemaMixin, 35 | "TOKEN_OBTAIN_PAIR_INPUT_SCHEMA", 36 | ) 37 | 38 | self._obtain_pair_refresh_schema = import_string( 39 | api_settings.TOKEN_OBTAIN_PAIR_REFRESH_INPUT_SCHEMA 40 | ) 41 | 42 | self.validate_type( 43 | self._obtain_pair_refresh_schema, 44 | InputSchemaMixin, 45 | "TOKEN_OBTAIN_PAIR_REFRESH_INPUT_SCHEMA", 46 | ) 47 | 48 | self._obtain_sliding_schema = import_string( 49 | api_settings.TOKEN_OBTAIN_SLIDING_INPUT_SCHEMA 50 | ) 51 | self.validate_type( 52 | self._obtain_sliding_schema, 53 | TokenInputSchemaMixin, 54 | "TOKEN_OBTAIN_SLIDING_INPUT_SCHEMA", 55 | ) 56 | 57 | self._obtain_sliding_refresh_schema = import_string( 58 | api_settings.TOKEN_OBTAIN_SLIDING_REFRESH_INPUT_SCHEMA 59 | ) 60 | self.validate_type( 61 | self._obtain_sliding_refresh_schema, 62 | InputSchemaMixin, 63 | "TOKEN_OBTAIN_SLIDING_REFRESH_INPUT_SCHEMA", 64 | ) 65 | 66 | def validate_type( 67 | self, schema_type: Type, sub_class: Type, settings_key: str 68 | ) -> None: 69 | if not issubclass(schema_type, sub_class): 70 | raise Exception(f"{settings_key} type must inherit from `{sub_class}`") 71 | 72 | @property 73 | def verify_schema(self) -> "TokenInputSchemaMixin": 74 | return self._verify_schema 75 | 76 | @property 77 | def blacklist_schema(self) -> "TokenInputSchemaMixin": 78 | return self._blacklist_schema 79 | 80 | @property 81 | def obtain_pair_schema(self) -> "TokenInputSchemaMixin": 82 | return self._obtain_pair_schema 83 | 84 | @property 85 | def obtain_pair_refresh_schema(self) -> "TokenInputSchemaMixin": 86 | return self._obtain_pair_refresh_schema 87 | 88 | @property 89 | def obtain_sliding_schema(self) -> "TokenInputSchemaMixin": 90 | return self._obtain_sliding_schema 91 | 92 | @property 93 | def obtain_sliding_refresh_schema(self) -> "TokenInputSchemaMixin": 94 | return self._obtain_sliding_refresh_schema 95 | -------------------------------------------------------------------------------- /ninja_jwt/settings.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | from typing import Any, List, Optional, Union 3 | 4 | from django.conf import settings 5 | from django.test.signals import setting_changed 6 | from ninja_extra.lazy import LazyStrImport 7 | from pydantic.v1 import AnyUrl, BaseModel, Field, root_validator 8 | 9 | 10 | class NinjaJWTUserDefinedSettingsMapper: 11 | def __init__(self, data: dict) -> None: 12 | self.__dict__ = data 13 | 14 | 15 | NinjaJWT_SETTINGS_DEFAULTS = { 16 | "USER_AUTHENTICATION_RULE": "ninja_jwt.authentication.default_user_authentication_rule", 17 | "AUTH_TOKEN_CLASSES": ["ninja_jwt.tokens.AccessToken"], 18 | "TOKEN_USER_CLASS": "ninja_jwt.models.TokenUser", 19 | } 20 | 21 | USER_SETTINGS = NinjaJWTUserDefinedSettingsMapper( 22 | getattr( 23 | settings, 24 | "SIMPLE_JWT", 25 | getattr(settings, "NINJA_JWT", NinjaJWT_SETTINGS_DEFAULTS), 26 | ) 27 | ) 28 | 29 | 30 | class NinjaJWTSettings(BaseModel): 31 | class Config: 32 | orm_mode = True 33 | validate_assignment = True 34 | 35 | ACCESS_TOKEN_LIFETIME: timedelta = Field(timedelta(minutes=5)) 36 | REFRESH_TOKEN_LIFETIME: timedelta = Field(timedelta(days=1)) 37 | ROTATE_REFRESH_TOKENS: bool = Field(False) 38 | BLACKLIST_AFTER_ROTATION: bool = Field(False) 39 | UPDATE_LAST_LOGIN: bool = Field(False) 40 | ALGORITHM: str = Field("HS256") 41 | SIGNING_KEY: str = Field(settings.SECRET_KEY) 42 | VERIFYING_KEY: Optional[str] = Field("") 43 | AUDIENCE: Optional[str] = Field(None) 44 | ISSUER: Optional[str] = Field(None) 45 | JWK_URL: Optional[AnyUrl] = Field(None) 46 | LEEWAY: Union[int, timedelta] = Field(0) 47 | 48 | # AUTH_HEADER_TYPES: Tuple[str] = Field(('Bearer',)) 49 | # AUTH_HEADER_NAME: str = Field('HTTP_AUTHORIZATION') 50 | 51 | USER_ID_FIELD: str = Field("id") 52 | USER_ID_CLAIM: str = Field("user_id") 53 | 54 | USER_AUTHENTICATION_RULE: Any = Field( 55 | "ninja_jwt.authentication.default_user_authentication_rule" 56 | ) 57 | TOKEN_USER_CLASS: Any = Field("ninja_jwt.models.TokenUser") 58 | AUTH_TOKEN_CLASSES: List[Any] = Field(["ninja_jwt.tokens.AccessToken"]) 59 | JSON_ENCODER: Optional[Any] = Field(None) 60 | TOKEN_TYPE_CLAIM: Optional[str] = Field("token_type") 61 | JTI_CLAIM: Optional[str] = Field("jti") 62 | SLIDING_TOKEN_REFRESH_EXP_CLAIM: str = Field("refresh_exp") 63 | SLIDING_TOKEN_LIFETIME: timedelta = Field(timedelta(minutes=5)) 64 | SLIDING_TOKEN_REFRESH_LIFETIME: timedelta = Field(timedelta(days=1)) 65 | 66 | TOKEN_OBTAIN_PAIR_INPUT_SCHEMA: Any = Field( 67 | "ninja_jwt.schema.TokenObtainPairInputSchema" 68 | ) 69 | TOKEN_OBTAIN_PAIR_REFRESH_INPUT_SCHEMA: Any = Field( 70 | "ninja_jwt.schema.TokenRefreshInputSchema" 71 | ) 72 | 73 | TOKEN_OBTAIN_SLIDING_INPUT_SCHEMA: Any = Field( 74 | "ninja_jwt.schema.TokenObtainSlidingInputSchema" 75 | ) 76 | TOKEN_OBTAIN_SLIDING_REFRESH_INPUT_SCHEMA: Any = Field( 77 | "ninja_jwt.schema.TokenRefreshSlidingInputSchema" 78 | ) 79 | 80 | TOKEN_BLACKLIST_INPUT_SCHEMA: Any = Field( 81 | "ninja_jwt.schema.TokenBlacklistInputSchema" 82 | ) 83 | TOKEN_VERIFY_INPUT_SCHEMA: Any = Field("ninja_jwt.schema.TokenVerifyInputSchema") 84 | 85 | @root_validator 86 | def validate_ninja_jwt_settings(cls, values): 87 | for item in NinjaJWT_SETTINGS_DEFAULTS.keys(): 88 | if isinstance(values[item], (tuple, list)) and isinstance( 89 | values[item][0], str 90 | ): 91 | values[item] = [LazyStrImport(str(klass)) for klass in values[item]] 92 | if isinstance(values[item], str): 93 | values[item] = LazyStrImport(values[item]) 94 | return values 95 | 96 | 97 | # convert to lazy object 98 | api_settings = NinjaJWTSettings.from_orm(USER_SETTINGS) 99 | 100 | 101 | def reload_api_settings(*args: Any, **kwargs: Any) -> None: 102 | global api_settings 103 | 104 | setting, value = kwargs["setting"], kwargs["value"] 105 | 106 | if setting in ["SIMPLE_JWT", "NINJA_JWT"]: 107 | api_settings = NinjaJWTSettings.from_orm( 108 | NinjaJWTUserDefinedSettingsMapper(value) 109 | ) 110 | 111 | 112 | setting_changed.connect(reload_api_settings) 113 | -------------------------------------------------------------------------------- /ninja_jwt/state.py: -------------------------------------------------------------------------------- 1 | from .backends import TokenBackend 2 | from .settings import api_settings 3 | 4 | token_backend = TokenBackend( 5 | api_settings.ALGORITHM, 6 | api_settings.SIGNING_KEY, 7 | api_settings.VERIFYING_KEY, 8 | api_settings.AUDIENCE, 9 | api_settings.ISSUER, 10 | api_settings.JWK_URL, 11 | api_settings.LEEWAY, 12 | api_settings.JSON_ENCODER, 13 | ) 14 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/__init__.py: -------------------------------------------------------------------------------- 1 | from django import VERSION 2 | 3 | if VERSION < (3, 2): 4 | default_app_config = "ninja_jwt.token_blacklist.apps.TokenBlacklistConfig" 5 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | from .models import BlacklistedToken, OutstandingToken 5 | 6 | 7 | class OutstandingTokenAdmin(admin.ModelAdmin): 8 | list_display = ( 9 | "jti", 10 | "user", 11 | "created_at", 12 | "expires_at", 13 | ) 14 | search_fields = ( 15 | "user__id", 16 | "jti", 17 | ) 18 | ordering = ("user",) 19 | 20 | def get_queryset(self, *args, **kwargs): 21 | qs = super().get_queryset(*args, **kwargs) 22 | 23 | return qs.select_related("user") 24 | 25 | # Read-only behavior defined below 26 | actions = None 27 | 28 | def get_readonly_fields(self, *args, **kwargs): 29 | return [f.name for f in self.model._meta.fields] 30 | 31 | def has_add_permission(self, *args, **kwargs): 32 | return False 33 | 34 | def has_delete_permission(self, *args, **kwargs): 35 | return False 36 | 37 | def has_change_permission(self, request, obj=None): 38 | return request.method in [ 39 | "GET", 40 | "HEAD", 41 | ] and super().has_change_permission( # noqa: W504 42 | request, obj 43 | ) 44 | 45 | 46 | admin.site.register(OutstandingToken, OutstandingTokenAdmin) 47 | 48 | 49 | class BlacklistedTokenAdmin(admin.ModelAdmin): 50 | list_display = ( 51 | "token_jti", 52 | "token_user", 53 | "token_created_at", 54 | "token_expires_at", 55 | "blacklisted_at", 56 | ) 57 | search_fields = ( 58 | "token__user__id", 59 | "token__jti", 60 | ) 61 | ordering = ("token__user",) 62 | 63 | def get_queryset(self, *args, **kwargs): 64 | qs = super().get_queryset(*args, **kwargs) 65 | 66 | return qs.select_related("token__user") 67 | 68 | def token_jti(self, obj): 69 | return obj.token.jti 70 | 71 | token_jti.short_description = _("jti") 72 | token_jti.admin_order_field = "token__jti" 73 | 74 | def token_user(self, obj): 75 | return obj.token.user 76 | 77 | token_user.short_description = _("user") 78 | token_user.admin_order_field = "token__user" 79 | 80 | def token_created_at(self, obj): 81 | return obj.token.created_at 82 | 83 | token_created_at.short_description = _("created at") 84 | token_created_at.admin_order_field = "token__created_at" 85 | 86 | def token_expires_at(self, obj): 87 | return obj.token.expires_at 88 | 89 | token_expires_at.short_description = _("expires at") 90 | token_expires_at.admin_order_field = "token__expires_at" 91 | 92 | 93 | admin.site.register(BlacklistedToken, BlacklistedTokenAdmin) 94 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class TokenBlacklistConfig(AppConfig): 6 | name = "ninja_jwt.token_blacklist" 7 | verbose_name = _("Token Blacklist") 8 | default_auto_field = "django.db.models.BigAutoField" 9 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/token_blacklist/management/__init__.py -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/token_blacklist/management/commands/__init__.py -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/management/commands/flushexpiredtokens.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from ninja_jwt.utils import aware_utcnow 4 | 5 | from ...models import OutstandingToken 6 | 7 | 8 | class Command(BaseCommand): 9 | help = "Flushes any expired tokens in the outstanding token list" 10 | 11 | def handle(self, *args, **kwargs): 12 | OutstandingToken.objects.filter(expires_at__lte=aware_utcnow()).delete() 13 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | import django.db.models.deletion 2 | from django.conf import settings 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | initial = True 8 | 9 | dependencies = [ 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="BlacklistedToken", 16 | fields=[ 17 | ( 18 | "id", 19 | models.AutoField( 20 | auto_created=True, 21 | primary_key=True, 22 | serialize=False, 23 | verbose_name="ID", 24 | ), 25 | ), 26 | ("blacklisted_at", models.DateTimeField(auto_now_add=True)), 27 | ], 28 | ), 29 | migrations.CreateModel( 30 | name="OutstandingToken", 31 | fields=[ 32 | ( 33 | "id", 34 | models.AutoField( 35 | auto_created=True, 36 | primary_key=True, 37 | serialize=False, 38 | verbose_name="ID", 39 | ), 40 | ), 41 | ("jti", models.UUIDField(unique=True)), 42 | ("token", models.TextField()), 43 | ("created_at", models.DateTimeField()), 44 | ("expires_at", models.DateTimeField()), 45 | ( 46 | "user", 47 | models.ForeignKey( 48 | on_delete=django.db.models.deletion.CASCADE, 49 | to=settings.AUTH_USER_MODEL, 50 | ), 51 | ), 52 | ], 53 | options={ 54 | "ordering": ("user",), 55 | }, 56 | ), 57 | migrations.AddField( 58 | model_name="blacklistedtoken", 59 | name="token", 60 | field=models.OneToOneField( 61 | on_delete=django.db.models.deletion.CASCADE, 62 | to="token_blacklist.OutstandingToken", 63 | ), 64 | ), 65 | ] 66 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/migrations/0002_outstandingtoken_jti_hex.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations, models 2 | 3 | 4 | class Migration(migrations.Migration): 5 | dependencies = [ 6 | ("token_blacklist", "0001_initial"), 7 | ] 8 | 9 | operations = [ 10 | migrations.AddField( 11 | model_name="outstandingtoken", 12 | name="jti_hex", 13 | field=models.CharField(blank=True, null=True, max_length=255), 14 | ), 15 | ] 16 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/migrations/0003_auto_20171017_2007.py: -------------------------------------------------------------------------------- 1 | from uuid import UUID 2 | 3 | from django.db import migrations 4 | 5 | 6 | def populate_jti_hex(apps, schema_editor): 7 | OutstandingToken = apps.get_model("token_blacklist", "OutstandingToken") 8 | 9 | db_alias = schema_editor.connection.alias 10 | for token in OutstandingToken.objects.using(db_alias).all(): 11 | token.jti_hex = token.jti.hex 12 | token.save() 13 | 14 | 15 | def reverse_populate_jti_hex(apps, schema_editor): # pragma: no cover 16 | OutstandingToken = apps.get_model("token_blacklist", "OutstandingToken") 17 | 18 | db_alias = schema_editor.connection.alias 19 | for token in OutstandingToken.objects.using(db_alias).all(): 20 | token.jti = UUID(hex=token.jti_hex) 21 | token.save() 22 | 23 | 24 | class Migration(migrations.Migration): 25 | dependencies = [ 26 | ("token_blacklist", "0002_outstandingtoken_jti_hex"), 27 | ] 28 | 29 | operations = [ 30 | migrations.RunPython(populate_jti_hex, reverse_populate_jti_hex), 31 | ] 32 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/migrations/0004_auto_20171017_2013.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations, models 2 | 3 | 4 | class Migration(migrations.Migration): 5 | dependencies = [ 6 | ("token_blacklist", "0003_auto_20171017_2007"), 7 | ] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="outstandingtoken", 12 | name="jti_hex", 13 | field=models.CharField(unique=True, max_length=255), 14 | ), 15 | ] 16 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/migrations/0005_remove_outstandingtoken_jti.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations 2 | 3 | 4 | class Migration(migrations.Migration): 5 | dependencies = [ 6 | ("token_blacklist", "0004_auto_20171017_2013"), 7 | ] 8 | 9 | operations = [ 10 | migrations.RemoveField( 11 | model_name="outstandingtoken", 12 | name="jti", 13 | ), 14 | ] 15 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/migrations/0006_auto_20171017_2113.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations 2 | 3 | 4 | class Migration(migrations.Migration): 5 | dependencies = [ 6 | ("token_blacklist", "0005_remove_outstandingtoken_jti"), 7 | ] 8 | 9 | operations = [ 10 | migrations.RenameField( 11 | model_name="outstandingtoken", 12 | old_name="jti_hex", 13 | new_name="jti", 14 | ), 15 | ] 16 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/migrations/0007_auto_20171017_2214.py: -------------------------------------------------------------------------------- 1 | import django.db.models.deletion 2 | from django.conf import settings 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("token_blacklist", "0006_auto_20171017_2113"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="outstandingtoken", 14 | name="created_at", 15 | field=models.DateTimeField(blank=True, null=True), 16 | ), 17 | migrations.AlterField( 18 | model_name="outstandingtoken", 19 | name="user", 20 | field=models.ForeignKey( 21 | blank=True, 22 | null=True, 23 | on_delete=django.db.models.deletion.CASCADE, 24 | to=settings.AUTH_USER_MODEL, 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/migrations/0008_migrate_to_bigautofield.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations, models 2 | 3 | 4 | class Migration(migrations.Migration): 5 | dependencies = [ 6 | ("token_blacklist", "0007_auto_20171017_2214"), 7 | ] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="blacklistedtoken", 12 | name="id", 13 | field=models.BigAutoField( 14 | auto_created=True, primary_key=True, serialize=False, verbose_name="ID" 15 | ), 16 | ), 17 | migrations.AlterField( 18 | model_name="outstandingtoken", 19 | name="id", 20 | field=models.BigAutoField( 21 | auto_created=True, primary_key=True, serialize=False, verbose_name="ID" 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/migrations/0010_fix_migrate_to_bigautofield.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.3 on 2021-05-27 17:46 2 | 3 | from pathlib import Path 4 | 5 | from django.db import migrations, models 6 | 7 | parent_dir = Path(__file__).resolve(strict=True).parent 8 | 9 | 10 | class Migration(migrations.Migration): 11 | dependencies = [ 12 | ("token_blacklist", "0008_migrate_to_bigautofield"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name="blacklistedtoken", 18 | name="id", 19 | field=models.BigAutoField(primary_key=True, serialize=False), 20 | ), 21 | migrations.AlterField( 22 | model_name="outstandingtoken", 23 | name="id", 24 | field=models.BigAutoField(primary_key=True, serialize=False), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/migrations/0011_linearizes_history.py: -------------------------------------------------------------------------------- 1 | import fnmatch 2 | import os 3 | from pathlib import Path 4 | 5 | from django.db import migrations, models # noqa F401 6 | 7 | parent_dir = Path(__file__).resolve(strict=True).parent 8 | 9 | 10 | class Migration(migrations.Migration): 11 | def __init__(self, *args, **kwargs): 12 | super().__init__(*args, **kwargs) 13 | self.dependencies = [("token_blacklist", "0010_fix_migrate_to_bigautofield")] 14 | _m = sorted(fnmatch.filter(os.listdir(parent_dir), "000*.py")) 15 | if len(_m) == 9: 16 | self.dependencies.insert(0, ("token_blacklist", os.path.splitext(_m[8])[0])) 17 | 18 | operations = [] 19 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/migrations/0012_alter_outstandingtoken_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.10 on 2022-01-24 06:42 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [ 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ("token_blacklist", "0011_linearizes_history"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="outstandingtoken", 17 | name="user", 18 | field=models.ForeignKey( 19 | blank=True, 20 | null=True, 21 | on_delete=django.db.models.deletion.SET_NULL, 22 | to=settings.AUTH_USER_MODEL, 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/ninja_jwt/token_blacklist/migrations/__init__.py -------------------------------------------------------------------------------- /ninja_jwt/token_blacklist/models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | 4 | 5 | class OutstandingToken(models.Model): 6 | id = models.BigAutoField(primary_key=True, serialize=False) 7 | user = models.ForeignKey( 8 | settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True 9 | ) 10 | 11 | jti = models.CharField(unique=True, max_length=255) 12 | token = models.TextField() 13 | 14 | created_at = models.DateTimeField(null=True, blank=True) 15 | expires_at = models.DateTimeField() 16 | 17 | class Meta: 18 | # Work around for a bug in Django: 19 | # https://code.djangoproject.com/ticket/19422 20 | # 21 | # Also see corresponding ticket: 22 | # https://github.com/encode/django-rest-framework/issues/705 23 | abstract = "ninja_jwt.token_blacklist" not in settings.INSTALLED_APPS 24 | ordering = ("user",) 25 | 26 | def __str__(self): 27 | return "Token for {} ({})".format( 28 | self.user, 29 | self.jti, 30 | ) 31 | 32 | 33 | class BlacklistedToken(models.Model): 34 | id = models.BigAutoField(primary_key=True, serialize=False) 35 | token = models.OneToOneField(OutstandingToken, on_delete=models.CASCADE) 36 | 37 | blacklisted_at = models.DateTimeField(auto_now_add=True) 38 | 39 | class Meta: 40 | # Work around for a bug in Django: 41 | # https://code.djangoproject.com/ticket/19422 42 | # 43 | # Also see corresponding ticket: 44 | # https://github.com/encode/django-rest-framework/issues/705 45 | abstract = "ninja_jwt.token_blacklist" not in settings.INSTALLED_APPS 46 | 47 | def __str__(self): 48 | return "Blacklisted token for {}".format(self.token.user) 49 | -------------------------------------------------------------------------------- /ninja_jwt/utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import typing as t 3 | from calendar import timegm 4 | from datetime import datetime 5 | from functools import wraps 6 | from importlib import import_module 7 | 8 | from django.conf import settings 9 | from django.utils.functional import lazy 10 | 11 | from ninja_jwt import exceptions 12 | 13 | try: 14 | from datetime import timezone 15 | except ImportError: 16 | from django.utils import timezone 17 | 18 | 19 | logger = logging.getLogger("django") 20 | 21 | 22 | def token_error(func): 23 | @wraps(func) 24 | def _wrap(*args, **kwargs): 25 | try: 26 | return func(*args, **kwargs) 27 | except exceptions.TokenError as tex: 28 | raise exceptions.InvalidToken(str(tex)) from tex 29 | except Exception as ex: 30 | logger.error(f"{func} raised exception: {str(ex)}") 31 | raise ex 32 | 33 | return _wrap 34 | 35 | 36 | def import_callable(path_or_callable): 37 | if callable(path_or_callable): 38 | return path_or_callable 39 | else: 40 | assert isinstance(path_or_callable, str) 41 | package, attr = path_or_callable.rsplit(".", 1) 42 | packages = import_module(package) 43 | return getattr(packages, attr) 44 | 45 | 46 | def make_utc(dt: datetime) -> datetime: 47 | if settings.USE_TZ and dt.tzinfo is None: 48 | return dt.replace(tzinfo=timezone.utc) 49 | 50 | return dt 51 | 52 | 53 | def aware_utcnow() -> datetime: 54 | dt = datetime.now(tz=timezone.utc) 55 | if not settings.USE_TZ: 56 | dt = dt.replace(tzinfo=None) 57 | 58 | return dt 59 | 60 | 61 | def datetime_to_epoch(dt: datetime) -> int: 62 | return timegm(dt.utctimetuple()) 63 | 64 | 65 | def datetime_from_epoch(ts: float) -> datetime: 66 | dt = datetime.fromtimestamp(ts, tz=timezone.utc) 67 | if not settings.USE_TZ: 68 | dt = dt.replace(tzinfo=None) 69 | 70 | return dt 71 | 72 | 73 | def format_lazy(s: str, *args, **kwargs) -> str: 74 | return s.format(*args, **kwargs) 75 | 76 | 77 | format_lazy: t.Callable = lazy(format_lazy, str) 78 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [tool.flit.module] 6 | name = "ninja_jwt" 7 | 8 | 9 | [project] 10 | name = "django-ninja-jwt" 11 | authors = [ 12 | {name = "Ezeudoh Tochukwu", email = "tochukwu.ezeudoh@gmail.com"}, 13 | ] 14 | dynamic = ["version", "description"] 15 | requires-python = ">=3.7" 16 | readme = "README.md" 17 | home-page = "https://github.com/eadwinCode/django-ninja-jwt" 18 | classifiers = [ 19 | "Intended Audience :: Information Technology", 20 | "Intended Audience :: System Administrators", 21 | "Operating System :: OS Independent", 22 | "Topic :: Internet", 23 | "Topic :: Software Development :: Libraries :: Application Frameworks", 24 | "Topic :: Software Development :: Libraries :: Python Modules", 25 | "Topic :: Software Development :: Libraries", 26 | "Topic :: Software Development", 27 | "Typing :: Typed", 28 | "Environment :: Web Environment", 29 | "Intended Audience :: Developers", 30 | "License :: OSI Approved :: MIT License", 31 | "Programming Language :: Python :: 3", 32 | "Programming Language :: Python :: 3.7", 33 | "Programming Language :: Python :: 3.8", 34 | "Programming Language :: Python :: 3.9", 35 | "Programming Language :: Python :: 3.10", 36 | "Programming Language :: Python :: 3.11", 37 | "Programming Language :: Python :: 3.12", 38 | "Programming Language :: Python :: 3 :: Only", 39 | "Framework :: Django", 40 | "Framework :: Django :: 3.1", 41 | "Framework :: Django :: 3.2", 42 | "Framework :: Django :: 4.0", 43 | "Framework :: Django :: 4.1", 44 | "Framework :: AsyncIO", 45 | "Topic :: Internet :: WWW/HTTP :: HTTP Servers", 46 | "Topic :: Internet :: WWW/HTTP", 47 | ] 48 | 49 | dependencies = [ 50 | "Django >= 2.1", 51 | "pyjwt>=1.7.1,<3", 52 | "pyjwt[crypto]", 53 | "django-ninja-extra >= 0.22.9", 54 | ] 55 | 56 | 57 | [project.urls] 58 | Documentation = "https://github.com/eadwinCode/django-ninja-jwt" 59 | Source = "https://github.com/eadwinCode/django-ninja-jwt" 60 | 61 | [project.optional-dependencies] 62 | crypto = [ 63 | "cryptography>=3.3.1", 64 | ] 65 | 66 | [tool.ruff] 67 | select = [ 68 | "E", # pycodestyle errors 69 | "W", # pycodestyle warnings 70 | "F", # pyflakes 71 | "I", # isort 72 | "C", # flake8-comprehensions 73 | "B", # flake8-bugbear 74 | ] 75 | ignore = [ 76 | "E501", # line too long, handled by black 77 | "B008", # do not perform function calls in argument defaults 78 | "C901", # too complex 79 | ] 80 | 81 | [tool.ruff.per-file-ignores] 82 | "__init__.py" = ["F401"] 83 | 84 | [tool.ruff.isort] 85 | known-third-party = ["django-ninja-extra",] 86 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts= -v --showlocals --durations 10 3 | python_paths= . 4 | xfail_strict=true 5 | 6 | [pytest-watch] 7 | runner= pytest --failed-first --maxfail=1 --no-success-flaky-report 8 | -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | markdown-include 2 | mdx-include >=1.4.1,<2.0.0 3 | mkdocs >=1.1.2,<2.0.0 4 | mkdocs-markdownextradata-plugin >=0.1.7,<0.3.0 5 | mkdocs-material 6 | mkdocstrings 7 | -------------------------------------------------------------------------------- /requirements-tests.txt: -------------------------------------------------------------------------------- 1 | click==8.1.8 2 | cryptography 3 | django-stubs 4 | freezegun 5 | ninja-schema>=0.14.1 6 | pytest 7 | pytest-asyncio==0.24.0 8 | pytest-cov 9 | pytest-django 10 | ruff ==0.11.7 11 | python-jose==3.4.0 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | -r requirements-docs.txt 3 | -r requirements-tests.txt 4 | 5 | pre-commit 6 | -------------------------------------------------------------------------------- /scripts/i18n_updater.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import os 3 | import subprocess 4 | 5 | 6 | def get_list_of_files(dir_name: str, extension: str): 7 | file_list = os.listdir(dir_name) 8 | 9 | result = [] 10 | for entry in file_list: 11 | full_path = os.path.join(dir_name, entry) 12 | if os.path.isdir(full_path): 13 | result = result + get_list_of_files(full_path, extension) 14 | else: 15 | if entry[-len(extension) : len(entry)] == extension: 16 | result.append(full_path) 17 | 18 | return result 19 | 20 | 21 | @contextlib.contextmanager 22 | def cache_creation(): 23 | # DO NOT cache the line number; the file may change 24 | cache: dict[str, str] = {} 25 | for file in get_list_of_files("./", ".po"): 26 | if os.path.isdir(file): 27 | continue 28 | 29 | with open(file) as f: 30 | for line in f.readlines(): 31 | if line.startswith('"POT-Creation-Date: '): 32 | cache[file] = line 33 | break 34 | yield 35 | for file, line_cache in cache.items(): 36 | with open(file, "r+") as f: 37 | lines = f.readlines() 38 | # clear file 39 | f.seek(0) 40 | f.truncate() 41 | 42 | # find line 43 | index = [ 44 | lines.index(x) for x in lines if x.startswith('"POT-Creation-Date: ') 45 | ][0] 46 | 47 | lines[index] = line_cache 48 | f.writelines(lines) 49 | 50 | 51 | def main(): 52 | with cache_creation(): 53 | subprocess.run(["django-admin", "makemessages", "-a"]) 54 | subprocess.run(["django-admin", "compilemessages"]) 55 | 56 | 57 | if __name__ == "__main__": 58 | main() 59 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eadwinCode/django-ninja-jwt/456058163ea81e86203f05806f700d1cc5af7133/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | def pytest_configure(): 2 | from django.conf import settings 3 | 4 | MIDDLEWARE = ( 5 | "django.middleware.common.CommonMiddleware", 6 | "django.contrib.sessions.middleware.SessionMiddleware", 7 | "django.contrib.auth.middleware.AuthenticationMiddleware", 8 | "django.contrib.messages.middleware.MessageMiddleware", 9 | ) 10 | 11 | settings.configure( 12 | DEBUG_PROPAGATE_EXCEPTIONS=True, 13 | DATABASES={ 14 | "default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}, 15 | "other": {"ENGINE": "django.db.backends.sqlite3", "NAME": "other"}, 16 | }, 17 | SITE_ID=1, 18 | SECRET_KEY="not very secret in tests", 19 | USE_I18N=True, 20 | USE_L10N=True, 21 | STATIC_URL="/static/", 22 | ROOT_URLCONF="tests.urls", 23 | TEMPLATES=[ 24 | { 25 | "BACKEND": "django.template.backends.django.DjangoTemplates", 26 | "APP_DIRS": True, 27 | }, 28 | ], 29 | MIDDLEWARE=MIDDLEWARE, 30 | MIDDLEWARE_CLASSES=MIDDLEWARE, 31 | INSTALLED_APPS=( 32 | "django.contrib.auth", 33 | "django.contrib.contenttypes", 34 | "django.contrib.sessions", 35 | "django.contrib.sites", 36 | "django.contrib.staticfiles", 37 | "ninja_extra", 38 | "ninja_jwt", 39 | "ninja_jwt.token_blacklist", 40 | "tests", 41 | ), 42 | PASSWORD_HASHERS=("django.contrib.auth.hashers.MD5PasswordHasher",), 43 | SIMPLE_JWT={ 44 | "BLACKLIST_AFTER_ROTATION": True, 45 | }, 46 | ) 47 | 48 | try: 49 | import django 50 | 51 | django.setup() 52 | except AttributeError: 53 | pass 54 | -------------------------------------------------------------------------------- /tests/keys.py: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY = """ 2 | -----BEGIN RSA PRIVATE KEY----- 3 | MIIEowIBAAKCAQEA3xMJfyl8TOdrsjDLSIodsArJ/NnQB3ZdfbFC5onxATDfRLLA 4 | CHFo3ye694doBKeSe1NFYbfXPvahl6ODX1a23oQyoRQwlL+M99cLcdCa0gGuJXdb 5 | AaF6Em8E+7uSb3290mI+rZmjqyc7gMtKVWKL4e5i2PerFFBoYkZ7E90KOp2t0ZAD 6 | x2uqF4VTOfYLHG0cPgSw9/ptDStJqJVAOiRRqbv0j0GOFMDYNcN0mDlnpryhQFbQ 7 | iMqn4IJIURZUVBJujFSa45cJPvSmMb6NrzZ1crg5UN6/5Mu2mxQzAi21+vpgGL+E 8 | EuekUd7sRgEAjTHjLKzotLAGo7EGa8sL1vMSFwIDAQABAoIBAQCGGWabF/BONswq 9 | CWUazVR9cG7uXm3NHp2jIr1p40CLC7scDCyeprZ5d+PQS4j/S1Ema++Ih8CQbCjG 10 | BJjD5lf2OhhJdt6hfOkcUBzkJZf8aOAsS6zctRqyHCUtwxuLhFZpM4AkUfjuuZ3u 11 | lcawv5YBkpG/hltE0fV+Jop0bWtpwiKxVsHXVcS0WEPXic0lsOTBCw8m81JXqjir 12 | PCBOnkxgNpHSt69S1xnW3l9fPUWVlduO3EIZ5PZG2BxU081eZW31yIlKsDJhfgm6 13 | R5Vlr5DynqeojAd6SNliCzNXZP28GOpQBrYIeVQWA1yMANvkvd4apz9GmDrjF/Fd 14 | g8Chah+5AoGBAPc/+zyuDZKVHK7MxwLPlchCm5Zb4eou4ycbwEB+P3gDS7MODGu4 15 | qvx7cstTZMuMavNRcJsfoiMMrke9JrqGe4rFGiKRFLVBY2Xwr+95pKNC11EWI1lF 16 | 5qDAmreDsj2alVJT5yZ9hsAWTsk2i+xj+/XHWYVkr67pRvOPRAmGMB+NAoGBAOb4 17 | CBHe184Hn6Ie+gSD4OjewyUVmr3JDJ41s8cjb1kBvDJ/wv9Rvo9yz2imMr2F0YGc 18 | ytHraM77v8KOJuJWpvGjEg8I0a/rSttxWQ+J0oYJSIPn+eDpAijNWfOp1aKRNALT 19 | pboCXcnSn+djJFKkNJ2hR7R/vrrM6Jyly1jcVS0zAoGAQpdt4Cr0pt0YS5AFraEh 20 | Mz2VUArRLtSQA3F69yPJjlY85i3LdJvZGYVaJp8AT74y8/OkQ3NipNP+gH3WV3hu 21 | /7IUVukCTcsdrVAE4pe9mucevM0cmie0dOlLAlArCmJ/Axxr7jbyuvuHHrRdPT60 22 | lr6pQr8afh6AKIsWhQYqIeUCgYA+v9IJcN52hhGzjPDl+yJGggbIc3cn6pA4B2UB 23 | TDo7F0KXAajrjrzT4iBBUS3l2Y5SxVNA9tDxsumlJNOhmGMgsOn+FapKPgWHWuMU 24 | WqBMdAc0dvinRwakKS4wCcsVsJdN0UxsHap3Y3a3+XJr1VrKHIALpM0fmP31WQHG 25 | 8Y1eiwKBgF6AYXxo0FzZacAommZrAYoxFZT1u4/rE/uvJ2K9HYRxLOVKZe+89ki3 26 | D7AOmrxe/CAc/D+nNrtUIv3RFGfadfSBWzyLw36ekW76xPdJgqJsSz5XJ/FgzDW+ 27 | WNC5oOtiPOMCymP75oKOjuZJZ2SPLRmiuO/qvI5uAzBHxRC1BKdt 28 | -----END RSA PRIVATE KEY----- 29 | """ 30 | 31 | PUBLIC_KEY = """ 32 | -----BEGIN PUBLIC KEY----- 33 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3xMJfyl8TOdrsjDLSIod 34 | sArJ/NnQB3ZdfbFC5onxATDfRLLACHFo3ye694doBKeSe1NFYbfXPvahl6ODX1a2 35 | 3oQyoRQwlL+M99cLcdCa0gGuJXdbAaF6Em8E+7uSb3290mI+rZmjqyc7gMtKVWKL 36 | 4e5i2PerFFBoYkZ7E90KOp2t0ZADx2uqF4VTOfYLHG0cPgSw9/ptDStJqJVAOiRR 37 | qbv0j0GOFMDYNcN0mDlnpryhQFbQiMqn4IJIURZUVBJujFSa45cJPvSmMb6NrzZ1 38 | crg5UN6/5Mu2mxQzAi21+vpgGL+EEuekUd7sRgEAjTHjLKzotLAGo7EGa8sL1vMS 39 | FwIDAQAB 40 | -----END PUBLIC KEY----- 41 | """ 42 | 43 | PRIVATE_KEY_2 = """ 44 | -----BEGIN RSA PRIVATE KEY----- 45 | MIIJKQIBAAKCAgEAwpwcivLRv5T/2pb9zZpfh4Fy1vla3vm5N2WtkhjB0DX1HmOh 46 | SN9O1Q3byW9VLQtuNAdZ8+hc2jU5DIVApFF+b1Uo1C76qZWOLLwK/yvweACTdT4M 47 | ISeYHaUd/B7YUNtUQEZF0xxjMM8jRXEuI902pLEUBx9cd2d8KzCgXyC2dsNIt3zg 48 | WCu6RhL1V10lhBXMwl0N1+DAIRsXjCazwaxBMrTNOsXrex98aaPk3++V1KkPbLnV 49 | Zk8I7dbpt9EYSvMB7DaXFiLL4XySamFfYmtq9n9bxKXxnzpObolWkD0SbioaxkCq 50 | vEFyy2/y4ZIxYC1mHhRgTtSicnk8WGOgv6Ax1sRHpbRVDtXMmDHn7oiJdd8YCGvT 51 | 9AQoibeccPUZbwaiuiULSVjphKErXLg7nH6/ct8bVYZnh0GnYTljC3O0f3/D1A9Z 52 | PxLHJPc9K2GCmZ+TNMmDDmGbLz/TgFiSZE3CQEORxbGyNh5MZuIrGub9xaKB4Hon 53 | Rf8HXwVa/zL4dKxpzYzHNBH8wqPOWCLXjp65lcMW7JBAnrqshI0wOCEbk13uLudB 54 | aNL4k/h9M9RKaEW24tKpg29XsWu9II3Bt94x7gwhokE7gICsycUSQWTGaBovbvn0 55 | C8k4gkPq6teGAD6AJ6/YV2hiPbdoRL5EkMS3E01hmaHk9xlOpo73IjUxhXUCAwEA 56 | AQKCAgEAjEfNx1cbXNdBysa2cuOJYvsr1cxu9XXbThRsFnjkFHsgkuRMWWQmxis0 57 | ODKZmlu396co70mazOw6kEzpeMkJs6UWRkULCP02PAbcgm2g7E+1+3hbc/a/jvb7 58 | 80Yktbw0MhS1tmSrF37otODN2qpV/kdq4Wt40tV0ywlFQO0qudcw7psEeGok3uhB 59 | k9Uf+uNf8uby2J84v2RxB+TKBJxvbuanXWtXwCvFGb07eTSRs3aeGMioDBSCojcd 60 | yBPgR/59b1E2fY1dm8+ZFzfTcvVtZ/wMIWdhEV8NNF6pWFW9mE2feTMaH5Op9P1g 61 | fbtM/kAbcSlM9uYNpyi/GBPQxvDpmttbBVyuSx2G7ct9EeMjJQ0QjC6DWG3zwz/b 62 | S8f9y/K2pnzKZdUrQBI9RRYu2OqHlfLQ/RWnr2/mRvFr9bd3pZEYchnB/PyKKvZB 63 | eS0X3LibP7ktmSFyB/xtsap/S/qHf9acY5Uu7w0gSXoNFTAG1zW/cHTXMYC0oYlD 64 | L8F3fO7ddo2nx2YOxEm5e5GDgc3V952GTgFZclsc6jn6AkOVyzoPYEfJy3JjPyTv 65 | doDrK5lPJ5ekmdyTdMhw376dHmkSB4D+27U/WMHN0EgMCmJznoWhvGuLG/mLbK+q 66 | d/K3Cy3ipUJDhb3OrDzfJ+Ps7E8BYBxZp69yhV5gV1T40pgG/WECggEBAPtF+3Uh 67 | CBh5amhFQOHc4hMR8UGTOBWF/uz15+PImzLcpN/gpgoWaIExQsrbQCStemMO7hhi 68 | N3/pf1+V0hbRb8cLN/B/BG/31pw3bCJKB+nYr1elCHOhS9+1txekcoqA3pVdmyZ0 69 | TcyTczgFluXmWurIJqj9dp+KOJlh6Q6qehKNX6D/E9RszuZiHOeKzgaDCy+WI3DK 70 | bxtthfddasBxBYji6ObRn0BY6RjUnkHAra6Lib3M8Y8qxJnQozjBuWCwf2bflN++ 71 | 2tcO/m5s2tksNDWfu4q+ruh2zWGPbaEvhEs0o2z0kucCukEBqqbMT+p9HdFbkom4 72 | XJOT4ZUCJfCdGIkCggEBAMZFQ3BW4I18TVRaMQMv/5ivp9/Qd/Ls/jU+wfiiZnq9 73 | zT5a/9LkI/rWq4G+gT93KxRtH43FpJlo1N+1e4sEYf6a0gcgo8inyE2MZCJFuN7g 74 | GbOU/qgzLmCtHmbUjyHNzBk/+SK2Jh4PpF3DLOSU8+AWmI8aF9Cl7UojXDTLGhmR 75 | qS7MBv/jNUT93xTJUrSvkp2HWF8GOwrVd9CUo6zNnFb3nIP3fUARCpa/aurPaiim 76 | U+mv7NlpK/wUiP8dWUu3+hqa3yPE9WGHixdfMd6o4KfAyqMb5WErR1laU3wbl0B1 77 | FzFxECn5Dkt9p93LzXMIn58eT2ZeneNdzWKQn0BOco0CggEBAOoN9/rUt+vEPR+/ 78 | Un6Q92z4C5gff+Bcnmcvb783v4kTCekYItHGqbWdoy++JvODPDtFTvcblcLqRyFM 79 | NxPWJp5rjsHQLtv1Kcz9uxX9i32Bv2KOcV7z4e8SHuhA4AivnaXYOYsKTuW+e1a1 80 | rieb+Rg1M/25i2N0puAI2cQ1e9wIIAmhUGFQsTDcNzxeiSZ7rlG3Mm//wJr13BHc 81 | zHFRVex6IKPQotyXdRkSBBAPYDjz9Wv8mQ3YsqTsOP3HRdwQy7uRi+UWrFYiu1E0 82 | yG3+xOsmTNUiZV5YO1si9OVtk3dSIuB8uNHCMqgW21Tff5lWzg2TlN4AAwvcdgYM 83 | qDaGvrECggEAOT+IkGhVYCTzAxcjrcLvLzwQ4dwEtlzdrawYP91Mb8Zb+9Q0p8T9 84 | 6pCPZuAF27hh9PzpLntR4oXVaV6ydFponSZA3JP9FpPzjwipZQfysE/Ou/6aZSCa 85 | FIoIDDL1vRH6C5RgMDid2vIzSGtxi/LCVALSPAeRtsoiMNTy6791IsrfKcb5gmst 86 | V2ViQ1M6ETfcwqVwy8c1xxQKC2zPsbaQnL/ULnqIbLY+83YDvhbzlRcphYEph0EJ 87 | 1ThsshTcUrOlgIcVRPO60lVbwPzYnmzuqSFOoTgNzDe92zvsfRpOWus0Li9yNlxW 88 | V0/J543QHZXw2PXcgTdyqVLNWddeVCgShQKCAQBp63G+/O26R/jYjh74FCg0PQ6r 89 | yAk2kmjHLsIPRFIiW/u6DXn5hufk2wM/JTQf/9LRXhjLdzqehOoQasQSJAR+wiOx 90 | CeTDD91sHtMqQb0sAbQyLGKsDBG3dfnB1BEGQccfZnDZKx7N7kvzkN5/4CYpscWn 91 | CNnBq6IE9iu31w3VEKxAc0bdLxPdOw79NhgwzW1JgysFBQAEdtZ1qlMPQOj9IMnB 92 | eszY1HhshLbaaUwuG8SUDvKzZUpEEq711yQoW4y1yzOrcMmTYzRFuMczcB4v0tqe 93 | /sno2/CDUEKR5SsDgnqB9hPzFJDclfN/MPdpx9X29JF1RlGsz8RJS29ztWyh 94 | -----END RSA PRIVATE KEY----- 95 | """ 96 | 97 | PUBLIC_KEY_2 = """ 98 | -----BEGIN PUBLIC KEY----- 99 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwpwcivLRv5T/2pb9zZpf 100 | h4Fy1vla3vm5N2WtkhjB0DX1HmOhSN9O1Q3byW9VLQtuNAdZ8+hc2jU5DIVApFF+ 101 | b1Uo1C76qZWOLLwK/yvweACTdT4MISeYHaUd/B7YUNtUQEZF0xxjMM8jRXEuI902 102 | pLEUBx9cd2d8KzCgXyC2dsNIt3zgWCu6RhL1V10lhBXMwl0N1+DAIRsXjCazwaxB 103 | MrTNOsXrex98aaPk3++V1KkPbLnVZk8I7dbpt9EYSvMB7DaXFiLL4XySamFfYmtq 104 | 9n9bxKXxnzpObolWkD0SbioaxkCqvEFyy2/y4ZIxYC1mHhRgTtSicnk8WGOgv6Ax 105 | 1sRHpbRVDtXMmDHn7oiJdd8YCGvT9AQoibeccPUZbwaiuiULSVjphKErXLg7nH6/ 106 | ct8bVYZnh0GnYTljC3O0f3/D1A9ZPxLHJPc9K2GCmZ+TNMmDDmGbLz/TgFiSZE3C 107 | QEORxbGyNh5MZuIrGub9xaKB4HonRf8HXwVa/zL4dKxpzYzHNBH8wqPOWCLXjp65 108 | lcMW7JBAnrqshI0wOCEbk13uLudBaNL4k/h9M9RKaEW24tKpg29XsWu9II3Bt94x 109 | 7gwhokE7gICsycUSQWTGaBovbvn0C8k4gkPq6teGAD6AJ6/YV2hiPbdoRL5EkMS3 110 | E01hmaHk9xlOpo73IjUxhXUCAwEAAQ== 111 | -----END PUBLIC KEY----- 112 | """ 113 | 114 | 115 | ES256_PRIVATE_KEY = """ 116 | -----BEGIN EC PRIVATE KEY----- 117 | MHcCAQEEIMtBPxiLHcJCrAGdz4jHvTtAh6Rw7351AckG3whXq2WOoAoGCCqGSM49 118 | AwEHoUQDQgAEMZHyNxbkr7+zqQ1dQk/zug2pwYdztmjhpC+XqK88q5NfIS1cBYYt 119 | zhHUS4vGpazNqbW8HA3ZIvJRmx4L96O6/w== 120 | -----END EC PRIVATE KEY----- 121 | """ 122 | 123 | ES256_PUBLIC_KEY = """ 124 | -----BEGIN PUBLIC KEY----- 125 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMZHyNxbkr7+zqQ1dQk/zug2pwYdz 126 | tmjhpC+XqK88q5NfIS1cBYYtzhHUS4vGpazNqbW8HA3ZIvJRmx4L96O6/w== 127 | -----END PUBLIC KEY----- 128 | """ 129 | -------------------------------------------------------------------------------- /tests/models.py: -------------------------------------------------------------------------------- 1 | # Add models here 2 | -------------------------------------------------------------------------------- /tests/test_authentication.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | import django 4 | import pytest 5 | from django.contrib.auth import get_user_model 6 | 7 | from ninja_jwt import authentication 8 | from ninja_jwt.exceptions import AuthenticationFailed, InvalidToken 9 | from ninja_jwt.models import TokenUser 10 | from ninja_jwt.settings import api_settings 11 | from ninja_jwt.tokens import AccessToken, SlidingToken 12 | 13 | User = get_user_model() 14 | AuthToken = api_settings.AUTH_TOKEN_CLASSES[0] 15 | 16 | 17 | class TestJWTAuth: 18 | @pytest.fixture(autouse=True) 19 | def setUp(self): 20 | self.init_backend() 21 | 22 | def init_backend(self): 23 | self.backend = authentication.JWTAuth() 24 | 25 | @pytest.mark.django_db 26 | def test_get_validated_token(self, monkeypatch): 27 | # Should raise InvalidToken if token not valid 28 | token = AuthToken() 29 | token.set_exp(lifetime=-timedelta(days=1)) 30 | with pytest.raises(InvalidToken): 31 | self.backend.get_validated_token(str(token)) 32 | 33 | # Otherwise, should return validated token 34 | token.set_exp() 35 | assert self.backend.get_validated_token(str(token)).payload == token.payload 36 | 37 | # Should not accept tokens not included in AUTH_TOKEN_CLASSES 38 | sliding_token = SlidingToken() 39 | with monkeypatch.context() as m: 40 | m.setattr( 41 | api_settings, "AUTH_TOKEN_CLASSES", ("ninja_jwt.tokens.AccessToken",) 42 | ) 43 | with pytest.raises(InvalidToken) as e: 44 | self.backend.get_validated_token(str(sliding_token)) 45 | 46 | details = e.value.detail 47 | assert len(details["messages"]) == 1 48 | assert details["messages"][0] == { 49 | "token_class": "AccessToken", 50 | "token_type": "access", 51 | "message": "Token has wrong type", 52 | } 53 | 54 | # Should accept tokens included in AUTH_TOKEN_CLASSES 55 | access_token = AccessToken() 56 | sliding_token = SlidingToken() 57 | with monkeypatch.context() as m: 58 | m.setattr( 59 | api_settings, 60 | "AUTH_TOKEN_CLASSES", 61 | ( 62 | "ninja_jwt.tokens.AccessToken", 63 | "ninja_jwt.tokens.SlidingToken", 64 | ), 65 | ) 66 | self.backend.get_validated_token(str(access_token)) 67 | self.backend.get_validated_token(str(sliding_token)) 68 | 69 | @pytest.mark.django_db 70 | def test_get_user(self): 71 | payload = {"some_other_id": "foo"} 72 | 73 | # Should raise error if no recognizable user identification 74 | with pytest.raises(InvalidToken): 75 | self.backend.get_user(payload) 76 | 77 | payload[api_settings.USER_ID_CLAIM] = 42 78 | 79 | # Should raise exception if user not found 80 | with pytest.raises(AuthenticationFailed): 81 | self.backend.get_user(payload) 82 | 83 | u = User.objects.create_user(username="markhamill") 84 | u.is_active = False 85 | u.save() 86 | 87 | payload[api_settings.USER_ID_CLAIM] = getattr(u, api_settings.USER_ID_FIELD) 88 | 89 | # Should raise exception if user is inactive 90 | with pytest.raises(AuthenticationFailed): 91 | self.backend.get_user(payload) 92 | 93 | u.is_active = True 94 | u.save() 95 | 96 | # Otherwise, should return correct user 97 | assert self.backend.get_user(payload).id == u.id 98 | 99 | 100 | class TestJWTTokenUserAuth: 101 | @pytest.fixture(autouse=True) 102 | def setUp(self): 103 | self.init_backend() 104 | 105 | def init_backend(self): 106 | self.backend = authentication.JWTTokenUserAuth() 107 | 108 | @pytest.mark.django_db 109 | def test_get_user(self): 110 | payload = {"some_other_id": "foo"} 111 | 112 | # Should raise error if no recognizable user identification 113 | with pytest.raises(InvalidToken): 114 | self.backend.get_user(payload) 115 | 116 | payload[api_settings.USER_ID_CLAIM] = 42 117 | 118 | # Otherwise, should return a token user object 119 | user = self.backend.get_user(payload) 120 | 121 | assert isinstance(user, TokenUser) 122 | assert user.id == 42 123 | 124 | @pytest.mark.django_db 125 | def test_custom_tokenuser(self, monkeypatch): 126 | from django.utils.functional import cached_property 127 | 128 | class BobSaget(TokenUser): 129 | @cached_property 130 | def username(self): 131 | return "bsaget" 132 | 133 | with monkeypatch.context() as m: 134 | m.setattr(api_settings, "TOKEN_USER_CLASS", BobSaget) 135 | 136 | # Should return a token user object 137 | payload = {api_settings.USER_ID_CLAIM: 42} 138 | user = self.backend.get_user(payload) 139 | 140 | assert isinstance(user, api_settings.TOKEN_USER_CLASS) 141 | assert user.id == 42 142 | assert user.username == "bsaget" 143 | 144 | 145 | if not django.VERSION < (3, 1): 146 | from asgiref.sync import sync_to_async 147 | 148 | class TestAsyncJWTAuth(TestJWTAuth): 149 | def init_backend(self): 150 | self.backend = authentication.AsyncJWTAuth() 151 | 152 | @pytest.mark.asyncio 153 | @pytest.mark.django_db 154 | async def test_get_validated_token(self, monkeypatch): 155 | _test_get_validated_token = sync_to_async( 156 | super(TestAsyncJWTAuth, self).test_get_validated_token 157 | ) 158 | await _test_get_validated_token(monkeypatch=monkeypatch) 159 | 160 | @pytest.mark.asyncio 161 | @pytest.mark.django_db 162 | async def test_get_user(self): 163 | _test_get_user = sync_to_async(super(TestAsyncJWTAuth, self).test_get_user) 164 | await _test_get_user() 165 | 166 | class TestAsyncJWTTokenUserAuth(TestJWTTokenUserAuth): 167 | def init_backend(self): 168 | self.backend = authentication.AsyncJWTTokenUserAuth() 169 | 170 | @pytest.mark.asyncio 171 | @pytest.mark.django_db 172 | async def test_get_user(self): 173 | _test_get_user = sync_to_async(super().test_get_user) 174 | await _test_get_user() 175 | 176 | @pytest.mark.asyncio 177 | @pytest.mark.django_db 178 | async def test_custom_tokenuser(self, monkeypatch): 179 | _test_custom_tokenuser = sync_to_async(super().test_custom_tokenuser) 180 | await _test_custom_tokenuser(monkeypatch=monkeypatch) 181 | -------------------------------------------------------------------------------- /tests/test_integration.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | import django 4 | import pytest 5 | from django.contrib.auth import get_user_model 6 | 7 | from ninja_jwt.compat import reverse 8 | from ninja_jwt.settings import api_settings 9 | from ninja_jwt.tokens import AccessToken 10 | 11 | from .utils import APIViewTestCase 12 | 13 | User = get_user_model() 14 | 15 | 16 | @pytest.mark.django_db 17 | class TestTestView(APIViewTestCase): 18 | namespace = "jwt" 19 | 20 | @property 21 | def view_name(self): 22 | return f"{self.namespace}:test_view" 23 | 24 | @pytest.fixture(autouse=True) 25 | def setUp(self): 26 | super().setUp() 27 | self.username = "test_user" 28 | self.password = "test_password" 29 | 30 | self.user = User.objects.create_user( 31 | username=self.username, 32 | password=self.password, 33 | ) 34 | 35 | def test_no_authorization(self): 36 | res = self.view_get() 37 | 38 | assert res.status_code == 401 39 | assert "Unauthorized" in res.data["detail"] 40 | 41 | def test_wrong_auth_type(self): 42 | res = self.view_post( 43 | url=reverse(f"{self.namespace}:token_obtain_sliding"), 44 | data={ 45 | User.USERNAME_FIELD: self.username, 46 | "password": self.password, 47 | }, 48 | content_type="application/json", 49 | ) 50 | 51 | token = res.data["token"] 52 | self.authenticate_with_token("Wrong", token) 53 | 54 | res = self.view_get() 55 | 56 | assert res.status_code == 401 57 | assert "Unauthorized" in res.data["detail"] 58 | 59 | def test_expired_token(self, monkeypatch): 60 | old_lifetime = AccessToken.lifetime 61 | AccessToken.lifetime = timedelta(seconds=0) 62 | try: 63 | res = self.view_post( 64 | url=reverse(f"{self.namespace}:token_obtain_pair"), 65 | data={ 66 | User.USERNAME_FIELD: self.username, 67 | "password": self.password, 68 | }, 69 | content_type="application/json", 70 | ) 71 | finally: 72 | AccessToken.lifetime = old_lifetime 73 | 74 | access = res.data["access"] 75 | self.authenticate_with_token("Bearer", access) 76 | 77 | with monkeypatch.context() as m: 78 | m.setattr( 79 | api_settings, "AUTH_TOKEN_CLASSES", ("ninja_jwt.tokens.AccessToken",) 80 | ) 81 | res = self.view_get() 82 | 83 | assert res.status_code == 401 84 | assert "token_not_valid" == res.data["code"] 85 | 86 | def test_user_can_get_sliding_token_and_use_it(self, monkeypatch): 87 | res = self.view_post( 88 | url=reverse(f"{self.namespace}:token_obtain_sliding"), 89 | data={ 90 | User.USERNAME_FIELD: self.username, 91 | "password": self.password, 92 | }, 93 | content_type="application/json", 94 | ) 95 | 96 | token = res.data["token"] 97 | self.authenticate_with_token("Bearer", token) 98 | 99 | with monkeypatch.context() as m: 100 | m.setattr( 101 | api_settings, "AUTH_TOKEN_CLASSES", ("ninja_jwt.tokens.SlidingToken",) 102 | ) 103 | res = self.view_get() 104 | 105 | assert res.status_code == 200 106 | assert res.data["foo"] == "bar" 107 | 108 | def test_user_can_get_access_and_refresh_tokens_and_use_them(self, monkeypatch): 109 | res = self.view_post( 110 | url=reverse(f"{self.namespace}:token_obtain_pair"), 111 | data={ 112 | User.USERNAME_FIELD: self.username, 113 | "password": self.password, 114 | }, 115 | content_type="application/json", 116 | ) 117 | 118 | access = res.data["access"] 119 | refresh = res.data["refresh"] 120 | 121 | self.authenticate_with_token("Bearer", access) 122 | 123 | with monkeypatch.context() as m: 124 | m.setattr( 125 | api_settings, "AUTH_TOKEN_CLASSES", ("ninja_jwt.tokens.AccessToken",) 126 | ) 127 | res = self.view_get() 128 | 129 | assert res.status_code == 200 130 | assert res.data["foo"] == "bar" 131 | 132 | res = self.view_post( 133 | url=reverse(f"{self.namespace}:token_refresh"), 134 | data={"refresh": refresh}, 135 | content_type="application/json", 136 | ) 137 | 138 | access = res.data["access"] 139 | 140 | self.authenticate_with_token("Bearer", access) 141 | with monkeypatch.context() as m: 142 | m.setattr( 143 | api_settings, "AUTH_TOKEN_CLASSES", ("ninja_jwt.tokens.AccessToken",) 144 | ) 145 | res = self.view_get() 146 | 147 | assert res.status_code == 200 148 | assert res.data["foo"] == "bar" 149 | 150 | 151 | if not django.VERSION < (3, 1): 152 | from asgiref.sync import sync_to_async 153 | 154 | class TestAsyncTestView(TestTestView): 155 | namespace = "jwt-async" 156 | 157 | @pytest.mark.asyncio 158 | @pytest.mark.django_db(transaction=True) 159 | async def test_no_authorization(self): 160 | _test_no_authorization = sync_to_async(super().test_no_authorization) 161 | await _test_no_authorization() 162 | 163 | @pytest.mark.asyncio 164 | @pytest.mark.django_db(transaction=True) 165 | async def test_wrong_auth_type(self): 166 | _test_wrong_auth_type = sync_to_async(super().test_wrong_auth_type) 167 | await _test_wrong_auth_type() 168 | 169 | @pytest.mark.asyncio 170 | @pytest.mark.django_db(transaction=True) 171 | async def test_expired_token(self, monkeypatch): 172 | _test_expired_token = sync_to_async(super().test_expired_token) 173 | await _test_expired_token(monkeypatch=monkeypatch) 174 | 175 | @pytest.mark.asyncio 176 | @pytest.mark.django_db(transaction=True) 177 | async def test_user_can_get_sliding_token_and_use_it(self, monkeypatch): 178 | _test_user_can_get_sliding_token_and_use_it = sync_to_async( 179 | super().test_user_can_get_sliding_token_and_use_it 180 | ) 181 | await _test_user_can_get_sliding_token_and_use_it(monkeypatch=monkeypatch) 182 | 183 | @pytest.mark.asyncio 184 | @pytest.mark.django_db(transaction=True) 185 | async def test_user_can_get_access_and_refresh_tokens_and_use_them( 186 | self, monkeypatch 187 | ): 188 | _test_user_can_get_access_and_refresh_tokens_and_use_them = sync_to_async( 189 | super().test_user_can_get_access_and_refresh_tokens_and_use_them 190 | ) 191 | await _test_user_can_get_access_and_refresh_tokens_and_use_them( 192 | monkeypatch=monkeypatch 193 | ) 194 | -------------------------------------------------------------------------------- /tests/test_models.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ninja_jwt.models import TokenUser 4 | from ninja_jwt.settings import api_settings 5 | 6 | AuthToken = api_settings.AUTH_TOKEN_CLASSES[0] 7 | 8 | 9 | class TestTokenUser: 10 | @pytest.fixture(autouse=True) 11 | def setUp(self): 12 | self.token = AuthToken() 13 | self.token[api_settings.USER_ID_CLAIM] = 42 14 | self.token["username"] = "deep-thought" 15 | self.token["some_other_stuff"] = "arstarst" 16 | 17 | self.user = TokenUser(self.token) 18 | 19 | def test_username(self): 20 | assert self.user.username == "deep-thought" 21 | 22 | def test_is_active(self): 23 | assert self.user.is_active 24 | 25 | def test_str(self): 26 | assert str(self.user) == "TokenUser 42" 27 | 28 | def test_id(self): 29 | assert self.user.id == 42 30 | 31 | def test_pk(self): 32 | assert self.user.pk == 42 33 | 34 | def test_is_staff(self): 35 | payload = {api_settings.USER_ID_CLAIM: 42} 36 | user = TokenUser(payload) 37 | 38 | assert not user.is_staff 39 | 40 | payload["is_staff"] = True 41 | user = TokenUser(payload) 42 | 43 | assert user.is_staff 44 | 45 | def test_is_superuser(self): 46 | payload = {api_settings.USER_ID_CLAIM: 42} 47 | user = TokenUser(payload) 48 | 49 | assert not user.is_superuser 50 | 51 | payload["is_superuser"] = True 52 | user = TokenUser(payload) 53 | 54 | assert user.is_superuser 55 | 56 | def test_eq(self): 57 | user1 = TokenUser({api_settings.USER_ID_CLAIM: 1}) 58 | user2 = TokenUser({api_settings.USER_ID_CLAIM: 2}) 59 | user3 = TokenUser({api_settings.USER_ID_CLAIM: 1}) 60 | 61 | assert user1 != user2 62 | assert user1 == user3 63 | 64 | def test_hash(self): 65 | assert hash(self.user) == hash(self.user.id) 66 | 67 | def test_not_implemented(self): 68 | with pytest.raises(NotImplementedError): 69 | self.user.save() 70 | 71 | with pytest.raises(NotImplementedError): 72 | self.user.delete() 73 | 74 | with pytest.raises(NotImplementedError): 75 | self.user.set_password("arst") 76 | 77 | with pytest.raises(NotImplementedError): 78 | self.user.check_password("arst") 79 | 80 | def test_groups(self): 81 | assert not self.user.groups.exists() 82 | 83 | def test_user_permissions(self): 84 | assert not self.user.user_permissions.exists() 85 | 86 | def test_get_group_permissions(self): 87 | assert len(self.user.get_group_permissions()) == 0 88 | 89 | def test_get_all_permissions(self): 90 | assert len(self.user.get_all_permissions()) == 0 91 | 92 | def test_has_perm(self): 93 | assert not self.user.has_perm("test_perm") 94 | 95 | def test_has_perms(self): 96 | assert not self.user.has_perms(["test_perm"]) 97 | 98 | def test_has_module_perms(self): 99 | assert not self.user.has_module_perms("test_module") 100 | 101 | def test_is_anonymous(self): 102 | assert not self.user.is_anonymous 103 | 104 | def test_is_authenticated(self): 105 | assert self.user.is_authenticated 106 | 107 | def test_get_username(self): 108 | assert self.user.get_username() == "deep-thought" 109 | -------------------------------------------------------------------------------- /tests/test_routers.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.contrib.auth import get_user_model 3 | from ninja import NinjaAPI 4 | from ninja_extra import exceptions 5 | from ninja_extra.testing import TestClient 6 | 7 | from ninja_jwt.routers.blacklist import blacklist_router 8 | from ninja_jwt.routers.obtain import obtain_pair_router, sliding_router 9 | from ninja_jwt.routers.verify import verify_router 10 | from ninja_jwt.tokens import RefreshToken, SlidingToken 11 | 12 | api = NinjaAPI() 13 | client = TestClient(api) 14 | 15 | 16 | def api_exception_handler(request, exc): 17 | headers = {} 18 | 19 | if isinstance(exc.detail, (list, dict)): 20 | data = exc.detail 21 | else: 22 | data = {"detail": exc.detail} 23 | 24 | response = api.create_response(request, data, status=exc.status_code) 25 | for k, v in headers.items(): 26 | response.setdefault(k, v) 27 | 28 | return response 29 | 30 | 31 | api.exception_handler(exceptions.APIException)(api_exception_handler) 32 | api.add_router("", obtain_pair_router) 33 | api.add_router("", sliding_router) 34 | api.add_router("", verify_router) 35 | api.add_router("", blacklist_router) 36 | 37 | User = get_user_model() 38 | 39 | 40 | @pytest.mark.django_db 41 | class TestObtainTokenRouter: 42 | @pytest.fixture(autouse=True) 43 | def setUp(self): 44 | self.username = "test_user" 45 | self.password = "test_password" 46 | self.user = User.objects.create( 47 | email="test@user.com", 48 | username="test_user", 49 | password="test_password", 50 | is_active=True, 51 | ) 52 | self.user.set_password(self.password) 53 | self.user.save() 54 | 55 | def test_obtain_tokek_pair_success(self): 56 | response = client.post( 57 | "/pair", 58 | json={ 59 | User.USERNAME_FIELD: self.username, 60 | "password": self.password, 61 | }, 62 | content_type="application/json", 63 | ) 64 | assert response.status_code == 200 65 | 66 | assert "access" in response.json() 67 | assert "refresh" in response.json() 68 | assert User.USERNAME_FIELD in response.json() 69 | 70 | def test_obtain_tokek_pair_fail(self): 71 | response = client.post( 72 | "/pair", 73 | json={ 74 | User.USERNAME_FIELD: self.username, 75 | "password": "wrong_password", 76 | }, 77 | content_type="application/json", 78 | ) 79 | assert response.status_code == 401 80 | 81 | assert "detail" in response.json() 82 | assert "code" in response.json() 83 | 84 | def test_obtain_tokek_pair_fail_user_not_active(self): 85 | self.user.is_active = False 86 | self.user.save() 87 | 88 | response = client.post( 89 | "/pair", 90 | json={ 91 | User.USERNAME_FIELD: self.username, 92 | "password": self.password, 93 | }, 94 | content_type="application/json", 95 | ) 96 | assert response.status_code == 401 97 | 98 | assert "detail" in response.json() 99 | assert "code" in response.json() 100 | 101 | def test_refresh_token_success(self): 102 | token = RefreshToken.for_user(self.user) 103 | 104 | response = client.post( 105 | "/refresh", 106 | json={ 107 | "refresh": str(token), 108 | }, 109 | content_type="application/json", 110 | ) 111 | assert response.status_code == 200 112 | 113 | assert "access" in response.json() 114 | assert "refresh" in response.json() 115 | 116 | def test_refresh_token_fail(self): 117 | response = client.post( 118 | "/refresh", 119 | json={ 120 | "refresh": "wrong_refresh_token", 121 | }, 122 | content_type="application/json", 123 | ) 124 | assert response.status_code == 401 125 | 126 | assert "detail" in response.json() 127 | assert "code" in response.json() 128 | 129 | def test_obtain_token_sliding_success(self): 130 | response = client.post( 131 | "/sliding", 132 | json={ 133 | User.USERNAME_FIELD: self.username, 134 | "password": self.password, 135 | }, 136 | content_type="application/json", 137 | ) 138 | assert response.status_code == 200 139 | 140 | assert "token" in response.json() 141 | assert User.USERNAME_FIELD in response.json() 142 | 143 | def test_obtain_token_sliding_fail(self): 144 | response = client.post( 145 | "/sliding", 146 | json={ 147 | User.USERNAME_FIELD: self.username, 148 | "password": "wrong_password", 149 | }, 150 | content_type="application/json", 151 | ) 152 | assert response.status_code == 401 153 | 154 | assert "detail" in response.json() 155 | assert "code" in response.json() 156 | 157 | def test_obtain_token_sliding_fail_user_not_active(self): 158 | self.user.is_active = False 159 | self.user.save() 160 | 161 | response = client.post( 162 | "/sliding", 163 | json={ 164 | User.USERNAME_FIELD: self.username, 165 | "password": self.password, 166 | }, 167 | content_type="application/json", 168 | ) 169 | assert response.status_code == 401 170 | 171 | assert "detail" in response.json() 172 | assert "code" in response.json() 173 | 174 | def test_refresh_sliding_token_success(self): 175 | token = SlidingToken.for_user(self.user) 176 | 177 | response = client.post( 178 | "/sliding/refresh", 179 | json={"token": str(token)}, 180 | content_type="application/json", 181 | ) 182 | assert response.status_code == 200 183 | 184 | assert "token" in response.json() 185 | 186 | def test_refresh_sliding_token_token_fail(self): 187 | token = SlidingToken.for_user(self.user) 188 | response = client.post( 189 | "/refresh", 190 | json={"refresh": str(token)}, 191 | content_type="application/json", 192 | ) 193 | assert response.status_code == 401 194 | 195 | assert "detail" in response.json() 196 | assert "code" in response.json() 197 | 198 | def test_verify_token_success(self): 199 | token = RefreshToken.for_user(self.user) 200 | response = client.post( 201 | "/verify", 202 | json={ 203 | "token": str(token), 204 | }, 205 | content_type="application/json", 206 | ) 207 | assert response.status_code == 200 208 | 209 | def test_verify_token_fail(self): 210 | response = client.post( 211 | "/verify", 212 | json={ 213 | "token": "wrong_token", 214 | }, 215 | content_type="application/json", 216 | ) 217 | assert response.status_code == 401 218 | 219 | assert "detail" in response.json() 220 | assert "code" in response.json() 221 | 222 | def test_blacklist_token_success(self): 223 | token = RefreshToken.for_user(self.user) 224 | response = client.post( 225 | "/blacklist", 226 | json={ 227 | "refresh": str(token), 228 | }, 229 | content_type="application/json", 230 | ) 231 | assert response.status_code == 200 232 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta, timezone 2 | 3 | from django.test.utils import override_settings 4 | from freezegun import freeze_time 5 | 6 | from ninja_jwt.utils import ( 7 | aware_utcnow, 8 | datetime_from_epoch, 9 | datetime_to_epoch, 10 | format_lazy, 11 | make_utc, 12 | ) 13 | 14 | 15 | class TestMakeUtc: 16 | def test_it_should_return_the_correct_values(self): 17 | # It should make a naive datetime into an aware, utc datetime if django 18 | # is configured to use timezones and the datetime doesn't already have 19 | # a timezone 20 | 21 | # Naive datetime 22 | dt = datetime(year=1970, month=12, day=1) 23 | 24 | with override_settings(USE_TZ=False): 25 | dt = make_utc(dt) 26 | assert dt.tzinfo is None 27 | 28 | with override_settings(USE_TZ=True): 29 | dt = make_utc(dt) 30 | assert dt.tzinfo is not None 31 | assert dt.utcoffset() == timedelta(seconds=0) 32 | 33 | 34 | class TestAwareUtcnow: 35 | def test_it_should_return_the_correct_value(self): 36 | now = datetime.now(tz=timezone.utc).replace(tzinfo=None) 37 | 38 | with freeze_time(now): 39 | # Should return aware utcnow if USE_TZ == True 40 | with override_settings(USE_TZ=True): 41 | assert now.replace(tzinfo=timezone.utc) == aware_utcnow() 42 | 43 | # Should return naive utcnow if USE_TZ == False 44 | with override_settings(USE_TZ=False): 45 | assert now == aware_utcnow() 46 | 47 | 48 | class TestDatetimeToEpoch: 49 | def assertEqual(self, value_1, value_2): 50 | assert value_1 == value_2 51 | 52 | def test_it_should_return_the_correct_values(self): 53 | self.assertEqual(datetime_to_epoch(datetime(year=1970, month=1, day=1)), 0) 54 | self.assertEqual( 55 | datetime_to_epoch(datetime(year=1970, month=1, day=1, second=1)), 1 56 | ) 57 | self.assertEqual( 58 | datetime_to_epoch(datetime(year=2000, month=1, day=1)), 946684800 59 | ) 60 | 61 | 62 | class TestDatetimeFromEpoch: 63 | def assertEqual(self, value_1, value_2): 64 | assert value_1 == value_2 65 | 66 | def test_it_should_return_the_correct_values(self): 67 | with override_settings(USE_TZ=False): 68 | assert datetime_from_epoch(0) == datetime(year=1970, month=1, day=1) 69 | assert datetime_from_epoch(1) == datetime( 70 | year=1970, month=1, day=1, second=1 71 | ) 72 | assert datetime_from_epoch(946684800) == datetime(year=2000, month=1, day=1) 73 | 74 | with override_settings(USE_TZ=True): 75 | self.assertEqual( 76 | datetime_from_epoch(0), make_utc(datetime(year=1970, month=1, day=1)) 77 | ) 78 | self.assertEqual( 79 | datetime_from_epoch(1), 80 | make_utc(datetime(year=1970, month=1, day=1, second=1)), 81 | ) 82 | self.assertEqual( 83 | datetime_from_epoch(946684800), 84 | make_utc(datetime(year=2000, month=1, day=1)), 85 | ) 86 | 87 | 88 | class TestFormatLazy: 89 | def test_it_should_work(self): 90 | obj = format_lazy("{} {}", "arst", "zxcv") 91 | 92 | assert not isinstance(obj, str) 93 | assert str(obj) == "arst zxcv" 94 | -------------------------------------------------------------------------------- /tests/urls.py: -------------------------------------------------------------------------------- 1 | import django 2 | from django.urls import path 3 | from ninja_extra import NinjaExtraAPI, api_controller, permissions 4 | 5 | from ninja_jwt import controller 6 | 7 | from . import views 8 | 9 | TokenObtainSlidingController = api_controller( 10 | "/token", 11 | permissions=[permissions.AllowAny], 12 | tags=["token"], 13 | )(controller.TokenObtainSlidingController) 14 | 15 | TokenBlackListController = api_controller( 16 | "/token", 17 | permissions=[permissions.AllowAny], 18 | tags=["token"], 19 | )(controller.TokenBlackListController) 20 | 21 | api = NinjaExtraAPI(urls_namespace="jwt") 22 | api.register_controllers( 23 | controller.NinjaJWTDefaultController, 24 | views.TestAPIController, 25 | TokenObtainSlidingController, 26 | TokenBlackListController, 27 | ) 28 | 29 | 30 | urlpatterns = [ 31 | path("api/", api.urls), 32 | ] 33 | 34 | if not django.VERSION < (3, 1): 35 | AsyncTokenObtainSlidingController = api_controller( 36 | "/token-async", 37 | permissions=[permissions.AllowAny], 38 | tags=["token-async"], 39 | )(controller.AsyncTokenObtainSlidingController) 40 | 41 | AsyncTokenBlackListController = api_controller( 42 | "/token-async", 43 | permissions=[permissions.AllowAny], 44 | tags=["token-async"], 45 | )(controller.AsyncTokenBlackListController) 46 | 47 | async_api = NinjaExtraAPI(urls_namespace="jwt-async") 48 | async_api.register_controllers( 49 | controller.AsyncNinjaJWTDefaultController, 50 | views.TestAPIAsyncController, 51 | AsyncTokenObtainSlidingController, 52 | AsyncTokenBlackListController, 53 | ) 54 | 55 | urlpatterns += [ 56 | path("api/async/", async_api.urls), 57 | ] 58 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django.db import connection 4 | from django.db.migrations.executor import MigrationExecutor 5 | from django.test import Client 6 | from django.test.utils import override_settings 7 | 8 | from ninja_jwt.compat import reverse 9 | 10 | 11 | def client_action_wrapper(action): 12 | def wrapper_method(self, *args, url=None, **kwargs): 13 | if not url and self.view_name is None: 14 | raise ValueError("Must give value for `view_name` property") 15 | 16 | reverse_args = kwargs.pop("reverse_args", ()) 17 | reverse_kwargs = kwargs.pop("reverse_kwargs", {}) 18 | query_string = kwargs.pop("query_string", None) 19 | 20 | if self.header: 21 | kwargs.update(self.header) 22 | self.header.clear() 23 | 24 | _url = url or reverse(self.view_name, args=reverse_args, kwargs=reverse_kwargs) 25 | if query_string is not None: 26 | _url = _url + "?{0}".format(query_string) 27 | 28 | response = getattr(self.client, action)(_url, *args, **kwargs) 29 | try: 30 | response.data = json.loads(response.content) 31 | except Exception: 32 | pass 33 | return response 34 | 35 | return wrapper_method 36 | 37 | 38 | class APIViewTestCase: 39 | client_class = Client 40 | header = {} 41 | 42 | def setUp(self): 43 | self.client = self.client_class() 44 | 45 | def authenticate_with_token(self, type, token): 46 | """ 47 | Authenticates requests with the given token. 48 | """ 49 | self.header = {"HTTP_AUTHORIZATION": "{} {}".format(type, token)} 50 | 51 | view_name = None 52 | 53 | view_post = client_action_wrapper("post") 54 | view_get = client_action_wrapper("get") 55 | 56 | 57 | def override_api_settings(**new_settings): 58 | from django.conf import settings 59 | 60 | old_settings = getattr(settings, "NINJA_JWT", {}) 61 | combined_settings = dict(old_settings) 62 | combined_settings.update(new_settings) 63 | return override_settings(SIMPLE_JWT=combined_settings) 64 | 65 | 66 | class MigrationTestCase: 67 | migrate_from = None 68 | migrate_to = None 69 | 70 | def setUp(self): 71 | self.migrate_from = [self.migrate_from] 72 | self.migrate_to = [self.migrate_to] 73 | 74 | # Reverse to the original migration 75 | executor = MigrationExecutor(connection) 76 | # executor.migrate(self.migrate_from) 77 | 78 | old_apps = executor.loader.project_state(self.migrate_from).apps 79 | self.setUpBeforeMigration(old_apps) 80 | 81 | # Run the migration to test 82 | executor.loader.build_graph() 83 | # executor.migrate(self.migrate_to) 84 | 85 | self.apps = executor.loader.project_state(self.migrate_to).apps 86 | 87 | def setUpBeforeMigration(self, apps): 88 | pass 89 | -------------------------------------------------------------------------------- /tests/views.py: -------------------------------------------------------------------------------- 1 | import django 2 | from ninja_extra import api_controller, http_get 3 | 4 | from ninja_jwt import authentication 5 | 6 | 7 | @api_controller("/test-view", auth=authentication.JWTAuth()) 8 | class TestAPIController: 9 | @http_get("/test", url_name="test_view") 10 | def test(self): 11 | return {"foo": "bar"} 12 | 13 | 14 | if not django.VERSION < (3, 1): 15 | 16 | @api_controller("/test-view-async", auth=authentication.AsyncJWTAuth()) 17 | class TestAPIAsyncController: 18 | @http_get("/test-async", url_name="test_view") 19 | async def test(self): 20 | return {"foo": "bar"} 21 | --------------------------------------------------------------------------------