fastapi-startkit-auth 0.3.0__tar.gz → 0.6.2__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 (108) hide show
  1. fastapi_startkit_auth-0.6.2/CHANGELOG.md +147 -0
  2. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/PKG-INFO +125 -17
  3. fastapi_startkit_auth-0.3.0/readme.md → fastapi_startkit_auth-0.6.2/README.md +118 -12
  4. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/pyproject.toml +14 -7
  5. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/__init__.py +103 -0
  6. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/apitokens/__init__.py +0 -2
  7. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/apitokens/manager.py +83 -20
  8. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/apitokens/orm.py +96 -0
  9. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/clients/models.py +4 -0
  10. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/clients/repository.py +2 -0
  11. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/concurrency.py +48 -0
  12. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/config.py +12 -6
  13. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/dependencies.py +8 -7
  14. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/facade.py +81 -5
  15. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/grants/__init__.py +17 -0
  16. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/grants/authorization_code.py +164 -0
  17. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/grants/client_credentials.py +11 -0
  18. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/grants/password.py +67 -0
  19. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/grants/refresh.py +70 -0
  20. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/guards/__init__.py +14 -0
  21. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/guards/guard.py +40 -13
  22. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/guards/session.py +132 -0
  23. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/guards/token.py +91 -0
  24. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/manager.py +153 -67
  25. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/middleware/session.py +5 -8
  26. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/orm.py +105 -0
  27. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/passwords/__init__.py +4 -0
  28. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/passwords/broker.py +131 -0
  29. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/provider.py +17 -1
  30. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/providers/base.py +118 -0
  31. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/providers/fields.py +35 -0
  32. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/providers/memory.py +9 -1
  33. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/providers/model.py +148 -0
  34. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000001_create_sessions_table.py +17 -0
  35. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000002_create_personal_api_tokens_table.py +18 -0
  36. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000003_create_oauth_access_tokens_table.py +20 -0
  37. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000004_create_oauth_refresh_tokens_table.py +18 -0
  38. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000005_create_oauth_auth_codes_table.py +17 -0
  39. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/routes.py +34 -28
  40. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/routes_spa.py +4 -2
  41. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/sessions/__init__.py +1 -2
  42. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/sessions/orm.py +102 -0
  43. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/startkit.py +3 -0
  44. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/tokens/orm.py +212 -0
  45. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/tokens/service.py +316 -0
  46. fastapi_startkit_auth-0.6.2/tests/conftest.py +166 -0
  47. fastapi_startkit_auth-0.6.2/tests/test_api_tokens.py +464 -0
  48. fastapi_startkit_auth-0.6.2/tests/test_async_auth.py +443 -0
  49. fastapi_startkit_auth-0.6.2/tests/test_async_safety.py +496 -0
  50. fastapi_startkit_auth-0.6.2/tests/test_async_stores.py +202 -0
  51. fastapi_startkit_auth-0.6.2/tests/test_clients.py +58 -0
  52. fastapi_startkit_auth-0.6.2/tests/test_grants.py +130 -0
  53. fastapi_startkit_auth-0.6.2/tests/test_guard_registry.py +71 -0
  54. fastapi_startkit_auth-0.6.2/tests/test_hashing.py +34 -0
  55. fastapi_startkit_auth-0.6.2/tests/test_integration.py +214 -0
  56. fastapi_startkit_auth-0.6.2/tests/test_jwt.py +57 -0
  57. fastapi_startkit_auth-0.6.2/tests/test_manager.py +75 -0
  58. fastapi_startkit_auth-0.6.2/tests/test_model_provider.py +69 -0
  59. fastapi_startkit_auth-0.6.2/tests/test_model_provider_postgres.py +74 -0
  60. fastapi_startkit_auth-0.6.2/tests/test_multi_provider.py +231 -0
  61. fastapi_startkit_auth-0.6.2/tests/test_password_reset.py +88 -0
  62. fastapi_startkit_auth-0.6.2/tests/test_reset_throttle_enumeration.py +43 -0
  63. fastapi_startkit_auth-0.6.2/tests/test_security_fixes.py +159 -0
  64. fastapi_startkit_auth-0.6.2/tests/test_session_auth.py +352 -0
  65. fastapi_startkit_auth-0.6.2/tests/test_session_store.py +132 -0
  66. fastapi_startkit_auth-0.6.2/tests/test_spa_csrf.py +320 -0
  67. fastapi_startkit_auth-0.6.2/tests/test_startkit_provider.py +131 -0
  68. fastapi_startkit_auth-0.6.2/tests/test_sync_regression.py +202 -0
  69. fastapi_startkit_auth-0.6.2/tests/test_token_repository.py +56 -0
  70. fastapi_startkit_auth-0.6.2/tests/test_token_service.py +110 -0
  71. fastapi_startkit_auth-0.6.2/tests/test_user_provider.py +76 -0
  72. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/__init__.py +0 -67
  73. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/apitokens/sql.py +0 -152
  74. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/grants/__init__.py +0 -13
  75. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/grants/authorization_code.py +0 -83
  76. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/grants/password.py +0 -32
  77. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/grants/refresh.py +0 -13
  78. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/guards/__init__.py +0 -5
  79. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/guards/session.py +0 -81
  80. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/guards/token.py +0 -58
  81. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/passwords/__init__.py +0 -4
  82. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/passwords/broker.py +0 -69
  83. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/providers/base.py +0 -38
  84. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/providers/model.py +0 -70
  85. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/sessions/sql.py +0 -158
  86. fastapi_startkit_auth-0.3.0/src/fastapi_startkit_auth/tokens/service.py +0 -189
  87. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/LICENSE +0 -0
  88. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/apitokens/models.py +0 -0
  89. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/apitokens/repository.py +0 -0
  90. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/application.py +0 -0
  91. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/clients/__init__.py +0 -0
  92. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/exceptions.py +0 -0
  93. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/grants/pkce.py +0 -0
  94. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/middleware/__init__.py +0 -0
  95. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/middleware/csrf.py +0 -0
  96. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/passwords/repository.py +0 -0
  97. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/providers/__init__.py +0 -0
  98. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/publishable/cors.py +0 -0
  99. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/py.typed +0 -0
  100. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/security/__init__.py +0 -0
  101. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/security/hashing.py +0 -0
  102. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/security/jwt.py +0 -0
  103. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/sessions/models.py +0 -0
  104. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/sessions/state.py +0 -0
  105. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/sessions/store.py +0 -0
  106. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/tokens/__init__.py +0 -0
  107. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/tokens/models.py +0 -0
  108. {fastapi_startkit_auth-0.3.0 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/tokens/repository.py +0 -0
@@ -0,0 +1,147 @@
1
+ # Changelog
2
+
3
+ All notable changes to `fastapi-startkit-auth` are documented in this file.
4
+
5
+ Release notes are also published on
6
+ [GitHub Releases](https://github.com/fastapi-startkit/fastapi-startkit-auth/releases).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Changed
11
+
12
+ - **Breaking:** persistence now goes through the fastapi-startkit ORM with models
13
+ shipped in `fastapi_startkit_auth.orm`; no raw SQL remains. Use
14
+ `{"store": "orm"}` (optional ORM `connection` name) for sessions, API tokens and
15
+ OAuth tokens.
16
+ - **Breaking:** the `startkit` extra now requires `fastapi-startkit[database]>=0.60`.
17
+ - Run the published migrations (`provider:publish -p auth`, then `migrate`) to
18
+ create the tables; stores no longer create tables themselves.
19
+
20
+ ### Removed
21
+
22
+ - **Breaking:** the `sql` / `async_sql` stores and the `SqlSessionStore`,
23
+ `SqlApiTokenRepository`, `AsyncSql*` classes and database adapters.
24
+
25
+ ### Added
26
+
27
+ - `Client.provider` (and `register(provider=...)`) binds an OAuth client to a
28
+ user provider. `AuthManager.owner_provider(client_id)` resolves it, falling
29
+ back to the default guard's provider.
30
+ - `AuthManager.warm_up()` and `AsyncModelUserProvider.warm_up()`, run from the
31
+ app lifespan by `AuthProvider`, precompute the dummy hash at startup.
32
+
33
+ ### Fixed
34
+
35
+ - Refresh, authorization-code exchange and introspection re-check the token
36
+ owner against the provider of the issuing client instead of always using the
37
+ default guard's provider. The password grant authenticates against the
38
+ client's provider, and `/oauth/authorize` rejects a client bound to another
39
+ provider.
40
+ - `AsyncModelUserProvider.is_active` only uses the threadpool for a sync
41
+ callable hook. Attribute checks and `async def` hooks run inline.
42
+ - The first unknown-user login no longer pays an extra bcrypt hash to create
43
+ the dummy hash.
44
+
45
+ ## [0.4.0]
46
+
47
+ ### Added
48
+
49
+ - Async support for async ORMs and drivers, side by side with the sync API:
50
+ - `AsyncModelUserProvider` (provider driver `"async_model"`) for models whose
51
+ `find` / `where(...).first()` / `save` are coroutines; bcrypt runs in a
52
+ worker thread.
53
+ - `AsyncPassportGuard`, `AsyncSessionGuard`, `AsyncTokenGuard`,
54
+ `AsyncTokenService`, `AsyncApiTokenManager`, `AsyncPasswordBroker`, the
55
+ `Async*Grant` variants and the `AsyncAuth` facade.
56
+ - `AuthManager` picks the async variant automatically when any provider
57
+ method or store method is async; sync configurations keep getting the sync
58
+ classes. Sync collaborators used from the async classes run in the
59
+ threadpool.
60
+ - `"async_sql"` stores for sessions (`AsyncSqlSessionStore`), personal API
61
+ tokens (`AsyncSqlApiTokenRepository`) and a new `AuthConfig.tokens` block
62
+ for OAuth access/refresh tokens and authorization codes
63
+ (`AsyncSqlTokenRepository`). They accept an asyncpg pool/connection, an
64
+ aiosqlite connection, or a zero-arg (async) factory. Supported backends are
65
+ PostgreSQL and SQLite. Refresh tokens and authorization codes are stored as
66
+ SHA-256 hashes; refresh revocation and code redemption are atomic and use
67
+ only portable SQL. Expired rows are purged via `expires_at` indexes.
68
+ - `fastapi-startkit` migrations for the five tables, published to
69
+ `databases/migrations/` by `AuthServiceProvider`.
70
+ - `password_key` provider option: the credentials key holding the plaintext
71
+ password, separate from the model column `password_field` (e.g.
72
+ `password_key="password"`, `password_field="hashed_password"`). Defaults to
73
+ `password_field`.
74
+ - `is_active` provider option (attribute name or callable; `async def` with
75
+ the `async_model` driver). Inactive or deleted users are rejected by the
76
+ password grant, the refresh and authorization-code grants, `Auth.attempt` /
77
+ `AsyncAuth.attempt` and every guard, and introspect as inactive.
78
+ - `AsyncMisconfiguration`: raised when a sync guard, grant, broker or `Auth`
79
+ receives an awaitable from a provider or store, so a half-async provider
80
+ fails closed. Sync providers reject an `async def` `is_active` hook.
81
+ - CI runs the asyncpg store and flow tests against a Postgres service.
82
+ - `Typing :: Typed` classifier (the package already ships `py.typed`).
83
+
84
+ ### Changed
85
+
86
+ - **Breaking (signature):** `current_context` and `optional_user` are now
87
+ `async def` dependencies. `Depends(current_user)` and friends keep working
88
+ unchanged; code calling them directly as plain functions must now await
89
+ them. Sync guards still run in the threadpool.
90
+ - The built-in OAuth, password-reset and SPA csrf-cookie routes await async
91
+ collaborators; `SessionMiddleware` accepts an async session store.
92
+ - `Auth` (sync facade) raises `RuntimeError` for async session guards; use
93
+ `AsyncAuth` there.
94
+ - Minimum supported Python is now 3.10. 0.3.0 advertised `>=3.9` but fails on
95
+ 3.9 because runtime-evaluated annotations use `X | None` syntax.
96
+ - The `startkit` extra is bounded to `fastapi-startkit>=0.51,<1.0`.
97
+ - The package `__init__` imports its public API directly instead of through a
98
+ lazy export table.
99
+ - The source distribution now ships the test suite and this changelog.
100
+ - `bin/release.sh` uploads to PyPI with `twine upload`, matching
101
+ fastapi-startkit; the tag-triggered trusted-publishing workflow is removed.
102
+
103
+ ## [0.3.0]
104
+
105
+ First release published to PyPI. Contains the 0.2.0 feature set, built with
106
+ the `uv_build` backend; no functional changes.
107
+
108
+ ## [0.2.0]
109
+
110
+ Adds three new authentication modes alongside the existing OAuth2/JWT server,
111
+ so the package now covers the full Laravel-style auth surface.
112
+
113
+ ### Added
114
+
115
+ - Guard-driver registry in `AuthManager`: guards resolve their driver
116
+ (`token`, `session`, …) from a pluggable registry, enabling multiple guard
117
+ types in one application (Phase 0).
118
+ - Cookie/session authentication: `Auth` facade, `SessionGuard`, session
119
+ middleware, and in-memory + SQL session stores (Phase 1).
120
+ - SPA authentication with CSRF protection: `/__auth__/csrf-cookie` endpoint,
121
+ double-submit-cookie middleware, and an `AuthServiceProvider` that publishes
122
+ the `config/cors.py` stub via `provider:publish -p auth` (Phase 2).
123
+ - Sanctum-style API tokens: `TokenGuard`, opaque hashed tokens with abilities,
124
+ and in-memory + SQL token stores (Phase 3).
125
+
126
+ ### Security / Hardening
127
+
128
+ - Session guard verifies against a constant-time dummy hash when no user is
129
+ found, closing a timing side-channel.
130
+ - SQL session and API-token stores prune expired rows; purge cadence is
131
+ configurable.
132
+ - CSRF cookie endpoint hardened; token repr for `NewApiToken` redacts the
133
+ plaintext value so it cannot leak into logs.
134
+ - DB-API connection vs. connection-factory detection now probes `execute()`
135
+ instead of `callable()`, fixing misdetection of raw `sqlite3.Connection`
136
+ objects in the SQL session and API-token stores.
137
+
138
+ ## [0.1.0]
139
+
140
+ Initial release.
141
+
142
+ - Passport-style OAuth2 authorization server for FastAPI: authorization code
143
+ (with PKCE), password, client credentials, and refresh token grants
144
+ - JWT access tokens with scopes, token guards, and route dependencies
145
+ - Personal access tokens
146
+ - Pluggable user/client providers (in-memory and Masonite ORM)
147
+ - Password reset flow with throttling hardened against account enumeration
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-startkit-auth
3
- Version: 0.3.0
3
+ Version: 0.6.2
4
4
  Summary: Laravel-style authentication for FastAPI: OAuth2/JWT authorization server, cookie/session guard, SPA + CSRF protection, and Sanctum-style API tokens.
5
5
  Keywords: fastapi,oauth2,jwt,auth,passport,sanctum,authentication,authorization,security,tokens,session,cookie,csrf,spa,starter-kit
6
6
  Author: Bedram Tamang
@@ -12,7 +12,6 @@ Classifier: Framework :: FastAPI
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.9
16
15
  Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
@@ -20,21 +19,24 @@ Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Topic :: Internet :: WWW/HTTP :: Session
21
20
  Classifier: Topic :: Security
22
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
23
  Requires-Dist: fastapi>=0.110
24
24
  Requires-Dist: pyjwt>=2.8
25
25
  Requires-Dist: bcrypt>=4.1
26
26
  Requires-Dist: pydantic>=2
27
27
  Requires-Dist: python-multipart>=0.0.9
28
28
  Requires-Dist: masonite-orm>=2.18 ; extra == 'masoniteorm'
29
- Requires-Dist: fastapi-startkit>=0.51 ; python_full_version >= '3.12' and extra == 'startkit'
29
+ Requires-Dist: fastapi-startkit[database]>=0.60,<1.0 ; python_full_version >= '3.12' and extra == 'startkit'
30
30
  Requires-Dist: pytest>=8 ; extra == 'test'
31
31
  Requires-Dist: pytest-asyncio>=0.23 ; extra == 'test'
32
32
  Requires-Dist: httpx>=0.27 ; extra == 'test'
33
- Requires-Python: >=3.9
33
+ Requires-Dist: aiosqlite>=0.20 ; extra == 'test'
34
+ Requires-Dist: asyncpg>=0.29 ; extra == 'test'
35
+ Requires-Python: >=3.10
34
36
  Project-URL: Homepage, https://github.com/fastapi-startkit/fastapi-startkit-auth
35
37
  Project-URL: Repository, https://github.com/fastapi-startkit/fastapi-startkit-auth
36
38
  Project-URL: Issues, https://github.com/fastapi-startkit/fastapi-startkit-auth/issues
37
- Project-URL: Changelog, https://github.com/fastapi-startkit/fastapi-startkit-auth/releases
39
+ Project-URL: Changelog, https://github.com/fastapi-startkit/fastapi-startkit-auth/blob/main/CHANGELOG.md
38
40
  Provides-Extra: masoniteorm
39
41
  Provides-Extra: startkit
40
42
  Provides-Extra: test
@@ -67,12 +69,29 @@ OAuth2 grants and signed JWT access tokens (per the
67
69
 
68
70
  ## Installation
69
71
 
72
+ Requires Python 3.10+.
73
+
70
74
  ```bash
71
75
  pip install fastapi-startkit-auth
72
- # optional ORM provider driver
76
+ # or
77
+ uv add fastapi-startkit-auth
78
+ ```
79
+
80
+ Optional extras:
81
+
82
+ | Extra | Installs | Use when |
83
+ | --- | --- | --- |
84
+ | `startkit` | `fastapi-startkit[database]>=0.60,<1.0` (Python 3.12+) | Registering `AuthServiceProvider`, publishing its config stub and migrations via `provider:publish -p auth`, and the `"orm"` stores |
85
+ | `masoniteorm` | `masonite-orm` | Using the `masoniteorm` user provider driver |
86
+
87
+ ```bash
88
+ pip install "fastapi-startkit-auth[startkit]"
73
89
  pip install "fastapi-startkit-auth[masoniteorm]"
74
90
  ```
75
91
 
92
+ The `startkit` and `masoniteorm` extras are mutually exclusive: `masonite-orm`
93
+ pins `cleo<2` while `fastapi-startkit` requires `cleo>=2.1`.
94
+
76
95
  > Uses `bcrypt` directly (not `passlib`, which imports the `crypt` stdlib module
77
96
  > removed in Python 3.13+), so it runs on modern Python.
78
97
 
@@ -144,13 +163,94 @@ class AuthConfig:
144
163
  | driver | meaning |
145
164
  | --- | --- |
146
165
  | `masoniteorm` / `orm` / `model` | wrap a model exposing `find(id)` and `where(field, value).first()` |
166
+ | `async_model` | same, for async ORMs where `find`, `first()` and `save()` are coroutines |
147
167
  | `memory` | in-memory dict store (`users=[...]`) — great for tests/demos |
148
168
  | `instance` | pass a ready `UserProvider` via `{"instance": ...}` |
149
169
  | `factory` | pass a zero-arg callable returning a `UserProvider` |
150
170
 
151
171
  Any object implementing the `UserProvider` protocol
152
172
  (`retrieve_by_id`, `retrieve_by_credentials`, `validate_credentials`,
153
- `get_identifier`, `update_password`) is a valid provider.
173
+ `get_identifier`, `update_password`) is a valid provider. Any of its methods
174
+ may be `async def`; one async method is enough for `AuthManager` to pick the
175
+ async guards, grants and broker. The sync classes refuse an awaitable result
176
+ with `AsyncMisconfiguration` instead of treating it as truthy.
177
+
178
+ Model-backed providers also accept `password_key` (the credentials key holding
179
+ the plaintext password, default: `password_field`) and `is_active` (a boolean
180
+ attribute name or a `callable(user) -> bool`; inactive users cannot log in,
181
+ authenticate, refresh or exchange a code, and introspect as inactive). The
182
+ `async_model` driver also takes an `async def` hook; the sync drivers reject
183
+ one at construction:
184
+
185
+ ```python
186
+ providers = {
187
+ "users": {
188
+ "driver": "async_model",
189
+ "model": User,
190
+ "password_field": "hashed_password",
191
+ "password_key": "password",
192
+ "is_active": "is_active",
193
+ }
194
+ }
195
+ ```
196
+
197
+ ### Multiple providers
198
+
199
+ With more than one provider, register each OAuth client with the provider whose
200
+ users it serves (Laravel Passport's `provider` column). The password grant then
201
+ authenticates against that provider, and refresh, code exchange and
202
+ introspection re-check the token owner against it. A client without a
203
+ `provider` uses the default guard's provider, and `/oauth/authorize` rejects a
204
+ client bound to a different provider with `unauthorized_client`:
205
+
206
+ ```python
207
+ client, secret = manager.client_repository.register(name="admin-panel", provider="admins")
208
+ ```
209
+
210
+ ### ORM stores and migrations
211
+
212
+ With an async provider or store, `AuthManager` builds the async guards, grants,
213
+ token service and password broker automatically (use the `AsyncAuth` facade for
214
+ session login/logout). Sessions, API tokens and OAuth tokens persist through the
215
+ fastapi-startkit ORM (`pip install "fastapi-startkit-auth[startkit]"`, which pulls
216
+ in `fastapi-startkit[database]`). The package ships its own models
217
+ (`fastapi_startkit_auth.orm`) and uses no raw SQL:
218
+
219
+ ```python
220
+ session = {"store": "orm"}
221
+ api_tokens = {"store": "orm"}
222
+ tokens = {"store": "orm", "connection": "auth"} # optional ORM connection name
223
+ ```
224
+
225
+ `connection` names an entry of your database config; omit it to use the default
226
+ connection. Publish and run the migrations once per app (they are reversible
227
+ and create the `sessions`, `personal_api_tokens`, `oauth_access_tokens`,
228
+ `oauth_refresh_tokens` and `oauth_auth_codes` tables with their indexes):
229
+
230
+ ```bash
231
+ python artisan provider:publish -p auth # copies them to databases/migrations/
232
+ python artisan migrate
233
+ python artisan migrate:rollback # drops them again
234
+ ```
235
+
236
+ Single-use guarantees rely on conditional `UPDATE`/`DELETE` statements issued
237
+ through the ORM query builder: refresh-token rotation and authorization-code
238
+ redemption each have exactly one winner under concurrency.
239
+
240
+ The former `sql` and `async_sql` stores were removed; configuring them raises a
241
+ `ValueError` pointing at `"orm"`. Use `"memory"` or an `"instance"` store for
242
+ sync setups.
243
+
244
+ In mixed setups (async stores with a sync provider or a sync session store),
245
+ the sync calls — lookups, bcrypt, `is_active` hooks, a sync session store in
246
+ `SessionMiddleware` — run in the threadpool, never on the event loop.
247
+ An `is_active` attribute name or `async def` hook runs inline, since neither
248
+ blocks.
249
+
250
+ `AuthProvider` warms the providers up when the app starts (`await
251
+ manager.warm_up()`), so `AsyncModelUserProvider` computes its dummy hash off the
252
+ event loop before the first request. A login for an unknown user then costs
253
+ one bcrypt verify, the same as a wrong password.
154
254
 
155
255
  ## HTTP endpoints
156
256
 
@@ -212,15 +312,19 @@ inspect the `AuthContext` (`ctx.can(...)`, `ctx.can_any(...)`, `ctx.scopes`).
212
312
  ## Testing
213
313
 
214
314
  ```bash
215
- pip install -e ".[test]"
216
- pytest
315
+ uv sync --group dev
316
+ uv run pytest
317
+ uv run ruff check .
217
318
  ```
218
319
 
219
- Or with [uv](https://docs.astral.sh/uv/):
320
+ The async store and flow tests run on aiosqlite; set `TEST_ASYNCPG_DSN` to a
321
+ disposable Postgres database to also run them on asyncpg (CI does).
322
+
323
+ Or with pip:
220
324
 
221
325
  ```bash
222
- uv sync --group dev
223
- uv run pytest
326
+ pip install -e ".[test]"
327
+ pytest
224
328
  ```
225
329
 
226
330
  ## Releasing
@@ -232,11 +336,15 @@ Releases are cut from `main` with the release script (maintainers only):
232
336
  ./bin/release.sh minor # or: major
233
337
  ```
234
338
 
235
- The script bumps the version, builds sdist + wheel, validates them with
236
- `twine check`, uploads to PyPI, then commits, tags `vX.Y.Z`, and creates a
237
- GitHub release. It requires `uv`, `gh` (authenticated), and PyPI credentials
238
- for `twine` (e.g. a `pypi-*` API token via `TWINE_USERNAME=__token__` /
239
- `TWINE_PASSWORD` or `~/.pypirc`).
339
+ The script bumps the version (`pyproject.toml`, `__version__`, `uv.lock`),
340
+ builds sdist + wheel, validates them with `twine check`, uploads them to PyPI
341
+ with `twine upload`, then commits, tags `vX.Y.Z`, pushes, and creates a GitHub
342
+ release. Move the `Unreleased` notes in `CHANGELOG.md` under the new version
343
+ before running it.
344
+
345
+ It requires `uv`, an authenticated `gh`, and PyPI credentials for twine: either
346
+ a `[pypi]` entry in `~/.pypirc` (`username = __token__`, `password = pypi-...`)
347
+ or `TWINE_USERNAME=__token__` and `TWINE_PASSWORD=pypi-...` in the environment.
240
348
 
241
349
  ## License
242
350
 
@@ -25,12 +25,29 @@ OAuth2 grants and signed JWT access tokens (per the
25
25
 
26
26
  ## Installation
27
27
 
28
+ Requires Python 3.10+.
29
+
28
30
  ```bash
29
31
  pip install fastapi-startkit-auth
30
- # optional ORM provider driver
32
+ # or
33
+ uv add fastapi-startkit-auth
34
+ ```
35
+
36
+ Optional extras:
37
+
38
+ | Extra | Installs | Use when |
39
+ | --- | --- | --- |
40
+ | `startkit` | `fastapi-startkit[database]>=0.60,<1.0` (Python 3.12+) | Registering `AuthServiceProvider`, publishing its config stub and migrations via `provider:publish -p auth`, and the `"orm"` stores |
41
+ | `masoniteorm` | `masonite-orm` | Using the `masoniteorm` user provider driver |
42
+
43
+ ```bash
44
+ pip install "fastapi-startkit-auth[startkit]"
31
45
  pip install "fastapi-startkit-auth[masoniteorm]"
32
46
  ```
33
47
 
48
+ The `startkit` and `masoniteorm` extras are mutually exclusive: `masonite-orm`
49
+ pins `cleo<2` while `fastapi-startkit` requires `cleo>=2.1`.
50
+
34
51
  > Uses `bcrypt` directly (not `passlib`, which imports the `crypt` stdlib module
35
52
  > removed in Python 3.13+), so it runs on modern Python.
36
53
 
@@ -102,13 +119,94 @@ class AuthConfig:
102
119
  | driver | meaning |
103
120
  | --- | --- |
104
121
  | `masoniteorm` / `orm` / `model` | wrap a model exposing `find(id)` and `where(field, value).first()` |
122
+ | `async_model` | same, for async ORMs where `find`, `first()` and `save()` are coroutines |
105
123
  | `memory` | in-memory dict store (`users=[...]`) — great for tests/demos |
106
124
  | `instance` | pass a ready `UserProvider` via `{"instance": ...}` |
107
125
  | `factory` | pass a zero-arg callable returning a `UserProvider` |
108
126
 
109
127
  Any object implementing the `UserProvider` protocol
110
128
  (`retrieve_by_id`, `retrieve_by_credentials`, `validate_credentials`,
111
- `get_identifier`, `update_password`) is a valid provider.
129
+ `get_identifier`, `update_password`) is a valid provider. Any of its methods
130
+ may be `async def`; one async method is enough for `AuthManager` to pick the
131
+ async guards, grants and broker. The sync classes refuse an awaitable result
132
+ with `AsyncMisconfiguration` instead of treating it as truthy.
133
+
134
+ Model-backed providers also accept `password_key` (the credentials key holding
135
+ the plaintext password, default: `password_field`) and `is_active` (a boolean
136
+ attribute name or a `callable(user) -> bool`; inactive users cannot log in,
137
+ authenticate, refresh or exchange a code, and introspect as inactive). The
138
+ `async_model` driver also takes an `async def` hook; the sync drivers reject
139
+ one at construction:
140
+
141
+ ```python
142
+ providers = {
143
+ "users": {
144
+ "driver": "async_model",
145
+ "model": User,
146
+ "password_field": "hashed_password",
147
+ "password_key": "password",
148
+ "is_active": "is_active",
149
+ }
150
+ }
151
+ ```
152
+
153
+ ### Multiple providers
154
+
155
+ With more than one provider, register each OAuth client with the provider whose
156
+ users it serves (Laravel Passport's `provider` column). The password grant then
157
+ authenticates against that provider, and refresh, code exchange and
158
+ introspection re-check the token owner against it. A client without a
159
+ `provider` uses the default guard's provider, and `/oauth/authorize` rejects a
160
+ client bound to a different provider with `unauthorized_client`:
161
+
162
+ ```python
163
+ client, secret = manager.client_repository.register(name="admin-panel", provider="admins")
164
+ ```
165
+
166
+ ### ORM stores and migrations
167
+
168
+ With an async provider or store, `AuthManager` builds the async guards, grants,
169
+ token service and password broker automatically (use the `AsyncAuth` facade for
170
+ session login/logout). Sessions, API tokens and OAuth tokens persist through the
171
+ fastapi-startkit ORM (`pip install "fastapi-startkit-auth[startkit]"`, which pulls
172
+ in `fastapi-startkit[database]`). The package ships its own models
173
+ (`fastapi_startkit_auth.orm`) and uses no raw SQL:
174
+
175
+ ```python
176
+ session = {"store": "orm"}
177
+ api_tokens = {"store": "orm"}
178
+ tokens = {"store": "orm", "connection": "auth"} # optional ORM connection name
179
+ ```
180
+
181
+ `connection` names an entry of your database config; omit it to use the default
182
+ connection. Publish and run the migrations once per app (they are reversible
183
+ and create the `sessions`, `personal_api_tokens`, `oauth_access_tokens`,
184
+ `oauth_refresh_tokens` and `oauth_auth_codes` tables with their indexes):
185
+
186
+ ```bash
187
+ python artisan provider:publish -p auth # copies them to databases/migrations/
188
+ python artisan migrate
189
+ python artisan migrate:rollback # drops them again
190
+ ```
191
+
192
+ Single-use guarantees rely on conditional `UPDATE`/`DELETE` statements issued
193
+ through the ORM query builder: refresh-token rotation and authorization-code
194
+ redemption each have exactly one winner under concurrency.
195
+
196
+ The former `sql` and `async_sql` stores were removed; configuring them raises a
197
+ `ValueError` pointing at `"orm"`. Use `"memory"` or an `"instance"` store for
198
+ sync setups.
199
+
200
+ In mixed setups (async stores with a sync provider or a sync session store),
201
+ the sync calls — lookups, bcrypt, `is_active` hooks, a sync session store in
202
+ `SessionMiddleware` — run in the threadpool, never on the event loop.
203
+ An `is_active` attribute name or `async def` hook runs inline, since neither
204
+ blocks.
205
+
206
+ `AuthProvider` warms the providers up when the app starts (`await
207
+ manager.warm_up()`), so `AsyncModelUserProvider` computes its dummy hash off the
208
+ event loop before the first request. A login for an unknown user then costs
209
+ one bcrypt verify, the same as a wrong password.
112
210
 
113
211
  ## HTTP endpoints
114
212
 
@@ -170,15 +268,19 @@ inspect the `AuthContext` (`ctx.can(...)`, `ctx.can_any(...)`, `ctx.scopes`).
170
268
  ## Testing
171
269
 
172
270
  ```bash
173
- pip install -e ".[test]"
174
- pytest
271
+ uv sync --group dev
272
+ uv run pytest
273
+ uv run ruff check .
175
274
  ```
176
275
 
177
- Or with [uv](https://docs.astral.sh/uv/):
276
+ The async store and flow tests run on aiosqlite; set `TEST_ASYNCPG_DSN` to a
277
+ disposable Postgres database to also run them on asyncpg (CI does).
278
+
279
+ Or with pip:
178
280
 
179
281
  ```bash
180
- uv sync --group dev
181
- uv run pytest
282
+ pip install -e ".[test]"
283
+ pytest
182
284
  ```
183
285
 
184
286
  ## Releasing
@@ -190,11 +292,15 @@ Releases are cut from `main` with the release script (maintainers only):
190
292
  ./bin/release.sh minor # or: major
191
293
  ```
192
294
 
193
- The script bumps the version, builds sdist + wheel, validates them with
194
- `twine check`, uploads to PyPI, then commits, tags `vX.Y.Z`, and creates a
195
- GitHub release. It requires `uv`, `gh` (authenticated), and PyPI credentials
196
- for `twine` (e.g. a `pypi-*` API token via `TWINE_USERNAME=__token__` /
197
- `TWINE_PASSWORD` or `~/.pypirc`).
295
+ The script bumps the version (`pyproject.toml`, `__version__`, `uv.lock`),
296
+ builds sdist + wheel, validates them with `twine check`, uploads them to PyPI
297
+ with `twine upload`, then commits, tags `vX.Y.Z`, pushes, and creates a GitHub
298
+ release. Move the `Unreleased` notes in `CHANGELOG.md` under the new version
299
+ before running it.
300
+
301
+ It requires `uv`, an authenticated `gh`, and PyPI credentials for twine: either
302
+ a `[pypi]` entry in `~/.pypirc` (`username = __token__`, `password = pypi-...`)
303
+ or `TWINE_USERNAME=__token__` and `TWINE_PASSWORD=pypi-...` in the environment.
198
304
 
199
305
  ## License
200
306
 
@@ -1,8 +1,8 @@
1
1
  [project]
2
2
  name = "fastapi-startkit-auth"
3
- version = "0.3.0"
3
+ version = "0.6.2"
4
4
  description = "Laravel-style authentication for FastAPI: OAuth2/JWT authorization server, cookie/session guard, SPA + CSRF protection, and Sanctum-style API tokens."
5
- readme = "readme.md"
5
+ readme = "README.md"
6
6
  license = "MIT"
7
7
  license-files = ["LICENSE"]
8
8
  keywords = [
@@ -25,14 +25,13 @@ keywords = [
25
25
  authors = [
26
26
  {name = "Bedram Tamang", email = "tmgbedu@gmail.com"}
27
27
  ]
28
- requires-python = ">=3.9"
28
+ requires-python = ">=3.10"
29
29
  classifiers = [
30
30
  "Development Status :: 4 - Beta",
31
31
  "Framework :: FastAPI",
32
32
  "Intended Audience :: Developers",
33
33
  "Operating System :: OS Independent",
34
34
  "Programming Language :: Python :: 3",
35
- "Programming Language :: Python :: 3.9",
36
35
  "Programming Language :: Python :: 3.10",
37
36
  "Programming Language :: Python :: 3.11",
38
37
  "Programming Language :: Python :: 3.12",
@@ -40,6 +39,7 @@ classifiers = [
40
39
  "Topic :: Internet :: WWW/HTTP :: Session",
41
40
  "Topic :: Security",
42
41
  "Topic :: Software Development :: Libraries :: Python Modules",
42
+ "Typing :: Typed",
43
43
  ]
44
44
  dependencies = [
45
45
  "fastapi>=0.110",
@@ -53,17 +53,19 @@ dependencies = [
53
53
  Homepage = "https://github.com/fastapi-startkit/fastapi-startkit-auth"
54
54
  Repository = "https://github.com/fastapi-startkit/fastapi-startkit-auth"
55
55
  Issues = "https://github.com/fastapi-startkit/fastapi-startkit-auth/issues"
56
- Changelog = "https://github.com/fastapi-startkit/fastapi-startkit-auth/releases"
56
+ Changelog = "https://github.com/fastapi-startkit/fastapi-startkit-auth/blob/main/CHANGELOG.md"
57
57
 
58
58
  [project.optional-dependencies]
59
59
  masoniteorm = ["masonite-orm>=2.18"]
60
60
  # The framework itself only supports Python >=3.12; on older interpreters the
61
61
  # extra resolves to nothing and the package stays standalone.
62
- startkit = ["fastapi-startkit>=0.51; python_version >= '3.12'"]
62
+ startkit = ["fastapi-startkit[database]>=0.60,<1.0; python_version >= '3.12'"]
63
63
  test = [
64
64
  "pytest>=8",
65
65
  "pytest-asyncio>=0.23",
66
66
  "httpx>=0.27",
67
+ "aiosqlite>=0.20",
68
+ "asyncpg>=0.29",
67
69
  ]
68
70
 
69
71
  [dependency-groups]
@@ -71,8 +73,10 @@ dev = [
71
73
  "pytest>=8",
72
74
  "pytest-asyncio>=0.23",
73
75
  "httpx>=0.27",
76
+ "aiosqlite>=0.20",
77
+ "asyncpg>=0.29",
74
78
  "ruff>=0.9.0",
75
- "twine>=6.0.0",
79
+ "twine>=6.1.0",
76
80
  ]
77
81
 
78
82
  [tool.uv]
@@ -105,3 +109,6 @@ norecursedirs = [".venv", "dist", "build", "__pycache__"]
105
109
  [build-system]
106
110
  requires = ["uv_build>=0.10.10,<0.11.0"]
107
111
  build-backend = "uv_build"
112
+
113
+ [tool.uv.build-backend]
114
+ source-include = ["CHANGELOG.md", "tests/**"]