fastapi-startkit-auth 0.6.1__tar.gz → 0.6.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/PKG-INFO +1 -1
  2. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/pyproject.toml +1 -1
  3. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/__init__.py +1 -1
  4. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/providers/model.py +15 -3
  5. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/conftest.py +1 -0
  6. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_async_stores.py +2 -0
  7. fastapi_startkit_auth-0.6.2/tests/test_model_provider_postgres.py +74 -0
  8. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/CHANGELOG.md +0 -0
  9. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/LICENSE +0 -0
  10. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/README.md +0 -0
  11. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/apitokens/__init__.py +0 -0
  12. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/apitokens/manager.py +0 -0
  13. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/apitokens/models.py +0 -0
  14. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/apitokens/orm.py +0 -0
  15. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/apitokens/repository.py +0 -0
  16. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/application.py +0 -0
  17. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/clients/__init__.py +0 -0
  18. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/clients/models.py +0 -0
  19. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/clients/repository.py +0 -0
  20. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/concurrency.py +0 -0
  21. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/config.py +0 -0
  22. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/dependencies.py +0 -0
  23. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/exceptions.py +0 -0
  24. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/facade.py +0 -0
  25. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/grants/__init__.py +0 -0
  26. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/grants/authorization_code.py +0 -0
  27. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/grants/client_credentials.py +0 -0
  28. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/grants/password.py +0 -0
  29. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/grants/pkce.py +0 -0
  30. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/grants/refresh.py +0 -0
  31. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/guards/__init__.py +0 -0
  32. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/guards/guard.py +0 -0
  33. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/guards/session.py +0 -0
  34. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/guards/token.py +0 -0
  35. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/manager.py +0 -0
  36. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/middleware/__init__.py +0 -0
  37. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/middleware/csrf.py +0 -0
  38. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/middleware/session.py +0 -0
  39. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/orm.py +0 -0
  40. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/passwords/__init__.py +0 -0
  41. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/passwords/broker.py +0 -0
  42. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/passwords/repository.py +0 -0
  43. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/provider.py +0 -0
  44. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/providers/__init__.py +0 -0
  45. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/providers/base.py +0 -0
  46. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/providers/fields.py +0 -0
  47. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/providers/memory.py +0 -0
  48. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/publishable/cors.py +0 -0
  49. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000001_create_sessions_table.py +0 -0
  50. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000002_create_personal_api_tokens_table.py +0 -0
  51. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000003_create_oauth_access_tokens_table.py +0 -0
  52. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000004_create_oauth_refresh_tokens_table.py +0 -0
  53. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/publishable/migrations/2026_09_30_000005_create_oauth_auth_codes_table.py +0 -0
  54. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/py.typed +0 -0
  55. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/routes.py +0 -0
  56. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/routes_spa.py +0 -0
  57. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/security/__init__.py +0 -0
  58. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/security/hashing.py +0 -0
  59. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/security/jwt.py +0 -0
  60. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/sessions/__init__.py +0 -0
  61. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/sessions/models.py +0 -0
  62. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/sessions/orm.py +0 -0
  63. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/sessions/state.py +0 -0
  64. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/sessions/store.py +0 -0
  65. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/startkit.py +0 -0
  66. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/tokens/__init__.py +0 -0
  67. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/tokens/models.py +0 -0
  68. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/tokens/orm.py +0 -0
  69. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/tokens/repository.py +0 -0
  70. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/src/fastapi_startkit_auth/tokens/service.py +0 -0
  71. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_api_tokens.py +0 -0
  72. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_async_auth.py +0 -0
  73. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_async_safety.py +0 -0
  74. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_clients.py +0 -0
  75. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_grants.py +0 -0
  76. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_guard_registry.py +0 -0
  77. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_hashing.py +0 -0
  78. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_integration.py +0 -0
  79. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_jwt.py +0 -0
  80. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_manager.py +0 -0
  81. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_model_provider.py +0 -0
  82. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_multi_provider.py +0 -0
  83. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_password_reset.py +0 -0
  84. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_reset_throttle_enumeration.py +0 -0
  85. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_security_fixes.py +0 -0
  86. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_session_auth.py +0 -0
  87. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_session_store.py +0 -0
  88. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_spa_csrf.py +0 -0
  89. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_startkit_provider.py +0 -0
  90. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_sync_regression.py +0 -0
  91. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_token_repository.py +0 -0
  92. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_token_service.py +0 -0
  93. {fastapi_startkit_auth-0.6.1 → fastapi_startkit_auth-0.6.2}/tests/test_user_provider.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-startkit-auth
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: Laravel-style authentication for FastAPI: OAuth2/JWT authorization server, cookie/session guard, SPA + CSRF protection, and Sanctum-style API tokens.
5
5
  Keywords: fastapi,oauth2,jwt,auth,passport,sanctum,authentication,authorization,security,tokens,session,cookie,csrf,spa,starter-kit
