jwtninja 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. jwtninja-0.0.0/.github/dependabot.yml +13 -0
  2. jwtninja-0.0.0/.github/release-drafter.yml +32 -0
  3. jwtninja-0.0.0/.github/workflows/check-and-test.yml +51 -0
  4. jwtninja-0.0.0/.github/workflows/pr-label-checks.yml +62 -0
  5. jwtninja-0.0.0/.github/workflows/publish.yml +41 -0
  6. jwtninja-0.0.0/.github/workflows/release-drafter.yml +24 -0
  7. jwtninja-0.0.0/.gitignore +11 -0
  8. jwtninja-0.0.0/.python-version +1 -0
  9. jwtninja-0.0.0/LICENSE +21 -0
  10. jwtninja-0.0.0/PKG-INFO +130 -0
  11. jwtninja-0.0.0/README.md +116 -0
  12. jwtninja-0.0.0/jwt_ninja/__init__.py +0 -0
  13. jwtninja-0.0.0/jwt_ninja/admin.py +26 -0
  14. jwtninja-0.0.0/jwt_ninja/api.py +163 -0
  15. jwtninja-0.0.0/jwt_ninja/auth_classes.py +55 -0
  16. jwtninja-0.0.0/jwt_ninja/authenticators.py +12 -0
  17. jwtninja-0.0.0/jwt_ninja/cryptography.py +60 -0
  18. jwtninja-0.0.0/jwt_ninja/errors.py +70 -0
  19. jwtninja-0.0.0/jwt_ninja/handlers.py +10 -0
  20. jwtninja-0.0.0/jwt_ninja/migrations/0001_initial.py +37 -0
  21. jwtninja-0.0.0/jwt_ninja/migrations/__init__.py +0 -0
  22. jwtninja-0.0.0/jwt_ninja/models.py +102 -0
  23. jwtninja-0.0.0/jwt_ninja/request.py +18 -0
  24. jwtninja-0.0.0/jwt_ninja/settings.py +38 -0
  25. jwtninja-0.0.0/jwt_ninja/tests/__init__.py +0 -0
  26. jwtninja-0.0.0/jwt_ninja/tests/conftest.py +116 -0
  27. jwtninja-0.0.0/jwt_ninja/tests/django_settings.py +12 -0
  28. jwtninja-0.0.0/jwt_ninja/tests/test_api_endpoints.py +229 -0
  29. jwtninja-0.0.0/jwt_ninja/tests/test_jwt_auth.py +129 -0
  30. jwtninja-0.0.0/jwt_ninja/types.py +46 -0
  31. jwtninja-0.0.0/pyproject.toml +69 -0
  32. jwtninja-0.0.0/pytest.ini +7 -0
  33. jwtninja-0.0.0/uv.lock +379 -0
@@ -0,0 +1,13 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "uv"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "daily"
7
+ groups:
8
+ production-dependencies:
9
+ dependency-type: "production"
10
+ update-types: [minor, patch]
11
+ development-dependencies:
12
+ dependency-type: "development"
13
+ update-types: [minor, patch]
@@ -0,0 +1,32 @@
1
+ name-template: 'v$RESOLVED_VERSION'
2
+ tag-template: '$RESOLVED_VERSION'
3
+ categories:
4
+ - title: '⚠️ Breaking Changes'
5
+ labels: [ 'breaking-change' ]
6
+ - title: '🚀 Features'
7
+ labels: [ 'breaking-change','feature' ]
8
+ - title: '🐛 Fixes'
9
+ labels: [ 'fix' ]
10
+ - title: '🧰 Maintenance'
11
+ labels: [ 'maintenance' ]
12
+ - title: '⚠️ Changes that include Migrations'
13
+ labels: [ 'migration' ]
14
+
15
+ change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
16
+ change-title-escapes: '\<*_&'
17
+
18
+ version-resolver:
19
+ major:
20
+ labels:
21
+ - 'breaking-change'
22
+ minor:
23
+ labels:
24
+ - 'feature'
25
+ patch:
26
+ labels:
27
+ - 'fix'
28
+ - 'maintenance'
29
+ default: patch
30
+
31
+ template: |
32
+ $CHANGES
@@ -0,0 +1,51 @@
1
+ name: Linting, Formatting and Tests
2
+
3
+ on: [ push ]
4
+
5
+ concurrency:
6
+ group: ci-${{ github.workflow }}-${{ github.ref }}
7
+ cancel-in-progress: true
8
+
9
+ jobs:
10
+ check-formatting:
11
+ name: Check Formatting
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - name: Checkout repository
15
+ uses: actions/checkout@v4
16
+ with:
17
+ fetch-depth: 1
18
+
19
+ - uses: astral-sh/ruff-action@v1
20
+ with:
21
+ args: "format --check"
22
+
23
+ check-linting:
24
+ name: Check Linting
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - name: Checkout repository
28
+ uses: actions/checkout@v4
29
+ with:
30
+ fetch-depth: 1
31
+
32
+ - uses: astral-sh/ruff-action@v1
33
+ with:
34
+ args: "check --output-format=github"
35
+
36
+ run-tests:
37
+ name: Run Tests (pytest)
38
+ runs-on: ubuntu-latest
39
+ container:
40
+ image: ghcr.io/astral-sh/uv:python3.12-bookworm-slim
41
+ steps:
42
+ - name: Checkout repository
43
+ uses: actions/checkout@v4
44
+ with:
45
+ fetch-depth: 1
46
+
47
+ - name: Install Dependencies
48
+ run: uv sync --frozen --no-install-project
49
+
50
+ - name: "pytest: Run tests"
51
+ run: .venv/bin/pytest .
@@ -0,0 +1,62 @@
1
+ name: PR Label Checks
2
+
3
+ on:
4
+ pull_request:
5
+ types: [ opened, synchronize, labeled ]
6
+
7
+ permissions:
8
+ contents: read
9
+ pull-requests: write
10
+ issues: write
11
+ repository-projects: write
12
+
13
+ jobs:
14
+ check-labels:
15
+ name: Check Version Labels and Migrations
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - name: Get changed files
24
+ id: changes
25
+ run: |
26
+ git diff --name-only origin/${{ github.event.pull_request.base.ref }} ${{ github.sha }} > changed_files.txt
27
+ cat changed_files.txt
28
+
29
+ - name: Apply "migration" label if migrations detected
30
+ run: |
31
+ if grep -q 'migrations/' changed_files.txt; then
32
+ echo "Migrations detected in the diff. Applying 'migration' label."
33
+ gh pr edit ${{ github.event.pull_request.number }} --add-label "migration"
34
+ else
35
+ echo "No migrations detected."
36
+ fi
37
+ env:
38
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39
+
40
+ - name: Check if PR has version labels
41
+ id: label-check
42
+ run: |
43
+ REQUIRED_LABELS=("breaking-change" "feature" "fix" "maintenance")
44
+ LABELS=$(jq -r '.pull_request.labels[].name' "$GITHUB_EVENT_PATH")
45
+ echo "Labels on this PR: $LABELS"
46
+
47
+ REQUIRED_LABEL_FOUND=false
48
+
49
+ # Check if any required label is present
50
+ for label in "${REQUIRED_LABELS[@]}"; do
51
+ if echo "$LABELS" | grep -q "$label"; then
52
+ REQUIRED_LABEL_FOUND=true
53
+ echo "Found required label: $label"
54
+ break
55
+ fi
56
+ done
57
+
58
+ if [ "$REQUIRED_LABEL_FOUND" = false ]; then
59
+ echo "Error: Pull request must have one of the following labels: ${REQUIRED_LABELS[*]}"
60
+ exit 1
61
+ fi
62
+ shell: bash
@@ -0,0 +1,41 @@
1
+ name: Publish to PyPI (uv)
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build-and-publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ id-token: write # OIDC → PyPI (token-free)
12
+ contents: read
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ with:
17
+ fetch-depth: 0
18
+ fetch-tags: true
19
+
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: '3.12'
23
+
24
+ - name: Install uv
25
+ run: |
26
+ curl -LsSf https://astral.sh/uv/install.sh | sh
27
+ echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
28
+
29
+ # --------------------------------------------------------------
30
+ # Build sdist & wheels – version is resolved automatically
31
+ # --------------------------------------------------------------
32
+ - name: Build distributions
33
+ run: uv build -o dist/
34
+
35
+ # --------------------------------------------------------------
36
+ # Publish to PyPI with trusted publishing (OIDC)
37
+ # --------------------------------------------------------------
38
+ - name: Publish
39
+ run: uv publish dist/*
40
+ env:
41
+ UV_PUBLISH_NON_INTERACTIVE: "1"
@@ -0,0 +1,24 @@
1
+ name: Release Drafter
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ update_release_draft:
13
+ permissions:
14
+ contents: write
15
+ pull-requests: write
16
+
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: release-drafter/release-drafter@v6
20
+ with:
21
+ config-name: release-drafter.yml
22
+ disable-autolabeler: true
23
+ env:
24
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,11 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+ .idea
@@ -0,0 +1 @@
1
+ 3.12
jwtninja-0.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Daniel van Flymen
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: jwtninja
3
+ Version: 0.0.0
4
+ Summary: A simple and fully-typed auth library for Django Ninja based on PyJWT.
5
+ Project-URL: Homepage, https://github.com/dvf/jwt-ninja
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: django-ninja>=1.3.0
9
+ Requires-Dist: pydantic-settings>=2.5.2
10
+ Requires-Dist: pydantic>=2.9.2
11
+ Requires-Dist: pyjwt>=2.9.0
12
+ Requires-Dist: uuid6>=2024.7.10
13
+ Description-Content-Type: text/markdown
14
+
15
+ ![JWT Ninja Logo](https://github.com/user-attachments/assets/2589db23-94c7-47c6-8687-eb29c6312272) <br/>
16
+
17
+ # JWT Ninja
18
+
19
+ *A session‑backed, fully‑typed authentication library for **Django Ninja**, powered by **PyJWT***
20
+
21
+ [![PyPI](https://img.shields.io/pypi/v/jwtninja.svg)](https://pypi.python.org/pypi/jwtninja)
22
+ [![CI Status](https://github.com/dvf/jwt-ninja/actions/workflows/check-and-test.yml/badge.svg)](https://github.com/dvf/jwt-ninja/actions/workflows/check-and-test.yml)
23
+ [![License](https://img.shields.io/github/license/dvf/jwt-ninja)](LICENSE)
24
+
25
+ > **❤️ Contributions Welcome!**
26
+ > Whether you’re fixing a bug, or want a new feature, feel free to submit a PR.
27
+
28
+ ---
29
+
30
+ ## 🚀 Quick Start
31
+
32
+ ### Installation
33
+
34
+ JWT Ninja is a standard Django app.
35
+
36
+ Install it with [uv](https://astral.sh/uv) or `pip`:
37
+
38
+ ```bash
39
+ pip install jwtninja
40
+ ```
41
+
42
+ Add it to **`INSTALLED_APPS`** in `settings.py`:
43
+
44
+ ```python
45
+ INSTALLED_APPS = [
46
+ ...,
47
+ "jwt_ninja",
48
+ ]
49
+ ```
50
+
51
+ Lastly, don't forget to run migrations:
52
+
53
+ ```bash
54
+ python manage.py migrate
55
+ ```
56
+
57
+ ### Usage
58
+
59
+ Register the built‑in router on your Ninja API:
60
+
61
+ ```python
62
+ from ninja import NinjaAPI
63
+ from jwt_ninja.api import router as auth_router
64
+ from jwt_ninja.errors import APIError as AuthAPIError
65
+ from jwt_ninja.handlers import error_handler
66
+
67
+ api = NinjaAPI()
68
+ api.add_router("auth/", auth_router)
69
+
70
+ # Explicit control over how you went errors to be returned
71
+ api.add_exception_handler(AuthAPIError, error_handler)
72
+ ```
73
+
74
+ Endpoints created:
75
+
76
+ | Method | Path | Purpose |
77
+ | ------ | ------------------- | ----------------------------------------------- |
78
+ | `POST` | `/auth/login/` | Issue a new **access** & **refresh** token pair |
79
+ | `POST` | `/auth/refresh/` | Refresh an access token |
80
+ | `GET` | `/auth/sessions/` | List active sessions |
81
+ | `POST` | `/auth/logout/` | Log out of the current session |
82
+ | `POST` | `/auth/logout/all/` | Log out of **all** sessions |
83
+
84
+ Protect views with `JWTAuth` and enjoy typed requests:
85
+
86
+ ```python
87
+ from ninja import Router
88
+ from jwt_ninja.auth_classes import JWTAuth, AuthedRequest
89
+
90
+
91
+ router = Router()
92
+
93
+ @router.get("/my-protected-endpoint/", auth=JWTAuth())
94
+ def my_protected_route(request: AuthedRequest):
95
+ request.auth.session.data["foo"] = 123
96
+ request.auth.session.save() # Persist session changes
97
+ return {"message": "Success!"}
98
+ ```
99
+
100
+ ---
101
+
102
+ ## ⚙️Settings
103
+
104
+ ```text
105
+ # settings.py defaults
106
+ JWT_SECRET_KEY = SECRET_KEY
107
+ JWT_ALGORITHM = "HS256"
108
+ JWT_ACCESS_TOKEN_EXPIRE_SECONDS = 300 # 5 minutes
109
+ JWT_REFRESH_TOKEN_EXPIRE_SECONDS = 365 * 3600 # 1 year
110
+ JWT_SESSION_EXPIRE_SECONDS = 365 * 3600 # 1 year
111
+ JWT_USER_LOGIN_AUTHENTICATOR = "jwt_ninja.authenticators.django_user_authenticator"
112
+ JWT_PAYLOAD_CLASS = "jwt_ninja.types.JWTPayload"
113
+ ```
114
+
115
+ ### Custom Claims
116
+
117
+ ```python
118
+ from jwt_ninja.types import JWTPayload
119
+
120
+ class CustomJWTPayload(JWTPayload):
121
+ discord_user_id: str
122
+ ip_address: str
123
+ email: str
124
+ ```
125
+
126
+ Configure it:
127
+
128
+ ```python
129
+ JWT_PAYLOAD_CLASS = "path.to.CustomJWTPayload"
130
+ ```
@@ -0,0 +1,116 @@
1
+ ![JWT Ninja Logo](https://github.com/user-attachments/assets/2589db23-94c7-47c6-8687-eb29c6312272) <br/>
2
+
3
+ # JWT Ninja
4
+
5
+ *A session‑backed, fully‑typed authentication library for **Django Ninja**, powered by **PyJWT***
6
+
7
+ [![PyPI](https://img.shields.io/pypi/v/jwtninja.svg)](https://pypi.python.org/pypi/jwtninja)
8
+ [![CI Status](https://github.com/dvf/jwt-ninja/actions/workflows/check-and-test.yml/badge.svg)](https://github.com/dvf/jwt-ninja/actions/workflows/check-and-test.yml)
9
+ [![License](https://img.shields.io/github/license/dvf/jwt-ninja)](LICENSE)
10
+
11
+ > **❤️ Contributions Welcome!**
12
+ > Whether you’re fixing a bug, or want a new feature, feel free to submit a PR.
13
+
14
+ ---
15
+
16
+ ## 🚀 Quick Start
17
+
18
+ ### Installation
19
+
20
+ JWT Ninja is a standard Django app.
21
+
22
+ Install it with [uv](https://astral.sh/uv) or `pip`:
23
+
24
+ ```bash
25
+ pip install jwtninja
26
+ ```
27
+
28
+ Add it to **`INSTALLED_APPS`** in `settings.py`:
29
+
30
+ ```python
31
+ INSTALLED_APPS = [
32
+ ...,
33
+ "jwt_ninja",
34
+ ]
35
+ ```
36
+
37
+ Lastly, don't forget to run migrations:
38
+
39
+ ```bash
40
+ python manage.py migrate
41
+ ```
42
+
43
+ ### Usage
44
+
45
+ Register the built‑in router on your Ninja API:
46
+
47
+ ```python
48
+ from ninja import NinjaAPI
49
+ from jwt_ninja.api import router as auth_router
50
+ from jwt_ninja.errors import APIError as AuthAPIError
51
+ from jwt_ninja.handlers import error_handler
52
+
53
+ api = NinjaAPI()
54
+ api.add_router("auth/", auth_router)
55
+
56
+ # Explicit control over how you went errors to be returned
57
+ api.add_exception_handler(AuthAPIError, error_handler)
58
+ ```
59
+
60
+ Endpoints created:
61
+
62
+ | Method | Path | Purpose |
63
+ | ------ | ------------------- | ----------------------------------------------- |
64
+ | `POST` | `/auth/login/` | Issue a new **access** & **refresh** token pair |
65
+ | `POST` | `/auth/refresh/` | Refresh an access token |
66
+ | `GET` | `/auth/sessions/` | List active sessions |
67
+ | `POST` | `/auth/logout/` | Log out of the current session |
68
+ | `POST` | `/auth/logout/all/` | Log out of **all** sessions |
69
+
70
+ Protect views with `JWTAuth` and enjoy typed requests:
71
+
72
+ ```python
73
+ from ninja import Router
74
+ from jwt_ninja.auth_classes import JWTAuth, AuthedRequest
75
+
76
+
77
+ router = Router()
78
+
79
+ @router.get("/my-protected-endpoint/", auth=JWTAuth())
80
+ def my_protected_route(request: AuthedRequest):
81
+ request.auth.session.data["foo"] = 123
82
+ request.auth.session.save() # Persist session changes
83
+ return {"message": "Success!"}
84
+ ```
85
+
86
+ ---
87
+
88
+ ## ⚙️Settings
89
+
90
+ ```text
91
+ # settings.py defaults
92
+ JWT_SECRET_KEY = SECRET_KEY
93
+ JWT_ALGORITHM = "HS256"
94
+ JWT_ACCESS_TOKEN_EXPIRE_SECONDS = 300 # 5 minutes
95
+ JWT_REFRESH_TOKEN_EXPIRE_SECONDS = 365 * 3600 # 1 year
96
+ JWT_SESSION_EXPIRE_SECONDS = 365 * 3600 # 1 year
97
+ JWT_USER_LOGIN_AUTHENTICATOR = "jwt_ninja.authenticators.django_user_authenticator"
98
+ JWT_PAYLOAD_CLASS = "jwt_ninja.types.JWTPayload"
99
+ ```
100
+
101
+ ### Custom Claims
102
+
103
+ ```python
104
+ from jwt_ninja.types import JWTPayload
105
+
106
+ class CustomJWTPayload(JWTPayload):
107
+ discord_user_id: str
108
+ ip_address: str
109
+ email: str
110
+ ```
111
+
112
+ Configure it:
113
+
114
+ ```python
115
+ JWT_PAYLOAD_CLASS = "path.to.CustomJWTPayload"
116
+ ```
File without changes
@@ -0,0 +1,26 @@
1
+ from django.contrib import admin
2
+
3
+ from .models import Session
4
+
5
+
6
+ @admin.register(Session)
7
+ class SessionAdmin(admin.ModelAdmin):
8
+ list_display = [
9
+ "id",
10
+ "user",
11
+ "created_at",
12
+ "updated_at",
13
+ "expired_at",
14
+ "ip_address",
15
+ ]
16
+ readonly_fields = [
17
+ "id",
18
+ "user",
19
+ "created_at",
20
+ "updated_at",
21
+ "expired_at",
22
+ "ip_address",
23
+ "data",
24
+ ]
25
+ list_filter = ["created_at", "expired_at"]
26
+ ordering = ["-id"]