├── .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 |
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 |
--------------------------------------------------------------------------------