fauth 0.2.0__tar.gz → 0.2.1__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 (56) hide show
  1. {fauth-0.2.0 → fauth-0.2.1}/CHANGELOG.md +16 -0
  2. {fauth-0.2.0 → fauth-0.2.1}/PKG-INFO +20 -7
  3. {fauth-0.2.0 → fauth-0.2.1}/README.md +19 -6
  4. {fauth-0.2.0 → fauth-0.2.1}/fauth/providers/protocols.py +6 -0
  5. {fauth-0.2.0 → fauth-0.2.1}/fauth/providers/provider.py +43 -3
  6. {fauth-0.2.0 → fauth-0.2.1}/fauth/testing/__init__.py +2 -1
  7. {fauth-0.2.0 → fauth-0.2.1}/fauth/testing/fakes.py +11 -0
  8. {fauth-0.2.0 → fauth-0.2.1}/pyproject.toml +1 -1
  9. {fauth-0.2.0 → fauth-0.2.1}/tests/providers/conftest.py +35 -4
  10. {fauth-0.2.0 → fauth-0.2.1}/tests/providers/test_provider.py +83 -0
  11. {fauth-0.2.0 → fauth-0.2.1}/uv.lock +1 -1
  12. {fauth-0.2.0 → fauth-0.2.1}/.github/dependabot.yml +0 -0
  13. {fauth-0.2.0 → fauth-0.2.1}/.github/workflows/cicd.yml +0 -0
  14. {fauth-0.2.0 → fauth-0.2.1}/.github/workflows/pre-commit-autoupdate.yml +0 -0
  15. {fauth-0.2.0 → fauth-0.2.1}/.gitignore +0 -0
  16. {fauth-0.2.0 → fauth-0.2.1}/.pre-commit-config.yaml +0 -0
  17. {fauth-0.2.0 → fauth-0.2.1}/LICENSE +0 -0
  18. {fauth-0.2.0 → fauth-0.2.1}/fauth/__init__.py +0 -0
  19. {fauth-0.2.0 → fauth-0.2.1}/fauth/api/__init__.py +0 -0
  20. {fauth-0.2.0 → fauth-0.2.1}/fauth/api/router.py +0 -0
  21. {fauth-0.2.0 → fauth-0.2.1}/fauth/core/__init__.py +0 -0
  22. {fauth-0.2.0 → fauth-0.2.1}/fauth/core/config.py +0 -0
  23. {fauth-0.2.0 → fauth-0.2.1}/fauth/core/exceptions.py +0 -0
  24. {fauth-0.2.0 → fauth-0.2.1}/fauth/core/schemas.py +0 -0
  25. {fauth-0.2.0 → fauth-0.2.1}/fauth/crypto/__init__.py +0 -0
  26. {fauth-0.2.0 → fauth-0.2.1}/fauth/crypto/jwt.py +0 -0
  27. {fauth-0.2.0 → fauth-0.2.1}/fauth/crypto/password.py +0 -0
  28. {fauth-0.2.0 → fauth-0.2.1}/fauth/providers/__init__.py +0 -0
  29. {fauth-0.2.0 → fauth-0.2.1}/fauth/testing/config.py +0 -0
  30. {fauth-0.2.0 → fauth-0.2.1}/fauth/testing/provider.py +0 -0
  31. {fauth-0.2.0 → fauth-0.2.1}/fauth/transports/__init__.py +0 -0
  32. {fauth-0.2.0 → fauth-0.2.1}/fauth/transports/base.py +0 -0
  33. {fauth-0.2.0 → fauth-0.2.1}/fauth/transports/bearer.py +0 -0
  34. {fauth-0.2.0 → fauth-0.2.1}/fauth/utils/__init__.py +0 -0
  35. {fauth-0.2.0 → fauth-0.2.1}/fauth/utils/logging.py +0 -0
  36. {fauth-0.2.0 → fauth-0.2.1}/pytest.ini +0 -0
  37. {fauth-0.2.0 → fauth-0.2.1}/requirements.txt +0 -0
  38. {fauth-0.2.0 → fauth-0.2.1}/tests/__init__.py +0 -0
  39. {fauth-0.2.0 → fauth-0.2.1}/tests/api/__init__.py +0 -0
  40. {fauth-0.2.0 → fauth-0.2.1}/tests/api/conftest.py +0 -0
  41. {fauth-0.2.0 → fauth-0.2.1}/tests/api/test_openapi.py +0 -0
  42. {fauth-0.2.0 → fauth-0.2.1}/tests/api/test_router.py +0 -0
  43. {fauth-0.2.0 → fauth-0.2.1}/tests/conftest.py +0 -0
  44. {fauth-0.2.0 → fauth-0.2.1}/tests/core/__init__.py +0 -0
  45. {fauth-0.2.0 → fauth-0.2.1}/tests/core/conftest.py +0 -0
  46. {fauth-0.2.0 → fauth-0.2.1}/tests/core/test_config.py +0 -0
  47. {fauth-0.2.0 → fauth-0.2.1}/tests/core/test_exceptions.py +0 -0
  48. {fauth-0.2.0 → fauth-0.2.1}/tests/crypto/__init__.py +0 -0
  49. {fauth-0.2.0 → fauth-0.2.1}/tests/crypto/conftest.py +0 -0
  50. {fauth-0.2.0 → fauth-0.2.1}/tests/crypto/test_jwt.py +0 -0
  51. {fauth-0.2.0 → fauth-0.2.1}/tests/crypto/test_password.py +0 -0
  52. {fauth-0.2.0 → fauth-0.2.1}/tests/providers/__init__.py +0 -0
  53. {fauth-0.2.0 → fauth-0.2.1}/tests/testing/__init__.py +0 -0
  54. {fauth-0.2.0 → fauth-0.2.1}/tests/testing/test_testing.py +0 -0
  55. {fauth-0.2.0 → fauth-0.2.1}/tests/utils/conftest.py +0 -0
  56. {fauth-0.2.0 → fauth-0.2.1}/tests/utils/test_logging.py +0 -0
