fastapi-authly 0.1.1__tar.gz → 0.1.3__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 (51) hide show
  1. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/PKG-INFO +29 -22
  2. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/README.md +28 -21
  3. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/README.zh.md +28 -0
  4. fastapi_authly-0.1.3/docs//345/207/275/346/225/260/345/274/217/347/274/226/347/250/213/344/270/203/346/255/246/345/231/250.md +11 -0
  5. fastapi_authly-0.1.3/examples/correct_usage.py +74 -0
  6. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/pyproject.toml +2 -1
  7. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/src/fastapi_authly/__about__.py +1 -1
  8. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/src/fastapi_authly/__init__.py +6 -2
  9. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/src/fastapi_authly/auth.py +12 -10
  10. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/src/fastapi_authly/core/__init__.py +2 -1
  11. fastapi_authly-0.1.3/src/fastapi_authly/core/config.py +98 -0
  12. fastapi_authly-0.1.3/src/fastapi_authly/deps.py +121 -0
  13. fastapi_authly-0.1.3/src/fastapi_authly/docs.py +70 -0
  14. fastapi_authly-0.1.3/src/fastapi_authly/static/scalar/standalone.js +34213 -0
  15. fastapi_authly-0.1.3/src/fastapi_authly/static/scalar/style.css +1 -0
  16. fastapi_authly-0.1.3/test.py +177 -0
  17. fastapi_authly-0.1.1/src/fastapi_authly/core/config.py +0 -58
  18. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/.gitignore +0 -0
  19. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/LICENSE +0 -0
  20. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/coverage.xml +0 -0
  21. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/.gitignore +0 -0
  22. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/class_index.html +0 -0
  23. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/coverage_html_cb_dd2e7eb5.js +0 -0
  24. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/favicon_32_cb_c827f16f.png +0 -0
  25. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/function_index.html +0 -0
  26. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/index.html +0 -0
  27. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/keybd_closed_cb_900cfef5.png +0 -0
  28. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/status.json +0 -0
  29. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/style_cb_9ff733b0.css +0 -0
  30. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/z_af1bec017750c6fc___init___py.html +0 -0
  31. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/z_af1bec017750c6fc_user_py.html +0 -0
  32. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/z_b9d93864b1b0ad6e___about___py.html +0 -0
  33. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/z_b9d93864b1b0ad6e___init___py.html +0 -0
  34. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/z_b9d93864b1b0ad6e_auth_py.html +0 -0
  35. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/z_b9d93864b1b0ad6e_interfaces_py.html +0 -0
  36. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/z_d015ea9b27b0258e___init___py.html +0 -0
  37. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/z_d015ea9b27b0258e_config_py.html +0 -0
  38. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/z_d015ea9b27b0258e_security_py.html +0 -0
  39. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/z_ddd01122054512b0___init___py.html +0 -0
  40. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/z_ddd01122054512b0_tortoise_pg_py.html +0 -0
  41. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/z_feee2d9ae7f7fd96___init___py.html +0 -0
  42. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/htmlcov/z_feee2d9ae7f7fd96_user_py.html +0 -0
  43. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/src/fastapi_authly/contrib/__init__.py +0 -0
  44. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/src/fastapi_authly/contrib/tortoise_pg.py +0 -0
  45. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/src/fastapi_authly/core/security.py +0 -0
  46. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/src/fastapi_authly/interfaces.py +0 -0
  47. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/src/fastapi_authly/models/__init__.py +0 -0
  48. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/src/fastapi_authly/models/user.py +0 -0
  49. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/src/fastapi_authly/schemas/__init__.py +0 -0
  50. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/src/fastapi_authly/schemas/user.py +0 -0
  51. {fastapi_authly-0.1.1 → fastapi_authly-0.1.3}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-authly
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: A modular authentication system for FastAPI with OAuth2, JWT, and password recovery
5
5
  Project-URL: Homepage, https://github.com/yourusername/fastapi-auth-module
6
6
  Project-URL: Documentation, https://yourusername.github.io/fastapi-auth-module/
@@ -109,6 +109,10 @@ deps = AuthDependencyConfig(user_repository=TortoiseUserRepository())
109
109
 
110
110
  auth_router = create_auth_router(config=config, dependencies=deps)
111
111
  app.include_router(auth_router)
