├── .github ├── release-drafter.yml └── workflows │ ├── build.yml │ ├── publish.yml │ └── release-drafter.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── ninja_apikey ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── security.py └── tests.py ├── pyproject.toml └── sample_project ├── manage.py ├── sample_api ├── __init__.py ├── api.py ├── apps.py └── urls.py └── sample_project ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: "v$NEXT_PATCH_VERSION" 2 | tag-template: "v$NEXT_PATCH_VERSION" 3 | 4 | categories: 5 | - title: "Breaking Changes" 6 | labels: [breaking] 7 | - title: "Features" 8 | labels: [enhancement] 9 | - title: "Fixes" 10 | labels: [bug] 11 | - title: "Documentation" 12 | labels: [documentation] 13 | - title: "Maintenance" 14 | labels: [maintenance, dependencies] 15 | 16 | include-labels: 17 | - "breaking" 18 | - "enhancement" 19 | - "bug" 20 | - "documentation" 21 | - "maintenance" 22 | - "dependencies" 23 | 24 | template: | 25 | $CHANGES 26 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | python-version: ["3.6", "3.7", "3.8", "3.9"] 17 | 18 | steps: 19 | - uses: actions/checkout@v2.3.4 20 | - uses: actions/setup-python@v2.2.2 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - uses: actions/cache@v2.1.6 24 | with: 25 | path: ~/.cache/pip 26 | key: ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('**/requirements.txt') }} 27 | restore-keys: | 28 | ${{ runner.os }}-${{ matrix.python-version }}-pip- 29 | - name: Install flit 30 | run: pip install flit 31 | - name: Build 32 | run: make 33 | - name: Upload coverage 34 | uses: codecov/codecov-action@v2.0.2 35 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2.3.4 13 | - uses: actions/setup-python@v2.2.2 14 | with: 15 | python-version: 3.9 16 | - name: Install flit 17 | run: pip install flit 18 | - name: Publish 19 | env: 20 | FLIT_USERNAME: ${{ secrets.FLIT_USERNAME }} 21 | FLIT_PASSWORD: ${{ secrets.FLIT_PASSWORD }} 22 | run: flit publish 23 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: release-drafter/release-drafter@v5.15.0 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Environments 2 | .env 3 | .env/ 4 | .venv/ 5 | env/ 6 | venv/ 7 | 8 | # IDEA 9 | .idea 10 | .vscode 11 | 12 | # Python 13 | *.egg 14 | *.egg-info 15 | *.pyc 16 | __pycache__ 17 | .pytest_cache 18 | build/ 19 | dist/ 20 | 21 | # Generated 22 | *.db 23 | *.sqlite 24 | *.sqlite3 25 | *.sqlite3-shm 26 | *.sqlite3-wal 27 | *.zip 28 | .coverage 29 | coverage.xml -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Setup 4 | 5 | Django Ninja API key uses Flit to build, package and publish the project. 6 | 7 | It's recommended to create and activate an virtual environment before installing the project. Simply run 8 | ``` 9 | python -m venv .venv 10 | ``` 11 | and activate the environment with 12 | ``` 13 | source .venv/bin/activate 14 | ``` 15 | Now install flit: 16 | ``` 17 | pip install flit 18 | ``` 19 | Now you are ready to install the project: 20 | ``` 21 | make install 22 | ``` 23 | Once you're you can check if all works with 24 | ``` 25 | make test 26 | ``` 27 | 28 | ## Tests 29 | Please make sure to write tests for your changes. You can run the tests with 30 | ``` 31 | make test 32 | ``` 33 | Also make sure the test coverage did not suffer with your contribution: 34 | ``` 35 | make cov 36 | ``` 37 | 38 | ## Style and Linting 39 | You can format the code with 40 | ``` 41 | make format 42 | ``` 43 | Before opening a pull request run all linters: 44 | ``` 45 | make lint 46 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Maximilian Wassink 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := build 2 | 3 | install: # Install dependencies 4 | flit install --deps develop --symlink 5 | 6 | fmt format: # Run code formatters 7 | isort --profile black . 8 | black . 9 | 10 | lint: # Run code linters 11 | isort --profile black --check --diff . 12 | black --check --diff --color . 13 | flake8 --max-line-length 88 --max-complexity 8 --select C,E,F,W,B,B950,S --ignore E203,E501 ninja_apikey 14 | mypy --strict ninja_apikey/security.py 15 | 16 | test: # Run tests 17 | pytest --ds=sample_project.settings -v sample_project ninja_apikey/tests.py 18 | 19 | cov test-cov: # Run tests with coverage 20 | pytest --ds=sample_project.settings --cov=ninja_apikey --cov-report=term-missing --cov-report=xml -v sample_project ninja_apikey/tests.py 21 | 22 | build: # Build project 23 | make install 24 | make lint 25 | make cov 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