@@ -1,6 +1,19 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v0.2.1 (2026-04-03)
5
+
6
+ ### Bug Fixes
7
+
8
+ - Implement AuthProvider for FastAPI authentication and add testing utilities
9
+ ([`f19f7ad`](https://github.com/justmatias/fauth/commit/f19f7ad32a8a79368d50862ffa3f4446dcc2cc70))
10
+
11
+ ### Chores
12
+
13
+ - **config**: Merge with main
14
+ ([`d8caae2`](https://github.com/justmatias/fauth/commit/d8caae2b3e357fcc2b3bc5f78c2929c1d0b12427))
15
+
16
+
4
17
  ## v0.2.0 (2026-04-02)
5
18
 
6
19
  ### Bug Fixes
@@ -32,6 +45,9 @@
32
45
 
33
46
  ### Bug Fixes
34
47
 
48
+ - **config**: Add authenticate method to AuthProvider with IdentityLoader support
49
+ ([`f905766`](https://github.com/justmatias/fauth/commit/f905766afa02394a28f97b319e52134576c4c614))
50
+
35
51
  - **config**: Add openapi security scheme support to AuthProvider and router dependencies
36
52
  ([`72d7754`](https://github.com/justmatias/fauth/commit/72d7754fb0519be979b6b00b5f253b245770698c))
37
53
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fauth
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Ergonomic, lightweight JWT authentication for FastAPI. Secure your routes instantly with plug-and-play dependency injection
5
5
  Project-URL: Homepage, https://github.com/justmatias/fauth
6
6
  Project-URL: Repository, https://github.com/justmatias/fauth
@@ -132,7 +132,8 @@ That's it. The `/me` endpoint is now protected. Requests without a valid `Bearer
132
132
  ## Full Example
133
133
 
134
134
  ```python
135
- from fastapi import FastAPI, Depends
135
+ from fastapi import FastAPI, Depends, HTTPException
136
+ from fastapi.security import OAuth2PasswordRequestForm
136
137
  from pydantic import BaseModel
137
138
  from fauth import AuthConfig, AuthProvider, TokenPayload, SecureAPIRouter
138
139
 
@@ -155,9 +156,21 @@ DB: dict[str, User] = {
155
156
  async def load_user(payload: TokenPayload) -> User | None:
156
157
  return DB.get(payload.sub)
157
158
 
158
- # 3. Instantiate the auth component
159
+ # 3. Define how to fetch a user by username/email for login
160
+ async def load_user_by_identity(identifier: str) -> User | None:
161
+ # In a real app, this would query your database
162
+ for user in DB.values():
163
+ if user.username == identifier:
164
+ return user
165
+ return None
166
+
167
+ # 4. Instantiate the auth component
159
168
  config = AuthConfig(secret_key="my-super-secret-key", algorithm="HS256")
160
- auth: AuthProvider[User] = AuthProvider(config=config, user_loader=load_user)
169
+ auth: AuthProvider[User] = AuthProvider(
170
+ config=config,
171
+ user_loader=load_user,
172
+ identity_loader=load_user_by_identity
173
+ )
161
174
 
162
175
  # --- Routes ---
163
176
 
@@ -168,17 +181,17 @@ async def login():
168
181
 
169
182
  @app.get("/me")
170
183
  async def get_me(user: User = Depends(auth.require_user)):
171
- # 5. `auth.require_user` secures the endpoint automatically
184
+ # 6. `auth.require_user` secures the endpoint automatically
172
185
  return {"message": f"Hello {user.username}"}
173
186
 
174
187
  @app.get("/admin")
175
188
  async def get_admin_data(user: User = Depends(auth.require_roles(["admin"]))):
176
- # 6. `auth.require_roles` enforces RBAC with list of roles
189
+ # 7. `auth.require_roles` enforces RBAC with list of roles
177
190
  return {"secret_data": "Top secret admin info"}
178
191
 
179
192
  # --- Securing Multiple Routes ---
180
193
 
181
- # 7. Use `SecureAPIRouter` to protect an entire group of routes.
194
+ # 8. Use `SecureAPIRouter` to protect an entire group of routes.
182
195
  # Any route added to this router will require an active user automatically.
183
196
  # This also enables the "Authorize" button in Swagger UI!
184
197
  secure_router = SecureAPIRouter(auth_provider=auth, prefix="/internal", tags=["Protected"])
@@ -99,7 +99,8 @@ That's it. The `/me` endpoint is now protected. Requests without a valid `Bearer
99
99
  ## Full Example
100
100
 
101
101
  ```python
102
- from fastapi import FastAPI, Depends
102
+ from fastapi import FastAPI, Depends, HTTPException
103
+ from fastapi.security import OAuth2PasswordRequestForm
103
104
  from pydantic import BaseModel
104
105
  from fauth import AuthConfig, AuthProvider, TokenPayload, SecureAPIRouter
105
106
 
@@ -122,9 +123,21 @@ DB: dict[str, User] = {
122
123
  async def load_user(payload: TokenPayload) -> User | None:
123
124
  return DB.get(payload.sub)
124
125
 
125
- # 3. Instantiate the auth component
126
+ # 3. Define how to fetch a user by username/email for login
127
+ async def load_user_by_identity(identifier: str) -> User | None:
128
+ # In a real app, this would query your database
129
+ for user in DB.values():
130
+ if user.username == identifier:
131
+ return user
132
+ return None
133
+
134
+ # 4. Instantiate the auth component
126
135
  config = AuthConfig(secret_key="my-super-secret-key", algorithm="HS256")
127
- auth: AuthProvider[User] = AuthProvider(config=config, user_loader=load_user)
136
+ auth: AuthProvider[User] = AuthProvider(
137
+ config=config,
138
+ user_loader=load_user,
139
+ identity_loader=load_user_by_identity
140
+ )
128
141
 
129
142
  # --- Routes ---
130
143
 
@@ -135,17 +148,17 @@ async def login():
135
148
 
136
149
  @app.get("/me")
137
150
  async def get_me(user: User = Depends(auth.require_user)):
138
- # 5. `auth.require_user` secures the endpoint automatically
151
+ # 6. `auth.require_user` secures the endpoint automatically
139
152
  return {"message": f"Hello {user.username}"}
140
153
 
141
154
  @app.get("/admin")
142
155
  async def get_admin_data(user: User = Depends(auth.require_roles(["admin"]))):
143
- # 6. `auth.require_roles` enforces RBAC with list of roles
156
+ # 7. `auth.require_roles` enforces RBAC with list of roles
144
157
  return {"secret_data": "Top secret admin info"}
145
158
 
146
159
  # --- Securing Multiple Routes ---
147
160
 
148
- # 7. Use `SecureAPIRouter` to protect an entire group of routes.
161
+ # 8. Use `SecureAPIRouter` to protect an entire group of routes.
149
162
  # Any route added to this router will require an active user automatically.
150
163
  # This also enables the "Authorize" button in Swagger UI!
151
164
  secure_router = SecureAPIRouter(auth_provider=auth, prefix="/internal", tags=["Protected"])
@@ -9,3 +9,9 @@ class UserLoader(Protocol[T]):
9
9
  """Consumer implements this to look up a user from JWT payload."""
10
10
 
11
11
  async def __call__(self, token_payload: TokenPayload) -> T | None: ...
12
+
13
+
14
+ class IdentityLoader(Protocol[T]):
15
+ """Look up a user from an identifier (username, email, etc.)."""
16
+
17
+ async def __call__(self, identifier: str) -> T | None: ...
@@ -11,11 +11,16 @@ from fauth.core import (
11
11
  TokenPayload,
12
12
  TokenResponse,
13
13
  )
14
- from fauth.crypto import create_access_token, create_refresh_token, decode_token
14
+ from fauth.crypto import (
15
+ create_access_token,
16
+ create_refresh_token,
17
+ decode_token,
18
+ verify_password,
19
+ )
15
20
  from fauth.transports import BearerTransport, Transport
16
21
  from fauth.utils import logger
17
22
 
18
- from .protocols import UserLoader
23
+ from .protocols import IdentityLoader, UserLoader
19
24
 
20
25
  T = TypeVar("T")
21
26
 
@@ -26,16 +31,20 @@ class AuthProvider(Generic[T]):
26
31
  and returns dependencies for routes.
27
32
  """
28
33
 
29
- def __init__(
34
+ def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
30
35
  self,
31
36
  config: AuthConfig,
32
37
  user_loader: UserLoader[T],
38
+ identity_loader: IdentityLoader[T] | None = None,
33
39
  transport: Transport | None = None,
34
40
  token_payload_schema: type[TokenPayload] = TokenPayload,
41
+ password_field_name: str = "hashed_password",
35
42
  ):
36
43
  self.config = config
37
44
  self.user_loader = user_loader
45
+ self.identity_loader = identity_loader
38
46
  self.token_payload_schema = token_payload_schema
47
+ self.password_field_name = password_field_name
39
48
 
40
49
  if not transport:
41
50
  transport = BearerTransport()
@@ -137,6 +146,37 @@ class AuthProvider(Generic[T]):
137
146
 
138
147
  return permission_checker
139
148
 
149
+ async def authenticate(self, identifier: str, password: str) -> T:
150
+ """
151
+ Authenticates a user by their identifier and password.
152
+ Returns the user if valid and active, otherwise raises HTTPException.
153
+ """
154
+ if not self.identity_loader:
155
+ raise RuntimeError("IdentityLoader must be provided to use authenticate()")
156
+
157
+ user = await self.identity_loader(identifier)
158
+ if not user:
159
+ raise HTTPException(
160
+ status_code=status.HTTP_401_UNAUTHORIZED,
161
+ detail="Invalid credentials",
162
+ )
163
+
164
+ hashed = getattr(user, self.password_field_name, None)
165
+ if not hashed or not verify_password(password, hashed):
166
+ raise HTTPException(
167
+ status_code=status.HTTP_401_UNAUTHORIZED,
168
+ detail="Invalid credentials",
169
+ )
170
+
171
+ # Ensure user is active if it has is_active attribute
172
+ if hasattr(user, "is_active") and not user.is_active:
173
+ raise HTTPException(
174
+ status_code=status.HTTP_401_UNAUTHORIZED,
175
+ detail="Inactive user",
176
+ )
177
+
178
+ return user
179
+
140
180
  async def login(
141
181
  self,
142
182
  sub: str,
@@ -1,8 +1,9 @@
1
1
  from .config import fake_auth_config
2
- from .fakes import FakeUserLoader
2
+ from .fakes import FakeIdentityLoader, FakeUserLoader
3
3
  from .provider import build_fake_auth_provider
4
4
 
5
5
  __all__ = [
6
+ "FakeIdentityLoader",
6
7
  "FakeUserLoader",
7
8
  "build_fake_auth_provider",
8
9
  "fake_auth_config",
@@ -16,3 +16,14 @@ class FakeUserLoader(Generic[T]):
16
16
 
17
17
  async def __call__(self, token_payload: TokenPayload) -> T | None:
18
18
  return self._users.get(token_payload.sub)
19
+
20
+
21
+ class FakeIdentityLoader(Generic[T]):
22
+ def __init__(self) -> None:
23
+ self._users: dict[str, T] = {}
24
+
25
+ def add_user(self, user_id: str, user: T) -> None:
26
+ self._users[user_id] = user
27
+
28
+ async def __call__(self, identifier: str) -> T | None:
29
+ return self._users.get(identifier)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fauth"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Ergonomic, lightweight JWT authentication for FastAPI. Secure your routes instantly with plug-and-play dependency injection"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -8,13 +8,14 @@ from fastapi.testclient import TestClient
8
8
  from pydantic import BaseModel, Field
9
9
 
10
10
  from fauth.core import AuthConfig
11
- from fauth.crypto import create_access_token
11
+ from fauth.crypto import create_access_token, hash_password
12
12
  from fauth.providers import AuthProvider
13
- from fauth.testing import FakeUserLoader
13
+ from fauth.testing import FakeIdentityLoader, FakeUserLoader
14
14
 
15
15
 
16
16
  class DummyUser(BaseModel):
17
17
  id_: UUID = Field(default_factory=uuid4)
18
+ hashed_password: str = Field(default=hash_password("secret_password"))
18
19
  is_active: bool = Field(default=True)
19
20
  roles: list[str] = Field(default_factory=list)
20
21
  permissions: list[str] = Field(default_factory=list)
@@ -35,11 +36,22 @@ def user_loader() -> FakeUserLoader[DummyUser]:
35
36
  return FakeUserLoader()
36
37
 
37
38
 
39
+ @pytest.fixture
40
+ def identity_loader() -> FakeIdentityLoader[DummyUser]:
41
+ return FakeIdentityLoader()
42
+
43
+
38
44
  @pytest.fixture
39
45
  def provider(
40
- auth_config: AuthConfig, user_loader: FakeUserLoader[DummyUser]
46
+ auth_config: AuthConfig,
47
+ user_loader: FakeUserLoader[DummyUser],
48
+ identity_loader: FakeIdentityLoader[DummyUser],
41
49
  ) -> AuthProvider[DummyUser]:
42
- return AuthProvider(config=auth_config, user_loader=user_loader)
50
+ return AuthProvider(
51
+ config=auth_config,
52
+ user_loader=user_loader,
53
+ identity_loader=identity_loader,
54
+ )
43
55
 
44
56
 
45
57
  @pytest.fixture
@@ -145,3 +157,22 @@ def _populate_no_perms_user(
145
157
  @pytest.fixture
146
158
  def no_perms_user_token(auth_config: AuthConfig, no_perms_user: DummyUser) -> str:
147
159
  return create_access_token(sub=str(no_perms_user.id_), config=auth_config)
160
+
161
+
162
+ @pytest.fixture
163
+ def _populate_identity_user(
164
+ identity_loader: FakeIdentityLoader[DummyUser], user: DummyUser
165
+ ) -> None:
166
+ identity_loader.add_user("alice", user)
167
+
168
+
169
+ @pytest.fixture
170
+ def _populate_identity_inactive_user(
171
+ identity_loader: FakeIdentityLoader[DummyUser], inactive_user: DummyUser
172
+ ) -> None:
173
+ identity_loader.add_user("alice", inactive_user)
174
+
175
+
176
+ @pytest.fixture
177
+ def password() -> str:
178
+ return "secret_password"
@@ -1,7 +1,14 @@
1
1
  import pytest
2
+ from fastapi import HTTPException, status
2
3
  from fastapi.testclient import TestClient
4
+ from pydantic import BaseModel, Field
3
5
 
6
+ from fauth.core import AuthConfig
7
+ from fauth.crypto import hash_password
4
8
  from fauth.providers import AuthProvider
9
+ from fauth.testing import FakeIdentityLoader
10
+
11
+ from .conftest import DummyUser, FakeUserLoader
5
12
 
6
13
 
7
14
  def test_require_user_expired_token(client: TestClient, expired_token: str) -> None:
@@ -85,3 +92,79 @@ async def test_login_returns_token_response(provider: AuthProvider) -> None:
85
92
  assert result.access_token
86
93
  assert result.refresh_token
87
94
  assert result.token_type == "bearer"
95
+
96
+
97
+ @pytest.mark.asyncio
98
+ @pytest.mark.usefixtures("_populate_identity_user")
99
+ async def test_authenticate_success(
100
+ provider: AuthProvider[DummyUser],
101
+ user: DummyUser,
102
+ password: str,
103
+ ) -> None:
104
+ authenticated_user = await provider.authenticate("alice", password)
105
+
106
+ assert authenticated_user.id_ == user.id_
107
+ assert authenticated_user.hashed_password == user.hashed_password
108
+
109
+
110
+ @pytest.mark.asyncio
111
+ @pytest.mark.usefixtures("_populate_identity_user")
112
+ async def test_authenticate_invalid_password(provider: AuthProvider[DummyUser]) -> None:
113
+ with pytest.raises(HTTPException) as excinfo:
114
+ await provider.authenticate("alice", "wrong_password")
115
+
116
+ assert excinfo.value.status_code == status.HTTP_401_UNAUTHORIZED
117
+ assert excinfo.value.detail == "Invalid credentials"
118
+
119
+
120
+ @pytest.mark.asyncio
121
+ async def test_authenticate_unknown_user(provider: AuthProvider[DummyUser]) -> None:
122
+ with pytest.raises(HTTPException) as excinfo:
123
+ await provider.authenticate("unknown", "password")
124
+
125
+ assert excinfo.value.status_code == status.HTTP_401_UNAUTHORIZED
126
+ assert excinfo.value.detail == "Invalid credentials"
127
+
128
+
129
+ @pytest.mark.asyncio
130
+ @pytest.mark.usefixtures("_populate_identity_inactive_user")
131
+ async def test_authenticate_inactive_user(
132
+ provider: AuthProvider[DummyUser],
133
+ password: str,
134
+ ) -> None:
135
+ with pytest.raises(HTTPException) as excinfo:
136
+ await provider.authenticate("alice", password)
137
+
138
+ assert excinfo.value.status_code == status.HTTP_401_UNAUTHORIZED
139
+ assert excinfo.value.detail == "Inactive user"
140
+
141
+
142
+ @pytest.mark.asyncio
143
+ async def test_authenticate_custom_password_field(auth_config: AuthConfig) -> None:
144
+ class CustomUser(BaseModel):
145
+ id: str
146
+ pw: str = Field(default=hash_password("secret_password"))
147
+
148
+ identity_loader = FakeIdentityLoader[CustomUser]()
149
+ user_loader = FakeUserLoader[CustomUser]()
150
+
151
+ provider = AuthProvider(
152
+ config=auth_config,
153
+ user_loader=user_loader,
154
+ identity_loader=identity_loader,
155
+ password_field_name="pw",
156
+ )
157
+
158
+ identity_loader.add_user("alice", CustomUser(id="user-1"))
159
+
160
+ authenticated_user = await provider.authenticate("alice", "secret_password")
161
+ assert authenticated_user.id == "user-1"
162
+
163
+
164
+ @pytest.mark.asyncio
165
+ async def test_authenticate_no_loader_raises_error(
166
+ auth_config: AuthConfig, user_loader: FakeUserLoader[DummyUser]
167
+ ) -> None:
168
+ provider = AuthProvider(config=auth_config, user_loader=user_loader)
169
+ with pytest.raises(RuntimeError, match="IdentityLoader must be provided"):
170
+ await provider.authenticate("alice", "password")
@@ -340,7 +340,7 @@ wheels = [
340
340
 
341
341
  [[package]]
342
342
  name = "fauth"
343
- version = "0.1.2"
343
+ version = "0.2.0"
344
344
  source = { editable = "." }
345
345
  dependencies = [
346
346
  { name = "fastapi" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes