kantu-auth 0.1.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.
- kantu_auth-0.1.0/.gitignore +21 -0
- kantu_auth-0.1.0/CHANGELOG.md +52 -0
- kantu_auth-0.1.0/LICENSE +21 -0
- kantu_auth-0.1.0/PKG-INFO +468 -0
- kantu_auth-0.1.0/README.md +350 -0
- kantu_auth-0.1.0/SECURITY.md +21 -0
- kantu_auth-0.1.0/docs/cli.md +75 -0
- kantu_auth-0.1.0/docs/configuration.md +244 -0
- kantu_auth-0.1.0/docs/email-adapters.md +291 -0
- kantu_auth-0.1.0/docs/routes.md +219 -0
- kantu_auth-0.1.0/docs/security.md +122 -0
- kantu_auth-0.1.0/pyproject.toml +144 -0
- kantu_auth-0.1.0/src/fastapi_auth/__init__.py +35 -0
- kantu_auth-0.1.0/src/fastapi_auth/adapters/__init__.py +6 -0
- kantu_auth-0.1.0/src/fastapi_auth/adapters/base.py +16 -0
- kantu_auth-0.1.0/src/fastapi_auth/adapters/smtp.py +52 -0
- kantu_auth-0.1.0/src/fastapi_auth/cli.py +160 -0
- kantu_auth-0.1.0/src/fastapi_auth/config.py +315 -0
- kantu_auth-0.1.0/src/fastapi_auth/db.py +38 -0
- kantu_auth-0.1.0/src/fastapi_auth/dependencies.py +283 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/__init__.py +0 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/celery_app.py +41 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/mailer.py +250 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/qr.py +24 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/templates/_layout.html +40 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/templates/google_account_linked.html +47 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/templates/google_account_unlinked.html +17 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/templates/new_login_alert.html +13 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/templates/password_changed.html +8 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/templates/password_reset.html +17 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/templates/two_factor_otp.html +8 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/templates/two_factor_security.html +13 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/templates/verify_email.html +17 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/templates/welcome.html +6 -0
- kantu_auth-0.1.0/src/fastapi_auth/extras/turnstile.py +59 -0
- kantu_auth-0.1.0/src/fastapi_auth/jwt.py +298 -0
- kantu_auth-0.1.0/src/fastapi_auth/models.py +376 -0
- kantu_auth-0.1.0/src/fastapi_auth/rate_limit.py +133 -0
- kantu_auth-0.1.0/src/fastapi_auth/redis_client.py +135 -0
- kantu_auth-0.1.0/src/fastapi_auth/router.py +116 -0
- kantu_auth-0.1.0/src/fastapi_auth/routers/__init__.py +0 -0
- kantu_auth-0.1.0/src/fastapi_auth/routers/auth.py +390 -0
- kantu_auth-0.1.0/src/fastapi_auth/routers/google.py +588 -0
- kantu_auth-0.1.0/src/fastapi_auth/routers/two_factor.py +427 -0
- kantu_auth-0.1.0/src/fastapi_auth/routers/users.py +23 -0
- kantu_auth-0.1.0/src/fastapi_auth/routers/verification.py +100 -0
- kantu_auth-0.1.0/src/fastapi_auth/schemas.py +352 -0
- kantu_auth-0.1.0/src/fastapi_auth/schemas_google.py +104 -0
- kantu_auth-0.1.0/src/fastapi_auth/security.py +36 -0
- kantu_auth-0.1.0/src/fastapi_auth/services/__init__.py +0 -0
- kantu_auth-0.1.0/src/fastapi_auth/services/auth_service.py +673 -0
- kantu_auth-0.1.0/src/fastapi_auth/services/oauth/__init__.py +0 -0
- kantu_auth-0.1.0/src/fastapi_auth/services/oauth/google_service.py +311 -0
- kantu_auth-0.1.0/src/fastapi_auth/services/recent_auth_service.py +124 -0
- kantu_auth-0.1.0/src/fastapi_auth/services/session_service.py +487 -0
- kantu_auth-0.1.0/src/fastapi_auth/services/two_factor_service.py +464 -0
- kantu_auth-0.1.0/src/fastapi_auth/services/verification_service.py +130 -0
- kantu_auth-0.1.0/src/fastapi_auth/tasks/celery_email.py +39 -0
- kantu_auth-0.1.0/src/fastapi_auth/tasks/emails.py +205 -0
- kantu_auth-0.1.0/src/fastapi_auth/templates/.env.example +56 -0
- kantu_auth-0.1.0/src/fastapi_auth/templates/KANTU_AUTH_SETUP.md +97 -0
- kantu_auth-0.1.0/src/fastapi_auth/templates/app/__init__.py +1 -0
- kantu_auth-0.1.0/src/fastapi_auth/templates/app/alembic.ini +39 -0
- kantu_auth-0.1.0/src/fastapi_auth/templates/app/core/__init__.py +1 -0
- kantu_auth-0.1.0/src/fastapi_auth/templates/app/core/celery_app.py +20 -0
- kantu_auth-0.1.0/src/fastapi_auth/templates/app/core/config.py +178 -0
- kantu_auth-0.1.0/src/fastapi_auth/templates/app/core/db.py +23 -0
- kantu_auth-0.1.0/src/fastapi_auth/templates/app/main.py +45 -0
- kantu_auth-0.1.0/src/fastapi_auth/templates/app/migrations/README +1 -0
- kantu_auth-0.1.0/src/fastapi_auth/templates/app/migrations/env.py +59 -0
- kantu_auth-0.1.0/src/fastapi_auth/templates/app/migrations/script.py.mako +24 -0
- kantu_auth-0.1.0/src/fastapi_auth/templates/app/migrations/versions/.gitkeep +1 -0
- kantu_auth-0.1.0/src/fastapi_auth/utils.py +190 -0
- kantu_auth-0.1.0/src/fastapi_auth/validators.py +32 -0
- kantu_auth-0.1.0/tests/conftest.py +267 -0
- kantu_auth-0.1.0/tests/test_cli.py +66 -0
- kantu_auth-0.1.0/tests/test_config_and_schemas.py +218 -0
- kantu_auth-0.1.0/tests/test_core_endpoints.py +356 -0
- kantu_auth-0.1.0/tests/test_email.py +148 -0
- kantu_auth-0.1.0/tests/test_google_endpoints.py +414 -0
- kantu_auth-0.1.0/tests/test_google_link_concurrency.py +107 -0
- kantu_auth-0.1.0/tests/test_google_security.py +974 -0
- kantu_auth-0.1.0/tests/test_password_mutation_locking.py +271 -0
- kantu_auth-0.1.0/tests/test_proxy_headers.py +217 -0
- kantu_auth-0.1.0/tests/test_rate_limit_and_turnstile.py +219 -0
- kantu_auth-0.1.0/tests/test_recent_auth.py +304 -0
- kantu_auth-0.1.0/tests/test_router.py +125 -0
- kantu_auth-0.1.0/tests/test_session_concurrency.py +361 -0
- kantu_auth-0.1.0/tests/test_token_hardening.py +246 -0
- kantu_auth-0.1.0/tests/test_two_factor_dependency.py +131 -0
- kantu_auth-0.1.0/tests/test_two_factor_endpoints.py +288 -0
- kantu_auth-0.1.0/tests/test_verification_endpoints.py +117 -0
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
|
|
12
|
+
# Local dev artifacts
|
|
13
|
+
*.db
|
|
14
|
+
.env
|
|
15
|
+
.env.*
|
|
16
|
+
!.env.example
|
|
17
|
+
|
|
18
|
+
# Runtime service state
|
|
19
|
+
dump.rdb
|
|
20
|
+
|
|
21
|
+
frontend
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Kantu Auth are documented here. The project follows
|
|
4
|
+
[Semantic Versioning](https://semver.org/) while its public API is pre-1.0.
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
### Security
|
|
9
|
+
|
|
10
|
+
- Validate proxy peers and forwarded IP chains with explicit trusted CIDRs.
|
|
11
|
+
- Prevent account enumeration through duplicate registration responses.
|
|
12
|
+
- Prevent implicit same-email Google account linking and linked-identity replacement.
|
|
13
|
+
- Bind Google account-link state to the authorizing active session and make
|
|
14
|
+
concurrent link completion first-writer-wins.
|
|
15
|
+
- Require authenticated, step-up-protected Google-only password setup.
|
|
16
|
+
- Invalidate undelivered Google password-setup tokens and return a controlled
|
|
17
|
+
`503` for email import, configuration, delivery, or queueing failures.
|
|
18
|
+
- Refresh verified provider emails only for passwordless Google-created
|
|
19
|
+
accounts, rejecting collisions without merging local identities.
|
|
20
|
+
- Bind Google session-exchange tokens to the provider identity.
|
|
21
|
+
- Revoke sessions and notify the account after Google unlinking.
|
|
22
|
+
- Issue a fresh session ID for every authenticated login and serialize
|
|
23
|
+
same-user session mutations so revoked access tokens cannot be reactivated.
|
|
24
|
+
- Rotate refresh tokens with a conditional database update so concurrent reuse
|
|
25
|
+
cannot produce two successful rotations.
|
|
26
|
+
- Retry only SQLite lock conflicts during concurrent same-device login while
|
|
27
|
+
preserving fresh session IDs and surfacing unrelated database failures.
|
|
28
|
+
- Use a consistent `User` → reset-token → session lock order for password
|
|
29
|
+
changes and resets, with conditional one-time reset-token consumption.
|
|
30
|
+
- Require a recent, session-bound password or Google proof before unenrolled
|
|
31
|
+
users can enroll 2FA or perform sensitive Google account actions; enrolled
|
|
32
|
+
users must continue to use their 2FA step-up.
|
|
33
|
+
- Fail closed for enrolled two-factor users when 2FA or Redis is unavailable.
|
|
34
|
+
- Send verification and reset secrets in frontend URL fragments and support
|
|
35
|
+
body-based email-verification confirmation.
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- Initial alpha version of the reusable FastAPI authentication package and
|
|
40
|
+
`kantu-auth init` application scaffold.
|
|
41
|
+
- Dedicated Google login, exchange, password-setup, and linking rate limits.
|
|
42
|
+
- Public `require_two_factor`, `require_sensitive_auth`, and
|
|
43
|
+
`require_google_only_user` dependencies.
|
|
44
|
+
- Safe `google_linked` account state in user responses.
|
|
45
|
+
- Python 3.11–3.14 continuous-integration matrix and distribution checks.
|
|
46
|
+
|
|
47
|
+
### Removed
|
|
48
|
+
|
|
49
|
+
- Removed the deprecated misspelled `fastapi_auth.adpters` compatibility
|
|
50
|
+
namespace; migrate imports to `fastapi_auth.adapters`.
|
|
51
|
+
- Removed the undocumented `/auth/google/auth/add-password/request` alias; use
|
|
52
|
+
authenticated `POST /auth/google/password/request` with an empty JSON body.
|
kantu_auth-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ukasha-hacks
|
|
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,468 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: kantu-auth
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reusable authentication for FastAPI with secure sessions, email verification, two-factor authentication, and optional OAuth.
|
|
5
|
+
Project-URL: Repository, https://github.com/ukasha-hacks/fastapi_auth
|
|
6
|
+
Project-URL: Documentation, https://github.com/ukasha-hacks/fastapi_auth#readme
|
|
7
|
+
Author: ukasha-hacks
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 ukasha-hacks
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: authentication,fastapi,jwt,oauth,two-factor
|
|
31
|
+
Classifier: Development Status :: 3 - Alpha
|
|
32
|
+
Classifier: Framework :: FastAPI
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Operating System :: OS Independent
|
|
36
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
41
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
42
|
+
Classifier: Topic :: Security
|
|
43
|
+
Requires-Python: >=3.11
|
|
44
|
+
Requires-Dist: alembic>=1.18.5
|
|
45
|
+
Requires-Dist: bcrypt<4.1
|
|
46
|
+
Requires-Dist: fastapi>=0.110
|
|
47
|
+
Requires-Dist: passlib[bcrypt]>=1.7.4
|
|
48
|
+
Requires-Dist: pydantic-settings>=2.4
|
|
49
|
+
Requires-Dist: pydantic[email]>=2.7
|
|
50
|
+
Requires-Dist: pyjwt>=2.8.0
|
|
51
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.30
|
|
52
|
+
Requires-Dist: sqlmodel>=0.0.21
|
|
53
|
+
Requires-Dist: typer>=0.15
|
|
54
|
+
Provides-Extra: all
|
|
55
|
+
Requires-Dist: aiosmtplib>=3.0; extra == 'all'
|
|
56
|
+
Requires-Dist: aiosqlite>=0.20; extra == 'all'
|
|
57
|
+
Requires-Dist: asyncpg>=0.29; extra == 'all'
|
|
58
|
+
Requires-Dist: celery>=5.3; extra == 'all'
|
|
59
|
+
Requires-Dist: cryptography>=42; extra == 'all'
|
|
60
|
+
Requires-Dist: google-auth>=2.30; extra == 'all'
|
|
61
|
+
Requires-Dist: httpx>=0.27; extra == 'all'
|
|
62
|
+
Requires-Dist: jinja2>=3.1; extra == 'all'
|
|
63
|
+
Requires-Dist: pyotp>=2.9.0; extra == 'all'
|
|
64
|
+
Requires-Dist: qrcode[pil]>=7.4; extra == 'all'
|
|
65
|
+
Requires-Dist: redis>=5.0; extra == 'all'
|
|
66
|
+
Requires-Dist: urllib3>=2.0; extra == 'all'
|
|
67
|
+
Provides-Extra: celery
|
|
68
|
+
Requires-Dist: aiosmtplib>=3.0; extra == 'celery'
|
|
69
|
+
Requires-Dist: celery>=5.3; extra == 'celery'
|
|
70
|
+
Requires-Dist: jinja2>=3.1; extra == 'celery'
|
|
71
|
+
Provides-Extra: dev
|
|
72
|
+
Requires-Dist: aiosmtplib>=3.0; extra == 'dev'
|
|
73
|
+
Requires-Dist: aiosqlite>=0.20; extra == 'dev'
|
|
74
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
75
|
+
Requires-Dist: celery>=5.3; extra == 'dev'
|
|
76
|
+
Requires-Dist: cryptography>=42; extra == 'dev'
|
|
77
|
+
Requires-Dist: fakeredis>=2.23; extra == 'dev'
|
|
78
|
+
Requires-Dist: google-auth>=2.30; extra == 'dev'
|
|
79
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
80
|
+
Requires-Dist: jinja2>=3.1; extra == 'dev'
|
|
81
|
+
Requires-Dist: pyotp>=2.9.0; extra == 'dev'
|
|
82
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
83
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
84
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
85
|
+
Requires-Dist: qrcode[pil]>=7.4; extra == 'dev'
|
|
86
|
+
Requires-Dist: redis>=5.0; extra == 'dev'
|
|
87
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
88
|
+
Requires-Dist: twine>=6.1; extra == 'dev'
|
|
89
|
+
Requires-Dist: urllib3>=2.0; extra == 'dev'
|
|
90
|
+
Provides-Extra: email
|
|
91
|
+
Requires-Dist: aiosmtplib>=3.0; extra == 'email'
|
|
92
|
+
Requires-Dist: jinja2>=3.1; extra == 'email'
|
|
93
|
+
Provides-Extra: encryption
|
|
94
|
+
Requires-Dist: cryptography>=42; extra == 'encryption'
|
|
95
|
+
Provides-Extra: google
|
|
96
|
+
Requires-Dist: aiosmtplib>=3.0; extra == 'google'
|
|
97
|
+
Requires-Dist: google-auth>=2.30; extra == 'google'
|
|
98
|
+
Requires-Dist: httpx>=0.27; extra == 'google'
|
|
99
|
+
Requires-Dist: jinja2>=3.1; extra == 'google'
|
|
100
|
+
Requires-Dist: redis>=5.0; extra == 'google'
|
|
101
|
+
Requires-Dist: urllib3>=2.0; extra == 'google'
|
|
102
|
+
Provides-Extra: postgres
|
|
103
|
+
Requires-Dist: asyncpg>=0.29; extra == 'postgres'
|
|
104
|
+
Provides-Extra: qr
|
|
105
|
+
Requires-Dist: qrcode[pil]>=7.4; extra == 'qr'
|
|
106
|
+
Provides-Extra: redis
|
|
107
|
+
Requires-Dist: redis>=5.0; extra == 'redis'
|
|
108
|
+
Provides-Extra: sqlite
|
|
109
|
+
Requires-Dist: aiosqlite>=0.20; extra == 'sqlite'
|
|
110
|
+
Provides-Extra: turnstile
|
|
111
|
+
Requires-Dist: httpx>=0.27; extra == 'turnstile'
|
|
112
|
+
Provides-Extra: two-factor
|
|
113
|
+
Requires-Dist: cryptography>=42; extra == 'two-factor'
|
|
114
|
+
Requires-Dist: pyotp>=2.9.0; extra == 'two-factor'
|
|
115
|
+
Requires-Dist: qrcode[pil]>=7.4; extra == 'two-factor'
|
|
116
|
+
Requires-Dist: redis>=5.0; extra == 'two-factor'
|
|
117
|
+
Description-Content-Type: text/markdown
|
|
118
|
+
|
|
119
|
+
# Kantu Auth
|
|
120
|
+
|
|
121
|
+
Kantu Auth is a reusable authentication package for FastAPI. It owns the
|
|
122
|
+
authentication models, services, dependencies, and routers; your application
|
|
123
|
+
owns its database engine, settings, migrations, and deployment.
|
|
124
|
+
|
|
125
|
+
The package currently provides:
|
|
126
|
+
|
|
127
|
+
- email/password registration and login;
|
|
128
|
+
- short-lived access tokens and rotating refresh-token sessions;
|
|
129
|
+
- session listing, per-session revocation, and logout-all;
|
|
130
|
+
- password changes and one-time password-reset tokens;
|
|
131
|
+
- optional email verification;
|
|
132
|
+
- optional TOTP two-factor authentication, recovery codes, and email OTP;
|
|
133
|
+
- optional Google OAuth login and account linking;
|
|
134
|
+
- optional Redis-backed rate limiting;
|
|
135
|
+
- optional Cloudflare Turnstile checks;
|
|
136
|
+
- SMTP or custom email delivery, inline or through Celery; and
|
|
137
|
+
- an application scaffold generated by `kantu-auth init`.
|
|
138
|
+
|
|
139
|
+
> Kantu Auth is currently pre-1.0. Review the [security checklist](docs/security.md),
|
|
140
|
+
> run your own threat model, and test the integration before using it in
|
|
141
|
+
> production.
|
|
142
|
+
|
|
143
|
+
## Install
|
|
144
|
+
|
|
145
|
+
Install the package with only the features your application uses:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pip install "kantu-auth[sqlite,email]" uvicorn
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
For development from this repository:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
git clone https://github.com/ukasha-hacks/fastapi_auth.git
|
|
155
|
+
cd fastapi_auth
|
|
156
|
+
python -m venv .venv
|
|
157
|
+
. .venv/bin/activate
|
|
158
|
+
pip install -e ".[dev]"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The distribution and command are named `kantu-auth`. The Python import remains
|
|
162
|
+
`fastapi_auth` for compatibility during the pre-1.0 rename.
|
|
163
|
+
|
|
164
|
+
### Optional dependencies
|
|
165
|
+
|
|
166
|
+
| Extra | Use it for |
|
|
167
|
+
| --- | --- |
|
|
168
|
+
| `sqlite` | Async SQLite through `aiosqlite` |
|
|
169
|
+
| `postgres` | Async PostgreSQL through `asyncpg` |
|
|
170
|
+
| `redis` | Rate limiting and other Redis-backed features |
|
|
171
|
+
| `email` | Jinja email templates and async SMTP |
|
|
172
|
+
| `celery` | Queued email delivery; includes the email dependencies |
|
|
173
|
+
| `qr` | QR data URLs returned by TOTP setup |
|
|
174
|
+
| `encryption` | Encryption of stored TOTP secrets |
|
|
175
|
+
| `two-factor` | Redis, QR generation, and TOTP-secret encryption; add `email` for emailed OTPs |
|
|
176
|
+
| `turnstile` | Cloudflare Turnstile verification |
|
|
177
|
+
| `google` | Google OAuth with PKCE; includes Redis and password-setup email support |
|
|
178
|
+
| `all` | Every runtime integration |
|
|
179
|
+
| `dev` | The local test toolchain |
|
|
180
|
+
|
|
181
|
+
## Initialize an application
|
|
182
|
+
|
|
183
|
+
From the root of a FastAPI project, run:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
kantu-auth init
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The command creates an integration you can edit and own:
|
|
190
|
+
|
|
191
|
+
```text
|
|
192
|
+
.
|
|
193
|
+
├── .env.example
|
|
194
|
+
├── KANTU_AUTH_SETUP.md
|
|
195
|
+
└── app
|
|
196
|
+
├── __init__.py
|
|
197
|
+
├── main.py
|
|
198
|
+
├── alembic.ini
|
|
199
|
+
├── core
|
|
200
|
+
│ ├── __init__.py
|
|
201
|
+
│ ├── celery_app.py
|
|
202
|
+
│ ├── config.py
|
|
203
|
+
│ └── db.py
|
|
204
|
+
└── migrations
|
|
205
|
+
├── README
|
|
206
|
+
├── env.py
|
|
207
|
+
├── script.py.mako
|
|
208
|
+
└── versions
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Existing files are skipped. Use `--dest PATH` to target another directory and
|
|
212
|
+
use `--force` only when you intentionally want to overwrite matching files:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
kantu-auth init --dest ./my-api
|
|
216
|
+
kantu-auth init --dest ./my-api --force
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Then configure and start the generated app:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
cp .env.example .env
|
|
223
|
+
# Replace JWT_SECRET_KEY and review every enabled feature.
|
|
224
|
+
|
|
225
|
+
alembic -c app/alembic.ini revision --autogenerate -m "init auth"
|
|
226
|
+
alembic -c app/alembic.ini upgrade head
|
|
227
|
+
uvicorn app.main:app --reload
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The generated `.env` is read by the generated host `AppSettings`, so it uses
|
|
231
|
+
ordinary names such as `DATABASE_URL` and `JWT_SECRET_KEY`. The host validates
|
|
232
|
+
them and passes the relevant values to Kantu Auth.
|
|
233
|
+
|
|
234
|
+
See [CLI and scaffold](docs/cli.md) for the generated-file contract and upgrade
|
|
235
|
+
guidance.
|
|
236
|
+
|
|
237
|
+
## Integrate without the scaffold
|
|
238
|
+
|
|
239
|
+
Kantu Auth does not create or own a global database engine. Build your engine
|
|
240
|
+
and session factory, configure the package, and inject the factory during app
|
|
241
|
+
startup:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
import os
|
|
245
|
+
from contextlib import asynccontextmanager
|
|
246
|
+
|
|
247
|
+
from fastapi import FastAPI
|
|
248
|
+
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
|
249
|
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
250
|
+
|
|
251
|
+
from fastapi_auth import configure, get_auth_router, set_session_factory
|
|
252
|
+
|
|
253
|
+
engine = create_async_engine("sqlite+aiosqlite:///./kantu.db")
|
|
254
|
+
session_factory = async_sessionmaker(
|
|
255
|
+
engine,
|
|
256
|
+
class_=AsyncSession,
|
|
257
|
+
expire_on_commit=False,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
configure(
|
|
261
|
+
APP_NAME="My API",
|
|
262
|
+
JWT_SECRET_KEY=os.environ["JWT_SECRET_KEY"],
|
|
263
|
+
FRONTEND_URL="http://localhost:3000",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@asynccontextmanager
|
|
268
|
+
async def lifespan(app: FastAPI):
|
|
269
|
+
set_session_factory(session_factory)
|
|
270
|
+
try:
|
|
271
|
+
yield
|
|
272
|
+
finally:
|
|
273
|
+
from fastapi_auth.redis_client import close_redis_client
|
|
274
|
+
|
|
275
|
+
await close_redis_client()
|
|
276
|
+
await engine.dispose()
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
app = FastAPI(lifespan=lifespan)
|
|
280
|
+
app.include_router(get_auth_router(), prefix="/api/v1")
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Use Alembic migrations in deployed environments. Import the models in the
|
|
284
|
+
Alembic environment before assigning `SQLModel.metadata` so autogeneration can
|
|
285
|
+
see every auth table; the generated scaffold already does this.
|
|
286
|
+
|
|
287
|
+
### Direct environment configuration
|
|
288
|
+
|
|
289
|
+
If you do not call `configure()`, Kantu Auth reads its own values lazily from
|
|
290
|
+
the environment with a `KANTU_AUTH_` prefix:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
export KANTU_AUTH_JWT_SECRET_KEY="$(openssl rand -hex 32)"
|
|
294
|
+
KANTU_AUTH_FRONTEND_URL=https://app.example.com
|
|
295
|
+
KANTU_AUTH_REDIS_URL=redis://localhost:6379/0
|
|
296
|
+
KANTU_AUTH_ENABLE_TWO_FACTOR=true
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Programmatic `configure()` rejects unknown names, which catches misspelled
|
|
300
|
+
security settings. See [configuration](docs/configuration.md) for every setting,
|
|
301
|
+
unit, dependency, and feature prerequisite.
|
|
302
|
+
|
|
303
|
+
## Authentication flow
|
|
304
|
+
|
|
305
|
+
1. Register with `POST /auth/register`.
|
|
306
|
+
2. If email verification is enabled, confirm the one-time link before login.
|
|
307
|
+
3. Log in with `POST /auth/login`.
|
|
308
|
+
4. If 2FA is disabled, the response contains access and refresh tokens. If 2FA
|
|
309
|
+
is enabled, it contains a short-lived challenge for
|
|
310
|
+
`POST /auth/two-factor/verify`.
|
|
311
|
+
5. Send the access token as `Authorization: Bearer <token>`.
|
|
312
|
+
6. Exchange the refresh token at `POST /auth/refresh`. Successful refreshes
|
|
313
|
+
rotate the token; replaying an older token revokes the session.
|
|
314
|
+
|
|
315
|
+
Registration deliberately returns the same `201` response for a new or an
|
|
316
|
+
existing email address. Do not make your frontend replace that neutral message
|
|
317
|
+
with an account-existence error.
|
|
318
|
+
|
|
319
|
+
All documented request bodies reject unknown fields. Passwords must be 8–128
|
|
320
|
+
characters and satisfy the rules implemented in `fastapi_auth.validators`.
|
|
321
|
+
|
|
322
|
+
The exact route catalogue and optional-router requirements are in
|
|
323
|
+
[routes](docs/routes.md).
|
|
324
|
+
|
|
325
|
+
### Protect a host-application route with recent authentication
|
|
326
|
+
|
|
327
|
+
Kantu Auth exports its sensitive-action dependency for routes owned by your API:
|
|
328
|
+
|
|
329
|
+
```python
|
|
330
|
+
from typing import Annotated
|
|
331
|
+
|
|
332
|
+
from fastapi import Depends
|
|
333
|
+
from fastapi_auth import require_sensitive_auth
|
|
334
|
+
from fastapi_auth.models import User
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
@app.delete("/billing/account")
|
|
338
|
+
async def delete_billing_account(
|
|
339
|
+
user: Annotated[User, Depends(require_sensitive_auth)],
|
|
340
|
+
):
|
|
341
|
+
return {"deleted_for": user.id}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Enrolled users must first call `POST /auth/two-factor/validate`. Unenrolled
|
|
345
|
+
users prove their current password at `POST /auth/reauth/password`, while
|
|
346
|
+
Google-only users use `GET /auth/google/reauth/request`. Every proof is bound
|
|
347
|
+
to the current session and expires quickly. Redis failures and contradictory
|
|
348
|
+
disabled-feature configuration fail closed with `503`.
|
|
349
|
+
|
|
350
|
+
The separate `require_two_factor` dependency remains available when a route
|
|
351
|
+
should let unenrolled users pass without a recent primary-credential proof.
|
|
352
|
+
|
|
353
|
+
## Optional features
|
|
354
|
+
|
|
355
|
+
Optional routers are disabled by default and must be both installed and
|
|
356
|
+
configured:
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
import os
|
|
360
|
+
|
|
361
|
+
configure(
|
|
362
|
+
JWT_SECRET_KEY=os.environ["JWT_SECRET_KEY"],
|
|
363
|
+
REDIS_URL="redis://localhost:6379/0",
|
|
364
|
+
ENABLE_TWO_FACTOR=True,
|
|
365
|
+
ENABLE_EMAIL_VERIFICATION=True,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
app.include_router(get_auth_router())
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Two-factor authentication, rate limiting, and Google OAuth rely on Redis for
|
|
372
|
+
security state. Google OAuth also requires its client ID, client secret, and
|
|
373
|
+
redirect URI. Turnstile requires its secret. Invalid feature combinations fail
|
|
374
|
+
during settings validation or router construction rather than silently running
|
|
375
|
+
partially configured.
|
|
376
|
+
|
|
377
|
+
TOTP and recovery-code 2FA come from the `two-factor` extra. The optional
|
|
378
|
+
`/auth/two-factor/send-otp` email path also needs the `email` extra, a sender,
|
|
379
|
+
and working SMTP or a custom adapter.
|
|
380
|
+
|
|
381
|
+
### Google login, linking, and password setup
|
|
382
|
+
|
|
383
|
+
Install `kantu-auth[google]`, enable Google, configure email delivery, and set
|
|
384
|
+
separate login and link callback URIs. Begin login with
|
|
385
|
+
`POST /auth/google/login`, send the user to the returned URL, then exchange the
|
|
386
|
+
callback's short `google_token` exactly once at `POST /auth/google/session`.
|
|
387
|
+
|
|
388
|
+
Kantu Auth never links accounts merely because Google returns the same email.
|
|
389
|
+
An existing user signs in locally and starts `GET /auth/google/link/request`
|
|
390
|
+
with a Bearer token after satisfying the appropriate session-bound recent-auth
|
|
391
|
+
proof. A linked identity cannot be silently replaced. Link state is bound to
|
|
392
|
+
that active session, and concurrent callbacks cannot replace the first identity
|
|
393
|
+
to complete.
|
|
394
|
+
|
|
395
|
+
A passwordless Google user can request password setup only for their own
|
|
396
|
+
authenticated account:
|
|
397
|
+
|
|
398
|
+
```http
|
|
399
|
+
POST /auth/google/password/request
|
|
400
|
+
Authorization: Bearer <access-token>
|
|
401
|
+
Content-Type: application/json
|
|
402
|
+
|
|
403
|
+
{}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
There is no target email field. The route is separately rate-limited and uses
|
|
407
|
+
the authenticated account's email. If delivery or queueing fails, the setup
|
|
408
|
+
token is invalidated and the route returns `503`. Unlinking Google requires a
|
|
409
|
+
local password, revokes every session, and sends a security notification.
|
|
410
|
+
|
|
411
|
+
### Reverse proxies
|
|
412
|
+
|
|
413
|
+
Do not trust `X-Forwarded-For` from the public internet. Either leave
|
|
414
|
+
`TRUST_PROXY_HEADERS=false` and configure Uvicorn's trusted forwarding peers,
|
|
415
|
+
or set `TRUST_PROXY_HEADERS=true`, list exact `TRUSTED_PROXY_CIDRS`, and run
|
|
416
|
+
Uvicorn with `--no-proxy-headers` so Kantu Auth can verify the socket peer.
|
|
417
|
+
See [configuration](docs/configuration.md#reverse-proxies-and-client-ips).
|
|
418
|
+
|
|
419
|
+
## Email delivery
|
|
420
|
+
|
|
421
|
+
For SMTP delivery, install the email extra and configure a sender:
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
pip install "kantu-auth[email]"
|
|
425
|
+
|
|
426
|
+
SMTP_HOST=smtp.example.com
|
|
427
|
+
SMTP_PORT=587
|
|
428
|
+
SMTP_USERNAME=mailer
|
|
429
|
+
SMTP_PASSWORD=replace-me
|
|
430
|
+
SMTP_FROM_EMAIL=no-reply@example.com
|
|
431
|
+
SMTP_FROM_NAME=My API
|
|
432
|
+
SMTP_START_TLS=true
|
|
433
|
+
SMTP_USE_TLS=false
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Without a Celery broker, sending is awaited inline. With
|
|
437
|
+
`CELERY_BROKER_URL`, messages are queued through the stable
|
|
438
|
+
`fastapi_auth.send_email` task. Custom transports can implement the small
|
|
439
|
+
`EmailAdapter` protocol; built-in templates can be selectively overridden.
|
|
440
|
+
|
|
441
|
+
See [email adapters and templates](docs/email-adapters.md) for examples and
|
|
442
|
+
worker setup.
|
|
443
|
+
|
|
444
|
+
## Documentation
|
|
445
|
+
|
|
446
|
+
- [CLI and generated scaffold](docs/cli.md)
|
|
447
|
+
- [Configuration reference](docs/configuration.md)
|
|
448
|
+
- [Routes and authentication flows](docs/routes.md)
|
|
449
|
+
- [Email adapters and templates](docs/email-adapters.md)
|
|
450
|
+
- [Security and production checklist](docs/security.md)
|
|
451
|
+
|
|
452
|
+
## Development
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
pip install -e ".[dev]"
|
|
456
|
+
pytest
|
|
457
|
+
python -m compileall -q src app
|
|
458
|
+
python -m build
|
|
459
|
+
python -m twine check dist/*
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
When changing a route or setting, update its focused document and tests in the
|
|
463
|
+
same change. Do not commit `.env`, `.env.local`, database dumps, or generated
|
|
464
|
+
tokens.
|
|
465
|
+
|
|
466
|
+
## License
|
|
467
|
+
|
|
468
|
+
Kantu Auth is available under the MIT License. See [LICENSE](LICENSE).
|