112
+
113
+ # Optional: Setup Scalar API documentation (static resources included, no manual setup needed)
114
+ from fastapi_authly import setup_scalar_docs
115
+ setup_scalar_docs(app, docs_url="/docs", static_url="/static")
112
116
  ```
113
117
 
114
118
  ### Advanced Usage (custom implementations)
@@ -135,6 +139,30 @@ deps = AuthDependencyConfig(
135
139
  auth_router = create_auth_router(config=config, dependencies=deps)
136
140
  ```
137
141
 
142
+ ## 📚 API Documentation
143
+
144
+ `fastapi-authly` includes built-in Scalar API documentation support with all necessary static resources:
145
+
146
+ ```python
147
+ from fastapi import FastAPI
148
+ from fastapi_authly import setup_scalar_docs
149
+
150
+ app = FastAPI(title="My API")
151
+
152
+ # One line to enable Scalar documentation
153
+ # Automatically mounts static files to /static and creates docs page at /docs
154
+ setup_scalar_docs(app)
155
+
156
+ # Custom configuration
157
+ setup_scalar_docs(
158
+ app,
159
+ docs_url="/api-docs", # Custom docs URL
160
+ static_url="/assets", # Custom static files prefix
161
+ title="Custom API Docs", # Custom title
162
+ openapi_url="/openapi.json" # Custom OpenAPI schema URL
163
+ )
164
+ ```
165
+
138
166
  ## 📋 API Endpoints
139
167
 
140
168
  ### Authentication
@@ -216,26 +244,6 @@ deps = AuthDependencyConfig(user_repository=TortoiseUserRepository())
216
244
  app.include_router(create_auth_router(config=config, dependencies=deps))
217
245
  ```
218
246
 
219
- ## 🧪 Testing
220
-
221
- ```bash
222
- # Install test dependencies
223
- uv pip install -e ".[test]"
224
-
225
- # Run tests
226
- uv run pytest
227
- ```
228
-
229
- ## 📦 Build & Publish
230
-
231
- ```bash
232
- # Build
233
- uv build
234
-
235
- # Publish to PyPI (set UV_PUBLISH_TOKEN or pass --token)
236
- uv publish --token pypi-xxxxx
237
- ```
238
-
239
247
  ## 🤝 Contributing
240
248
 
241
249
  Contributions are welcome! Please feel free to submit a Pull Request.
@@ -261,6 +269,5 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
261
269
 
262
270
  If you have any questions or need help:
263
271
 
264
- - 📧 Email: your.email@example.com
265
272
  - 💬 GitHub Issues: [Create an issue](https://github.com/yourusername/fastapi-authly/issues)
266
273
  - 📖 Documentation: [Read the docs](https://yourusername.github.io/fastapi-authly/)
@@ -57,6 +57,10 @@ deps = AuthDependencyConfig(user_repository=TortoiseUserRepository())
57
57
 
58
58
  auth_router = create_auth_router(config=config, dependencies=deps)
59
59
  app.include_router(auth_router)
60
+
61
+ # Optional: Setup Scalar API documentation (static resources included, no manual setup needed)
62
+ from fastapi_authly import setup_scalar_docs
63
+ setup_scalar_docs(app, docs_url="/docs", static_url="/static")
60
64
  ```
61
65
 
62
66
  ### Advanced Usage (custom implementations)