Django Ninja APIKey

3 | Easy to use API key authentication for Django Ninja REST Framework 4 |
5 |
6 |
7 |

8 | 9 | build 10 | 11 | 12 | coverage 13 | 14 | 15 | pypi 16 | 17 | 18 | black 19 | 20 |

21 |
22 | 23 | --- 24 | 25 | This is an unofficial [Django](https://github.com/django/django) app which makes it **easy** to manage API keys for the [Django Ninja REST Framework](https://github.com/vitalik/django-ninja). 26 | 27 | **Key Features:** 28 | - **Easy** integration in your projects 29 | - Well integrated in the **admin interface** 30 | - **Secure** API keys due to hashing 31 | - Works with the **standard** user model 32 | 33 | ## Installation 34 | 35 | ``` 36 | pip install django-ninja-apikey 37 | ``` 38 | 39 | ## Usage 40 | Add `ninja_apikey` to your installed apps in your django project: 41 | ```Python 42 | # settings.py 43 | 44 | INSTALLED_APPS = [ 45 | # ... 46 | "ninja_apikey", 47 | ] 48 | ``` 49 | Run the included migrations: 50 | ``` 51 | python manage.py migrate 52 | ``` 53 | Secure an api endpoint with the API keys: 54 | ```Python 55 | # api.py 56 | 57 | from ninja import NinjaAPI 58 | from ninja_apikey.security import APIKeyAuth 59 | 60 | # ... 61 | 62 | auth = APIKeyAuth() 63 | api = NinjaAPI() 64 | 65 | # ... 66 | 67 | @api.get("/secure_endpoint", auth=auth) 68 | def secure_endpoint(request): 69 | return f"Hello, {request.user}!" 70 | ``` 71 | Or secure your whole api (or a specific [router](https://django-ninja.rest-framework.com/tutorial/routers/)) with the API keys: 72 | ```Python 73 | # api.py 74 | 75 | from ninja import NinjaAPI 76 | from ninja_apikey.security import APIKeyAuth 77 | 78 | # ... 79 | 80 | api = NinjaAPI(auth=APIKeyAuth()) 81 | 82 | # ... 83 | 84 | @api.get("/secure_endpoint") 85 | def secure_endpoint(request): 86 | return f"Hello, {request.user}!" 87 | ``` 88 | You can create now API keys from django's admin interface. 89 | 90 | ## License 91 | This project is licensed under the terms of the MIT license. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | Security is very important for Django Ninja API key. Learn more about it below. 3 | 4 | ## Version 5 | Only the latest versions of Django Ninja API key are supported. 6 | 7 | You are encouraged to write tests for your application and update your Django Ninja API key version frequently after ensuring that your tests are passing. This way you will benefit from the latest features, bug fixes, and **security fixes**. 8 | 9 | It's a good practise to pin your dependencies for your application and use a tool like `dependabot` to keep up to date. 10 | 11 | ## Reporting a Vulnerability 12 | If you think you found a vulnerability, and even if you are not sure about it, please report it right away by sending an email to: wassink.maximilian@protonmail.com. Please try to be as explicit as possible, describing all the steps and example code to reproduce the security issue. 13 | 14 | ## Public Discussions 15 | To limit the potential impacts of a security vulnerability, please restrain from publicly discussing the issue. 16 | 17 | --- 18 | 19 | Thanks for your help! -------------------------------------------------------------------------------- /ninja_apikey/__init__.py: -------------------------------------------------------------------------------- 1 | """Easy to use API key authentication for Django Ninja REST Framework""" 2 | 3 | __version__ = "0.2.1" 4 | -------------------------------------------------------------------------------- /ninja_apikey/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin, messages 2 | 3 | from .models import APIKey 4 | from .security import generate_key 5 | 6 | 7 | @admin.action(description="Revoke selected API keys") # type: ignore 8 | def revoke_key(modeladmin, request, queryset): 9 | queryset.update(revoked=True) # pragma: no cover 10 | 11 | 12 | @admin.register(APIKey) 13 | class APIKeyAdmin(admin.ModelAdmin): 14 | list_display = [ 15 | "prefix", 16 | "user", 17 | "label", 18 | "created_at", 19 | "expires_at", 20 | "revoked", 21 | "is_active", 22 | ] 23 | readonly_fields = ["prefix", "hashed_key", "created_at"] 24 | actions = [revoke_key] 25 | list_filter = ["revoked"] 26 | 27 | @admin.display # type: ignore 28 | def is_active(self, obj: APIKey): 29 | return obj.is_valid # pragma: no cover 30 | 31 | is_active.boolean = True # Display property as boolean 32 | 33 | def save_model(self, request, obj: APIKey, form, change): 34 | if not obj.prefix: # New API key 35 | key = generate_key() 36 | obj.prefix = key.prefix 37 | obj.hashed_key = key.hashed_key 38 | 39 | if request: 40 | messages.add_message( # pragma: no cover 41 | request, 42 | messages.WARNING, 43 | f"The API key for {obj} is '{key.prefix}.{key.key}'." 44 | "You should store it somewhere safe: " 45 | "you will not be able to see the key again.", 46 | ) 47 | 48 | obj.save() 49 | return obj 50 | -------------------------------------------------------------------------------- /ninja_apikey/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class NinjaApikeyConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "ninja_apikey" 7 | verbose_name = "API keys" 8 | -------------------------------------------------------------------------------- /ninja_apikey/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.5 on 2021-07-13 08:57 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 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name="APIKey", 19 | fields=[ 20 | ( 21 | "prefix", 22 | models.CharField(max_length=8, primary_key=True, serialize=False), 23 | ), 24 | ("hashed_key", models.CharField(max_length=100)), 25 | ("label", models.CharField(max_length=40)), 26 | ("revoked", models.BooleanField(default=False)), 27 | ("created_at", models.DateTimeField(auto_now_add=True)), 28 | ("expires_at", models.DateTimeField(blank=True, null=True)), 29 | ( 30 | "user", 31 | models.ForeignKey( 32 | on_delete=django.db.models.deletion.CASCADE, 33 | to=settings.AUTH_USER_MODEL, 34 | ), 35 | ), 36 | ], 37 | options={ 38 | "verbose_name": "API key", 39 | "verbose_name_plural": "API keys", 40 | "ordering": ["-created_at"], 41 | }, 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /ninja_apikey/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawassk/django-ninja-apikey/9408288166409ccbce164a4fa8411c6d3190b600/ninja_apikey/migrations/__init__.py -------------------------------------------------------------------------------- /ninja_apikey/models.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | from django.contrib.auth import get_user_model 3 | from django.db import models 4 | from django.utils import timezone 5 | 6 | 7 | class APIKey(models.Model): 8 | prefix = models.CharField(max_length=8, primary_key=True) 9 | hashed_key = models.CharField(max_length=100) 10 | user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) 11 | label = models.CharField(max_length=40) 12 | revoked = models.BooleanField(default=False) 13 | created_at = models.DateTimeField(auto_now_add=True) 14 | expires_at = models.DateTimeField(null=True, blank=True) 15 | 16 | class Meta: 17 | ordering = ["-created_at"] 18 | verbose_name = "API key" 19 | verbose_name_plural = "API keys" 20 | 21 | @property 22 | def is_valid(self): 23 | if self.revoked: 24 | return False 25 | 26 | if not self.expires_at: 27 | return True # No expiration 28 | 29 | return self.expires_at >= timezone.now() 30 | 31 | def __str__(self): 32 | return f"{self.user.username}<{self.prefix}>" 33 | -------------------------------------------------------------------------------- /ninja_apikey/security.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from typing import Any, Optional 3 | 4 | from django.contrib.auth.hashers import check_password, make_password 5 | from django.http import HttpRequest 6 | from django.utils.crypto import get_random_string 7 | from ninja.security import APIKeyHeader 8 | 9 | from .models import APIKey # type: ignore 10 | 11 | KeyData = namedtuple("KeyData", "prefix key hashed_key") 12 | 13 | 14 | def generate_key() -> KeyData: 15 | prefix = get_random_string(8) 16 | key = get_random_string(56) 17 | hashed_key = make_password(key) 18 | return KeyData(prefix, key, hashed_key) 19 | 20 | 21 | def check_apikey(api_key: str) -> Any: 22 | if not api_key: 23 | return False 24 | 25 | if "." not in api_key: # Check API key format ({prefix}.{key}) 26 | return False 27 | 28 | data = api_key.split(".") 29 | 30 | prefix = data[0] 31 | key = data[1] 32 | 33 | persistent_key = APIKey.objects.filter(prefix=prefix).first() 34 | 35 | if not persistent_key: 36 | return False 37 | 38 | if not check_password(key, persistent_key.hashed_key): 39 | return False 40 | 41 | if not persistent_key.is_valid: 42 | return False 43 | 44 | user = persistent_key.user 45 | 46 | if not user: 47 | return False 48 | 49 | if not user.is_active: 50 | return False 51 | 52 | return user 53 | 54 | 55 | class APIKeyAuth(APIKeyHeader): 56 | param_name = "X-API-Key" 57 | 58 | def authenticate(self, request: HttpRequest, key: Optional[str]) -> Any: 59 | if not key: 60 | return False 61 | 62 | user = check_apikey(key) 63 | 64 | if not user: 65 | return False 66 | 67 | request.user = user 68 | return user 69 | -------------------------------------------------------------------------------- /ninja_apikey/tests.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from datetime import timedelta 3 | 4 | import pytest 5 | from django.contrib.admin.sites import AdminSite 6 | from django.contrib.auth.hashers import check_password 7 | from django.contrib.auth.models import User 8 | from django.utils import timezone 9 | from django.utils.crypto import get_random_string 10 | 11 | from .admin import APIKeyAdmin 12 | from .models import APIKey 13 | from .security import check_apikey, generate_key 14 | 15 | 16 | def test_apikey_validation(): 17 | key = APIKey() 18 | assert key 19 | assert key.is_valid 20 | key.revoked = True 21 | assert not key.is_valid 22 | key.revoked = False 23 | assert key.is_valid 24 | key.expires_at = timezone.now() - timedelta(minutes=1) 25 | assert not key.is_valid 26 | key.expires_at = timezone.now() + timedelta(minutes=1) 27 | assert key.is_valid 28 | key.expires_at = None 29 | assert key.is_valid 30 | 31 | 32 | def test_key_generation(): 33 | data = generate_key() 34 | assert data 35 | assert data.prefix 36 | assert data.key 37 | assert data.hashed_key 38 | assert check_password(data.key, data.hashed_key) 39 | 40 | 41 | @pytest.mark.django_db 42 | def test_apikey_check(): 43 | assert not check_apikey(None) 44 | user = User() 45 | user.name = get_random_string(10) 46 | user.password = get_random_string(10) 47 | user.save() 48 | assert user 49 | key = APIKey() 50 | key.user = user 51 | key_data = generate_key() 52 | key.prefix = key_data.prefix 53 | key.hashed_key = key_data.hashed_key 54 | key.save() 55 | assert key 56 | assert user.username in str(key) 57 | assert not check_apikey(key_data.key) 58 | assert not check_apikey(key.prefix) 59 | assert not check_apikey(f"{key_data.prefix}.{get_random_string(10)}") 60 | assert check_apikey(f"{key_data.prefix}.{key_data.key}") 61 | user.is_active = False 62 | user.save() 63 | assert not check_apikey(f"{key_data.prefix}.{key_data.key}") 64 | user.delete() 65 | assert not check_apikey(f"{key_data.prefix}.{key_data.key}") 66 | 67 | 68 | @pytest.mark.django_db 69 | def test_admin_save(): 70 | admin_site = AdminSite() 71 | apikey_admin = APIKeyAdmin(APIKey, admin_site=admin_site) 72 | assert admin_site 73 | assert apikey_admin 74 | user = User() 75 | user.name = get_random_string(10) 76 | user.password = get_random_string(10) 77 | user.save() 78 | assert user 79 | key = APIKey() 80 | key.user = user 81 | key = apikey_admin.save_model(request=None, obj=key, form=None, change=None) 82 | assert key 83 | assert key.prefix 84 | assert key.hashed_key 85 | assert key.user == user 86 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "django-ninja-apikey" 7 | authors = [{name = "Maximilian Wassink", email="wassink.maximilian@protonmail.com"}] 8 | readme = "README.md" 9 | requires-python = "~=3.6.2" 10 | classifiers = [ 11 | "Environment :: Web Environment", 12 | "Framework :: Django", 13 | "Framework :: Django :: 3.0", 14 | "Framework :: Django :: 3.1", 15 | "Framework :: Django :: 3.2", 16 | "Intended Audience :: Developers", 17 | "Intended Audience :: Information Technology", 18 | "Intended Audience :: System Administrators", 19 | "License :: OSI Approved :: MIT License", 20 | "Operating System :: OS Independent", 21 | "Programming Language :: Python :: 3", 22 | "Programming Language :: Python :: 3 :: Only", 23 | "Programming Language :: Python :: 3.6", 24 | "Programming Language :: Python :: 3.7", 25 | "Programming Language :: Python :: 3.8", 26 | "Programming Language :: Python :: 3.9", 27 | "Topic :: Internet", 28 | "Topic :: Internet :: WWW/HTTP", 29 | "Topic :: Internet :: WWW/HTTP :: HTTP Servers", 30 | "Topic :: Security", 31 | "Topic :: Software Development :: Libraries", 32 | "Topic :: Software Development :: Libraries :: Application Frameworks", 33 | "Topic :: Software Development :: Libraries :: Python Modules", 34 | ] 35 | dynamic = ["version", "description"] 36 | keywords = ["django", "rest", "ninja", "auth", "apikey"] 37 | dependencies = [ 38 | "django", 39 | "django-ninja" 40 | ] 41 | 42 | [project.urls] 43 | Source = "https://github.com/mawassk/django-ninja-apikey" 44 | 45 | [project.optional-dependencies] 46 | test = [ 47 | "pytest", 48 | "pytest-cov", 49 | "pytest-django", 50 | "black", 51 | "isort", 52 | "flake8", 53 | "flake8-bugbear", 54 | "flake8-bandit", 55 | "mypy", 56 | "django-stubs", 57 | ] 58 | 59 | [tool.flit.module] 60 | name = "ninja_apikey" -------------------------------------------------------------------------------- /sample_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_project.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /sample_project/sample_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawassk/django-ninja-apikey/9408288166409ccbce164a4fa8411c6d3190b600/sample_project/sample_api/__init__.py -------------------------------------------------------------------------------- /sample_project/sample_api/api.py: -------------------------------------------------------------------------------- 1 | from ninja import NinjaAPI 2 | 3 | from ninja_apikey.security import APIKeyAuth 4 | 5 | auth = APIKeyAuth() 6 | 7 | api = NinjaAPI(title="Sample API", docs_url="/", auth=auth) 8 | 9 | 10 | @api.get("/hello") 11 | def hello(request): 12 | return f"Hello, {request.user}!" 13 | -------------------------------------------------------------------------------- /sample_project/sample_api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SampleApiConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "sample_api" 7 | -------------------------------------------------------------------------------- /sample_project/sample_api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .api import api 4 | 5 | urlpatterns = [path("", api.urls)] 6 | -------------------------------------------------------------------------------- /sample_project/sample_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mawassk/django-ninja-apikey/9408288166409ccbce164a4fa8411c6d3190b600/sample_project/sample_project/__init__.py -------------------------------------------------------------------------------- /sample_project/sample_project/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for sample_project project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_project.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /sample_project/sample_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for sample_project project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "django-insecure-uq75&de!zyah=_6-iwz9tmjz4%*f)^0l^r414rm^ne5#$(u_jp" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | "ninja_apikey", 41 | "sample_api", 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | "django.middleware.security.SecurityMiddleware", 46 | "django.contrib.sessions.middleware.SessionMiddleware", 47 | "django.middleware.common.CommonMiddleware", 48 | "django.middleware.csrf.CsrfViewMiddleware", 49 | "django.contrib.auth.middleware.AuthenticationMiddleware", 50 | "django.contrib.messages.middleware.MessageMiddleware", 51 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 52 | ] 53 | 54 | ROOT_URLCONF = "sample_project.urls" 55 | 56 | TEMPLATES = [ 57 | { 58 | "BACKEND": "django.template.backends.django.DjangoTemplates", 59 | "DIRS": [], 60 | "APP_DIRS": True, 61 | "OPTIONS": { 62 | "context_processors": [ 63 | "django.template.context_processors.debug", 64 | "django.template.context_processors.request", 65 | "django.contrib.auth.context_processors.auth", 66 | "django.contrib.messages.context_processors.messages", 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = "sample_project.wsgi.application" 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 77 | 78 | DATABASES = { 79 | "default": { 80 | "ENGINE": "django.db.backends.sqlite3", 81 | "NAME": BASE_DIR / "db.sqlite3", 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | "NAME": "django.contrib.auth." 92 | "password_validation.UserAttributeSimilarityValidator", 93 | }, 94 | { 95 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 96 | }, 97 | { 98 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 99 | }, 100 | { 101 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 102 | }, 103 | ] 104 | 105 | 106 | # Internationalization 107 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 108 | 109 | LANGUAGE_CODE = "en-us" 110 | 111 | TIME_ZONE = "UTC" 112 | 113 | USE_I18N = True 114 | 115 | USE_L10N = True 116 | 117 | USE_TZ = True 118 | 119 | 120 | # Static files (CSS, JavaScript, Images) 121 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 122 | 123 | STATIC_URL = "/static/" 124 | 125 | # Default primary key field type 126 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 127 | 128 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 129 | -------------------------------------------------------------------------------- /sample_project/sample_project/urls.py: -------------------------------------------------------------------------------- 1 | """sample_project URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import include, path 18 | 19 | urlpatterns = [path("admin/", admin.site.urls), path("", include("sample_api.urls"))] 20 | -------------------------------------------------------------------------------- /sample_project/sample_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for sample_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sample_project.settings") 15 | 16 | application = get_wsgi_application() 17 | --------------------------------------------------------------------------------