fastapi-startkit-auth 0.6.3__tar.gz → 0.8.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.
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/CHANGELOG.md +53 -1
- fastapi_startkit_auth-0.8.0/PKG-INFO +67 -0
- fastapi_startkit_auth-0.8.0/README.md +23 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/pyproject.toml +2 -2
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/__init__.py +50 -24
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/apitokens/manager.py +3 -77
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/commands/oauth2_client.py +2 -2
- fastapi_startkit_auth-0.8.0/src/fastapi_startkit_auth/concurrency.py +22 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/config/__init__.py +6 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/config/auth.py +45 -19
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/dependencies.py +51 -6
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/exceptions.py +41 -16
- fastapi_startkit_auth-0.8.0/src/fastapi_startkit_auth/facade.py +95 -0
- fastapi_startkit_auth-0.8.0/src/fastapi_startkit_auth/grants/__init__.py +13 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/grants/authorization_code.py +10 -101
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/grants/client_credentials.py +3 -21
- fastapi_startkit_auth-0.8.0/src/fastapi_startkit_auth/grants/password.py +56 -0
- fastapi_startkit_auth-0.8.0/src/fastapi_startkit_auth/grants/refresh.py +59 -0
- fastapi_startkit_auth-0.8.0/src/fastapi_startkit_auth/guards/__init__.py +13 -0
- fastapi_startkit_auth-0.8.0/src/fastapi_startkit_auth/guards/chain.py +77 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/guards/guard.py +22 -41
- fastapi_startkit_auth-0.8.0/src/fastapi_startkit_auth/guards/session.py +79 -0
- fastapi_startkit_auth-0.8.0/src/fastapi_startkit_auth/guards/token.py +63 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/manager.py +108 -110
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/middleware/csrf.py +2 -2
- fastapi_startkit_auth-0.8.0/src/fastapi_startkit_auth/passwords/__init__.py +2 -0
- fastapi_startkit_auth-0.8.0/src/fastapi_startkit_auth/passwords/broker.py +70 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/policy.py +12 -12
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/provider.py +4 -4
- fastapi_startkit_auth-0.8.0/src/fastapi_startkit_auth/providers/base.py +51 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/providers/fields.py +1 -8
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/providers/memory.py +12 -10
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/providers/model.py +3 -64
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/routes.py +38 -38
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/routes_password.py +2 -2
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/security/jwt.py +2 -2
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/sessions/facade.py +2 -2
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/tokens/service.py +9 -182
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/conftest.py +2 -2
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_api_tokens.py +87 -87
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_async_auth.py +50 -61
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_async_refresh_race.py +9 -9
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_async_safety.py +93 -100
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_class_config.py +3 -3
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_client_scopes.py +15 -15
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_facade_api.py +13 -13
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_grants.py +43 -43
- fastapi_startkit_auth-0.8.0/tests/test_guard_dependency.py +298 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_integration.py +24 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_jwt.py +7 -7
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_manager.py +17 -17
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_model_provider.py +8 -8
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_model_provider_postgres.py +3 -23
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_multi_provider.py +21 -23
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_oauth21.py +58 -57
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_oauth2_client_command.py +3 -3
- fastapi_startkit_auth-0.8.0/tests/test_oauth_config_aliases.py +29 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_oauth_hardening.py +67 -67
- fastapi_startkit_auth-0.8.0/tests/test_password_reset.py +88 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_resource_binding.py +103 -103
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_security_fixes.py +6 -6
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_session_auth.py +37 -37
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_spa_csrf.py +5 -5
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_startkit_provider.py +3 -3
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_store_names.py +9 -9
- fastapi_startkit_auth-0.8.0/tests/test_token_service.py +114 -0
- fastapi_startkit_auth-0.8.0/tests/test_user_provider.py +76 -0
- fastapi_startkit_auth-0.6.3/PKG-INFO +0 -398
- fastapi_startkit_auth-0.6.3/README.md +0 -354
- fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/concurrency.py +0 -48
- fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/facade.py +0 -200
- fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/grants/__init__.py +0 -17
- fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/grants/password.py +0 -96
- fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/grants/refresh.py +0 -92
- fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/guards/__init__.py +0 -14
- fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/guards/session.py +0 -149
- fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/guards/token.py +0 -112
- fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/passwords/__init__.py +0 -4
- fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/passwords/broker.py +0 -131
- fastapi_startkit_auth-0.6.3/src/fastapi_startkit_auth/providers/base.py +0 -118
- fastapi_startkit_auth-0.6.3/tests/test_password_reset.py +0 -88
- fastapi_startkit_auth-0.6.3/tests/test_sync_regression.py +0 -227
- fastapi_startkit_auth-0.6.3/tests/test_token_service.py +0 -114
- fastapi_startkit_auth-0.6.3/tests/test_user_provider.py +0 -76
- /fastapi_startkit_auth-0.6.3/LICENSE → /fastapi_startkit_auth-0.8.0/LICENSE.md +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/apitokens/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/apitokens/facade.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/apitokens/models.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/apitokens/orm.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/apitokens/repository.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/clients/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/clients/credentials.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/clients/models.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/clients/orm.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/clients/repository.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/commands/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/grants/pkce.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/middleware/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/middleware/context.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/middleware/session.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/orm.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/passwords/repository.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/providers/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/publishable/cors.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000001_create_sessions_table.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000002_create_personal_api_tokens_table.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000003_create_oauth_access_tokens_table.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000004_create_oauth_refresh_tokens_table.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000005_create_oauth_auth_codes_table.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/publishable/migrations/2026_10_02_000001_add_resource_to_oauth_tables.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/publishable/migrations/2026_10_04_000001_create_oauth_clients_table.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/publishable/migrations/2026_10_04_000002_add_family_id_to_oauth_refresh_tokens_table.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/publishable/migrations/2026_10_05_000001_add_scopes_to_oauth_clients_table.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/py.typed +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/request_context.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/routes_spa.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/scoped_method.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/security/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/security/hashing.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/sessions/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/sessions/models.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/sessions/orm.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/sessions/state.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/sessions/store.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/tokens/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/tokens/models.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/tokens/orm.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/tokens/repository.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_async_stores.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_clients.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_guard_registry.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_hashing.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_reset_throttle_enumeration.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_session_store.py +0 -0
- {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/tests/test_token_repository.py +0 -0
|
@@ -11,6 +11,26 @@ Release notes are also published on
|
|
|
11
11
|
|
|
12
12
|
Each item lists what changed and what to do.
|
|
13
13
|
|
|
14
|
+
- **The package is async-only; every sync class is removed.** The sync
|
|
15
|
+
`Auth`, `SessionGuard`, `PassportGuard`, `TokenGuard`, `ChainGuard`,
|
|
16
|
+
`TokenService`, `ApiTokenManager`, `PasswordBroker`, `PasswordGrant`,
|
|
17
|
+
`ClientCredentialsGrant`, `AuthorizationCodeGrant`, `RefreshTokenGrant`,
|
|
18
|
+
`ModelUserProvider` and `UserProvider` are gone, and each `Async*` class now
|
|
19
|
+
carries the plain name (`AsyncAuth` is `Auth`, `AsyncTokenService` is
|
|
20
|
+
`TokenService`, and so on). `user_is_active_async`, `find_user_async`,
|
|
21
|
+
`active_user_async` and `AuthManager.owner_provider_async` lose their
|
|
22
|
+
`_async` suffix; `AuthManager.owner_provider` is now a coroutine.
|
|
23
|
+
`AsyncMisconfiguration`, `ensure_sync`, `is_async_provider`,
|
|
24
|
+
`guard_is_async`, `is_async` and `has_async_methods` are removed, and the
|
|
25
|
+
`"async_model"` provider driver is dropped in favour of `"model"`;
|
|
26
|
+
configuring it raises a `ValueError` naming the replacement. The `"model"`
|
|
27
|
+
driver accepts sync and async models: a sync model's `find`,
|
|
28
|
+
`where(...).first()` and `save()` run in the threadpool.
|
|
29
|
+
`InMemoryUserProvider` methods are coroutines. User-supplied stores,
|
|
30
|
+
listeners and `is_active` hooks may still be sync; they run in the
|
|
31
|
+
threadpool. *Migrate:* import the plain names, `await` every `Auth`, guard,
|
|
32
|
+
provider, broker, grant and token-service call, and change
|
|
33
|
+
`driver="async_model"` to `driver="model"`.
|
|
14
34
|
- **Feature providers replace `Application` and `AuthServiceProvider`.**
|
|
15
35
|
Register `AuthProvider` first, then the features your guards use:
|
|
16
36
|
`AuthSessionProvider(SessionConfig)`, `AuthOAuth2Provider(OAuth2Config)` and
|
|
@@ -66,7 +86,7 @@ Each item lists what changed and what to do.
|
|
|
66
86
|
in `SessionConfig.csrf_exempt_paths`.
|
|
67
87
|
- **OAuth client store defaults to `"database"`** (the ORM `oauth_clients`
|
|
68
88
|
table). *Migrate:* publish and run the migrations, or set
|
|
69
|
-
`OAuth2Config(clients=
|
|
89
|
+
`OAuth2Config(clients=OAuthClientConfig(store="memory"))`.
|
|
70
90
|
- **The in-memory OAuth client store is async.** `InMemoryClientRepository`
|
|
71
91
|
methods (`register`, `add`, `find`, `all`, `authenticate`, `revoke`,
|
|
72
92
|
`delete`) are coroutines, like `OrmClientRepository`, and `store="memory"` now
|
|
@@ -74,6 +94,14 @@ Each item lists what changed and what to do.
|
|
|
74
94
|
synchronous custom store passed with `store="instance"` keeps the sync grants.
|
|
75
95
|
*Migrate:*
|
|
76
96
|
`await` client-store calls and the `handle` / `issue_code` of those grants.
|
|
97
|
+
- **Exception classes take an `Exception` suffix.** `AuthError` is now
|
|
98
|
+
`AuthException`, and every subclass gets the suffix (`InvalidToken` →
|
|
99
|
+
`InvalidTokenException`, `InsufficientScope` → `MissingScopeException`, and so
|
|
100
|
+
on). Wire error codes and HTTP statuses are unchanged. The old names remain as
|
|
101
|
+
deprecated aliases. *Migrate:* import the `*Exception` names.
|
|
102
|
+
- **Bad password on the password grant raises `InvalidCredentialsException`**, a
|
|
103
|
+
subclass of `InvalidGrantException`. The response is unchanged (`invalid_grant`,
|
|
104
|
+
400), so existing `except InvalidGrantException` blocks still catch it.
|
|
77
105
|
- **Persistent stores are named `"database"`.** `store="orm"` still works for
|
|
78
106
|
sessions, API tokens, OAuth tokens and OAuth clients, but emits a
|
|
79
107
|
`DeprecationWarning` and will be removed in a future release. *Migrate:*
|
|
@@ -82,6 +110,18 @@ Each item lists what changed and what to do.
|
|
|
82
110
|
`revoke_token_chain`, and `store_refresh_token` takes `family_id`.
|
|
83
111
|
- Error responses carry `Cache-Control: no-store`; `invalid_client` challenges
|
|
84
112
|
with `WWW-Authenticate: Basic`.
|
|
113
|
+
- **Class-level `Auth.guard(name)` is a FastAPI dependency.**
|
|
114
|
+
`Auth.guard(name)` / `AsyncAuth.guard(name)` on the class now returns a
|
|
115
|
+
`GuardDependency`: `user = Depends(Auth.guard("api"))` resolves the user
|
|
116
|
+
through that guard and raises 401 otherwise, and equal names compare equal,
|
|
117
|
+
so `app.dependency_overrides[Auth.guard("api")]` works. Inside a request,
|
|
118
|
+
attribute access (`provider`, `user_from_token`, `authenticate`, ...) still
|
|
119
|
+
reaches the named guard, but the value is no longer the guard object itself,
|
|
120
|
+
so identity and `isinstance` checks fail, and outside a request attributes
|
|
121
|
+
raise `AttributeError`. On an instance (`auth.guard(name)` from
|
|
122
|
+
`Depends(Auth.scoped)`) it still returns the guard. *Migrate:* where you need
|
|
123
|
+
the guard object, use `auth.guard(name)` from `Depends(Auth.scoped)` or
|
|
124
|
+
`manager.guard(name)`.
|
|
85
125
|
|
|
86
126
|
### Migrations
|
|
87
127
|
|
|
@@ -96,6 +136,18 @@ using the ORM stores:
|
|
|
96
136
|
|
|
97
137
|
### Added
|
|
98
138
|
|
|
139
|
+
- `ChainGuard(guards=[...])` (`{"driver": "chain"}`): authenticates through the
|
|
140
|
+
first member guard that accepts the request, e.g. a bearer token backed by
|
|
141
|
+
the browser session. A token the request presents is final for every member
|
|
142
|
+
type (oauth2 bearer, or an API token in `Authorization` or a custom header):
|
|
143
|
+
when the member that reads it rejects it, the chain fails instead of trying
|
|
144
|
+
the session. `user_from_token` skips session members.
|
|
145
|
+
- `OAuth2Guard(first_party_only=True)` refuses access tokens issued to an OAuth
|
|
146
|
+
client (`client_id` set), so a first-party API accepts only the
|
|
147
|
+
application's own login and personal tokens.
|
|
148
|
+
- `AuthManager.on_authenticated(listener)`: `listener(request, user, guard)`
|
|
149
|
+
runs whenever a guard dependency resolves a user, before the route handler,
|
|
150
|
+
for activity tracking or request-scoped state.
|
|
99
151
|
- Refresh token families with reuse detection: replaying a rotated refresh token
|
|
100
152
|
revokes the whole family, whoever presents it: the owning client, another
|
|
101
153
|
authenticated client, or no client at all for unbound password-grant tokens.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-startkit-auth
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Laravel-style authentication for FastAPI: OAuth2/JWT authorization server, cookie/session guard, SPA + CSRF protection, and Sanctum-style API tokens.
|
|
5
|
+
Keywords: fastapi,oauth2,jwt,auth,passport,sanctum,authentication,authorization,security,tokens,session,cookie,csrf,spa,starter-kit
|
|
6
|
+
Author: Bedram Tamang
|
|
7
|
+
Author-email: Bedram Tamang <tmgbedu@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE.md
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Framework :: FastAPI
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Session
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Dist: fastapi>=0.110
|
|
24
|
+
Requires-Dist: pyjwt>=2.10.1
|
|
25
|
+
Requires-Dist: bcrypt>=4.1
|
|
26
|
+
Requires-Dist: pydantic>=2
|
|
27
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
28
|
+
Requires-Dist: masonite-orm>=2.18 ; extra == 'masoniteorm'
|
|
29
|
+
Requires-Dist: fastapi-startkit[database]>=0.60,<1.0 ; python_full_version >= '3.12' and extra == 'startkit'
|
|
30
|
+
Requires-Dist: pytest>=8 ; extra == 'test'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23 ; extra == 'test'
|
|
32
|
+
Requires-Dist: httpx>=0.27 ; extra == 'test'
|
|
33
|
+
Requires-Dist: aiosqlite>=0.20 ; extra == 'test'
|
|
34
|
+
Requires-Dist: asyncpg>=0.29 ; extra == 'test'
|
|
35
|
+
Requires-Python: >=3.10
|
|
36
|
+
Project-URL: Homepage, https://github.com/fastapi-startkit/fastapi-startkit-auth
|
|
37
|
+
Project-URL: Repository, https://github.com/fastapi-startkit/fastapi-startkit-auth
|
|
38
|
+
Project-URL: Issues, https://github.com/fastapi-startkit/fastapi-startkit-auth/issues
|
|
39
|
+
Project-URL: Changelog, https://github.com/fastapi-startkit/fastapi-startkit-auth/blob/main/CHANGELOG.md
|
|
40
|
+
Provides-Extra: masoniteorm
|
|
41
|
+
Provides-Extra: startkit
|
|
42
|
+
Provides-Extra: test
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
|
|
45
|
+
# FastAPI Startkit Auth
|
|
46
|
+
|
|
47
|
+
FastAPI Startkit Auth provides authentication for FastAPI: an OAuth2/JWT authorization server, cookie/session guards, SPA + CSRF protection, and API tokens.
|
|
48
|
+
|
|
49
|
+
## Official Documentation
|
|
50
|
+
|
|
51
|
+
Documentation for FastAPI Startkit Auth can be found on the [FastAPI Startkit website](https://fastapi-startkit.github.io/docs/authentication).
|
|
52
|
+
|
|
53
|
+
## Contributing
|
|
54
|
+
|
|
55
|
+
Thank you for considering contributing to FastAPI Startkit Auth! The contribution guide can be found in the [FastAPI Startkit documentation](https://fastapi-startkit.github.io/docs/authentication).
|
|
56
|
+
|
|
57
|
+
## Code of Conduct
|
|
58
|
+
|
|
59
|
+
In order to ensure that the FastAPI Startkit community is welcoming to all, please review and abide by the code of conduct in the [FastAPI Startkit documentation](https://fastapi-startkit.github.io/docs/authentication).
|
|
60
|
+
|
|
61
|
+
## Security Vulnerabilities
|
|
62
|
+
|
|
63
|
+
If you discover a security vulnerability within FastAPI Startkit Auth, please report it privately to the maintainers rather than opening a public issue. Details can be found in the [FastAPI Startkit documentation](https://fastapi-startkit.github.io/docs/authentication).
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
FastAPI Startkit Auth is open-sourced software licensed under the [MIT license](LICENSE.md).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# FastAPI Startkit Auth
|
|
2
|
+
|
|
3
|
+
FastAPI Startkit Auth provides authentication for FastAPI: an OAuth2/JWT authorization server, cookie/session guards, SPA + CSRF protection, and API tokens.
|
|
4
|
+
|
|
5
|
+
## Official Documentation
|
|
6
|
+
|
|
7
|
+
Documentation for FastAPI Startkit Auth can be found on the [FastAPI Startkit website](https://fastapi-startkit.github.io/docs/authentication).
|
|
8
|
+
|
|
9
|
+
## Contributing
|
|
10
|
+
|
|
11
|
+
Thank you for considering contributing to FastAPI Startkit Auth! The contribution guide can be found in the [FastAPI Startkit documentation](https://fastapi-startkit.github.io/docs/authentication).
|
|
12
|
+
|
|
13
|
+
## Code of Conduct
|
|
14
|
+
|
|
15
|
+
In order to ensure that the FastAPI Startkit community is welcoming to all, please review and abide by the code of conduct in the [FastAPI Startkit documentation](https://fastapi-startkit.github.io/docs/authentication).
|
|
16
|
+
|
|
17
|
+
## Security Vulnerabilities
|
|
18
|
+
|
|
19
|
+
If you discover a security vulnerability within FastAPI Startkit Auth, please report it privately to the maintainers rather than opening a public issue. Details can be found in the [FastAPI Startkit documentation](https://fastapi-startkit.github.io/docs/authentication).
|
|
20
|
+
|
|
21
|
+
## License
|
|
22
|
+
|
|
23
|
+
FastAPI Startkit Auth is open-sourced software licensed under the [MIT license](LICENSE.md).
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fastapi-startkit-auth"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.0"
|
|
4
4
|
description = "Laravel-style authentication for FastAPI: OAuth2/JWT authorization server, cookie/session guard, SPA + CSRF protection, and Sanctum-style API tokens."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
7
|
-
license-files = ["LICENSE"]
|
|
7
|
+
license-files = ["LICENSE.md"]
|
|
8
8
|
keywords = [
|
|
9
9
|
"fastapi",
|
|
10
10
|
"oauth2",
|
{fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/__init__.py
RENAMED
|
@@ -8,21 +8,23 @@ Passport while staying idiomatic FastAPI:
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
from .apitokens.facade import ApiToken
|
|
11
|
-
from .apitokens.manager import ApiTokenManager,
|
|
11
|
+
from .apitokens.manager import ApiTokenManager, NewApiToken
|
|
12
12
|
from .apitokens.models import ApiTokenRecord
|
|
13
13
|
from .apitokens.repository import ApiTokenRepository, InMemoryApiTokenRepository
|
|
14
|
-
from .concurrency import AsyncMisconfiguration
|
|
15
14
|
from .clients.models import Client
|
|
16
15
|
from .clients.repository import InMemoryClientRepository
|
|
17
16
|
from .config import (
|
|
18
17
|
ApiTokenConfig,
|
|
19
18
|
AuthConfig,
|
|
20
19
|
OAuth2Config,
|
|
20
|
+
OAuthClientConfig,
|
|
21
21
|
OAuthClientsConfig,
|
|
22
|
+
OAuthTokenConfig,
|
|
22
23
|
OAuthTokensConfig,
|
|
23
24
|
SessionConfig,
|
|
24
25
|
)
|
|
25
26
|
from .dependencies import (
|
|
27
|
+
GuardDependency,
|
|
26
28
|
auth,
|
|
27
29
|
current_user,
|
|
28
30
|
optional_user,
|
|
@@ -31,44 +33,61 @@ from .dependencies import (
|
|
|
31
33
|
)
|
|
32
34
|
from .exceptions import (
|
|
33
35
|
AccessDenied,
|
|
36
|
+
AccessDeniedException,
|
|
37
|
+
AuthenticationException,
|
|
34
38
|
AuthError,
|
|
39
|
+
AuthException,
|
|
35
40
|
CsrfTokenMismatch,
|
|
41
|
+
CsrfTokenMismatchException,
|
|
36
42
|
InsufficientScope,
|
|
37
43
|
InvalidClient,
|
|
44
|
+
InvalidClientException,
|
|
45
|
+
InvalidCredentialsException,
|
|
38
46
|
InvalidGrant,
|
|
47
|
+
InvalidGrantException,
|
|
39
48
|
InvalidScope,
|
|
49
|
+
InvalidScopeException,
|
|
40
50
|
InvalidSession,
|
|
41
51
|
InvalidTarget,
|
|
52
|
+
InvalidTargetException,
|
|
42
53
|
InvalidToken,
|
|
54
|
+
InvalidTokenException,
|
|
55
|
+
MissingScopeException,
|
|
43
56
|
UnauthorizedClient,
|
|
57
|
+
UnauthorizedClientException,
|
|
44
58
|
UnsupportedGrantType,
|
|
59
|
+
UnsupportedGrantTypeException,
|
|
45
60
|
UnsupportedResponseType,
|
|
61
|
+
UnsupportedResponseTypeException,
|
|
46
62
|
)
|
|
47
63
|
from .policy import GrantPolicy
|
|
48
|
-
from .facade import
|
|
49
|
-
from .guards.
|
|
50
|
-
from .guards.
|
|
51
|
-
from .guards.
|
|
64
|
+
from .facade import Auth
|
|
65
|
+
from .guards.chain import ChainGuard
|
|
66
|
+
from .guards.guard import PassportGuard
|
|
67
|
+
from .guards.session import SessionGuard
|
|
68
|
+
from .guards.token import TokenGuard
|
|
52
69
|
from .manager import AuthManager, FeatureNotRegistered
|
|
53
70
|
from .middleware.context import AuthMiddleware
|
|
54
71
|
from .middleware.csrf import CsrfMiddleware
|
|
55
72
|
from .middleware.session import SessionMiddleware
|
|
56
|
-
from .passwords.broker import
|
|
73
|
+
from .passwords.broker import PasswordBroker
|
|
57
74
|
from .provider import AuthApiTokenProvider, AuthOAuth2Provider, AuthProvider, AuthSessionProvider
|
|
58
75
|
from .providers.memory import InMemoryUserProvider
|
|
59
|
-
from .providers.model import
|
|
76
|
+
from .providers.model import ModelUserProvider
|
|
60
77
|
from .sessions.facade import Session
|
|
61
78
|
from .sessions.models import SessionRecord
|
|
62
79
|
from .sessions.store import InMemorySessionStore, SessionStore
|
|
63
|
-
from .tokens.service import
|
|
80
|
+
from .tokens.service import TokenService
|
|
64
81
|
|
|
65
|
-
__version__ = "0.
|
|
82
|
+
__version__ = "0.8.0"
|
|
66
83
|
|
|
67
84
|
__all__ = (
|
|
68
85
|
"AuthConfig",
|
|
69
86
|
"SessionConfig",
|
|
70
87
|
"OAuth2Config",
|
|
88
|
+
"OAuthClientConfig",
|
|
71
89
|
"OAuthClientsConfig",
|
|
90
|
+
"OAuthTokenConfig",
|
|
72
91
|
"OAuthTokensConfig",
|
|
73
92
|
"ApiTokenConfig",
|
|
74
93
|
"AuthProvider",
|
|
@@ -85,23 +104,37 @@ __all__ = (
|
|
|
85
104
|
"InMemoryClientRepository",
|
|
86
105
|
"OrmClientRepository",
|
|
87
106
|
"OrmSessionStore",
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
107
|
+
"AccessDeniedException",
|
|
108
|
+
"UnauthorizedClientException",
|
|
109
|
+
"UnsupportedGrantTypeException",
|
|
110
|
+
"UnsupportedResponseTypeException",
|
|
92
111
|
"current_user",
|
|
112
|
+
"GuardDependency",
|
|
93
113
|
"optional_user",
|
|
94
114
|
"require_scopes",
|
|
95
115
|
"require_abilities",
|
|
116
|
+
"AuthException",
|
|
117
|
+
"InvalidGrantException",
|
|
118
|
+
"InvalidClientException",
|
|
119
|
+
"InvalidCredentialsException",
|
|
120
|
+
"InvalidTokenException",
|
|
121
|
+
"MissingScopeException",
|
|
122
|
+
"InvalidScopeException",
|
|
123
|
+
"InvalidTargetException",
|
|
124
|
+
"GrantPolicy",
|
|
125
|
+
"AuthenticationException",
|
|
126
|
+
"CsrfTokenMismatchException",
|
|
127
|
+
"AccessDenied",
|
|
128
|
+
"UnauthorizedClient",
|
|
129
|
+
"UnsupportedGrantType",
|
|
130
|
+
"UnsupportedResponseType",
|
|
96
131
|
"AuthError",
|
|
97
|
-
"AsyncMisconfiguration",
|
|
98
132
|
"InvalidGrant",
|
|
99
133
|
"InvalidClient",
|
|
100
134
|
"InvalidToken",
|
|
101
135
|
"InsufficientScope",
|
|
102
136
|
"InvalidScope",
|
|
103
137
|
"InvalidTarget",
|
|
104
|
-
"GrantPolicy",
|
|
105
138
|
"InvalidSession",
|
|
106
139
|
"CsrfTokenMismatch",
|
|
107
140
|
"Auth",
|
|
@@ -117,19 +150,12 @@ __all__ = (
|
|
|
117
150
|
"ApiTokenRecord",
|
|
118
151
|
"ApiTokenRepository",
|
|
119
152
|
"InMemoryApiTokenRepository",
|
|
120
|
-
"AsyncAuth",
|
|
121
153
|
"PassportGuard",
|
|
122
|
-
"
|
|
123
|
-
"AsyncSessionGuard",
|
|
124
|
-
"AsyncTokenGuard",
|
|
125
|
-
"AsyncApiTokenManager",
|
|
154
|
+
"ChainGuard",
|
|
126
155
|
"PasswordBroker",
|
|
127
|
-
"AsyncPasswordBroker",
|
|
128
156
|
"InMemoryUserProvider",
|
|
129
157
|
"ModelUserProvider",
|
|
130
|
-
"AsyncModelUserProvider",
|
|
131
158
|
"TokenService",
|
|
132
|
-
"AsyncTokenService",
|
|
133
159
|
)
|
|
134
160
|
|
|
135
161
|
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import secrets
|
|
4
|
-
import threading
|
|
5
4
|
import time
|
|
6
5
|
from dataclasses import dataclass
|
|
7
6
|
from typing import Any
|
|
8
7
|
|
|
9
8
|
from ..concurrency import call
|
|
10
|
-
from ..exceptions import
|
|
9
|
+
from ..exceptions import InvalidTokenException
|
|
11
10
|
from .models import ApiTokenRecord
|
|
12
|
-
from .repository import
|
|
11
|
+
from .repository import generate_token_secret, hash_token_secret
|
|
13
12
|
|
|
14
13
|
# Every verification failure raises this exact message so responses (and their
|
|
15
14
|
# timing, via the dummy compare below) never reveal whether the id exists.
|
|
@@ -29,7 +28,7 @@ def _check_secret(record: ApiTokenRecord | None, secret: str) -> None:
|
|
|
29
28
|
expected = record.token_hash if record is not None else _DUMMY_HASH
|
|
30
29
|
matched = secrets.compare_digest(hash_token_secret(secret), expected)
|
|
31
30
|
if record is None or not matched or record.expired:
|
|
32
|
-
raise
|
|
31
|
+
raise InvalidTokenException(_GENERIC_FAILURE)
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
def _creation_fields(
|
|
@@ -69,79 +68,6 @@ class NewApiToken:
|
|
|
69
68
|
|
|
70
69
|
|
|
71
70
|
class ApiTokenManager:
|
|
72
|
-
"""Issues, verifies, revokes, and lists opaque API tokens.
|
|
73
|
-
|
|
74
|
-
Exposed as ``AuthManager.api_tokens``; apps wire their own management
|
|
75
|
-
routes around it (the package ships none, per the app-owns-routes
|
|
76
|
-
decision).
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
def __init__(
|
|
80
|
-
self,
|
|
81
|
-
repository: ApiTokenRepository,
|
|
82
|
-
default_ttl: float | None = None,
|
|
83
|
-
purge_interval: float = 300,
|
|
84
|
-
) -> None:
|
|
85
|
-
self.repository = repository
|
|
86
|
-
self._default_ttl = default_ttl
|
|
87
|
-
self._purge_interval = purge_interval
|
|
88
|
-
self._purge_lock = threading.Lock()
|
|
89
|
-
self._last_purge = time.time()
|
|
90
|
-
|
|
91
|
-
def _maybe_purge(self) -> None:
|
|
92
|
-
"""Drop expired records periodically so stores stay bounded without a
|
|
93
|
-
scheduler (mirrors the purge-on-issue pattern of ``TokenService``)."""
|
|
94
|
-
now = time.time()
|
|
95
|
-
with self._purge_lock:
|
|
96
|
-
if now - self._last_purge < self._purge_interval:
|
|
97
|
-
return
|
|
98
|
-
self._last_purge = now
|
|
99
|
-
self.repository.purge_expired()
|
|
100
|
-
|
|
101
|
-
def create(
|
|
102
|
-
self,
|
|
103
|
-
user_id: Any,
|
|
104
|
-
name: str | None = None,
|
|
105
|
-
abilities: list[str] | None = None,
|
|
106
|
-
expires_at: float | None = None,
|
|
107
|
-
) -> NewApiToken:
|
|
108
|
-
"""Issue a token for ``user_id``; the plaintext is returned exactly once.
|
|
109
|
-
|
|
110
|
-
``expires_at`` overrides the configured default ttl; ``None`` with no
|
|
111
|
-
configured ttl means the token never expires. ``abilities`` default to
|
|
112
|
-
the unrestricted ``["*"]``.
|
|
113
|
-
"""
|
|
114
|
-
self._maybe_purge()
|
|
115
|
-
secret = generate_token_secret()
|
|
116
|
-
record = self.repository.create(
|
|
117
|
-
**_creation_fields(user_id, secret, name, abilities, expires_at, self._default_ttl)
|
|
118
|
-
)
|
|
119
|
-
return NewApiToken(record=record, plain_text=f"{record.id}|{secret}")
|
|
120
|
-
|
|
121
|
-
def verify(self, token: str) -> ApiTokenRecord:
|
|
122
|
-
"""Resolve ``"{id}|{secret}"`` to its live record or raise 401.
|
|
123
|
-
|
|
124
|
-
Lookup is by id (O(1), no scan); the secret comparison is constant-time
|
|
125
|
-
against the stored hash — or against a dummy hash when the id is
|
|
126
|
-
unknown, so the miss path costs the same as a mismatch.
|
|
127
|
-
"""
|
|
128
|
-
token_id, secret = _split_token(token)
|
|
129
|
-
record = self.repository.find(token_id) if token_id else None
|
|
130
|
-
_check_secret(record, secret)
|
|
131
|
-
self.repository.touch(record.id)
|
|
132
|
-
return record
|
|
133
|
-
|
|
134
|
-
def revoke(self, token_id: str) -> bool:
|
|
135
|
-
return self.repository.revoke(token_id)
|
|
136
|
-
|
|
137
|
-
def revoke_all(self, user_id: Any) -> int:
|
|
138
|
-
return self.repository.revoke_all_for_user(user_id)
|
|
139
|
-
|
|
140
|
-
def tokens_for(self, user_id: Any) -> list[ApiTokenRecord]:
|
|
141
|
-
return self.repository.list_for_user(user_id)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
class AsyncApiTokenManager:
|
|
145
71
|
def __init__(self, repository: Any, default_ttl: float | None = None, purge_interval: float = 300) -> None:
|
|
146
72
|
self.repository = repository
|
|
147
73
|
self._default_ttl = default_ttl
|
|
@@ -5,7 +5,7 @@ from cleo.helpers import option
|
|
|
5
5
|
from fastapi_startkit.console import Command
|
|
6
6
|
|
|
7
7
|
from ..concurrency import call
|
|
8
|
-
from ..exceptions import
|
|
8
|
+
from ..exceptions import InvalidScopeException
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def _valid_redirect(uri: str | None) -> bool:
|
|
@@ -45,7 +45,7 @@ class OAuth2ClientCommand(Command):
|
|
|
45
45
|
return 1
|
|
46
46
|
try:
|
|
47
47
|
manager.grant_policy.check_scopes(scopes)
|
|
48
|
-
except
|
|
48
|
+
except InvalidScopeException as error:
|
|
49
49
|
self.line_error(str(error))
|
|
50
50
|
return 1
|
|
51
51
|
client, secret = asyncio.run(
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
from starlette.concurrency import run_in_threadpool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def resolve(value: Any) -> Any:
|
|
10
|
+
if inspect.isawaitable(value):
|
|
11
|
+
return await value
|
|
12
|
+
return value
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def call(function: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
|
|
16
|
+
if is_coroutine_callable(function):
|
|
17
|
+
return await function(*args, **kwargs)
|
|
18
|
+
return await resolve(await run_in_threadpool(function, *args, **kwargs))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_coroutine_callable(obj: Any) -> bool:
|
|
22
|
+
return inspect.iscoroutinefunction(obj) or inspect.iscoroutinefunction(getattr(obj, "__call__", None))
|
|
@@ -4,9 +4,12 @@ from .auth import (
|
|
|
4
4
|
ApiTokenConfig,
|
|
5
5
|
ApiTokenGuard,
|
|
6
6
|
AuthConfig,
|
|
7
|
+
ChainGuard,
|
|
7
8
|
OAuth2Config,
|
|
8
9
|
OAuth2Guard,
|
|
10
|
+
OAuthClientConfig,
|
|
9
11
|
OAuthClientsConfig,
|
|
12
|
+
OAuthTokenConfig,
|
|
10
13
|
OAuthTokensConfig,
|
|
11
14
|
SessionConfig,
|
|
12
15
|
SessionGuard,
|
|
@@ -19,12 +22,15 @@ __all__ = (
|
|
|
19
22
|
"AuthConfig",
|
|
20
23
|
"SessionConfig",
|
|
21
24
|
"OAuth2Config",
|
|
25
|
+
"OAuthClientConfig",
|
|
22
26
|
"OAuthClientsConfig",
|
|
27
|
+
"OAuthTokenConfig",
|
|
23
28
|
"OAuthTokensConfig",
|
|
24
29
|
"ApiTokenConfig",
|
|
25
30
|
"SessionGuard",
|
|
26
31
|
"OAuth2Guard",
|
|
27
32
|
"ApiTokenGuard",
|
|
33
|
+
"ChainGuard",
|
|
28
34
|
"DEFAULT_GRANT_TYPES",
|
|
29
35
|
"DEPRECATED_OAUTH2_ATTRIBUTES",
|
|
30
36
|
"as_config_class",
|
{fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.8.0}/src/fastapi_startkit_auth/config/auth.py
RENAMED
|
@@ -8,14 +8,14 @@ from typing import Any, Literal, TypeVar
|
|
|
8
8
|
ConfigT = TypeVar("ConfigT")
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def
|
|
12
|
-
if
|
|
11
|
+
def _canonical_driver(driver: str, section: str) -> str:
|
|
12
|
+
if driver == "orm":
|
|
13
13
|
# stacklevel 4: this helper, __post_init__, the dataclass __init__, then the caller.
|
|
14
14
|
warnings.warn(
|
|
15
|
-
f'{section}(
|
|
15
|
+
f'{section}(driver="orm") is deprecated; use driver="database".', DeprecationWarning, stacklevel=4
|
|
16
16
|
)
|
|
17
17
|
return "database"
|
|
18
|
-
return
|
|
18
|
+
return driver
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
@dataclass
|
|
@@ -28,6 +28,7 @@ class SessionGuard:
|
|
|
28
28
|
class OAuth2Guard:
|
|
29
29
|
provider: str = "users"
|
|
30
30
|
audience: str | None = None
|
|
31
|
+
first_party_only: bool = False
|
|
31
32
|
driver: Literal["oauth2"] = "oauth2"
|
|
32
33
|
|
|
33
34
|
|
|
@@ -37,15 +38,23 @@ class ApiTokenGuard:
|
|
|
37
38
|
driver: Literal["token"] = "token"
|
|
38
39
|
|
|
39
40
|
|
|
41
|
+
@dataclass
|
|
42
|
+
class ChainGuard:
|
|
43
|
+
"""Authenticates through the first of ``guards`` (guard names) that accepts the request."""
|
|
44
|
+
|
|
45
|
+
guards: list[str] = field(default_factory=list)
|
|
46
|
+
driver: Literal["chain"] = "chain"
|
|
47
|
+
|
|
48
|
+
|
|
40
49
|
@dataclass
|
|
41
50
|
class SessionConfig:
|
|
42
51
|
"""Cookie sessions, enabled by ``AuthSessionProvider``.
|
|
43
52
|
|
|
44
|
-
``
|
|
53
|
+
``driver`` is "memory", "database" (the ORM ``sessions`` table; "orm" is a
|
|
45
54
|
deprecated alias) or "instance" (a ready-made store under ``instance``).
|
|
46
55
|
"""
|
|
47
56
|
|
|
48
|
-
|
|
57
|
+
driver: str = "memory"
|
|
49
58
|
connection: str | None = None
|
|
50
59
|
instance: Any = None
|
|
51
60
|
cookie: str = "startkit_session"
|
|
@@ -63,7 +72,7 @@ class SessionConfig:
|
|
|
63
72
|
csrf_exempt_paths: list[str] = field(default_factory=list)
|
|
64
73
|
|
|
65
74
|
def __post_init__(self) -> None:
|
|
66
|
-
self.
|
|
75
|
+
self.driver = _canonical_driver(self.driver, "SessionConfig")
|
|
67
76
|
|
|
68
77
|
|
|
69
78
|
@dataclass
|
|
@@ -74,7 +83,7 @@ class ApiTokenConfig:
|
|
|
74
83
|
accept the ``session_guard`` cookie session from those origins.
|
|
75
84
|
"""
|
|
76
85
|
|
|
77
|
-
|
|
86
|
+
driver: str = "memory"
|
|
78
87
|
connection: str | None = None
|
|
79
88
|
instance: Any = None
|
|
80
89
|
header: str = "Authorization"
|
|
@@ -84,7 +93,7 @@ class ApiTokenConfig:
|
|
|
84
93
|
session_guard: str = "web"
|
|
85
94
|
|
|
86
95
|
def __post_init__(self) -> None:
|
|
87
|
-
self.
|
|
96
|
+
self.driver = _canonical_driver(self.driver, "ApiTokenConfig")
|
|
88
97
|
|
|
89
98
|
@property
|
|
90
99
|
def stateful(self) -> bool:
|
|
@@ -92,25 +101,42 @@ class ApiTokenConfig:
|
|
|
92
101
|
|
|
93
102
|
|
|
94
103
|
@dataclass
|
|
95
|
-
class
|
|
104
|
+
class OAuthClientConfig:
|
|
96
105
|
"""Where OAuth clients live: "database" (the ORM ``oauth_clients`` table), "memory" or "instance"."""
|
|
97
106
|
|
|
98
|
-
|
|
107
|
+
driver: str = "database"
|
|
99
108
|
connection: str | None = None
|
|
100
109
|
instance: Any = None
|
|
101
110
|
|
|
102
111
|
def __post_init__(self) -> None:
|
|
103
|
-
self.
|
|
112
|
+
self.driver = _canonical_driver(self.driver, "OAuthClientConfig")
|
|
104
113
|
|
|
105
114
|
|
|
106
115
|
@dataclass
|
|
107
|
-
class
|
|
108
|
-
|
|
116
|
+
class OAuthTokenConfig:
|
|
117
|
+
driver: str = "memory"
|
|
109
118
|
connection: str | None = None
|
|
110
119
|
instance: Any = None
|
|
111
120
|
|
|
112
121
|
def __post_init__(self) -> None:
|
|
113
|
-
self.
|
|
122
|
+
self.driver = _canonical_driver(self.driver, "OAuthTokenConfig")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _deprecated_alias(config_type: type[ConfigT], old_name: str) -> type[ConfigT]:
|
|
126
|
+
class Alias(config_type):
|
|
127
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
128
|
+
warnings.warn(
|
|
129
|
+
f"{old_name} is deprecated; use {config_type.__name__}.", DeprecationWarning, stacklevel=2
|
|
130
|
+
)
|
|
131
|
+
super().__init__(*args, **kwargs)
|
|
132
|
+
|
|
133
|
+
Alias.__name__ = old_name
|
|
134
|
+
Alias.__qualname__ = old_name
|
|
135
|
+
return Alias
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
OAuthClientsConfig = _deprecated_alias(OAuthClientConfig, "OAuthClientsConfig")
|
|
139
|
+
OAuthTokensConfig = _deprecated_alias(OAuthTokenConfig, "OAuthTokensConfig")
|
|
114
140
|
|
|
115
141
|
|
|
116
142
|
DEFAULT_GRANT_TYPES = ("authorization_code", "client_credentials", "refresh_token")
|
|
@@ -145,12 +171,12 @@ class OAuth2Config:
|
|
|
145
171
|
require_redirect_uri: bool = True
|
|
146
172
|
grant_types: list[str] = field(default_factory=lambda: list(DEFAULT_GRANT_TYPES))
|
|
147
173
|
authorization_guard: str | None = None
|
|
148
|
-
tokens:
|
|
149
|
-
clients:
|
|
174
|
+
tokens: OAuthTokenConfig = field(default_factory=OAuthTokenConfig)
|
|
175
|
+
clients: OAuthClientConfig = field(default_factory=OAuthClientConfig)
|
|
150
176
|
|
|
151
177
|
def __post_init__(self) -> None:
|
|
152
|
-
self.tokens = resolve_config(self.tokens,
|
|
153
|
-
self.clients = resolve_config(self.clients,
|
|
178
|
+
self.tokens = resolve_config(self.tokens, OAuthTokenConfig)
|
|
179
|
+
self.clients = resolve_config(self.clients, OAuthClientConfig)
|
|
154
180
|
|
|
155
181
|
|
|
156
182
|
# v0.6 AuthConfig attributes that moved to OAuth2Config; still read, with a warning.
|