@@ -83,6 +87,30 @@ deps = AuthDependencyConfig(
83
87
  auth_router = create_auth_router(config=config, dependencies=deps)
84
88
  ```
85
89
 
90
+ ## 📚 API Documentation
91
+
92
+ `fastapi-authly` includes built-in Scalar API documentation support with all necessary static resources:
93
+
94
+ ```python
95
+ from fastapi import FastAPI
96
+ from fastapi_authly import setup_scalar_docs
97
+
98
+ app = FastAPI(title="My API")
99
+
100
+ # One line to enable Scalar documentation
101
+ # Automatically mounts static files to /static and creates docs page at /docs
102
+ setup_scalar_docs(app)
103
+
104
+ # Custom configuration
105
+ setup_scalar_docs(
106
+ app,
107
+ docs_url="/api-docs", # Custom docs URL
108
+ static_url="/assets", # Custom static files prefix
109
+ title="Custom API Docs", # Custom title
110
+ openapi_url="/openapi.json" # Custom OpenAPI schema URL
111
+ )
112
+ ```
113
+
86
114
  ## 📋 API Endpoints
87
115
 
88
116
  ### Authentication
@@ -164,26 +192,6 @@ deps = AuthDependencyConfig(user_repository=TortoiseUserRepository())
164
192
  app.include_router(create_auth_router(config=config, dependencies=deps))
165
193
  ```
166
194
 
167
- ## 🧪 Testing
168
-
169
- ```bash
170
- # Install test dependencies
171
- uv pip install -e ".[test]"
172
-
173
- # Run tests
174
- uv run pytest
175
- ```
176
-
177
- ## 📦 Build & Publish
178
-
179
- ```bash
180
- # Build
181
- uv build
182
-
183
- # Publish to PyPI (set UV_PUBLISH_TOKEN or pass --token)
184
- uv publish --token pypi-xxxxx
185
- ```
186
-
187
195
  ## 🤝 Contributing
188
196
 
189
197
  Contributions are welcome! Please feel free to submit a Pull Request.
@@ -209,6 +217,5 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
209
217
 
210
218
  If you have any questions or need help:
211
219
 
212
- - 📧 Email: your.email@example.com
213
220
  - 💬 GitHub Issues: [Create an issue](https://github.com/yourusername/fastapi-authly/issues)
214
221
  - 📖 Documentation: [Read the docs](https://yourusername.github.io/fastapi-authly/)
@@ -41,6 +41,10 @@ register_tortoise(
41
41
  config = AuthConfig(token_url="login")
42
42
  deps = AuthDependencyConfig(user_repository=TortoiseUserRepository())
43
43
  app.include_router(create_auth_router(config=config, dependencies=deps))
44
+
45
+ # 可选:设置 Scalar API 文档(内置静态资源,无需手动配置)
46
+ from fastapi_authly import setup_scalar_docs
47
+ setup_scalar_docs(app, docs_url="/docs", static_url="/static")
44
48
  ```
45
49
 
46
50
  ## 🔌 自定义实现示例
@@ -65,6 +69,30 @@ deps = AuthDependencyConfig(user_repository=MyRepo(), mailer=MyMailer())
65
69
  router = create_auth_router(config=config, dependencies=deps)
66
70
  ```
67
71
 
72
+ ## 📚 API 文档功能
73
+
74
+ `fastapi-authly` 内置了 Scalar API 文档支持,包含所有必要的静态资源,无需手动配置:
75
+
76
+ ```python
77
+ from fastapi import FastAPI
78
+ from fastapi_authly import setup_scalar_docs
79
+
80
+ app = FastAPI(title="My API")
81
+
82
+ # 一行代码启用 Scalar 文档
83
+ # 自动挂载静态文件到 /static,创建文档页面到 /docs
84
+ setup_scalar_docs(app)
85
+
86
+ # 自定义配置
87
+ setup_scalar_docs(
88
+ app,
89
+ docs_url="/api-docs", # 自定义文档 URL
90
+ static_url="/assets", # 自定义静态文件前缀
91
+ title="Custom API Docs", # 自定义标题
92
+ openapi_url="/openapi.json" # 自定义 OpenAPI schema URL
93
+ )
94
+ ```
95
+
68
96
  ## 📋 主要接口
69
97
 
70
98
  - `POST /auth/login`:登录,返回 access_token(可选 refresh_token)
@@ -0,0 +1,11 @@
1
+
2
+ ## map: 数据转换的瑞士军刀
3
+
4
+ - 作用: 对可迭代对象中的每个元素应用指定函数,并返回新的迭代器
5
+ - 语法: map(function, iterable)
6
+ - 特点:
7
+ - 惰性求值: 不立即执行,节省内存
8
+ - 简洁高效: 一行代码实现批量转换
9
+ - 应用场景:
10
+ - 数据清洗、格式转换
11
+ - 特征工程、数据预处理。
@@ -0,0 +1,74 @@
1
+ """
2
+ 正确的使用方式示例
3
+ """
4
+
5
+ from fastapi import FastAPI
6
+ from tortoise.contrib.fastapi import register_tortoise
7
+ from fastapi_authly import (
8
+ AuthConfig,
9
+ AuthDependencyConfig,
10
+ create_auth_router,
11
+ JwtConfig,
12
+ setup_scalar_docs
13
+ )
14
+ from fastapi_authly.contrib.tortoise_pg import TortoiseUserRepository
15
+
16
+ app = FastAPI(title="FastAPI Authly 测试应用")
17
+
18
+ # 初始化 Tortoise + Postgres
19
+ register_tortoise(
20
+ app,
21
+ db_url="postgres://user:password@localhost:5432/testdb",
22
+ modules={"models": ["fastapi_authly.models.user"]},
23
+ generate_schemas=True,
24
+ add_exception_handlers=True,
25
+ )
26
+
27
+ # 方式 1: 使用 JwtConfig 实例(推荐)
28
+ config = AuthConfig(
29
+ jwt=JwtConfig(
30
+ secret_key="your-secret-key-change-in-production",
31
+ algorithm="HS256",
32
+ access_token_expires_minutes=30,
33
+ refresh_token_expire_days=7,
34
+ ),
35
+ router_prefix="/auth",
36
+ token_url="login",
37
+ )
38
+
39
+ # 方式 2: 使用字典更新(也可以)
40
+ # config = AuthConfig(
41
+ # jwt={
42
+ # "secret_key": "your-secret-key-change-in-production",
43
+ # "algorithm": "HS256",
44
+ # "access_token_expires_minutes": 30,
45
+ # },
46
+ # router_prefix="/auth",
47
+ # token_url="login",
48
+ # )
49
+
50
+ # 方式 3: 使用默认配置,然后通过环境变量覆盖
51
+ # 设置环境变量: AUTH_JWT__SECRET_KEY=your-secret-key
52
+ # config = AuthConfig(
53
+ # router_prefix="/auth",
54
+ # token_url="login",
55
+ # )
56
+
57
+ deps = AuthDependencyConfig(
58
+ user_repository=TortoiseUserRepository(),
59
+ )
60
+
61
+ # 注册认证路由
62
+ auth_router = create_auth_router(config=config, dependencies=deps)
63
+ app.include_router(auth_router)
64
+
65
+ # 设置 Scalar 文档
66
+ setup_scalar_docs(app, docs_url="/docs", static_url="/static")
67
+
68
+ @app.get("/")
69
+ async def root():
70
+ return {"message": "FastAPI Authly 测试应用已启动"}
71
+
72
+ if __name__ == "__main__":
73
+ import uvicorn
74
+ uvicorn.run(app, host="0.0.0.0", port=8000)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fastapi-authly"
7
- version = "0.1.1"
7
+ version = "0.1.3"
8
8
  description = "A modular authentication system for FastAPI with OAuth2, JWT, and password recovery"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -75,6 +75,7 @@ path = "src/fastapi_authly/__about__.py"
75
75
 
76
76
  [tool.hatch.build.targets.wheel]
77
77
  packages = ["src/fastapi_authly"]
78
+ # 静态文件在包目录内,会自动包含,无需额外配置
78
79
 
79
80
  [tool.hatch.build.targets.sdist]
80
81
  exclude = [
@@ -1,3 +1,3 @@
1
1
  """Version information."""
2
2
 
3
- __version__ = "0.1.1"
3
+ __version__ = "0.1.3"
@@ -7,23 +7,27 @@ This package provides a complete authentication solution with:
7
7
  - Password recovery
8
8
  - User management
9
9
  - Modular and configurable design
10
+ - Built-in Scalar API documentation
10
11
  """
11
12
 
12
13
  from .auth import AuthModule, create_auth_router
13
- from .core import AuthConfig, AuthDependencyConfig, BcryptPasswordHasher, JWTTokenService
14
+ from .core import AuthConfig, AuthDependencyConfig, JwtConfig, BcryptPasswordHasher, JWTTokenService
15
+ from .docs import setup_scalar_docs
14
16
  from .interfaces import Mailer, PasswordHasher, TokenService, UserRepository
15
17
  from .schemas.user import UserBase, UserCreate, UserUpdate, UserPublic, Token, TokenData
16
18
 
17
- __version__ = "0.1.0"
19
+ __version__ = "0.1.1"
18
20
 
19
21
  __all__ = [
20
22
  # Main classes
21
23
  "AuthModule",
22
24
  "AuthConfig",
23
25
  "AuthDependencyConfig",
26
+ "JwtConfig",
24
27
 
25
28
  # Core functions
26
29
  "create_auth_router",
30
+ "setup_scalar_docs",
27
31
 
28
32
  # Security utilities
29
33
  "BcryptPasswordHasher",
@@ -7,7 +7,7 @@ from fastapi.security import OAuth2PasswordBearer
7
7
  from jose import JWTError
8
8
  from pydantic import EmailStr
9
9
 
10
- from .core.config import AuthConfig, AuthDependencyConfig
10
+ from .core.config import AuthConfig, AuthDependencyConfig, _config
11
11
  from .core.security import BcryptPasswordHasher, JWTTokenService
12
12
  from .interfaces import Mailer, PasswordHasher, TokenService, UserRepository
13
13
  from .schemas.user import (
@@ -24,11 +24,9 @@ from fastapi_authly.contrib.tortoise_pg import TortoiseUserRepository
24
24
 
25
25
  def _merge_config(config: Optional[AuthConfig]) -> AuthConfig:
26
26
  """Environment-driven config, overridden by user-supplied config when provided."""
27
- env_config = AuthConfig()
28
27
  if config is None:
29
- return env_config
30
- # user config overrides environment values
31
- return env_config.model_copy(update=config.model_dump(exclude_none=True))
28
+ return _config
29
+ return _config.model_copy(update=config.model_dump(exclude_none=True))
32
30
 
33
31
 
34
32
  class AuthModule:
@@ -64,9 +62,13 @@ class AuthModule:
64
62
  self.router = APIRouter(
65
63
  prefix=self.config.router_prefix, tags=self.config.router_tags
66
64
  )
67
- self.oauth2_scheme = OAuth2PasswordBearer(
68
- tokenUrl=f"{self.config.router_prefix.rstrip('/')}/{self.config.token_url}".strip(
69
- "/"
65
+ # token 提取依赖:默认使用 OAuth2PasswordBearer,可通过 AuthDependencyConfig.token_dependency 覆盖
66
+ self.token_dependency = (
67
+ self.dependencies.token_dependency
68
+ or OAuth2PasswordBearer(
69
+ tokenUrl=f"{self.config.router_prefix.rstrip('/')}/{self.config.token_url}".strip(
70
+ "/"
71
+ )
70
72
  )
71
73
  )
72
74
 
@@ -120,7 +122,7 @@ class AuthModule:
120
122
  return Token(access_token=access_token, refresh_token=refresh_token)
121
123
 
122
124
  @self.router.post("/token/verify")
123
- async def verify_token(token: str = Depends(self.oauth2_scheme)) -> Dict[str, Any]:
125
+ async def verify_token(token: str = Depends(self.token_dependency)) -> Dict[str, Any]:
124
126
  try:
125
127
  payload = self.token_service.decode_token(token)
126
128
  token_data = TokenData(**payload)
@@ -142,7 +144,7 @@ class AuthModule:
142
144
  return await repo.to_public(created)
143
145
 
144
146
  @self.router.get("/me", response_model=UserPublic)
145
- async def get_current_user(token: str = Depends(self.oauth2_scheme)) -> UserPublic:
147
+ async def get_current_user(token: str = Depends(self.token_dependency)) -> UserPublic:
146
148
  repo = self._ensure_user_repo()
147
149
  try:
148
150
  payload = self.token_service.decode_token(token, verify_type="access")
@@ -1,11 +1,12 @@
1
1
  """Core functionality for fastapi-authly."""
2
2
 
3
- from .config import AuthConfig, AuthDependencyConfig
3
+ from .config import AuthConfig, AuthDependencyConfig, JwtConfig
4
4
  from .security import BcryptPasswordHasher, JWTTokenService
5
5
 
6
6
  __all__ = [
7
7
  "AuthConfig",
8
8
  "AuthDependencyConfig",
9
+ "JwtConfig",
9
10
  "BcryptPasswordHasher",
10
11
  "JWTTokenService",
11
12
  ]
@@ -0,0 +1,98 @@
1
+ """Configuration settings for fastapi-authly."""
2
+
3
+ from typing import List, Optional, Any
4
+ from datetime import timedelta
5
+ from pydantic import BaseModel, Field, ConfigDict
6
+ from pydantic_settings import BaseSettings, SettingsConfigDict
7
+
8
+ class JwtConfig(BaseModel):
9
+ secret_key: str = "xxx"
10
+ algorithm: str = "HS256"
11
+ scheme: str = "JWT"
12
+ token_expires_time: str = "1200" # seconds, legacy name
13
+ access_token_expires_minutes: int | None = None
14
+ refresh_token_expires_days: int = 7
15
+
16
+ @property
17
+ def access_expires(self) -> timedelta:
18
+ """Preferred access token expiry."""
19
+ if self.access_token_expires_minutes is not None:
20
+ return timedelta(minutes=self.access_token_expires_minutes)
21
+ # fallback to legacy seconds-based setting
22
+ return timedelta(seconds=int(self.token_expires_time))
23
+
24
+ @property
25
+ def refresh_expires(self) -> timedelta:
26
+ return timedelta(days=self.refresh_token_expires_days)
27
+
28
+
29
+
30
+
31
+ class AuthConfig(BaseSettings):
32
+ """
33
+ Runtime configuration loaded from environment variables with ``AUTH_`` prefix.
34
+
35
+ Host applications can still pass an ``AuthConfig`` instance directly;
36
+ in that case, the passed values override the environment values.
37
+ """
38
+ jwt: JwtConfig = JwtConfig()
39
+
40
+ router_prefix: str = "/auth"
41
+ router_tags: List[str] = Field(default_factory=lambda: ["authentication"])
42
+
43
+ enable_password_recovery: bool = True
44
+ enable_user_registration: bool = True
45
+ enable_token_refresh: bool = True
46
+ enable_html_content: bool = True
47
+
48
+ email_from: str = "noreply@example.com"
49
+ email_from_name: str = "Auth System"
50
+ password_reset_url_template: str = (
51
+ "https://yourapp.com/reset-password?token={token}"
52
+ )
53
+ verification_url_template: str = (
54
+ "https://yourapp.com/verify-email?token={token}"
55
+ )
56
+
57
+ model_config = SettingsConfigDict(
58
+ env_prefix="AUTH_",
59
+ extra="ignore",
60
+ )
61
+
62
+ # 属性访问器:直接访问 jwt 中的属性,兼容现有代码
63
+ @property
64
+ def secret_key(self) -> str:
65
+ """JWT secret key"""
66
+ return self.jwt.secret_key
67
+
68
+ @property
69
+ def algorithm(self) -> str:
70
+ """JWT algorithm"""
71
+ return self.jwt.algorithm
72
+
73
+ @property
74
+ def access_token_expire_minutes(self) -> Optional[int]:
75
+ """Access token expiration in minutes (compatible with auth.py)"""
76
+ return self.jwt.access_token_expires_minutes
77
+
78
+ @property
79
+ def refresh_token_expire_days(self) -> int:
80
+ """Refresh token expiration in days"""
81
+ return self.jwt.refresh_token_expire_days
82
+
83
+ _config = AuthConfig()
84
+
85
+ class AuthDependencyConfig(BaseModel):
86
+ """
87
+ Optional dependency injection container.
88
+
89
+ Host projects can supply custom implementations for the overridable hooks.
90
+ """
91
+ model_config = ConfigDict(arbitrary_types_allowed=True)
92
+
93
+ user_repository: Optional[Any] = None
94
+ password_hasher: Optional[Any] = None
95
+ token_service: Optional[Any] = None
96
+ mailer: Optional[Any] = None
97
+ # 可选:自定义 token 提取依赖(例如自定义 Bearer/JWT 解析)
98
+ token_dependency: Optional[Any] = None
@@ -0,0 +1,121 @@
1
+ from typing import Annotated, Optional, List
2
+ from fastapi import Request, HTTPException, status, Depends
3
+ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
4
+ from fastapi.security.utils import get_authorization_scheme_param
5
+ import jwt
6
+ from jwt.exceptions import InvalidTokenError
7
+ from pydantic import BaseModel, ValidationError
8
+ from .core.config import _config
9
+ from .models.user import User
10
+
11
+
12
+ class FlexibleHTTPBearer(HTTPBearer):
13
+ """支持自定义 scheme 的 HTTP Bearer"""
14
+
15
+ def __init__(
16
+ self,
17
+ *,
18
+ accepted_schemes: Optional[List[str]] = None,
19
+ scheme_name: Optional[str] = None,
20
+ auto_error: bool = True,
21
+ ):
22
+ self.accepted_schemes_lower = [
23
+ s.lower() for s in (accepted_schemes or ["bearer", "jwt"])
24
+ ]
25
+ super().__init__(
26
+ scheme_name=scheme_name or "Bearer/JWT",
27
+ auto_error=auto_error,
28
+ )
29
+
30
+ async def __call__(
31
+ self, request: Request
32
+ ) -> Optional[HTTPAuthorizationCredentials]:
33
+ authorization = request.headers.get("Authorization")
34
+ scheme, credentials = get_authorization_scheme_param(authorization)
35
+
36
+ if not (authorization and scheme and credentials):
37
+ if self.auto_error:
38
+ raise HTTPException(
39
+ status_code=status.HTTP_401_UNAUTHORIZED,
40
+ detail="Not authenticated",
41
+ )
42
+ return None
43
+
44
+ if scheme.lower() not in self.accepted_schemes_lower:
45
+ if self.auto_error:
46
+ raise HTTPException(
47
+ status_code=status.HTTP_401_UNAUTHORIZED,
48
+ detail=f"Invalid auth scheme. Accepted: {', '.join(self.accepted_schemes_lower)}",
49
+ )
50
+ return None
51
+
52
+ return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)
53
+
54
+
55
+ jwt_scheme = FlexibleHTTPBearer(
56
+ accepted_schemes=["bearer", "jwt"], # 可以改成 ["bearer"] 或 ["jwt"]
57
+ auto_error=True
58
+ )
59
+
60
+ class TokenPayload(BaseModel):
61
+ """
62
+ JWT 载荷模型
63
+ - sub: 用户 ID
64
+ - type: 令牌类型(access/refresh),旧 token 可能缺少
65
+ - exp/nbf 等字段由 jwt 库自行校验
66
+ """
67
+
68
+ sub: int
69
+ type: str | None = None
70
+
71
+ async def get_token(
72
+ credentials: HTTPAuthorizationCredentials = Depends(jwt_scheme),
73
+ ) -> str:
74
+ if credentials is None:
75
+ raise HTTPException(
76
+ status_code=status.HTTP_401_UNAUTHORIZED,
77
+ detail="Not authenticated",
78
+ )
79
+ scheme = credentials.scheme # "Bearer" / "JWT"
80
+ token = credentials.credentials
81
+
82
+ if scheme.lower() not in ("bearer", "jwt"):
83
+ raise HTTPException(
84
+ status_code=status.HTTP_401_UNAUTHORIZED,
85
+ detail=f"Invalid auth scheme. Expected 'Bearer' or 'JWT', got '{scheme}'",
86
+ )
87
+ return token
88
+
89
+ TokenDep = Annotated[str, Depends(get_token)]
90
+
91
+ async def get_current_user(token: TokenDep) -> User:
92
+ try:
93
+ payload = jwt.decode(
94
+ token,
95
+ _config.jwt.secret_key,
96
+ algorithms=[_config.jwt.algorithm],
97
+ )
98
+ token_data = TokenPayload(**payload)
99
+ if token_data.type and token_data.type != "access":
100
+ raise InvalidTokenError("Invalid token type")
101
+ except (InvalidTokenError, ValidationError):
102
+ raise HTTPException(
103
+ status_code=status.HTTP_403_FORBIDDEN,
104
+ detail="Could not validate credentials",
105
+ )
106
+
107
+ user = await User.get_or_none(id=token_data.sub)
108
+ if not user:
109
+ raise HTTPException(status_code=404, detail="User not found")
110
+ if not user.is_active:
111
+ raise HTTPException(status_code=400, detail="Inactive user")
112
+ return user
113
+
114
+ CurrentUser = Annotated[User, Depends(get_current_user)]
115
+
116
+ def get_current_active_superuser(current_user: CurrentUser) -> User:
117
+ if not current_user.is_superuser:
118
+ raise HTTPException(
119
+ status_code=403, detail="The user doesn't have enough privileges"
120
+ )
121
+ return current_user