fastapi-startkit-auth 0.6.3__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 (119) hide show
  1. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/CHANGELOG.md +9 -1
  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.3 → fastapi_startkit_auth-0.7.0}/pyproject.toml +2 -2
  5. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/__init__.py +39 -7
  6. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/manager.py +2 -2
  7. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/commands/oauth2_client.py +2 -2
  8. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/config/__init__.py +4 -0
  9. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/config/auth.py +36 -19
  10. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/dependencies.py +6 -6
  11. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/exceptions.py +41 -16
  12. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/facade.py +4 -4
  13. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/authorization_code.py +7 -7
  14. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/client_credentials.py +2 -2
  15. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/password.py +5 -5
  16. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/refresh.py +4 -4
  17. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/guards/guard.py +5 -5
  18. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/guards/session.py +9 -9
  19. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/guards/token.py +9 -9
  20. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/manager.py +21 -21
  21. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/middleware/csrf.py +2 -2
  22. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/passwords/broker.py +11 -11
  23. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/policy.py +12 -12
  24. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/provider.py +4 -4
  25. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/routes.py +38 -38
  26. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/routes_password.py +2 -2
  27. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/security/jwt.py +2 -2
  28. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/facade.py +2 -2
  29. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/tokens/service.py +11 -11
  30. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/conftest.py +2 -2
  31. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_api_tokens.py +12 -12
  32. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_async_auth.py +6 -6
  33. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_async_refresh_race.py +7 -7
  34. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_async_safety.py +3 -3
  35. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_class_config.py +3 -3
  36. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_client_scopes.py +6 -6
  37. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_facade_api.py +2 -2
  38. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_grants.py +8 -8
  39. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_integration.py +24 -0
  40. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_jwt.py +7 -7
  41. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_manager.py +2 -2
  42. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_multi_provider.py +1 -1
  43. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_oauth21.py +2 -2
  44. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_oauth2_client_command.py +3 -3
  45. fastapi_startkit_auth-0.7.0/tests/test_oauth_config_aliases.py +29 -0
  46. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_oauth_hardening.py +19 -19
  47. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_password_reset.py +5 -5
  48. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_resource_binding.py +25 -25
  49. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_security_fixes.py +2 -2
  50. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_session_auth.py +6 -6
  51. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_spa_csrf.py +2 -2
  52. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_startkit_provider.py +3 -3
  53. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_store_names.py +9 -9
  54. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_sync_regression.py +7 -7
  55. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_token_service.py +8 -8
  56. fastapi_startkit_auth-0.6.3/PKG-INFO +0 -398
  57. fastapi_startkit_auth-0.6.3/README.md +0 -354
  58. /fastapi_startkit_auth-0.6.3/LICENSE → /fastapi_startkit_auth-0.7.0/LICENSE.md +0 -0
  59. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/__init__.py +0 -0
  60. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/facade.py +0 -0
  61. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/models.py +0 -0
  62. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/orm.py +0 -0
  63. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/apitokens/repository.py +0 -0
  64. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/clients/__init__.py +0 -0
  65. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/clients/credentials.py +0 -0
  66. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/clients/models.py +0 -0
  67. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/clients/orm.py +0 -0
  68. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/clients/repository.py +0 -0
  69. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/commands/__init__.py +0 -0
  70. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/concurrency.py +0 -0
  71. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/__init__.py +0 -0
  72. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/grants/pkce.py +0 -0
  73. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/guards/__init__.py +0 -0
  74. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/middleware/__init__.py +0 -0
  75. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/middleware/context.py +0 -0
  76. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/middleware/session.py +0 -0
  77. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/orm.py +0 -0
  78. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/passwords/__init__.py +0 -0
  79. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/passwords/repository.py +0 -0
  80. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/__init__.py +0 -0
  81. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/base.py +0 -0
  82. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/fields.py +0 -0
  83. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/memory.py +0 -0
  84. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/providers/model.py +0 -0
  85. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/cors.py +0 -0
  86. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000001_create_sessions_table.py +0 -0
  87. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000002_create_personal_api_tokens_table.py +0 -0
  88. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000003_create_oauth_access_tokens_table.py +0 -0
  89. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000004_create_oauth_refresh_tokens_table.py +0 -0
  90. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000005_create_oauth_auth_codes_table.py +0 -0
  91. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/migrations/2026_10_02_000001_add_resource_to_oauth_tables.py +0 -0
  92. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/migrations/2026_10_04_000001_create_oauth_clients_table.py +0 -0
  93. {fastapi_startkit_auth-0.6.3 → 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 +0 -0
  94. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/publishable/migrations/2026_10_05_000001_add_scopes_to_oauth_clients_table.py +0 -0
  95. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/py.typed +0 -0
  96. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/request_context.py +0 -0
  97. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/routes_spa.py +0 -0
  98. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/scoped_method.py +0 -0
  99. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/security/__init__.py +0 -0
  100. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/security/hashing.py +0 -0
  101. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/__init__.py +0 -0
  102. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/models.py +0 -0
  103. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/orm.py +0 -0
  104. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/state.py +0 -0
  105. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/sessions/store.py +0 -0
  106. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/tokens/__init__.py +0 -0
  107. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/tokens/models.py +0 -0
  108. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/tokens/orm.py +0 -0
  109. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/src/fastapi_startkit_auth/tokens/repository.py +0 -0
  110. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_async_stores.py +0 -0
  111. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_clients.py +0 -0
  112. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_guard_registry.py +0 -0
  113. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_hashing.py +0 -0
  114. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_model_provider.py +0 -0
  115. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_model_provider_postgres.py +0 -0
  116. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_reset_throttle_enumeration.py +0 -0
  117. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_session_store.py +0 -0
  118. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_token_repository.py +0 -0
  119. {fastapi_startkit_auth-0.6.3 → fastapi_startkit_auth-0.7.0}/tests/test_user_provider.py +0 -0
@@ -66,7 +66,7 @@ Each item lists what changed and what to do.
66
66
  in `SessionConfig.csrf_exempt_paths`.
67
67
  - **OAuth client store defaults to `"database"`** (the ORM `oauth_clients`
68
68
  table). *Migrate:* publish and run the migrations, or set
69
- `OAuth2Config(clients=OAuthClientsConfig(store="memory"))`.
69
+ `OAuth2Config(clients=OAuthClientConfig(store="memory"))`.
70
70
  - **The in-memory OAuth client store is async.** `InMemoryClientRepository`
71
71
  methods (`register`, `add`, `find`, `all`, `authenticate`, `revoke`,
72
72
  `delete`) are coroutines, like `OrmClientRepository`, and `store="memory"` now
@@ -74,6 +74,14 @@ Each item lists what changed and what to do.
74
74
  synchronous custom store passed with `store="instance"` keeps the sync grants.
75
75
  *Migrate:*
76
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.
77
85
  - **Persistent stores are named `"database"`.** `store="orm"` still works for
78
86
  sessions, API tokens, OAuth tokens and OAuth clients, but emits a
79
87
  `DeprecationWarning` and will be removed in a future release. *Migrate:*
@@ -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.3"
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",
@@ -18,7 +18,9 @@ from .config import (
18
18
  ApiTokenConfig,
19
19
  AuthConfig,
20
20
  OAuth2Config,
21
+ OAuthClientConfig,
21
22
  OAuthClientsConfig,
23
+ OAuthTokenConfig,
22
24
  OAuthTokensConfig,
23
25
  SessionConfig,
24
26
  )
@@ -31,18 +33,32 @@ from .dependencies import (
31
33
  )
32
34
  from .exceptions import (
33
35
  AccessDenied,
36
+ AccessDeniedException,
37
+ AuthenticationException,
34
38
  AuthError,
39
+ AuthException,
35
40
  CsrfTokenMismatch,
41
+ CsrfTokenMismatchException,
36
42
  InsufficientScope,
37
43
  InvalidClient,
44
+ InvalidClientException,
45
+ InvalidCredentialsException,
38
46
  InvalidGrant,
47
+ InvalidGrantException,
39
48
  InvalidScope,
49
+ InvalidScopeException,
40
50
  InvalidSession,
41
51
  InvalidTarget,
52
+ InvalidTargetException,
42
53
  InvalidToken,
54
+ InvalidTokenException,
55
+ MissingScopeException,
43
56
  UnauthorizedClient,
57
+ UnauthorizedClientException,
44
58
  UnsupportedGrantType,
59
+ UnsupportedGrantTypeException,
45
60
  UnsupportedResponseType,
61
+ UnsupportedResponseTypeException,
46
62
  )
47
63
  from .policy import GrantPolicy
48
64
  from .facade import AsyncAuth, Auth
@@ -62,13 +78,15 @@ from .sessions.models import SessionRecord
62
78
  from .sessions.store import InMemorySessionStore, SessionStore
63
79
  from .tokens.service import AsyncTokenService, TokenService
64
80
 
65
- __version__ = "0.6.3"
81
+ __version__ = "0.7.0"
66
82
 
67
83
  __all__ = (
68
84
  "AuthConfig",
69
85
  "SessionConfig",
70
86
  "OAuth2Config",
87
+ "OAuthClientConfig",
71
88
  "OAuthClientsConfig",
89
+ "OAuthTokenConfig",
72
90
  "OAuthTokensConfig",
73
91
  "ApiTokenConfig",
74
92
  "AuthProvider",
@@ -85,23 +103,37 @@ __all__ = (
85
103
  "InMemoryClientRepository",
86
104
  "OrmClientRepository",
87
105
  "OrmSessionStore",
88
- "AccessDenied",
89
- "UnauthorizedClient",
90
- "UnsupportedGrantType",
91
- "UnsupportedResponseType",
106
+ "AccessDeniedException",
107
+ "UnauthorizedClientException",
108
+ "UnsupportedGrantTypeException",
109
+ "UnsupportedResponseTypeException",
92
110
  "current_user",
93
111
  "optional_user",
94
112
  "require_scopes",
95
113
  "require_abilities",
96
- "AuthError",
114
+ "AuthException",
97
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",
98
131
  "InvalidGrant",
99
132
  "InvalidClient",
100
133
  "InvalidToken",
101
134
  "InsufficientScope",
102
135
  "InvalidScope",
103
136
  "InvalidTarget",
104
- "GrantPolicy",
105
137
  "InvalidSession",
106
138
  "CsrfTokenMismatch",
107
139
  "Auth",
@@ -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(
@@ -5,7 +5,7 @@ from cleo.helpers import option
5
5
  from fastapi_startkit.console import Command
6
6
 
7
7
  from ..concurrency import call
8
- from ..exceptions import InvalidScope
8
+ from ..exceptions import InvalidScopeException
9
9
 
10
10
 
11
11
  def _valid_redirect(uri: str | None) -> bool:
@@ -45,7 +45,7 @@ class OAuth2ClientCommand(Command):
45
45
  return 1
46
46
  try:
47
47
  manager.grant_policy.check_scopes(scopes)
48
- except InvalidScope as error:
48
+ except InvalidScopeException as error:
49
49
  self.line_error(str(error))
50
50
  return 1
51
51
  client, secret = asyncio.run(
@@ -6,7 +6,9 @@ from .auth import (
6
6
  AuthConfig,
7
7
  OAuth2Config,
8
8
  OAuth2Guard,
9
+ OAuthClientConfig,
9
10
  OAuthClientsConfig,
11
+ OAuthTokenConfig,
10
12
  OAuthTokensConfig,
11
13
  SessionConfig,
12
14
  SessionGuard,
@@ -19,7 +21,9 @@ __all__ = (
19
21
  "AuthConfig",
20
22
  "SessionConfig",
21
23
  "OAuth2Config",
24
+ "OAuthClientConfig",
22
25
  "OAuthClientsConfig",
26
+ "OAuthTokenConfig",
23
27
  "OAuthTokensConfig",
24
28
  "ApiTokenConfig",
25
29
  "SessionGuard",
@@ -8,14 +8,14 @@ from typing import Any, Literal, TypeVar
8
8
  ConfigT = TypeVar("ConfigT")
9
9
 
10
10
 
11
- def _canonical_store(store: str, section: str) -> str:
12
- if store == "orm":
11
+ def _canonical_driver(driver: str, section: str) -> str:
12
+ if driver == "orm":
13
13
  # stacklevel 4: this helper, __post_init__, the dataclass __init__, then the caller.
14
14
  warnings.warn(
15
- f'{section}(store="orm") is deprecated; use store="database".', DeprecationWarning, stacklevel=4
15
+ f'{section}(driver="orm") is deprecated; use driver="database".', DeprecationWarning, stacklevel=4
16
16
  )
17
17
  return "database"
18
- return store
18
+ return driver
19
19
 
20
20
 
21
21
  @dataclass
@@ -41,11 +41,11 @@ class ApiTokenGuard:
41
41
  class SessionConfig:
42
42
  """Cookie sessions, enabled by ``AuthSessionProvider``.
43
43
 
44
- ``store`` is "memory", "database" (the ORM ``sessions`` table; "orm" is a
44
+ ``driver`` is "memory", "database" (the ORM ``sessions`` table; "orm" is a
45
45
  deprecated alias) or "instance" (a ready-made store under ``instance``).
46
46
  """
47
47
 
48
- store: str = "memory"
48
+ driver: str = "memory"
49
49
  connection: str | None = None
50
50
  instance: Any = None
51
51
  cookie: str = "startkit_session"
@@ -63,7 +63,7 @@ class SessionConfig:
63
63
  csrf_exempt_paths: list[str] = field(default_factory=list)
64
64
 
65
65
  def __post_init__(self) -> None:
66
- self.store = _canonical_store(self.store, "SessionConfig")
66
+ self.driver = _canonical_driver(self.driver, "SessionConfig")
67
67
 
68
68
 
69
69
  @dataclass
@@ -74,7 +74,7 @@ class ApiTokenConfig:
74
74
  accept the ``session_guard`` cookie session from those origins.
75
75
  """
76
76
 
77
- store: str = "memory"
77
+ driver: str = "memory"
78
78
  connection: str | None = None
79
79
  instance: Any = None
80
80
  header: str = "Authorization"
@@ -84,7 +84,7 @@ class ApiTokenConfig:
84
84
  session_guard: str = "web"
85
85
 
86
86
  def __post_init__(self) -> None:
87
- self.store = _canonical_store(self.store, "ApiTokenConfig")
87
+ self.driver = _canonical_driver(self.driver, "ApiTokenConfig")
88
88
 
89
89
  @property
90
90
  def stateful(self) -> bool:
@@ -92,25 +92,42 @@ class ApiTokenConfig:
92
92
 
93
93
 
94
94
  @dataclass
95
- class OAuthClientsConfig:
95
+ class OAuthClientConfig:
96
96
  """Where OAuth clients live: "database" (the ORM ``oauth_clients`` table), "memory" or "instance"."""
97
97
 
98
- store: str = "database"
98
+ driver: str = "database"
99
99
  connection: str | None = None
100
100
  instance: Any = None
101
101
 
102
102
  def __post_init__(self) -> None:
103
- self.store = _canonical_store(self.store, "OAuthClientsConfig")
103
+ self.driver = _canonical_driver(self.driver, "OAuthClientConfig")
104
104
 
105
105
 
106
106
  @dataclass
107
- class OAuthTokensConfig:
108
- store: str = "memory"
107
+ class OAuthTokenConfig:
108
+ driver: str = "memory"
109
109
  connection: str | None = None
110
110
  instance: Any = None
111
111
 
112
112
  def __post_init__(self) -> None:
113
- self.store = _canonical_store(self.store, "OAuthTokensConfig")
113
+ self.driver = _canonical_driver(self.driver, "OAuthTokenConfig")
114
+
115
+
116
+ def _deprecated_alias(config_type: type[ConfigT], old_name: str) -> type[ConfigT]:
117
+ class Alias(config_type):
118
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
119
+ warnings.warn(
120
+ f"{old_name} is deprecated; use {config_type.__name__}.", DeprecationWarning, stacklevel=2
121
+ )
122
+ super().__init__(*args, **kwargs)
123
+
124
+ Alias.__name__ = old_name
125
+ Alias.__qualname__ = old_name
126
+ return Alias
127
+
128
+
129
+ OAuthClientsConfig = _deprecated_alias(OAuthClientConfig, "OAuthClientsConfig")
130
+ OAuthTokensConfig = _deprecated_alias(OAuthTokenConfig, "OAuthTokensConfig")
114
131
 
115
132
 
116
133
  DEFAULT_GRANT_TYPES = ("authorization_code", "client_credentials", "refresh_token")
@@ -145,12 +162,12 @@ class OAuth2Config:
145
162
  require_redirect_uri: bool = True
146
163
  grant_types: list[str] = field(default_factory=lambda: list(DEFAULT_GRANT_TYPES))
147
164
  authorization_guard: str | None = None
148
- tokens: OAuthTokensConfig = field(default_factory=OAuthTokensConfig)
149
- clients: OAuthClientsConfig = field(default_factory=OAuthClientsConfig)
165
+ tokens: OAuthTokenConfig = field(default_factory=OAuthTokenConfig)
166
+ clients: OAuthClientConfig = field(default_factory=OAuthClientConfig)
150
167
 
151
168
  def __post_init__(self) -> None:
152
- self.tokens = resolve_config(self.tokens, OAuthTokensConfig)
153
- self.clients = resolve_config(self.clients, OAuthClientsConfig)
169
+ self.tokens = resolve_config(self.tokens, OAuthTokenConfig)
170
+ self.clients = resolve_config(self.clients, OAuthClientConfig)
154
171
 
155
172
 
156
173
  # v0.6 AuthConfig attributes that moved to OAuth2Config; still read, with a warning.
@@ -6,7 +6,7 @@ from fastapi import Depends, Request
6
6
  from fastapi.security import OAuth2PasswordBearer
7
7
 
8
8
  from .concurrency import call
9
- from .exceptions import AuthError, InsufficientScope, InvalidToken
9
+ from .exceptions import AuthenticationException, AuthException, InvalidTokenException, MissingScopeException
10
10
  from .guards.guard import AuthContext
11
11
  from .manager import AuthManager
12
12
  from .request_context import _auth_request
@@ -46,7 +46,7 @@ async def resolve_context(request: Request, manager: AuthManager, guard: str | N
46
46
  return await call(authenticate, request)
47
47
  token = _bearer_token(request)
48
48
  if not token:
49
- raise InvalidToken("Not authenticated.")
49
+ raise AuthenticationException("Not authenticated.")
50
50
  return await call(resolved.user_from_token, token)
51
51
 
52
52
 
@@ -62,14 +62,14 @@ async def current_context(
62
62
  async def auth(context: AuthContext = Depends(current_context)) -> AuthContext:
63
63
  """Like ``current_context`` but also rejects user-less (client_credentials) tokens."""
64
64
  if context.user is None:
65
- raise InvalidToken("This token is not associated with a user.")
65
+ raise InvalidTokenException("This token is not associated with a user.")
66
66
  return context
67
67
 
68
68
 
69
69
  def current_user(context: AuthContext = Depends(current_context)) -> Any:
70
70
  """Return the authenticated user model (401 if the token carries no user)."""
71
71
  if context.user is None:
72
- raise InvalidToken("This token is not associated with a user.")
72
+ raise InvalidTokenException("This token is not associated with a user.")
73
73
  return context.user
74
74
 
75
75
 
@@ -81,7 +81,7 @@ async def optional_user(
81
81
  """Return the authenticated user, or ``None`` if unauthenticated/invalid."""
82
82
  try:
83
83
  return (await resolve_context(request, manager)).user
84
- except AuthError:
84
+ except AuthException:
85
85
  return None
86
86
 
87
87
 
@@ -96,7 +96,7 @@ def require_scopes(*scopes: str, mode: str = "all") -> Callable[..., AuthContext
96
96
  def dependency(context: AuthContext = Depends(current_context)) -> AuthContext:
97
97
  ok = context.can_any(required) if mode == "any" else context.can_all(required)
98
98
  if not ok:
99
- raise InsufficientScope(
99
+ raise MissingScopeException(
100
100
  f"Requires scope(s): {' '.join(required)} (mode={mode})."
101
101
  )
102
102
  return context
@@ -1,4 +1,4 @@
1
- """Auth error hierarchy.
1
+ """Auth exception hierarchy.
2
2
 
3
3
  Error codes follow the OAuth2 error registry (RFC 6749 §5.2) so they can be
4
4
  surfaced verbatim in token-endpoint responses.
@@ -6,7 +6,7 @@ surfaced verbatim in token-endpoint responses.
6
6
  from __future__ import annotations
7
7
 
8
8
 
9
- class AuthError(Exception):
9
+ class AuthException(Exception):
10
10
  """Base class for all authentication/authorization failures."""
11
11
 
12
12
  error = "invalid_request"
@@ -20,99 +20,124 @@ class AuthError(Exception):
20
20
  return {"error": self.error, "error_description": self.description}
21
21
 
22
22
 
23
- class InvalidRequest(AuthError):
23
+ class InvalidRequestException(AuthException):
24
24
  """The request is missing a required parameter or is otherwise malformed."""
25
25
 
26
26
  error = "invalid_request"
27
27
  status_code = 400
28
28
 
29
29
 
30
- class InvalidClient(AuthError):
30
+ class InvalidClientException(AuthException):
31
31
  """Client authentication failed (unknown client or bad secret)."""
32
32
 
33
33
  error = "invalid_client"
34
34
  status_code = 401
35
35
 
36
36
 
37
- class InvalidGrant(AuthError):
37
+ class InvalidGrantException(AuthException):
38
38
  """The provided credentials, code, or refresh token are invalid or expired."""
39
39
 
40
40
  error = "invalid_grant"
41
41
  status_code = 400
42
42
 
43
43
 
44
- class UnsupportedGrantType(AuthError):
44
+ class InvalidCredentialsException(InvalidGrantException):
45
+ """The username or password is incorrect (password grant)."""
46
+
47
+ error = "invalid_grant"
48
+ status_code = 400
49
+
50
+
51
+ class UnsupportedGrantTypeException(AuthException):
45
52
  """The authorization server does not support this grant type."""
46
53
 
47
54
  error = "unsupported_grant_type"
48
55
  status_code = 400
49
56
 
50
57
 
51
- class UnauthorizedClient(AuthError):
58
+ class UnauthorizedClientException(AuthException):
52
59
  """The client is not permitted to use this grant type."""
53
60
 
54
61
  error = "unauthorized_client"
55
62
  status_code = 400
56
63
 
57
64
 
58
- class InvalidToken(AuthError):
65
+ class InvalidTokenException(AuthException):
59
66
  """The access token is expired, malformed, revoked, or otherwise invalid."""
60
67
 
61
68
  error = "invalid_token"
62
69
  status_code = 401
63
70
 
64
71
 
65
- class InvalidSession(AuthError):
72
+ class AuthenticationException(AuthException):
66
73
  """The session cookie is missing, expired, or no longer valid."""
67
74
 
68
75
  error = "invalid_session"
69
76
  status_code = 401
70
77
 
71
78
 
72
- class CsrfTokenMismatch(AuthError):
79
+ class CsrfTokenMismatchException(AuthException):
73
80
  """The request is missing a valid CSRF token for this session."""
74
81
 
75
82
  error = "csrf_token_mismatch"
76
83
  status_code = 403
77
84
 
78
85
 
79
- class InsufficientScope(AuthError):
86
+ class MissingScopeException(AuthException):
80
87
  """The token does not carry the scopes required for this resource."""
81
88
 
82
89
  error = "insufficient_scope"
83
90
  status_code = 403
84
91
 
85
92
 
86
- class ThrottleException(AuthError):
93
+ class ThrottleException(AuthException):
87
94
  """Too many requests for this action; retry later."""
88
95
 
89
96
  error = "throttled"
90
97
  status_code = 429
91
98
 
92
99
 
93
- class InvalidScope(AuthError):
100
+ class InvalidScopeException(AuthException):
94
101
  """The requested scope is unknown or not allowed."""
95
102
 
96
103
  error = "invalid_scope"
97
104
  status_code = 400
98
105
 
99
106
 
100
- class InvalidTarget(AuthError):
107
+ class InvalidTargetException(AuthException):
101
108
  """The requested resource is unknown or does not match the grant (RFC 8707)."""
102
109
 
103
110
  error = "invalid_target"
104
111
  status_code = 400
105
112
 
106
113
 
107
- class UnsupportedResponseType(AuthError):
114
+ class UnsupportedResponseTypeException(AuthException):
108
115
  """The authorization endpoint only issues authorization codes."""
109
116
 
110
117
  error = "unsupported_response_type"
111
118
  status_code = 400
112
119
 
113
120
 
114
- class AccessDenied(AuthError):
121
+ class AccessDeniedException(AuthException):
115
122
  """The resource owner did not approve the authorization request."""
116
123
 
117
124
  error = "access_denied"
118
125
  status_code = 400
126
+
127
+
128
+ # Deprecated aliases for the names used before the Exception-suffix rename.
129
+ # Remove them in the next major release.
130
+ AuthError = AuthException
131
+ InvalidRequest = InvalidRequestException
132
+ InvalidClient = InvalidClientException
133
+ InvalidGrant = InvalidGrantException
134
+ UnsupportedGrantType = UnsupportedGrantTypeException
135
+ UnauthorizedClient = UnauthorizedClientException
136
+ InvalidToken = InvalidTokenException
137
+ InvalidSession = AuthenticationException
138
+ CsrfTokenMismatch = CsrfTokenMismatchException
139
+ InsufficientScope = MissingScopeException
140
+ InvalidScope = InvalidScopeException
141
+ InvalidTarget = InvalidTargetException
142
+ UnsupportedResponseType = UnsupportedResponseTypeException
143
+ AccessDenied = AccessDeniedException
@@ -6,7 +6,7 @@ from fastapi import Depends, Request
6
6
 
7
7
  from .dependencies import _bearer_token, get_auth_manager, resolve_context
8
8
  from .concurrency import call, ensure_sync
9
- from .exceptions import AuthError
9
+ from .exceptions import AuthException
10
10
  from .guards.session import AsyncSessionGuard, SessionGuard
11
11
  from .manager import AuthManager
12
12
  from .providers.base import user_is_active, user_is_active_async
@@ -26,7 +26,7 @@ class Auth:
26
26
  @api.post("/login")
27
27
  def login(payload: Credentials, auth: Auth = Depends(Auth.scoped)):
28
28
  if not auth.attempt(payload.model_dump()):
29
- raise InvalidSession("Invalid credentials.")
29
+ raise AuthenticationException("Invalid credentials.")
30
30
  return {"ok": True}
31
31
 
32
32
  @api.post("/logout")
@@ -97,7 +97,7 @@ class Auth:
97
97
  if token is None:
98
98
  return None
99
99
  return ensure_sync(resolved.user_from_token(token), "The guard's user_from_token").user
100
- except AuthError:
100
+ except AuthException:
101
101
  return None
102
102
 
103
103
  @ScopedMethod
@@ -174,7 +174,7 @@ class AsyncAuth:
174
174
  async def user(self, guard: str | None = None) -> Any | None:
175
175
  try:
176
176
  return (await resolve_context(self._request, self._manager, guard)).user
177
- except AuthError:
177
+ except AuthException:
178
178
  return None
179
179
 
180
180
  @ScopedMethod