fastapi-startkit-auth 0.6.2__tar.gz → 0.7.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.7.0/CHANGELOG.md +304 -0
- fastapi_startkit_auth-0.7.0/PKG-INFO +67 -0
- fastapi_startkit_auth-0.7.0/README.md +23 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/pyproject.toml +3 -3
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/__init__.py +181 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/apitokens/facade.py +63 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/manager.py +2 -2
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/clients/credentials.py +46 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/clients/models.py +12 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/clients/orm.py +91 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/clients/repository.py +61 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/commands/oauth2_client.py +67 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/config/__init__.py +37 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/config/auth.py +240 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/dependencies.py +42 -27
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/exceptions.py +143 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/facade.py +55 -38
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/authorization_code.py +82 -16
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/grants/client_credentials.py +54 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/password.py +37 -8
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/refresh.py +30 -8
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/guards/guard.py +16 -10
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/guards/session.py +39 -22
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/guards/token.py +32 -11
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/manager.py +571 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/middleware/context.py +69 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/middleware/csrf.py +41 -9
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/middleware/session.py +9 -1
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/orm.py +19 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/passwords/broker.py +11 -11
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/policy.py +83 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/provider.py +221 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/publishable/migrations/2026_10_02_000001_add_resource_to_oauth_tables.py +15 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/publishable/migrations/2026_10_04_000001_create_oauth_clients_table.py +18 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/publishable/migrations/2026_10_04_000002_add_family_id_to_oauth_refresh_tokens_table.py +16 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/publishable/migrations/2026_10_05_000001_add_scopes_to_oauth_clients_table.py +11 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/py.typed +0 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/request_context.py +27 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/routes.py +497 -0
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/routes_password.py +53 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/routes_spa.py +1 -12
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/scoped_method.py +20 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/security/jwt.py +21 -5
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/sessions/facade.py +48 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/orm.py +6 -12
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/state.py +3 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/store.py +21 -11
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/tokens/models.py +9 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/tokens/orm.py +70 -16
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/tokens/repository.py +47 -8
- fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/tokens/service.py +469 -0
- fastapi_startkit_auth-0.7.0/tests/conftest.py +303 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_api_tokens.py +21 -28
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_async_auth.py +34 -29
- fastapi_startkit_auth-0.7.0/tests/test_async_refresh_race.py +77 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_async_safety.py +47 -33
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_async_stores.py +26 -0
- fastapi_startkit_auth-0.7.0/tests/test_class_config.py +128 -0
- fastapi_startkit_auth-0.7.0/tests/test_client_scopes.py +325 -0
- fastapi_startkit_auth-0.7.0/tests/test_clients.py +58 -0
- fastapi_startkit_auth-0.7.0/tests/test_facade_api.py +174 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_grants.py +52 -17
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_guard_registry.py +13 -8
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_integration.py +36 -14
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_jwt.py +25 -3
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_manager.py +8 -9
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_model_provider.py +3 -3
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_multi_provider.py +25 -18
- fastapi_startkit_auth-0.7.0/tests/test_oauth21.py +549 -0
- fastapi_startkit_auth-0.7.0/tests/test_oauth2_client_command.py +146 -0
- fastapi_startkit_auth-0.7.0/tests/test_oauth_config_aliases.py +29 -0
- fastapi_startkit_auth-0.7.0/tests/test_oauth_hardening.py +406 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_password_reset.py +5 -5
- fastapi_startkit_auth-0.7.0/tests/test_resource_binding.py +500 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_security_fixes.py +10 -11
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_session_auth.py +28 -28
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_spa_csrf.py +99 -48
- fastapi_startkit_auth-0.7.0/tests/test_startkit_provider.py +193 -0
- fastapi_startkit_auth-0.7.0/tests/test_store_names.py +49 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_sync_regression.py +40 -15
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_token_service.py +12 -8
- fastapi_startkit_auth-0.6.2/CHANGELOG.md +0 -147
- fastapi_startkit_auth-0.6.2/PKG-INFO +0 -351
- fastapi_startkit_auth-0.6.2/README.md +0 -307
- fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/__init__.py +0 -103
- fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/application.py +0 -38
- fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/clients/repository.py +0 -71
- fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/config.py +0 -109
- fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/exceptions.py +0 -90
- fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/grants/client_credentials.py +0 -30
- fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/manager.py +0 -406
- fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/provider.py +0 -85
- fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/routes.py +0 -368
- fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/startkit.py +0 -41
- fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/tokens/service.py +0 -316
- fastapi_startkit_auth-0.6.2/tests/conftest.py +0 -166
- fastapi_startkit_auth-0.6.2/tests/test_clients.py +0 -58
- fastapi_startkit_auth-0.6.2/tests/test_startkit_provider.py +0 -131
- /fastapi_startkit_auth-0.6.2/LICENSE → /fastapi_startkit_auth-0.7.0/LICENSE.md +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/models.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/orm.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/repository.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/clients/__init__.py +0 -0
- /fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/py.typed → /fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/commands/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/concurrency.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/pkce.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/guards/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/middleware/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/passwords/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/passwords/repository.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/base.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/fields.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/memory.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/model.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/cors.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000001_create_sessions_table.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000002_create_personal_api_tokens_table.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000003_create_oauth_access_tokens_table.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000004_create_oauth_refresh_tokens_table.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000005_create_oauth_auth_codes_table.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/security/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/security/hashing.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/models.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/tokens/__init__.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_hashing.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_model_provider_postgres.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_reset_throttle_enumeration.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_session_store.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_token_repository.py +0 -0
- {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_user_provider.py +0 -0
|
@@ -0,0 +1,304 @@
|
|
|
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
|
+
### Breaking changes and upgrade notes
|
|
11
|
+
|
|
12
|
+
Each item lists what changed and what to do.
|
|
13
|
+
|
|
14
|
+
- **Feature providers replace `Application` and `AuthServiceProvider`.**
|
|
15
|
+
Register `AuthProvider` first, then the features your guards use:
|
|
16
|
+
`AuthSessionProvider(SessionConfig)`, `AuthOAuth2Provider(OAuth2Config)` and
|
|
17
|
+
`AuthApiTokenProvider(ApiTokenConfig)`. Standalone apps call
|
|
18
|
+
`Provider(config).register(app)`; Startkit apps list the providers in the
|
|
19
|
+
application. *Migrate:* replace `Application([(AuthProvider, Config)])` with a
|
|
20
|
+
`FastAPI()` app plus the providers, and `AuthServiceProvider` with
|
|
21
|
+
`AuthProvider` + the feature providers.
|
|
22
|
+
- **Feature settings moved off `AuthConfig`.** The `session`, `api_tokens` and
|
|
23
|
+
`spa` dicts are rejected at startup; use `SessionConfig`, `ApiTokenConfig`
|
|
24
|
+
(`stateful_origins` turns on SPA mode) instead. *Migrate:* move each dict's
|
|
25
|
+
keys onto the matching dataclass and pass it to its provider.
|
|
26
|
+
- **OAuth settings moved to `OAuth2Config`.** `key`, `algorithm`, the `*_ttl`
|
|
27
|
+
values, `tokens`, `issuer`, `resources`, `scopes`, `pkce_methods` and
|
|
28
|
+
`require_pkce` now live on `OAuth2Config`. Setting them on `AuthConfig` still
|
|
29
|
+
works but emits a `DeprecationWarning`, and an explicit `OAuth2Config` value
|
|
30
|
+
wins. *Migrate:* move them to `OAuth2Config`; the fallback will be removed in a
|
|
31
|
+
future release.
|
|
32
|
+
- **Password grant is opt-in.** `OAuth2Config.grant_types` defaults to
|
|
33
|
+
`["authorization_code", "client_credentials", "refresh_token"]`; `/oauth/token`
|
|
34
|
+
with `grant_type=password` and `/token` answer `unsupported_grant_type`.
|
|
35
|
+
*Migrate:* add `"password"` to `grant_types` to keep it.
|
|
36
|
+
- **Client-less refresh tokens need the password grant.** Refresh tokens are now
|
|
37
|
+
bound to the issuing client: refreshing or revoking one requires that client's
|
|
38
|
+
authentication. Tokens without a client (issued by the password grant) are only
|
|
39
|
+
accepted while `"password"` is enabled, and client credentials sent alongside
|
|
40
|
+
one are authenticated (`invalid_client` if wrong). *Migrate:* send client credentials on
|
|
41
|
+
refresh; keep `"password"` enabled while old client-less tokens are in use.
|
|
42
|
+
- **Consent is mandatory.** `POST /oauth/authorize` returns `access_denied`
|
|
43
|
+
unless the body has `"approved": true`, and always returns `iss` (body and
|
|
44
|
+
redirect, RFC 9207). `response_type` other than `code` fails with
|
|
45
|
+
`unsupported_response_type`. *Migrate:* send `approved: true` after the user
|
|
46
|
+
consents; `GET /oauth/authorize` validates a request for your consent screen.
|
|
47
|
+
- **PKCE no longer defaults to S256.** A missing `code_challenge_method` means
|
|
48
|
+
`plain`, which the default `pkce_methods=["S256"]` refuses. RFC 7636 formats are
|
|
49
|
+
enforced: an S256 challenge is 43 base64url characters, a verifier 43–128
|
|
50
|
+
unreserved characters. *Migrate:* always send `code_challenge_method=S256` and
|
|
51
|
+
a verifier of at least 43 characters.
|
|
52
|
+
- **`client_credentials` is for confidential clients only**; public, revoked or
|
|
53
|
+
grant-disallowed clients get `unauthorized_client` / `invalid_client`.
|
|
54
|
+
Confidential clients must authenticate on the code exchange. *Migrate:* give
|
|
55
|
+
machine clients a secret and list the grant (or leave `grant_types` empty).
|
|
56
|
+
- **Introspection requires a confidential client.** *Migrate:* call
|
|
57
|
+
`/oauth/introspect` with a confidential client's credentials.
|
|
58
|
+
- **`/oauth/clients` HTTP routes are removed.** *Migrate:* create clients with
|
|
59
|
+
`auth:oauth2:client` (Startkit) or `manager.client_repository.register(...)`.
|
|
60
|
+
- **Password reset routes** move to their own router, mounted by `AuthProvider`
|
|
61
|
+
whenever `AuthConfig.passwords` is set (no OAuth needed). Paths are unchanged.
|
|
62
|
+
- **Default guard changed** from `"api"` to `"web"` and `AuthConfig.guards`
|
|
63
|
+
defaults to empty. *Migrate:* set `default` and `guards` explicitly.
|
|
64
|
+
- **CSRF is always on with sessions**, and also accepts the `_token` form field.
|
|
65
|
+
*Migrate:* send `X-XSRF-TOKEN` (or `_token`) on unsafe requests, or list paths
|
|
66
|
+
in `SessionConfig.csrf_exempt_paths`.
|
|
67
|
+
- **OAuth client store defaults to `"database"`** (the ORM `oauth_clients`
|
|
68
|
+
table). *Migrate:* publish and run the migrations, or set
|
|
69
|
+
`OAuth2Config(clients=OAuthClientConfig(store="memory"))`.
|
|
70
|
+
- **The in-memory OAuth client store is async.** `InMemoryClientRepository`
|
|
71
|
+
methods (`register`, `add`, `find`, `all`, `authenticate`, `revoke`,
|
|
72
|
+
`delete`) are coroutines, like `OrmClientRepository`, and `store="memory"` now
|
|
73
|
+
makes the manager build the async refresh and authorization-code grants. A
|
|
74
|
+
synchronous custom store passed with `store="instance"` keeps the sync grants.
|
|
75
|
+
*Migrate:*
|
|
76
|
+
`await` client-store calls and the `handle` / `issue_code` of those grants.
|
|
77
|
+
- **Exception classes take an `Exception` suffix.** `AuthError` is now
|
|
78
|
+
`AuthException`, and every subclass gets the suffix (`InvalidToken` →
|
|
79
|
+
`InvalidTokenException`, `InsufficientScope` → `MissingScopeException`, and so
|
|
80
|
+
on). Wire error codes and HTTP statuses are unchanged. The old names remain as
|
|
81
|
+
deprecated aliases. *Migrate:* import the `*Exception` names.
|
|
82
|
+
- **Bad password on the password grant raises `InvalidCredentialsException`**, a
|
|
83
|
+
subclass of `InvalidGrantException`. The response is unchanged (`invalid_grant`,
|
|
84
|
+
400), so existing `except InvalidGrantException` blocks still catch it.
|
|
85
|
+
- **Persistent stores are named `"database"`.** `store="orm"` still works for
|
|
86
|
+
sessions, API tokens, OAuth tokens and OAuth clients, but emits a
|
|
87
|
+
`DeprecationWarning` and will be removed in a future release. *Migrate:*
|
|
88
|
+
replace `store="orm"` with `store="database"`.
|
|
89
|
+
- **Custom token repositories** must implement `consume_refresh_token` and
|
|
90
|
+
`revoke_token_chain`, and `store_refresh_token` takes `family_id`.
|
|
91
|
+
- Error responses carry `Cache-Control: no-store`; `invalid_client` challenges
|
|
92
|
+
with `WWW-Authenticate: Basic`.
|
|
93
|
+
|
|
94
|
+
### Migrations
|
|
95
|
+
|
|
96
|
+
All additive; the v0.6.x migrations are unchanged. Publish and run them when
|
|
97
|
+
using the ORM stores:
|
|
98
|
+
|
|
99
|
+
- `2026_10_04_000001_create_oauth_clients_table` — the `oauth_clients` table.
|
|
100
|
+
- `2026_10_04_000002_add_family_id_to_oauth_refresh_tokens_table` — a nullable,
|
|
101
|
+
indexed `family_id` column on `oauth_refresh_tokens`.
|
|
102
|
+
- `2026_10_05_000001_add_scopes_to_oauth_clients_table` — a nullable `scopes`
|
|
103
|
+
column on `oauth_clients`; existing rows (NULL) stay unrestricted.
|
|
104
|
+
|
|
105
|
+
### Added
|
|
106
|
+
|
|
107
|
+
- Refresh token families with reuse detection: replaying a rotated refresh token
|
|
108
|
+
revokes the whole family, whoever presents it: the owning client, another
|
|
109
|
+
authenticated client, or no client at all for unbound password-grant tokens.
|
|
110
|
+
Refresh tokens are introspectable.
|
|
111
|
+
- `authentication.md`: the full authentication guide.
|
|
112
|
+
- `POST /oauth/revoke` (RFC 7009) revokes the token and its access/refresh chain;
|
|
113
|
+
`GET`/`DELETE /oauth/tokens` and `DELETE /oauth/tokens/{jti}` let a user list
|
|
114
|
+
and revoke their OAuth tokens. `DELETE /oauth/personal-access-tokens` revokes
|
|
115
|
+
all personal access tokens, which also accept a `ttl`.
|
|
116
|
+
- `/.well-known/oauth-authorization-server` metadata (RFC 8414).
|
|
117
|
+
- `OAuth2Config.default_scopes`, `grant_types`, `require_redirect_uri` and
|
|
118
|
+
`authorization_guard`.
|
|
119
|
+
- `OrmClientRepository` and the `auth:oauth2:client` command (`--public`,
|
|
120
|
+
`--name`, repeatable `--redirect-uri`).
|
|
121
|
+
- Per-client scope restrictions: `Client.scopes` lists the scopes a client may
|
|
122
|
+
request (empty means any). A request outside the list, including
|
|
123
|
+
`default_scopes` applied to a request naming none, fails with `invalid_scope`
|
|
124
|
+
on `/oauth/authorize`, the `client_credentials` grant and the password grant
|
|
125
|
+
when a client authenticates. `"*"` is not expanded when matching the allow-list, but a token granted `"*"`
|
|
126
|
+
passes every `require_scopes` check, so never give a client `"*"` (the command
|
|
127
|
+
refuses it).
|
|
128
|
+
Set it with `auth:oauth2:client --scopes`; client repositories gain `revoke`.
|
|
129
|
+
- `AuthMiddleware` and request-scoped facades: `Auth.user()`, `Session.token()`,
|
|
130
|
+
`ApiToken.create(...)` work on the class inside a request. `Auth.validate`
|
|
131
|
+
checks credentials without logging in.
|
|
132
|
+
- `auth` dependency; token guards fall back to the SPA session cookie.
|
|
133
|
+
- Guest sessions are persisted lazily, only when the response is sent.
|
|
134
|
+
|
|
135
|
+
### Added (resource indicators, #27)
|
|
136
|
+
|
|
137
|
+
- RFC 8707 resource indicators: `OAuth2Config.resources` lists the resources tokens
|
|
138
|
+
may be bound to. The token endpoint and `/oauth/authorize` accept `resource`;
|
|
139
|
+
the code and refresh token remember it and the access token carries it as `aud`.
|
|
140
|
+
Unknown or mismatched resources fail with `invalid_target`.
|
|
141
|
+
- Guards accept an `audience`: a passport guard with `"audience": "<uri>"` only
|
|
142
|
+
accepts tokens bound to that resource, and resource-bound tokens are refused by
|
|
143
|
+
guards without it.
|
|
144
|
+
- `OAuth2Config.issuer` stamps and verifies the `iss` claim.
|
|
145
|
+
- `OAuth2Config.scopes` is a scope catalog: when set, grants refuse unknown scopes
|
|
146
|
+
with `invalid_scope`.
|
|
147
|
+
- `OAuth2Config.pkce_methods` restricts `code_challenge_method` (e.g. `["S256"]`),
|
|
148
|
+
compared case-insensitively; an unknown `code_challenge_method` is now refused
|
|
149
|
+
at `/oauth/authorize` with `invalid_request`.
|
|
150
|
+
- `OAuth2Config.require_pkce` demands a `code_challenge` from confidential clients
|
|
151
|
+
too (OAuth 2.1 / MCP).
|
|
152
|
+
- A repeated `resource` parameter on the token endpoint fails with `invalid_target`.
|
|
153
|
+
- Building a guard whose `audience` is not listed in `OAuth2Config.resources` emits
|
|
154
|
+
a `UserWarning`.
|
|
155
|
+
- `GrantPolicy`, `InvalidScope` and `InvalidTarget` are exported; introspection
|
|
156
|
+
reports `aud` and `iss` when present.
|
|
157
|
+
- Migration `add_resource_to_oauth_tables` adds a nullable `resource` column to
|
|
158
|
+
`oauth_auth_codes` and `oauth_refresh_tokens`; publish and run it when using the
|
|
159
|
+
ORM store.
|
|
160
|
+
|
|
161
|
+
### Changed
|
|
162
|
+
|
|
163
|
+
- **Breaking:** persistence now goes through the fastapi-startkit ORM with models
|
|
164
|
+
shipped in `fastapi_startkit_auth.orm`; no raw SQL remains. Use
|
|
165
|
+
`{"store": "orm"}` (optional ORM `connection` name) for sessions, API tokens and
|
|
166
|
+
OAuth tokens.
|
|
167
|
+
- Requires `pyjwt>=2.10.1`.
|
|
168
|
+
- **Security:** refresh tokens are bound to the client they were issued to
|
|
169
|
+
(RFC 6749 §6). `RefreshTokenGrant.handle` takes `client_id` and fails with
|
|
170
|
+
`invalid_grant` when it differs from the token's client; the token endpoint
|
|
171
|
+
passes the authenticated client. Callers refreshing a client-issued token must
|
|
172
|
+
now send that client's credentials.
|
|
173
|
+
- **Breaking:** the `startkit` extra now requires `fastapi-startkit[database]>=0.60`.
|
|
174
|
+
- Run the published migrations (`provider:publish -p auth`, then `migrate`) to
|
|
175
|
+
create the tables; stores no longer create tables themselves.
|
|
176
|
+
|
|
177
|
+
### Removed
|
|
178
|
+
|
|
179
|
+
- **Breaking:** the `sql` / `async_sql` stores and the `SqlSessionStore`,
|
|
180
|
+
`SqlApiTokenRepository`, `AsyncSql*` classes and database adapters.
|
|
181
|
+
|
|
182
|
+
### Added
|
|
183
|
+
|
|
184
|
+
- `Client.provider` (and `register(provider=...)`) binds an OAuth client to a
|
|
185
|
+
user provider. `AuthManager.owner_provider(client_id)` resolves it, falling
|
|
186
|
+
back to the default guard's provider.
|
|
187
|
+
- `AuthManager.warm_up()` and `AsyncModelUserProvider.warm_up()`, run from the
|
|
188
|
+
app lifespan by `AuthProvider`, precompute the dummy hash at startup.
|
|
189
|
+
|
|
190
|
+
### Fixed
|
|
191
|
+
|
|
192
|
+
- Refresh, authorization-code exchange and introspection re-check the token
|
|
193
|
+
owner against the provider of the issuing client instead of always using the
|
|
194
|
+
default guard's provider. The password grant authenticates against the
|
|
195
|
+
client's provider, and `/oauth/authorize` rejects a client bound to another
|
|
196
|
+
provider.
|
|
197
|
+
- `AsyncModelUserProvider.is_active` only uses the threadpool for a sync
|
|
198
|
+
callable hook. Attribute checks and `async def` hooks run inline.
|
|
199
|
+
- The first unknown-user login no longer pays an extra bcrypt hash to create
|
|
200
|
+
the dummy hash.
|
|
201
|
+
|
|
202
|
+
## [0.4.0]
|
|
203
|
+
|
|
204
|
+
### Added
|
|
205
|
+
|
|
206
|
+
- Async support for async ORMs and drivers, side by side with the sync API:
|
|
207
|
+
- `AsyncModelUserProvider` (provider driver `"async_model"`) for models whose
|
|
208
|
+
`find` / `where(...).first()` / `save` are coroutines; bcrypt runs in a
|
|
209
|
+
worker thread.
|
|
210
|
+
- `AsyncPassportGuard`, `AsyncSessionGuard`, `AsyncTokenGuard`,
|
|
211
|
+
`AsyncTokenService`, `AsyncApiTokenManager`, `AsyncPasswordBroker`, the
|
|
212
|
+
`Async*Grant` variants and the `AsyncAuth` facade.
|
|
213
|
+
- `AuthManager` picks the async variant automatically when any provider
|
|
214
|
+
method or store method is async; sync configurations keep getting the sync
|
|
215
|
+
classes. Sync collaborators used from the async classes run in the
|
|
216
|
+
threadpool.
|
|
217
|
+
- `"async_sql"` stores for sessions (`AsyncSqlSessionStore`), personal API
|
|
218
|
+
tokens (`AsyncSqlApiTokenRepository`) and a new `AuthConfig.tokens` block
|
|
219
|
+
for OAuth access/refresh tokens and authorization codes
|
|
220
|
+
(`AsyncSqlTokenRepository`). They accept an asyncpg pool/connection, an
|
|
221
|
+
aiosqlite connection, or a zero-arg (async) factory. Supported backends are
|
|
222
|
+
PostgreSQL and SQLite. Refresh tokens and authorization codes are stored as
|
|
223
|
+
SHA-256 hashes; refresh revocation and code redemption are atomic and use
|
|
224
|
+
only portable SQL. Expired rows are purged via `expires_at` indexes.
|
|
225
|
+
- `fastapi-startkit` migrations for the five tables, published to
|
|
226
|
+
`databases/migrations/` by `AuthServiceProvider`.
|
|
227
|
+
- `password_key` provider option: the credentials key holding the plaintext
|
|
228
|
+
password, separate from the model column `password_field` (e.g.
|
|
229
|
+
`password_key="password"`, `password_field="hashed_password"`). Defaults to
|
|
230
|
+
`password_field`.
|
|
231
|
+
- `is_active` provider option (attribute name or callable; `async def` with
|
|
232
|
+
the `async_model` driver). Inactive or deleted users are rejected by the
|
|
233
|
+
password grant, the refresh and authorization-code grants, `Auth.attempt` /
|
|
234
|
+
`AsyncAuth.attempt` and every guard, and introspect as inactive.
|
|
235
|
+
- `AsyncMisconfiguration`: raised when a sync guard, grant, broker or `Auth`
|
|
236
|
+
receives an awaitable from a provider or store, so a half-async provider
|
|
237
|
+
fails closed. Sync providers reject an `async def` `is_active` hook.
|
|
238
|
+
- CI runs the asyncpg store and flow tests against a Postgres service.
|
|
239
|
+
- `Typing :: Typed` classifier (the package already ships `py.typed`).
|
|
240
|
+
|
|
241
|
+
### Changed
|
|
242
|
+
|
|
243
|
+
- **Breaking (signature):** `current_context` and `optional_user` are now
|
|
244
|
+
`async def` dependencies. `Depends(current_user)` and friends keep working
|
|
245
|
+
unchanged; code calling them directly as plain functions must now await
|
|
246
|
+
them. Sync guards still run in the threadpool.
|
|
247
|
+
- The built-in OAuth, password-reset and SPA csrf-cookie routes await async
|
|
248
|
+
collaborators; `SessionMiddleware` accepts an async session store.
|
|
249
|
+
- `Auth` (sync facade) raises `RuntimeError` for async session guards; use
|
|
250
|
+
`AsyncAuth` there.
|
|
251
|
+
- Minimum supported Python is now 3.10. 0.3.0 advertised `>=3.9` but fails on
|
|
252
|
+
3.9 because runtime-evaluated annotations use `X | None` syntax.
|
|
253
|
+
- The `startkit` extra is bounded to `fastapi-startkit>=0.51,<1.0`.
|
|
254
|
+
- The package `__init__` imports its public API directly instead of through a
|
|
255
|
+
lazy export table.
|
|
256
|
+
- The source distribution now ships the test suite and this changelog.
|
|
257
|
+
- `bin/release.sh` uploads to PyPI with `twine upload`, matching
|
|
258
|
+
fastapi-startkit; the tag-triggered trusted-publishing workflow is removed.
|
|
259
|
+
|
|
260
|
+
## [0.3.0]
|
|
261
|
+
|
|
262
|
+
First release published to PyPI. Contains the 0.2.0 feature set, built with
|
|
263
|
+
the `uv_build` backend; no functional changes.
|
|
264
|
+
|
|
265
|
+
## [0.2.0]
|
|
266
|
+
|
|
267
|
+
Adds three new authentication modes alongside the existing OAuth2/JWT server,
|
|
268
|
+
so the package now covers the full Laravel-style auth surface.
|
|
269
|
+
|
|
270
|
+
### Added
|
|
271
|
+
|
|
272
|
+
- Guard-driver registry in `AuthManager`: guards resolve their driver
|
|
273
|
+
(`token`, `session`, …) from a pluggable registry, enabling multiple guard
|
|
274
|
+
types in one application (Phase 0).
|
|
275
|
+
- Cookie/session authentication: `Auth` facade, `SessionGuard`, session
|
|
276
|
+
middleware, and in-memory + SQL session stores (Phase 1).
|
|
277
|
+
- SPA authentication with CSRF protection: `/__auth__/csrf-cookie` endpoint,
|
|
278
|
+
double-submit-cookie middleware, and an `AuthServiceProvider` that publishes
|
|
279
|
+
the `config/cors.py` stub via `provider:publish -p auth` (Phase 2).
|
|
280
|
+
- Sanctum-style API tokens: `TokenGuard`, opaque hashed tokens with abilities,
|
|
281
|
+
and in-memory + SQL token stores (Phase 3).
|
|
282
|
+
|
|
283
|
+
### Security / Hardening
|
|
284
|
+
|
|
285
|
+
- Session guard verifies against a constant-time dummy hash when no user is
|
|
286
|
+
found, closing a timing side-channel.
|
|
287
|
+
- SQL session and API-token stores prune expired rows; purge cadence is
|
|
288
|
+
configurable.
|
|
289
|
+
- CSRF cookie endpoint hardened; token repr for `NewApiToken` redacts the
|
|
290
|
+
plaintext value so it cannot leak into logs.
|
|
291
|
+
- DB-API connection vs. connection-factory detection now probes `execute()`
|
|
292
|
+
instead of `callable()`, fixing misdetection of raw `sqlite3.Connection`
|
|
293
|
+
objects in the SQL session and API-token stores.
|
|
294
|
+
|
|
295
|
+
## [0.1.0]
|
|
296
|
+
|
|
297
|
+
Initial release.
|
|
298
|
+
|
|
299
|
+
- Passport-style OAuth2 authorization server for FastAPI: authorization code
|
|
300
|
+
(with PKCE), password, client credentials, and refresh token grants
|
|
301
|
+
- JWT access tokens with scopes, token guards, and route dependencies
|
|
302
|
+
- Personal access tokens
|
|
303
|
+
- Pluggable user/client providers (in-memory and Masonite ORM)
|
|
304
|
+
- Password reset flow with throttling hardened against account enumeration
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-startkit-auth
|
|
3
|
+
Version: 0.7.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.7.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",
|
|
@@ -43,7 +43,7 @@ classifiers = [
|
|
|
43
43
|
]
|
|
44
44
|
dependencies = [
|
|
45
45
|
"fastapi>=0.110",
|
|
46
|
-
"pyjwt>=2.
|
|
46
|
+
"pyjwt>=2.10.1",
|
|
47
47
|
"bcrypt>=4.1",
|
|
48
48
|
"pydantic>=2",
|
|
49
49
|
"python-multipart>=0.0.9",
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Passport-style OAuth2 + JWT authentication for FastAPI.
|
|
2
|
+
|
|
3
|
+
Public API mirrors the config-driven guard/provider/passwords model of Laravel
|
|
4
|
+
Passport while staying idiomatic FastAPI:
|
|
5
|
+
|
|
6
|
+
from fastapi_startkit_auth import AuthProvider, AuthConfig, OAuth2Config
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .apitokens.facade import ApiToken
|
|
11
|
+
from .apitokens.manager import ApiTokenManager, AsyncApiTokenManager, NewApiToken
|
|
12
|
+
from .apitokens.models import ApiTokenRecord
|
|
13
|
+
from .apitokens.repository import ApiTokenRepository, InMemoryApiTokenRepository
|
|
14
|
+
from .concurrency import AsyncMisconfiguration
|
|
15
|
+
from .clients.models import Client
|
|
16
|
+
from .clients.repository import InMemoryClientRepository
|
|
17
|
+
from .config import (
|
|
18
|
+
ApiTokenConfig,
|
|
19
|
+
AuthConfig,
|
|
20
|
+
OAuth2Config,
|
|
21
|
+
OAuthClientConfig,
|
|
22
|
+
OAuthClientsConfig,
|
|
23
|
+
OAuthTokenConfig,
|
|
24
|
+
OAuthTokensConfig,
|
|
25
|
+
SessionConfig,
|
|
26
|
+
)
|
|
27
|
+
from .dependencies import (
|
|
28
|
+
auth,
|
|
29
|
+
current_user,
|
|
30
|
+
optional_user,
|
|
31
|
+
require_abilities,
|
|
32
|
+
require_scopes,
|
|
33
|
+
)
|
|
34
|
+
from .exceptions import (
|
|
35
|
+
AccessDenied,
|
|
36
|
+
AccessDeniedException,
|
|
37
|
+
AuthenticationException,
|
|
38
|
+
AuthError,
|
|
39
|
+
AuthException,
|
|
40
|
+
CsrfTokenMismatch,
|
|
41
|
+
CsrfTokenMismatchException,
|
|
42
|
+
InsufficientScope,
|
|
43
|
+
InvalidClient,
|
|
44
|
+
InvalidClientException,
|
|
45
|
+
InvalidCredentialsException,
|
|
46
|
+
InvalidGrant,
|
|
47
|
+
InvalidGrantException,
|
|
48
|
+
InvalidScope,
|
|
49
|
+
InvalidScopeException,
|
|
50
|
+
InvalidSession,
|
|
51
|
+
InvalidTarget,
|
|
52
|
+
InvalidTargetException,
|
|
53
|
+
InvalidToken,
|
|
54
|
+
InvalidTokenException,
|
|
55
|
+
MissingScopeException,
|
|
56
|
+
UnauthorizedClient,
|
|
57
|
+
UnauthorizedClientException,
|
|
58
|
+
UnsupportedGrantType,
|
|
59
|
+
UnsupportedGrantTypeException,
|
|
60
|
+
UnsupportedResponseType,
|
|
61
|
+
UnsupportedResponseTypeException,
|
|
62
|
+
)
|
|
63
|
+
from .policy import GrantPolicy
|
|
64
|
+
from .facade import AsyncAuth, Auth
|
|
65
|
+
from .guards.guard import AsyncPassportGuard, PassportGuard
|
|
66
|
+
from .guards.session import AsyncSessionGuard, SessionGuard
|
|
67
|
+
from .guards.token import AsyncTokenGuard, TokenGuard
|
|
68
|
+
from .manager import AuthManager, FeatureNotRegistered
|
|
69
|
+
from .middleware.context import AuthMiddleware
|
|
70
|
+
from .middleware.csrf import CsrfMiddleware
|
|
71
|
+
from .middleware.session import SessionMiddleware
|
|
72
|
+
from .passwords.broker import AsyncPasswordBroker, PasswordBroker
|
|
73
|
+
from .provider import AuthApiTokenProvider, AuthOAuth2Provider, AuthProvider, AuthSessionProvider
|
|
74
|
+
from .providers.memory import InMemoryUserProvider
|
|
75
|
+
from .providers.model import AsyncModelUserProvider, ModelUserProvider
|
|
76
|
+
from .sessions.facade import Session
|
|
77
|
+
from .sessions.models import SessionRecord
|
|
78
|
+
from .sessions.store import InMemorySessionStore, SessionStore
|
|
79
|
+
from .tokens.service import AsyncTokenService, TokenService
|
|
80
|
+
|
|
81
|
+
__version__ = "0.7.0"
|
|
82
|
+
|
|
83
|
+
__all__ = (
|
|
84
|
+
"AuthConfig",
|
|
85
|
+
"SessionConfig",
|
|
86
|
+
"OAuth2Config",
|
|
87
|
+
"OAuthClientConfig",
|
|
88
|
+
"OAuthClientsConfig",
|
|
89
|
+
"OAuthTokenConfig",
|
|
90
|
+
"OAuthTokensConfig",
|
|
91
|
+
"ApiTokenConfig",
|
|
92
|
+
"AuthProvider",
|
|
93
|
+
"AuthSessionProvider",
|
|
94
|
+
"AuthOAuth2Provider",
|
|
95
|
+
"AuthApiTokenProvider",
|
|
96
|
+
"AuthMiddleware",
|
|
97
|
+
"AuthManager",
|
|
98
|
+
"FeatureNotRegistered",
|
|
99
|
+
"auth",
|
|
100
|
+
"Session",
|
|
101
|
+
"ApiToken",
|
|
102
|
+
"Client",
|
|
103
|
+
"InMemoryClientRepository",
|
|
104
|
+
"OrmClientRepository",
|
|
105
|
+
"OrmSessionStore",
|
|
106
|
+
"AccessDeniedException",
|
|
107
|
+
"UnauthorizedClientException",
|
|
108
|
+
"UnsupportedGrantTypeException",
|
|
109
|
+
"UnsupportedResponseTypeException",
|
|
110
|
+
"current_user",
|
|
111
|
+
"optional_user",
|
|
112
|
+
"require_scopes",
|
|
113
|
+
"require_abilities",
|
|
114
|
+
"AuthException",
|
|
115
|
+
"AsyncMisconfiguration",
|
|
116
|
+
"InvalidGrantException",
|
|
117
|
+
"InvalidClientException",
|
|
118
|
+
"InvalidCredentialsException",
|
|
119
|
+
"InvalidTokenException",
|
|
120
|
+
"MissingScopeException",
|
|
121
|
+
"InvalidScopeException",
|
|
122
|
+
"InvalidTargetException",
|
|
123
|
+
"GrantPolicy",
|
|
124
|
+
"AuthenticationException",
|
|
125
|
+
"CsrfTokenMismatchException",
|
|
126
|
+
"AccessDenied",
|
|
127
|
+
"UnauthorizedClient",
|
|
128
|
+
"UnsupportedGrantType",
|
|
129
|
+
"UnsupportedResponseType",
|
|
130
|
+
"AuthError",
|
|
131
|
+
"InvalidGrant",
|
|
132
|
+
"InvalidClient",
|
|
133
|
+
"InvalidToken",
|
|
134
|
+
"InsufficientScope",
|
|
135
|
+
"InvalidScope",
|
|
136
|
+
"InvalidTarget",
|
|
137
|
+
"InvalidSession",
|
|
138
|
+
"CsrfTokenMismatch",
|
|
139
|
+
"Auth",
|
|
140
|
+
"SessionGuard",
|
|
141
|
+
"SessionMiddleware",
|
|
142
|
+
"CsrfMiddleware",
|
|
143
|
+
"SessionRecord",
|
|
144
|
+
"SessionStore",
|
|
145
|
+
"InMemorySessionStore",
|
|
146
|
+
"TokenGuard",
|
|
147
|
+
"ApiTokenManager",
|
|
148
|
+
"NewApiToken",
|
|
149
|
+
"ApiTokenRecord",
|
|
150
|
+
"ApiTokenRepository",
|
|
151
|
+
"InMemoryApiTokenRepository",
|
|
152
|
+
"AsyncAuth",
|
|
153
|
+
"PassportGuard",
|
|
154
|
+
"AsyncPassportGuard",
|
|
155
|
+
"AsyncSessionGuard",
|
|
156
|
+
"AsyncTokenGuard",
|
|
157
|
+
"AsyncApiTokenManager",
|
|
158
|
+
"PasswordBroker",
|
|
159
|
+
"AsyncPasswordBroker",
|
|
160
|
+
"InMemoryUserProvider",
|
|
161
|
+
"ModelUserProvider",
|
|
162
|
+
"AsyncModelUserProvider",
|
|
163
|
+
"TokenService",
|
|
164
|
+
"AsyncTokenService",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
_ORM_EXPORTS = {
|
|
169
|
+
"OrmClientRepository": ".clients.orm",
|
|
170
|
+
"OrmSessionStore": ".sessions.orm",
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def __getattr__(name: str):
|
|
175
|
+
# The ORM stores need the optional fastapi-startkit framework; resolving them
|
|
176
|
+
# on access keeps plain-FastAPI installs importable.
|
|
177
|
+
if name in _ORM_EXPORTS:
|
|
178
|
+
from importlib import import_module
|
|
179
|
+
|
|
180
|
+
return getattr(import_module(_ORM_EXPORTS[name], __name__), name)
|
|
181
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from fastapi import Depends, Request
|
|
6
|
+
|
|
7
|
+
from ..concurrency import call
|
|
8
|
+
from ..dependencies import get_auth_manager
|
|
9
|
+
from ..scoped_method import ScopedMethod
|
|
10
|
+
from .manager import NewApiToken
|
|
11
|
+
from .models import ApiTokenRecord
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ..manager import AuthManager
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ApiToken:
|
|
18
|
+
"""Issue and revoke opaque API tokens; awaitable for sync and async token stores alike."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, manager: AuthManager, request: Request | None = None) -> None:
|
|
21
|
+
self._manager = manager
|
|
22
|
+
self._request = request
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def scoped(cls, request: Request, manager: AuthManager = Depends(get_auth_manager)) -> ApiToken:
|
|
26
|
+
return cls(manager, request)
|
|
27
|
+
|
|
28
|
+
@ScopedMethod
|
|
29
|
+
async def create(
|
|
30
|
+
self,
|
|
31
|
+
user_or_id: Any,
|
|
32
|
+
name: str | None = None,
|
|
33
|
+
abilities: list[str] | None = None,
|
|
34
|
+
expires_at: float | None = None,
|
|
35
|
+
guard: str | None = None,
|
|
36
|
+
) -> NewApiToken:
|
|
37
|
+
return await call(
|
|
38
|
+
self._manager.api_tokens.create,
|
|
39
|
+
self._user_id(user_or_id, guard),
|
|
40
|
+
name=name,
|
|
41
|
+
abilities=abilities,
|
|
42
|
+
expires_at=expires_at,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@ScopedMethod
|
|
46
|
+
async def tokens(self, user_or_id: Any, guard: str | None = None) -> list[ApiTokenRecord]:
|
|
47
|
+
return await call(self._manager.api_tokens.tokens_for, self._user_id(user_or_id, guard))
|
|
48
|
+
|
|
49
|
+
@ScopedMethod
|
|
50
|
+
async def revoke(self, token_id: str) -> bool:
|
|
51
|
+
return await call(self._manager.api_tokens.revoke, token_id)
|
|
52
|
+
|
|
53
|
+
@ScopedMethod
|
|
54
|
+
async def revoke_all(self, user_or_id: Any, guard: str | None = None) -> int:
|
|
55
|
+
return await call(self._manager.api_tokens.revoke_all, self._user_id(user_or_id, guard))
|
|
56
|
+
|
|
57
|
+
def _user_id(self, user_or_id: Any, guard: str | None) -> Any:
|
|
58
|
+
if isinstance(user_or_id, (int, str)):
|
|
59
|
+
return user_or_id
|
|
60
|
+
resolved = self._manager.guard(guard) if guard else self._manager.guard_for_driver("token")
|
|
61
|
+
if resolved is None:
|
|
62
|
+
raise RuntimeError('ApiToken needs a {"driver": "token"} guard to identify users; pass a user id instead.')
|
|
63
|
+
return resolved.provider.get_identifier(user_or_id)
|
|
@@ -7,7 +7,7 @@ from dataclasses import dataclass
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
9
|
from ..concurrency import call
|
|
10
|
-
from ..exceptions import
|
|
10
|
+
from ..exceptions import InvalidTokenException
|
|
11
11
|
from .models import ApiTokenRecord
|
|
12
12
|
from .repository import ApiTokenRepository, generate_token_secret, hash_token_secret
|
|
13
13
|
|
|
@@ -29,7 +29,7 @@ def _check_secret(record: ApiTokenRecord | None, secret: str) -> None:
|
|
|
29
29
|
expected = record.token_hash if record is not None else _DUMMY_HASH
|
|
30
30
|
matched = secrets.compare_digest(hash_token_secret(secret), expected)
|
|
31
31
|
if record is None or not matched or record.expired:
|
|
32
|
-
raise
|
|
32
|
+
raise InvalidTokenException(_GENERIC_FAILURE)
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def _creation_fields(
|