django-ninja-starter 1.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.
- django_ninja_starter-1.0.0/LICENSE +21 -0
- django_ninja_starter-1.0.0/PKG-INFO +436 -0
- django_ninja_starter-1.0.0/README.md +390 -0
- django_ninja_starter-1.0.0/pyproject.toml +135 -0
- django_ninja_starter-1.0.0/setup.cfg +4 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/__init__.py +3 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/__main__.py +4 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/cli.py +99 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/py.typed +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/Makefile +29 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/README.md +199 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/_env.example +57 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/_github/workflows/ci.yml +21 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/_gitignore +16 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/conftest.py +23 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/README.md +111 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/accounts.md +238 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/auth/core.md +194 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/auth/email-code.md +88 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/auth/magic-link.md +98 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/auth/password.md +155 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/auth/sms-code.md +91 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/auth/twofactor.md +196 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/credentials.md +156 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/oauth/apple.md +102 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/oauth/core.md +216 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/oauth/github.md +89 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/oauth/google.md +92 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/oauth/microsoft.md +103 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/oauth/rotation.md +215 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/oauth/session.md +164 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/docs/oauth/sliding.md +129 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/manage.py +20 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/pyproject.toml +95 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/apps/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/config/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/config/api.py +60 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/config/api_registry.json +12 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/config/asgi.py +7 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/config/settings/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/config/settings/base.py +333 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/config/settings/development.py +4 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/config/settings/production.py +21 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/config/settings/test.py +50 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/config/urls.py +18 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/config/wsgi.py +7 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/admin.py +95 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/api.py +93 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/apps.py +38 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/managers.py +68 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/migrations/0001_initial.py +148 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/models.py +148 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/profiles.py +37 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/schemas.py +48 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/signals.py +35 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/tests/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/tests/test_api.py +272 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/accounts/tests/test_models.py +170 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/__init__.py +7 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/admin.py +41 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/apps.py +79 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/challenges.py +338 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/checks.py +97 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/codes.py +49 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/delivery.py +98 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/errors.py +58 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/flows.py +207 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/identities.py +165 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/migrations/0001_initial.py +114 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/models.py +125 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/schemas.py +88 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/sessions.py +418 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/tests/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/tests/test_challenges.py +178 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/tests/test_checks.py +94 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/tests/test_delivery.py +84 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/tests/test_flows.py +208 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/tests/test_identities.py +138 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/tests/test_internals.py +198 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/tests/test_interoperability.py +296 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/tests/test_routes.py +151 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/tests/test_sessions.py +252 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/core/throttling.py +50 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/email_code/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/email_code/api.py +125 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/email_code/apps.py +32 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/email_code/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/email_code/schemas.py +16 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/email_code/tests/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/email_code/tests/test_api.py +250 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/magic_link/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/magic_link/api.py +125 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/magic_link/apps.py +38 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/magic_link/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/magic_link/schemas.py +21 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/magic_link/tests/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/magic_link/tests/test_api.py +169 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/password/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/password/api.py +211 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/password/apps.py +37 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/password/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/password/schemas.py +46 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/password/services.py +36 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/password/tests/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/password/tests/test_api.py +414 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/sms_code/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/sms_code/api.py +134 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/sms_code/apps.py +35 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/sms_code/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/sms_code/schemas.py +16 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/sms_code/tests/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/sms_code/tests/test_api.py +238 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/twofactor/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/twofactor/admin.py +48 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/twofactor/api.py +360 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/twofactor/apps.py +31 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/twofactor/migrations/0001_initial.py +96 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/twofactor/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/twofactor/models.py +104 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/twofactor/schemas.py +53 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/twofactor/services.py +220 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/twofactor/tests/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/twofactor/tests/test_api.py +817 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/auth/twofactor/tests/test_services.py +180 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/admin.py +63 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/api.py +34 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/app_labels.py +19 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/apps.py +10 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/appsettings.py +90 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/checks.py +114 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/errors.py +26 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/management/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/management/commands/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/management/commands/authdocs.py +324 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/management/commands/startapi.py +125 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/registry.py +77 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/schemas.py +12 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/services.py +15 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/tests/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/tests/test_admin.py +167 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/tests/test_api.py +56 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/tests/test_authdocs.py +167 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/tests/test_checks.py +280 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/tests/test_registry.py +62 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/common/tests/test_startapi.py +66 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/apple/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/apple/admin.py +38 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/apple/api.py +17 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/apple/apps.py +57 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/apple/migrations/0001_initial.py +59 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/apple/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/apple/models.py +11 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/apple/provider.py +80 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/admin.py +97 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/apps.py +41 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/checks.py +51 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/credentials.py +151 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/crypto.py +34 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/jwt_tokens.py +178 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/migrations/0001_initial.py +243 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/migrations/0002_socialloginattempt.py +58 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/migrations/0003_socialloginattempt_binding_hash.py +17 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/models.py +316 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/provider.py +175 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/schemas.py +65 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/social.py +352 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/tests/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/tests/test_checks.py +68 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/tests/test_jwt_tokens.py +145 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/tests/test_models.py +139 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/tests/test_providers.py +412 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/tests/test_social.py +314 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/core/tokens.py +14 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/github/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/github/admin.py +39 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/github/api.py +17 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/github/apps.py +45 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/github/migrations/0001_initial.py +58 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/github/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/github/models.py +10 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/github/provider.py +73 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/google/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/google/admin.py +39 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/google/api.py +17 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/google/apps.py +45 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/google/migrations/0001_initial.py +58 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/google/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/google/models.py +10 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/google/provider.py +41 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/microsoft/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/microsoft/admin.py +39 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/microsoft/api.py +17 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/microsoft/apps.py +51 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/microsoft/migrations/0001_initial.py +58 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/microsoft/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/microsoft/models.py +10 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/microsoft/provider.py +90 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/rotation/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/rotation/admin.py +108 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/rotation/api.py +94 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/rotation/apps.py +31 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/rotation/migrations/0001_initial.py +306 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/rotation/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/rotation/models.py +173 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/rotation/services.py +167 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/rotation/tests/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/rotation/tests/test_api.py +208 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/session/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/session/admin.py +63 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/session/api.py +89 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/session/apps.py +31 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/session/migrations/0001_initial.py +184 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/session/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/session/models.py +101 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/session/services.py +106 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/session/tests/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/session/tests/test_api.py +172 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/session/tests/urls.py +24 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/sliding/__init__.py +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/sliding/admin.py +39 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/sliding/api.py +91 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/sliding/apps.py +31 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/sliding/migrations/0001_initial.py +131 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/sliding/migrations/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/sliding/models.py +76 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/sliding/services.py +89 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/sliding/tests/__init__.py +0 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/sliding/tests/test_api.py +203 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter/template/src/infrastructure/oauth/sliding/tests/urls.py +24 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter.egg-info/PKG-INFO +436 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter.egg-info/SOURCES.txt +245 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter.egg-info/dependency_links.txt +1 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter.egg-info/entry_points.txt +2 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter.egg-info/requires.txt +18 -0
- django_ninja_starter-1.0.0/src/django_ninja_starter.egg-info/top_level.txt +1 -0
- django_ninja_starter-1.0.0/tests/test_app_isolation.py +189 -0
- django_ninja_starter-1.0.0/tests/test_cli.py +102 -0
- django_ninja_starter-1.0.0/tests/test_packaging.py +235 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 django-ninja-starter contributors
|
|
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.
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-ninja-starter
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A production-oriented Django Ninja API template and project generator
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/CtrlAltDevelop/django-ninja-starter
|
|
7
|
+
Project-URL: Repository, https://github.com/CtrlAltDevelop/django-ninja-starter
|
|
8
|
+
Project-URL: Issues, https://github.com/CtrlAltDevelop/django-ninja-starter/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/CtrlAltDevelop/django-ninja-starter/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Documentation, https://github.com/CtrlAltDevelop/django-ninja-starter/tree/main/docs
|
|
11
|
+
Keywords: django,django-ninja,api,starter,template,authentication,oauth,jwt,two-factor
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Framework :: Django
|
|
15
|
+
Classifier: Framework :: Django :: 5.2
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
23
|
+
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: Django<7.0,>=5.2; extra == "dev"
|
|
30
|
+
Requires-Dist: cryptography<48,>=45; extra == "dev"
|
|
31
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
32
|
+
Requires-Dist: django-ninja<2.0,>=1.4; extra == "dev"
|
|
33
|
+
Requires-Dist: httpx<1.0,>=0.28; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy>=1.15; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest>=8.3; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-cov>=6.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-django>=4.11; extra == "dev"
|
|
38
|
+
Requires-Dist: python-dotenv<2.0,>=1.1; extra == "dev"
|
|
39
|
+
Requires-Dist: PyJWT<3.0,>=2.10; extra == "dev"
|
|
40
|
+
Requires-Dist: pyotp<3.0,>=2.9; extra == "dev"
|
|
41
|
+
Requires-Dist: redis<7.0,>=5.0; extra == "dev"
|
|
42
|
+
Requires-Dist: fakeredis<3.0,>=2.26; extra == "dev"
|
|
43
|
+
Requires-Dist: ruff>=0.11; extra == "dev"
|
|
44
|
+
Requires-Dist: twine>=6.0; extra == "dev"
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
|
|
47
|
+
# Django Ninja Starter
|
|
48
|
+
|
|
49
|
+
A production-oriented Django and Django Ninja starter, available as both a GitHub
|
|
50
|
+
Template and an installable Python project generator.
|
|
51
|
+
|
|
52
|
+
It includes environment-specific settings, secure production defaults, health checks,
|
|
53
|
+
OpenAPI documentation, tests, typing, linting, coverage, CI, and a feature-first source
|
|
54
|
+
layout.
|
|
55
|
+
|
|
56
|
+
Authentication is included and opt-in: four login methods, four second factors, four
|
|
57
|
+
social providers and three token modes, each a separate app that installs nothing until
|
|
58
|
+
you name it. Every login ends by minting a signed JWT.
|
|
59
|
+
|
|
60
|
+
**[Read the documentation](docs/README.md)** — one page per app, covering its routes,
|
|
61
|
+
models, admin, setup and usage.
|
|
62
|
+
|
|
63
|
+
## Requirements
|
|
64
|
+
|
|
65
|
+
- Python 3.12 or newer
|
|
66
|
+
- Django 5.2 or newer (Django 6.x is supported)
|
|
67
|
+
|
|
68
|
+
## Create a project
|
|
69
|
+
|
|
70
|
+
### Option 1: Python package
|
|
71
|
+
|
|
72
|
+
After the package is published to PyPI, install the generator with `pipx` and create a
|
|
73
|
+
project:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pipx install django-ninja-starter
|
|
77
|
+
django-ninja-starter my-api
|
|
78
|
+
cd my-api
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
To use the package directly from this checkout before publishing:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pipx install .
|
|
85
|
+
django-ninja-starter my-api
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Choose a different output directory when needed:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
django-ninja-starter my-api --directory ./services/my-api
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The generator refuses to overwrite a non-empty directory.
|
|
95
|
+
|
|
96
|
+
### Option 2: GitHub Template
|
|
97
|
+
|
|
98
|
+
Repository administrators must enable **Settings → General → Template repository** once.
|
|
99
|
+
Users can then select **Use this template**, create a new repository, and clone it. GitHub
|
|
100
|
+
copies the default branch files into the new repository with an independent history.
|
|
101
|
+
|
|
102
|
+
After creating a repository from the template, update the project `name` and `description`
|
|
103
|
+
in `pyproject.toml`, then follow the setup below.
|
|
104
|
+
|
|
105
|
+
## Set up the generated project
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
python3 -m venv .venv
|
|
109
|
+
source .venv/bin/activate
|
|
110
|
+
make install
|
|
111
|
+
cp .env.example .env
|
|
112
|
+
make migrate
|
|
113
|
+
make run
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Open <http://127.0.0.1:8000/api/docs> for interactive API documentation. Swagger's
|
|
117
|
+
top bar lets you select any registered API version.
|
|
118
|
+
|
|
119
|
+
## Create a versioned API
|
|
120
|
+
|
|
121
|
+
Use the included Django management command to scaffold and register a feature API:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
python manage.py startapi users --api-version v1
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
This creates `src/apps/users/api/v1.py` and a matching endpoint test, then adds the app and router to
|
|
128
|
+
`src/config/api_registry.json`, and exposes the example endpoint at
|
|
129
|
+
`GET /api/v1/users/`. It appears automatically in both the v1 OpenAPI schema and the
|
|
130
|
+
Swagger version selector at `/api/docs`.
|
|
131
|
+
|
|
132
|
+
Create another API version without duplicating the feature app:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
python manage.py startapi users --api-version v2
|
|
136
|
+
python manage.py startapi reports --api-version v2 --prefix /internal-reports
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Versions accept `v1`, `v1.1`, or `v1.1.0`. App names use lowercase Python identifiers,
|
|
140
|
+
such as `users` or `order_items`. The command refuses to overwrite an existing version or
|
|
141
|
+
register a duplicate prefix.
|
|
142
|
+
|
|
143
|
+
## Architecture
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
src/
|
|
147
|
+
├── apps/ # User-created business applications
|
|
148
|
+
├── infrastructure/
|
|
149
|
+
│ ├── common/ # Project-owned foundation application
|
|
150
|
+
│ ├── auth/ # Login methods and second factors
|
|
151
|
+
│ │ ├── core/ # Challenge store, delivery, throttling, credential issuance
|
|
152
|
+
│ │ ├── password/ # Username-or-email and password
|
|
153
|
+
│ │ ├── email_code/ # One-time code by email
|
|
154
|
+
│ │ ├── sms_code/ # One-time code by SMS
|
|
155
|
+
│ │ ├── magic_link/ # Single-use emailed link
|
|
156
|
+
│ │ └── twofactor/ # TOTP, SMS, email, and recovery second factors
|
|
157
|
+
│ ├── oauth_core/ # Shared clients, scopes, consent, PKCE, and audit models
|
|
158
|
+
│ ├── oauth_sliding/ # Sliding token mode
|
|
159
|
+
│ ├── oauth_session/ # Server-side session mode
|
|
160
|
+
│ └── oauth_rotation/ # Access/refresh rotation mode
|
|
161
|
+
└── config/
|
|
162
|
+
├── settings/ # Base, development, test, and production settings
|
|
163
|
+
├── api.py # Versioned NinjaAPI composition root
|
|
164
|
+
├── urls.py
|
|
165
|
+
├── asgi.py
|
|
166
|
+
└── wsgi.py
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Create business features under `src/apps/`. Keep foundational code and technical
|
|
170
|
+
integrations under `src/infrastructure/`. `config/api.py` mounts feature routers and should
|
|
171
|
+
remain free of business logic.
|
|
172
|
+
|
|
173
|
+
The recommended dependency direction is `transport → services → models/integrations`.
|
|
174
|
+
Models should not import API schemas or routers. Cross-feature workflows belong in explicit
|
|
175
|
+
services unless event semantics are intentional.
|
|
176
|
+
|
|
177
|
+
## OAuth storage modes
|
|
178
|
+
|
|
179
|
+
Select one independently installable token model with `DJANGO_OAUTH_MODE`:
|
|
180
|
+
|
|
181
|
+
| Mode | Stored models | Intended behavior |
|
|
182
|
+
| --- | --- | --- |
|
|
183
|
+
| `none` | None | OAuth storage is disabled (default) |
|
|
184
|
+
| `sliding` | `SlidingToken`, `SlidingTokenEvent` | One opaque bearer token whose idle expiry advances up to an absolute limit |
|
|
185
|
+
| `session` | `OAuthSession`, `SessionAccessToken`, `SessionRevocation` | Server-side device sessions with access-token and whole-session revocation |
|
|
186
|
+
| `rotation` | `TokenFamily`, rotating access/refresh tokens, reuse events | Every refresh replaces its parent; reuse revokes the entire token family |
|
|
187
|
+
| `all` | All models above | Development or projects that deliberately support every mode |
|
|
188
|
+
|
|
189
|
+
Every enabled mode also installs `oauth_core`, which provides scopes, public/confidential
|
|
190
|
+
clients, consent, hashed PKCE authorization codes, and audit events. Token and client-secret
|
|
191
|
+
values are intentionally never stored in plaintext: generate a value once, return it to the
|
|
192
|
+
caller, and persist only `hash_token(value)`. The included code is the persistence and domain
|
|
193
|
+
model layer; projects should add their own issuance/authentication routes and policies around it.
|
|
194
|
+
|
|
195
|
+
Set the mode before migrations:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
DJANGO_OAUTH_MODE=rotation python manage.py migrate
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Keep the selected mode stable for a deployed database. Run `makemigrations` and `migrate` if
|
|
202
|
+
you later change it, and explicitly plan how existing credentials will be revoked or migrated.
|
|
203
|
+
|
|
204
|
+
## Social OAuth providers
|
|
205
|
+
|
|
206
|
+
Google, Apple, Microsoft, and GitHub are separate infrastructure apps. Enable only the ones a
|
|
207
|
+
project uses:
|
|
208
|
+
|
|
209
|
+
```env
|
|
210
|
+
DJANGO_OAUTH_PROVIDERS=google,apple,microsoft,github
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Each enabled provider adds these versioned routes and OpenAPI entries:
|
|
214
|
+
|
|
215
|
+
- `GET /api/<version>/oauth/<provider>/start` — create protected state and redirect to consent
|
|
216
|
+
- `GET /api/<version>/oauth/<provider>/callback` — Google, Microsoft, and GitHub callback
|
|
217
|
+
- `POST /api/<version>/oauth/apple/callback` — Apple's `form_post` callback
|
|
218
|
+
|
|
219
|
+
Provider accounts are linked by the provider's stable subject identifier, never automatically by
|
|
220
|
+
email. A callback signs in an existing linked account, links it to the authenticated user who
|
|
221
|
+
started the flow, or creates a local user when `DJANGO_OAUTH_AUTO_CREATE_USERS=true`. Set
|
|
222
|
+
`DJANGO_OAUTH_USER_RESOLVER` to a dotted callable for custom user provisioning.
|
|
223
|
+
|
|
224
|
+
Required credentials:
|
|
225
|
+
|
|
226
|
+
| Provider | Required variables |
|
|
227
|
+
| --- | --- |
|
|
228
|
+
| Google | `GOOGLE_OAUTH_CLIENT_ID`, `GOOGLE_OAUTH_CLIENT_SECRET` |
|
|
229
|
+
| Apple | `APPLE_OAUTH_CLIENT_ID`, `APPLE_OAUTH_TEAM_ID`, `APPLE_OAUTH_KEY_ID`, `APPLE_OAUTH_PRIVATE_KEY` |
|
|
230
|
+
| Microsoft | `MICROSOFT_OAUTH_CLIENT_ID`, `MICROSOFT_OAUTH_CLIENT_SECRET`; tenant is `common`, `organizations`, `consumers`, or a tenant GUID |
|
|
231
|
+
| GitHub | `GITHUB_OAUTH_CLIENT_ID`, `GITHUB_OAUTH_CLIENT_SECRET` |
|
|
232
|
+
|
|
233
|
+
Set each provider's `*_OAUTH_REDIRECT_URI` to its exact registered callback URL. Apple requires an
|
|
234
|
+
HTTPS domain and posts its callback. The flow uses single-use hashed state, OIDC nonce validation,
|
|
235
|
+
PKCE where the provider supports it, signed Apple client secrets, validated ID-token signatures,
|
|
236
|
+
audiences and issuers, and short network timeouts.
|
|
237
|
+
|
|
238
|
+
`start` also sets a short-lived `HttpOnly` binding cookie and stores only its hash on the attempt.
|
|
239
|
+
The callback refuses a state that arrives from a different browser, so a stolen `state`/`code` pair
|
|
240
|
+
cannot be replayed into a victim's browser to sign them into the attacker's account. Apple's
|
|
241
|
+
`form_post` callback is cross-site, so its binding cookie is issued as `SameSite=None; Secure`;
|
|
242
|
+
the redirect-based providers use `SameSite=Lax`.
|
|
243
|
+
|
|
244
|
+
`python manage.py check` reports enabled providers with missing credentials, invalid Apple callback
|
|
245
|
+
schemes, ambiguous Microsoft tenants, and unsafe timeout/state/clock-skew limits before deployment.
|
|
246
|
+
|
|
247
|
+
Upstream access and refresh tokens are not retained by default. Set
|
|
248
|
+
`DJANGO_OAUTH_STORE_PROVIDER_TOKENS=true` only when the application needs provider APIs. Stored
|
|
249
|
+
tokens and transient PKCE verifiers are encrypted with `DJANGO_OAUTH_ENCRYPTION_KEY`; when empty,
|
|
250
|
+
a key is derived from `DJANGO_SECRET_KEY`. Changing either key requires a credential migration or
|
|
251
|
+
provider reauthorization.
|
|
252
|
+
|
|
253
|
+
## Login methods and two-factor authentication
|
|
254
|
+
|
|
255
|
+
Each login method is a separate infrastructure app. Enable only what a project uses:
|
|
256
|
+
|
|
257
|
+
```env
|
|
258
|
+
DJANGO_AUTH_METHODS=password,email_code,sms_code,magic_link
|
|
259
|
+
DJANGO_AUTH_SECOND_FACTORS=totp,sms,email,recovery
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Every method mounts under `/api/<version>/auth/<method>` and answers with the same shape, so a
|
|
263
|
+
client writes the two-step branch once:
|
|
264
|
+
|
|
265
|
+
| Method | Routes |
|
|
266
|
+
| --- | --- |
|
|
267
|
+
| `password` | `POST /auth/password/{signup,login,logout,forgot,reset,change}` |
|
|
268
|
+
| `email_code` | `POST /auth/email-code/{signup/start,signup/verify,login/start,login/verify,logout}` |
|
|
269
|
+
| `sms_code` | `POST /auth/sms-code/{signup/start,signup/verify,login/start,login/verify,logout}` |
|
|
270
|
+
| `magic_link` | `POST /auth/magic-link/{signup/start,login/start,verify,logout}` |
|
|
271
|
+
|
|
272
|
+
A successful first factor returns either a credential or a ticket:
|
|
273
|
+
|
|
274
|
+
```jsonc
|
|
275
|
+
// no second factor enrolled
|
|
276
|
+
{"requires_second_factor": false, "credentials": {"token_type": "bearer", "access_token": "..."}}
|
|
277
|
+
|
|
278
|
+
// second factor required
|
|
279
|
+
{"requires_second_factor": true, "login_ticket": "...", "methods": ["totp"]}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
The client then posts the ticket and a code to `POST /auth/2fa/verify`. For `sms` and `email`
|
|
283
|
+
factors it first calls `POST /auth/2fa/challenge` to have a code sent; that returns the *same*
|
|
284
|
+
ticket, so the client only ever tracks one. Enrolment lives at `POST /auth/2fa/totp/enroll`,
|
|
285
|
+
`/totp/confirm`, `/sms/enroll`, `/sms/confirm`, `/email/enroll`, `/email/confirm`, and
|
|
286
|
+
`/recovery/generate`, with `GET /auth/2fa/methods` and `DELETE /auth/2fa/{method}` to manage
|
|
287
|
+
what is enabled.
|
|
288
|
+
|
|
289
|
+
### Two-step state lives in Redis, hashed
|
|
290
|
+
|
|
291
|
+
Pending logins and one-time codes never touch the database. `DJANGO_AUTH_CHALLENGE_STORE`
|
|
292
|
+
selects the backend; the default keeps them in Redis under `DJANGO_AUTH_REDIS_URL` and lets the
|
|
293
|
+
TTL expire them.
|
|
294
|
+
|
|
295
|
+
Nothing is stored in a form that can be replayed from a dump. A ticket is stored only as the
|
|
296
|
+
SHA-256 of itself, and a code as an HMAC keyed with `DJANGO_SECRET_KEY` and salted with the
|
|
297
|
+
ticket and purpose — a six-digit code has far too little entropy to survive a bare digest.
|
|
298
|
+
Tickets are bound to the purpose that minted them, so a sign-up ticket cannot be spent on a
|
|
299
|
+
login. Codes are single use, capped at `DJANGO_AUTH_CHALLENGE_MAX_ATTEMPTS` guesses, and
|
|
300
|
+
rate-limited per destination by `DJANGO_AUTH_RESEND_COOLDOWN_SECONDS` and
|
|
301
|
+
`DJANGO_AUTH_MAX_SENDS_PER_HOUR`.
|
|
302
|
+
|
|
303
|
+
The pending-login ticket is read rather than consumed during 2FA, so a mistyped authenticator
|
|
304
|
+
code does not send the user back to the password prompt; repeated failures still retire it.
|
|
305
|
+
Authenticator codes record the time step they were accepted at and are refused a second time,
|
|
306
|
+
and a recovery code is only ever spent when asked for by name — never inferred from a wrong TOTP
|
|
307
|
+
code.
|
|
308
|
+
|
|
309
|
+
`LocMemChallengeStore` exists for tests and single-worker development. It does not survive
|
|
310
|
+
across processes, so a multi-worker deployment would hand step two to a worker that never saw
|
|
311
|
+
step one; `python manage.py check` warns when it is configured.
|
|
312
|
+
|
|
313
|
+
### Credentials come from the OAuth token modes
|
|
314
|
+
|
|
315
|
+
Authentication does not invent its own token format. `DJANGO_AUTH_TOKEN_MODE` picks which of the
|
|
316
|
+
storage modes above issues the credential, so a password login and a social login produce the
|
|
317
|
+
same records and share one revocation story. Every method ends by minting a signed JWT, and a
|
|
318
|
+
token from any one of them is accepted by every endpoint in the project.
|
|
319
|
+
|
|
320
|
+
Left unset, the mode follows `DJANGO_OAUTH_MODE` when that names one; otherwise it is `rotation`
|
|
321
|
+
as soon as anything signs users in, and `none` only when nothing does. Enabling just a login
|
|
322
|
+
method therefore gets you a real bearer token rather than a session cookie and an empty
|
|
323
|
+
`access_token`. Ask for `none` explicitly to sign in with a Django session instead. The active
|
|
324
|
+
mode's app is installed for you, and `check` reports a mode whose app is missing.
|
|
325
|
+
|
|
326
|
+
Changing a password or completing a reset revokes every live credential for that account across
|
|
327
|
+
all three mode tables.
|
|
328
|
+
|
|
329
|
+
### Delivery backends
|
|
330
|
+
|
|
331
|
+
SMS and email are swapped by dotted path through `DJANGO_AUTH_SMS_BACKEND` and
|
|
332
|
+
`DJANGO_AUTH_EMAIL_BACKEND`. The defaults never reach the network: SMS goes to the log, email
|
|
333
|
+
goes through whatever Django is already configured to use. Point them at a carrier for anything
|
|
334
|
+
real — `check` warns when SMS codes are being written to a log.
|
|
335
|
+
|
|
336
|
+
### What the endpoints deliberately do not reveal
|
|
337
|
+
|
|
338
|
+
`login/start` and `signup/start` behave identically whether or not an address or number has an
|
|
339
|
+
account, and `password/forgot` returns a ticket either way — for an unknown address that ticket
|
|
340
|
+
is a decoy bound to a code that was never sent. Responses mask where a code went
|
|
341
|
+
(`z***@example.com`, `***0101`) rather than echoing it back. A password login runs a throwaway
|
|
342
|
+
hash when no account matches, so a missing identifier costs the same time as a wrong password.
|
|
343
|
+
|
|
344
|
+
Phone numbers, verified addresses, enrolled factors, and a hashed audit trail (`AuthEvent`) are
|
|
345
|
+
stored under `infrastructure/auth/core`; identifiers in the audit log are digests, not a second
|
|
346
|
+
user table.
|
|
347
|
+
|
|
348
|
+
Each method, factor, provider and token mode has its own page under [`docs/`](docs/README.md)
|
|
349
|
+
covering its routes, models, admin, setup and usage. Start with
|
|
350
|
+
[credentials and token modes](docs/credentials.md).
|
|
351
|
+
|
|
352
|
+
## Included endpoints
|
|
353
|
+
|
|
354
|
+
- `GET /api/v1/health/live` — confirms that the web process is serving requests
|
|
355
|
+
- `GET /api/v1/health/ready` — confirms that the database is available
|
|
356
|
+
- `GET /api/docs` — Swagger documentation with an API-version selector
|
|
357
|
+
- `GET /api/<version>/docs` — Swagger documentation opened on a specific version
|
|
358
|
+
- `GET /api/<version>/openapi.json` — OpenAPI schema for a specific version
|
|
359
|
+
|
|
360
|
+
## Configuration
|
|
361
|
+
|
|
362
|
+
Development uses SQLite by default. Copy `.env.example` to `.env` and configure these
|
|
363
|
+
variables as needed:
|
|
364
|
+
|
|
365
|
+
| Variable | Purpose | Default |
|
|
366
|
+
| --- | --- | --- |
|
|
367
|
+
| `DJANGO_SETTINGS_MODULE` | Active settings module | `config.settings.development` |
|
|
368
|
+
| `DJANGO_SECRET_KEY` | Django signing key | Unsafe development value |
|
|
369
|
+
| `DJANGO_ALLOWED_HOSTS` | Comma-separated hostnames | `localhost,127.0.0.1` in `.env.example` |
|
|
370
|
+
| `DJANGO_OAUTH_MODE` | OAuth model set: `none`, `sliding`, `session`, `rotation`, or `all` | `none` |
|
|
371
|
+
| `DJANGO_OAUTH_PROVIDERS` | Comma-separated `google`, `apple`, `microsoft`, and/or `github` | Empty |
|
|
372
|
+
| `DJANGO_OAUTH_ENCRYPTION_KEY` | Fernet key for recoverable provider credentials | Derived from secret key |
|
|
373
|
+
| `DJANGO_OAUTH_STORE_PROVIDER_TOKENS` | Persist encrypted upstream tokens | `false` |
|
|
374
|
+
| `DJANGO_OAUTH_CLOCK_SKEW_SECONDS` | Allowed ID-token clock skew, from 0 through 300 | `60` |
|
|
375
|
+
| `DJANGO_AUTH_METHODS` | Comma-separated `password`, `email_code`, `sms_code`, and/or `magic_link` | Empty |
|
|
376
|
+
| `DJANGO_AUTH_SECOND_FACTORS` | Comma-separated `totp`, `sms`, `email`, and/or `recovery` | Empty |
|
|
377
|
+
| `DJANGO_AUTH_TOKEN_MODE` | Which mode issues credentials: `none`, `sliding`, `session`, `rotation` | `DJANGO_OAUTH_MODE` |
|
|
378
|
+
| `DJANGO_AUTH_REDIS_URL` | Redis holding pending logins and one-time codes | `redis://127.0.0.1:6379/0` |
|
|
379
|
+
| `DJANGO_AUTH_CHALLENGE_STORE` | Dotted path to the challenge backend | `RedisChallengeStore` |
|
|
380
|
+
| `DJANGO_AUTH_CHALLENGE_TTL_SECONDS` | Code lifetime, from 60 through 3600 | `300` |
|
|
381
|
+
| `DJANGO_AUTH_CHALLENGE_MAX_ATTEMPTS` | Guesses allowed per code, from 1 through 20 | `5` |
|
|
382
|
+
| `DJANGO_AUTH_PENDING_LOGIN_TTL_SECONDS` | How long a login may wait on its second factor | `600` |
|
|
383
|
+
| `DJANGO_AUTH_CODE_DIGITS` | One-time code length, from 4 through 10 | `6` |
|
|
384
|
+
| `DJANGO_AUTH_RESEND_COOLDOWN_SECONDS` | Minimum gap between sends to one destination | `30` |
|
|
385
|
+
| `DJANGO_AUTH_MAX_SENDS_PER_HOUR` | Hourly send ceiling per destination | `10` |
|
|
386
|
+
| `DJANGO_AUTH_SMS_BACKEND` | Dotted path to the SMS transport | Console (logs only) |
|
|
387
|
+
| `DJANGO_AUTH_EMAIL_BACKEND` | Dotted path to the email transport | Django mail |
|
|
388
|
+
| `DJANGO_AUTH_MAGIC_LINK_BASE_URL` | Page that reads the token out of the link | Empty (required for `magic_link`) |
|
|
389
|
+
| `DJANGO_AUTH_PASSWORD_RESET_BASE_URL` | Page that reads a reset ticket | Empty |
|
|
390
|
+
| `DJANGO_AUTH_AUTO_CREATE_USERS` | Create an account on first passwordless sign-in | `true` |
|
|
391
|
+
| `DJANGO_AUTH_TOTP_ISSUER` | Name shown in authenticator apps | `Django Ninja Starter` |
|
|
392
|
+
| `DJANGO_AUTH_RECOVERY_CODE_COUNT` | Codes issued per batch, from 5 through 30 | `10` |
|
|
393
|
+
| `DJANGO_DB_ENGINE` | Django database backend | SQLite |
|
|
394
|
+
| `DJANGO_DB_NAME` | Database name or path | `db.sqlite3` |
|
|
395
|
+
| `DJANGO_DB_USER` | Database user | Empty |
|
|
396
|
+
| `DJANGO_DB_PASSWORD` | Database password | Empty |
|
|
397
|
+
| `DJANGO_DB_HOST` | Database host | Empty |
|
|
398
|
+
| `DJANGO_DB_PORT` | Database port | Empty |
|
|
399
|
+
|
|
400
|
+
Production uses `config.settings.production`. It rejects the development secret key and an
|
|
401
|
+
empty allowed-host list, enables secure cookies, HTTPS redirects, HSTS, and defensive HTTP
|
|
402
|
+
headers. Set `DJANGO_SECURE_SSL_REDIRECT=false` only when TLS termination and proxy handling
|
|
403
|
+
make that appropriate for your deployment.
|
|
404
|
+
|
|
405
|
+
## Development commands
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
make check # lint, formatting, types, Django checks, and migration drift
|
|
409
|
+
make test # tests with branch coverage (minimum 90%)
|
|
410
|
+
make docs # regenerate the reference sections of docs/
|
|
411
|
+
make package # build and validate wheel and source distribution
|
|
412
|
+
make migrations # create migrations
|
|
413
|
+
make migrate # apply migrations
|
|
414
|
+
make superuser # create an admin user
|
|
415
|
+
make run # start the development server
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Package maintenance and publishing
|
|
419
|
+
|
|
420
|
+
The Python package lives in `src/django_ninja_starter/`; its bundled project scaffold lives
|
|
421
|
+
in `src/django_ninja_starter/template/`. Update the runnable root starter and the bundled
|
|
422
|
+
scaffold together when changing project behavior.
|
|
423
|
+
|
|
424
|
+
To publish a release:
|
|
425
|
+
|
|
426
|
+
1. Update `version` in `pyproject.toml` and `__version__` in
|
|
427
|
+
`src/django_ninja_starter/__init__.py`.
|
|
428
|
+
2. Run `make check`, `make test`, and `make package`.
|
|
429
|
+
3. Configure a PyPI Trusted Publisher for `.github/workflows/release.yml` with the `pypi`
|
|
430
|
+
GitHub environment and require approval on that environment.
|
|
431
|
+
4. Create and publish a GitHub Release. The release workflow builds and uploads the wheel
|
|
432
|
+
and source distribution without a long-lived PyPI token.
|
|
433
|
+
|
|
434
|
+
## License
|
|
435
|
+
|
|
436
|
+
MIT. See [LICENSE](LICENSE).
|