6
6
  Author: Bedram Tamang
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fastapi-startkit-auth"
3
- version = "0.6.1"
3
+ version = "0.6.2"
4
4
  description = "Laravel-style authentication for FastAPI: OAuth2/JWT authorization server, cookie/session guard, SPA + CSRF protection, and Sanctum-style API tokens."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -43,7 +43,7 @@ from .sessions.models import SessionRecord
43
43
  from .sessions.store import InMemorySessionStore, SessionStore
44
44
  from .tokens.service import AsyncTokenService, TokenService
45
45
 
46
- __version__ = "0.6.1"
46
+ __version__ = "0.6.2"
47
47
 
48
48
  __all__ = (
49
49
  "AuthConfig",
@@ -2,13 +2,25 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import secrets
5
- from typing import Any
5
+ from typing import Any, get_type_hints
6
6
 
7
7
  from ..concurrency import call, is_coroutine_callable, resolve
8
8
  from ..security.hashing import BcryptHasher, Hasher
9
9
  from .fields import ActiveCheck, active_check, read_attribute, write_attribute
10
10
 
11
11
 
12
+ def _identifier_for_model(model: Any, identifier: Any, id_field: str) -> Any:
13
+ if not isinstance(identifier, str) or not identifier.isdecimal():
14
+ return identifier
15
+ try:
16
+ key_type = get_type_hints(model).get(id_field)
17
+ except (NameError, TypeError):
18
+ key_type = getattr(model, "__annotations__", {}).get(id_field)
19
+ if key_type is int or key_type == "int":
20
+ return int(identifier)
21
+ return identifier
22
+
23
+
12
24
  class ModelUserProvider:
13
25
  """User provider backed by an active-record-style model class.
14
26
 
@@ -41,7 +53,7 @@ class ModelUserProvider:
41
53
  self._hasher.verify("invalid", self._dummy_hash)
42
54
 
43
55
  def retrieve_by_id(self, identifier: Any) -> Any | None:
44
- return self._model.find(identifier)
56
+ return self._model.find(_identifier_for_model(self._model, identifier, self._id_field))
45
57
 
46
58
  def retrieve_by_credentials(self, credentials: dict[str, Any]) -> Any | None:
47
59
  username = credentials.get(self._username_field)
@@ -103,7 +115,7 @@ class AsyncModelUserProvider:
103
115
  await asyncio.to_thread(self._hasher.verify, "invalid", self._dummy_hash)
104
116
 
105
117
  async def retrieve_by_id(self, identifier: Any) -> Any | None:
106
- return await resolve(self._model.find(identifier))
118
+ return await resolve(self._model.find(_identifier_for_model(self._model, identifier, self._id_field)))
107
119
 
108
120
  async def retrieve_by_credentials(self, credentials: dict[str, Any]) -> Any | None:
109
121
  username = credentials.get(self._username_field)
@@ -148,6 +148,7 @@ def _orm_config(backend, tmp_path):
148
148
  @pytest.fixture(params=["sqlite", "postgres"])
149
149
  async def orm_database(request, tmp_path):
150
150
  """Name of a migrated ORM connection: SQLite, or the throwaway Postgres in TEST_ASYNCPG_DSN."""
151
+ pytest.importorskip("fastapi_startkit.masoniteorm.models")
151
152
  from fastapi_startkit.application import Application as StartkitApplication
152
153
  from fastapi_startkit.masoniteorm import Migrator, Model
153
154
  from fastapi_startkit.masoniteorm.connections.factory import ConnectionFactory
@@ -3,6 +3,8 @@ import time
3
3
 
4
4
  import pytest
5
5
 
6
+ pytest.importorskip("fastapi_startkit.masoniteorm.models")
7
+
6
8
  from fastapi_startkit_auth import orm
7
9
  from fastapi_startkit_auth.apitokens.orm import OrmApiTokenRepository
8
10
  from fastapi_startkit_auth.sessions.orm import OrmSessionStore
@@ -0,0 +1,74 @@
1
+ import asyncio
2
+ import os
3
+ from uuid import UUID
4
+
5
+ import asyncpg
6
+ import pytest
7
+
8
+ from fastapi_startkit_auth.providers.model import AsyncModelUserProvider, ModelUserProvider
9
+
10
+ POSTGRES_DSN = os.environ.get("TEST_ASYNCPG_DSN")
11
+
12
+
13
+ @pytest.fixture
14
+ async def postgres_users():
15
+ if not POSTGRES_DSN:
16
+ pytest.skip("TEST_ASYNCPG_DSN is not set")
17
+ connection = await asyncpg.connect(POSTGRES_DSN)
18
+ await connection.execute("DROP TABLE IF EXISTS auth_provider_int_users, auth_provider_uuid_users")
19
+ await connection.execute("CREATE TABLE auth_provider_int_users (id integer PRIMARY KEY)")
20
+ await connection.execute("INSERT INTO auth_provider_int_users (id) VALUES (7)")
21
+ await connection.execute("CREATE TABLE auth_provider_uuid_users (id uuid PRIMARY KEY)")
22
+ await connection.execute("INSERT INTO auth_provider_uuid_users (id) VALUES ($1)", UUID("d8ea1aa2-47e5-4fef-a3e8-dcd5fd68d217"))
23
+
24
+ class IntegerUser:
25
+ id: int
26
+
27
+ @classmethod
28
+ async def find(cls, identifier):
29
+ return await connection.fetchrow("SELECT id FROM auth_provider_int_users WHERE id = $1", identifier)
30
+
31
+ class UUIDUser:
32
+ id: UUID
33
+
34
+ @classmethod
35
+ async def find(cls, identifier):
36
+ return await connection.fetchrow("SELECT id FROM auth_provider_uuid_users WHERE id = $1", identifier)
37
+
38
+ yield connection, IntegerUser, UUIDUser
39
+ await connection.execute("DROP TABLE auth_provider_int_users, auth_provider_uuid_users")
40
+ await connection.close()
41
+
42
+
43
+ @pytest.mark.asyncio
44
+ async def test_async_provider_coerces_numeric_subject_for_integer_key(postgres_users):
45
+ _, integer_user, _ = postgres_users
46
+ user = await AsyncModelUserProvider(integer_user).retrieve_by_id("7")
47
+ assert user["id"] == 7
48
+
49
+
50
+ def test_sync_provider_coerces_numeric_subject_for_integer_key(postgres_users):
51
+ class SyncIntegerUser:
52
+ id: int
53
+
54
+ @classmethod
55
+ def find(cls, identifier):
56
+ async def fetch():
57
+ connection = await asyncpg.connect(POSTGRES_DSN)
58
+ try:
59
+ return await connection.fetchrow("SELECT id FROM auth_provider_int_users WHERE id = $1", identifier)
60
+ finally:
61
+ await connection.close()
62
+
63
+ return asyncio.run(fetch())
64
+
65
+ user = ModelUserProvider(SyncIntegerUser()).retrieve_by_id("7")
66
+ assert user["id"] == 7
67
+
68
+
69
+ @pytest.mark.asyncio
70
+ async def test_async_provider_preserves_uuid_subject(postgres_users):
71
+ _, _, uuid_user = postgres_users
72
+ identifier = "d8ea1aa2-47e5-4fef-a3e8-dcd5fd68d217"
73
+ user = await AsyncModelUserProvider(uuid_user).retrieve_by_id(identifier)
74
+ assert user["id"] == UUID(identifier)