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.
Files changed (134) hide show
  1. fastapi_startkit_auth-0.7.0/CHANGELOG.md +304 -0
  2. fastapi_startkit_auth-0.7.0/PKG-INFO +67 -0
  3. fastapi_startkit_auth-0.7.0/README.md +23 -0
  4. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/pyproject.toml +3 -3
  5. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/__init__.py +181 -0
  6. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/apitokens/facade.py +63 -0
  7. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/manager.py +2 -2
  8. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/clients/credentials.py +46 -0
  9. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/clients/models.py +12 -0
  10. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/clients/orm.py +91 -0
  11. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/clients/repository.py +61 -0
  12. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/commands/oauth2_client.py +67 -0
  13. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/config/__init__.py +37 -0
  14. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/config/auth.py +240 -0
  15. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/dependencies.py +42 -27
  16. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/exceptions.py +143 -0
  17. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/facade.py +55 -38
  18. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/authorization_code.py +82 -16
  19. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/grants/client_credentials.py +54 -0
  20. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/password.py +37 -8
  21. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/refresh.py +30 -8
  22. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/guards/guard.py +16 -10
  23. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/guards/session.py +39 -22
  24. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/guards/token.py +32 -11
  25. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/manager.py +571 -0
  26. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/middleware/context.py +69 -0
  27. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/middleware/csrf.py +41 -9
  28. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/middleware/session.py +9 -1
  29. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/orm.py +19 -0
  30. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/passwords/broker.py +11 -11
  31. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/policy.py +83 -0
  32. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/provider.py +221 -0
  33. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/publishable/migrations/2026_10_02_000001_add_resource_to_oauth_tables.py +15 -0
  34. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/publishable/migrations/2026_10_04_000001_create_oauth_clients_table.py +18 -0
  35. 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
  36. 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
  37. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/py.typed +0 -0
  38. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/request_context.py +27 -0
  39. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/routes.py +497 -0
  40. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/routes_password.py +53 -0
  41. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/routes_spa.py +1 -12
  42. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/scoped_method.py +20 -0
  43. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/security/jwt.py +21 -5
  44. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/sessions/facade.py +48 -0
  45. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/orm.py +6 -12
  46. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/state.py +3 -0
  47. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/store.py +21 -11
  48. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/tokens/models.py +9 -0
  49. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/tokens/orm.py +70 -16
  50. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/tokens/repository.py +47 -8
  51. fastapi_startkit_auth-0.7.0/src/fastapi_startkit_auth/tokens/service.py +469 -0
  52. fastapi_startkit_auth-0.7.0/tests/conftest.py +303 -0
  53. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_api_tokens.py +21 -28
  54. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_async_auth.py +34 -29
  55. fastapi_startkit_auth-0.7.0/tests/test_async_refresh_race.py +77 -0
  56. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_async_safety.py +47 -33
  57. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_async_stores.py +26 -0
  58. fastapi_startkit_auth-0.7.0/tests/test_class_config.py +128 -0
  59. fastapi_startkit_auth-0.7.0/tests/test_client_scopes.py +325 -0
  60. fastapi_startkit_auth-0.7.0/tests/test_clients.py +58 -0
  61. fastapi_startkit_auth-0.7.0/tests/test_facade_api.py +174 -0
  62. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_grants.py +52 -17
  63. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_guard_registry.py +13 -8
  64. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_integration.py +36 -14
  65. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_jwt.py +25 -3
  66. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_manager.py +8 -9
  67. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_model_provider.py +3 -3
  68. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_multi_provider.py +25 -18
  69. fastapi_startkit_auth-0.7.0/tests/test_oauth21.py +549 -0
  70. fastapi_startkit_auth-0.7.0/tests/test_oauth2_client_command.py +146 -0
  71. fastapi_startkit_auth-0.7.0/tests/test_oauth_config_aliases.py +29 -0
  72. fastapi_startkit_auth-0.7.0/tests/test_oauth_hardening.py +406 -0
  73. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_password_reset.py +5 -5
  74. fastapi_startkit_auth-0.7.0/tests/test_resource_binding.py +500 -0
  75. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_security_fixes.py +10 -11
  76. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_session_auth.py +28 -28
  77. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_spa_csrf.py +99 -48
  78. fastapi_startkit_auth-0.7.0/tests/test_startkit_provider.py +193 -0
  79. fastapi_startkit_auth-0.7.0/tests/test_store_names.py +49 -0
  80. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_sync_regression.py +40 -15
  81. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_token_service.py +12 -8
  82. fastapi_startkit_auth-0.6.2/CHANGELOG.md +0 -147
  83. fastapi_startkit_auth-0.6.2/PKG-INFO +0 -351
  84. fastapi_startkit_auth-0.6.2/README.md +0 -307
  85. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/__init__.py +0 -103
  86. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/application.py +0 -38
  87. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/clients/repository.py +0 -71
  88. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/config.py +0 -109
  89. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/exceptions.py +0 -90
  90. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/grants/client_credentials.py +0 -30
  91. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/manager.py +0 -406
  92. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/provider.py +0 -85
  93. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/routes.py +0 -368
  94. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/startkit.py +0 -41
  95. fastapi_startkit_auth-0.6.2/src/fastapi_startkit_auth/tokens/service.py +0 -316
  96. fastapi_startkit_auth-0.6.2/tests/conftest.py +0 -166
  97. fastapi_startkit_auth-0.6.2/tests/test_clients.py +0 -58
  98. fastapi_startkit_auth-0.6.2/tests/test_startkit_provider.py +0 -131
  99. /fastapi_startkit_auth-0.6.2/LICENSE → /fastapi_startkit_auth-0.7.0/LICENSE.md +0 -0
  100. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/__init__.py +0 -0
  101. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/models.py +0 -0
  102. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/orm.py +0 -0
  103. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/repository.py +0 -0
  104. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/clients/__init__.py +0 -0
  105. /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
  106. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/concurrency.py +0 -0
  107. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/__init__.py +0 -0
  108. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/pkce.py +0 -0
  109. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/guards/__init__.py +0 -0
  110. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/middleware/__init__.py +0 -0
  111. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/passwords/__init__.py +0 -0
  112. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/passwords/repository.py +0 -0
  113. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/__init__.py +0 -0
  114. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/base.py +0 -0
  115. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/fields.py +0 -0
  116. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/memory.py +0 -0
  117. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/model.py +0 -0
  118. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/cors.py +0 -0
  119. {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
  120. {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
  121. {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
  122. {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
  123. {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
  124. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/security/__init__.py +0 -0
  125. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/security/hashing.py +0 -0
  126. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/__init__.py +0 -0
  127. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/models.py +0 -0
  128. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/tokens/__init__.py +0 -0
  129. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_hashing.py +0 -0
  130. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_model_provider_postgres.py +0 -0
  131. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_reset_throttle_enumeration.py +0 -0
  132. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_session_store.py +0 -0
  133. {fastapi_startkit_auth-0.6.2 → fastapi_startkit_auth-0.7.0}/tests/test_token_repository.py +0 -0
  134. {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.6.2"
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.8",
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 InvalidToken
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 InvalidToken(_GENERIC_FAILURE)
32
+ raise InvalidTokenException(_GENERIC_FAILURE)
33
33
 
34
34
 
35
35
  def _creation_fields(