rainycode 1.1.1__tar.gz → 1.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.
- {rainycode-1.1.1 → rainycode-1.1.3}/PKG-INFO +1 -1
- {rainycode-1.1.1 → rainycode-1.1.3}/common_base/consts.py +0 -1
- {rainycode-1.1.1 → rainycode-1.1.3}/common_servers/api/auth_api.py +5 -17
- {rainycode-1.1.1 → rainycode-1.1.3}/common_servers/service/auth_svc.py +21 -41
- {rainycode-1.1.1 → rainycode-1.1.3}/pyproject.toml +1 -1
- {rainycode-1.1.1 → rainycode-1.1.3}/rainycode.egg-info/PKG-INFO +1 -1
- {rainycode-1.1.1 → rainycode-1.1.3}/common_base/aiorequests.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_base/exception.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_base/logging.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_base/response.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_depends/auth_depend.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_models/__init__.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_models/base_model.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_models/user_model.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_models/wechat_model.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_servers/api/wechat_api.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_servers/router.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_servers/schemas/__init__.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_servers/schemas/auth_schema.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_servers/service/wechat_svc.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_utils/bcrypt_util.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_utils/captcha_util.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_utils/ip_util.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_utils/jwt_util.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_utils/snowflake_util.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/common_utils/wechat_util.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/core/base_config.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/core/databases/aiodb.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/core/databases/aioredis.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/core/middleware/http_middleware.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/core/start.py +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/rainycode.egg-info/SOURCES.txt +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/rainycode.egg-info/dependency_links.txt +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/rainycode.egg-info/requires.txt +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/rainycode.egg-info/top_level.txt +0 -0
- {rainycode-1.1.1 → rainycode-1.1.3}/setup.cfg +0 -0
|
@@ -4,6 +4,5 @@ REDIS_PREFIX_USER_INFO: str = "user:info:"
|
|
|
4
4
|
REDIS_PREFIX_OAUTH_STATE: str = "oauth:state:"
|
|
5
5
|
REDIS_PREFIX_WECHAT_CODE: str = "wechat:code:"
|
|
6
6
|
REDIS_PREFIX_WECHAT_OAUTH_CODE: str = "wechat:oauth:code:"
|
|
7
|
-
REDIS_PREFIX_TEMP_CODE: str = "oauth:temp_code:"
|
|
8
7
|
REDIS_PREFIX_LOGIN_LOCK_USER: str = "auth:login_lock:user:"
|
|
9
8
|
REDIS_PREFIX_LOGIN_LOCK_IP: str = "auth:login_lock:ip:"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from common_models.user_model import User
|
|
2
|
-
from fastapi import APIRouter, Security, Body,
|
|
2
|
+
from fastapi import APIRouter, Security, Body, Query, Request
|
|
3
3
|
from common_base.response import SuccessReturn
|
|
4
|
-
from common_depends.auth_depend import login_require
|
|
4
|
+
from common_depends.auth_depend import login_require
|
|
5
5
|
from common_servers.schemas.auth_schema import *
|
|
6
6
|
from common_servers.service.auth_svc import AuthService
|
|
7
7
|
|
|
@@ -58,7 +58,7 @@ async def login_mini(wechat_in: WechatLogin):
|
|
|
58
58
|
|
|
59
59
|
@router.get("/wechat/oauth", summary="获取微信网页授权链接 - 回调(/oauth/callback)")
|
|
60
60
|
async def get_wechat_oauth_url(
|
|
61
|
-
|
|
61
|
+
app_pk: int = Query(..., description="微信应用主键ID"),
|
|
62
62
|
redirect_uri: str = Query(..., description="授权成功后跳转地址"),
|
|
63
63
|
state: str | None = Query(default=None, description="自定义状态参数"),
|
|
64
64
|
):
|
|
@@ -69,17 +69,5 @@ async def get_wechat_oauth_url(
|
|
|
69
69
|
- 如果用户已注册,回调后直接返回 token
|
|
70
70
|
- 如果用户未注册,自动重定向到 snsapi_userinfo 授权
|
|
71
71
|
"""
|
|
72
|
-
authorize_url = await AuthService.get_wechat_oauth_url(
|
|
73
|
-
return SuccessReturn(data={"authorize_url": authorize_url})
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
@router.get("/wechat/token", summary="通过临时授权码获取 token")
|
|
77
|
-
async def get_token_by_temp_code(code: str = Query(..., description="临时授权码")):
|
|
78
|
-
"""
|
|
79
|
-
OAuth 回调后,前端使用临时码换取 token
|
|
80
|
-
|
|
81
|
-
- 临时码有效期:120 秒
|
|
82
|
-
- 一次性使用,获取后立即失效
|
|
83
|
-
"""
|
|
84
|
-
token_data = await AuthService.get_token_by_temp_code(code)
|
|
85
|
-
return SuccessReturn(data=token_data, msg="获取成功")
|
|
72
|
+
authorize_url = await AuthService.get_wechat_oauth_url(app_pk, redirect_uri, state)
|
|
73
|
+
return SuccessReturn(data={"authorize_url": authorize_url})
|
|
@@ -11,7 +11,6 @@ from common_base.consts import (
|
|
|
11
11
|
REDIS_PREFIX_USER_INFO,
|
|
12
12
|
REDIS_PREFIX_WECHAT_CODE,
|
|
13
13
|
REDIS_PREFIX_WECHAT_OAUTH_CODE,
|
|
14
|
-
REDIS_PREFIX_TEMP_CODE,
|
|
15
14
|
REDIS_PREFIX_LOGIN_LOCK_USER,
|
|
16
15
|
REDIS_PREFIX_LOGIN_LOCK_IP,
|
|
17
16
|
)
|
|
@@ -33,7 +32,6 @@ from core.base_config import base_config
|
|
|
33
32
|
LOGIN_FAIL_LIMIT = 5 # 最大失败次数
|
|
34
33
|
LOGIN_LOCK_SECONDS = 900 # 锁定时间(15分钟)
|
|
35
34
|
IP_FAIL_LIMIT = 20 # 单 IP 最大失败次数
|
|
36
|
-
TEMP_CODE_EXPIRE_SECONDS = 120 # 临时授权码有效期
|
|
37
35
|
|
|
38
36
|
|
|
39
37
|
class AuthService:
|
|
@@ -148,17 +146,17 @@ class AuthService:
|
|
|
148
146
|
logger.info(f"用户登出: user_id={user_id}, username={username}")
|
|
149
147
|
|
|
150
148
|
@classmethod
|
|
151
|
-
async def get_wechat_oauth_url(cls,
|
|
149
|
+
async def get_wechat_oauth_url(cls, app_pk: int, redirect_uri: str, state: str | None = None) -> str:
|
|
152
150
|
"""
|
|
153
151
|
生成微信网页授权链接(snsapi_base)
|
|
154
152
|
|
|
155
|
-
:param
|
|
153
|
+
:param app_pk: 微信应用主键ID
|
|
156
154
|
:param redirect_uri: 授权成功后跳转的前端地址
|
|
157
155
|
:param state: 自定义状态参数
|
|
158
156
|
:return: 微信授权链接
|
|
159
157
|
"""
|
|
160
158
|
# 验证公众号配置
|
|
161
|
-
app_config = await WechatApp.get_or_none(
|
|
159
|
+
app_config = await WechatApp.get_or_none(id=app_pk, app_type=AppTypeEnum.OFFICIAL_ACCOUNT)
|
|
162
160
|
if not app_config:
|
|
163
161
|
raise CustomException(msg="无效的公众号配置")
|
|
164
162
|
|
|
@@ -170,8 +168,8 @@ class AuthService:
|
|
|
170
168
|
# 生成内部 state
|
|
171
169
|
internal_state = state or str(uuid.uuid4())
|
|
172
170
|
|
|
173
|
-
# 缓存 state -> (redirect_uri,
|
|
174
|
-
cache_value = json.dumps({"redirect_uri": redirect_uri, "
|
|
171
|
+
# 缓存 state -> (redirect_uri, app_pk) 映射,有效期 5 分钟
|
|
172
|
+
cache_value = json.dumps({"redirect_uri": redirect_uri, "app_pk": app_pk})
|
|
175
173
|
await AioRedis.set(f"{REDIS_PREFIX_OAUTH_STATE}{internal_state}", cache_value, ex=300)
|
|
176
174
|
|
|
177
175
|
# 构建回调地址
|
|
@@ -204,11 +202,11 @@ class AuthService:
|
|
|
204
202
|
try:
|
|
205
203
|
cached_data = json.loads(cache_value)
|
|
206
204
|
redirect_uri = cached_data.get("redirect_uri")
|
|
207
|
-
|
|
205
|
+
app_pk = cached_data.get("app_pk")
|
|
208
206
|
except (json.JSONDecodeError, TypeError):
|
|
209
207
|
raise CustomException(msg="授权数据格式错误")
|
|
210
208
|
|
|
211
|
-
if not redirect_uri or not
|
|
209
|
+
if not redirect_uri or not app_pk:
|
|
212
210
|
raise CustomException(msg="授权数据不完整")
|
|
213
211
|
|
|
214
212
|
# 删除已使用的 state
|
|
@@ -220,7 +218,7 @@ class AuthService:
|
|
|
220
218
|
raise CustomException(msg="授权码已失效,请重新授权")
|
|
221
219
|
|
|
222
220
|
# 获取公众号配置
|
|
223
|
-
app_config = await WechatApp.get_or_none(
|
|
221
|
+
app_config = await WechatApp.get_or_none(id=app_pk, app_type=AppTypeEnum.OFFICIAL_ACCOUNT)
|
|
224
222
|
if not app_config:
|
|
225
223
|
raise CustomException(msg="公众号配置不存在")
|
|
226
224
|
|
|
@@ -283,22 +281,6 @@ class AuthService:
|
|
|
283
281
|
|
|
284
282
|
return RedirectResponse(url=userinfo_auth_url, status_code=302)
|
|
285
283
|
|
|
286
|
-
@classmethod
|
|
287
|
-
async def get_token_by_temp_code(cls, temp_code: str) -> dict:
|
|
288
|
-
"""
|
|
289
|
-
通过临时授权码获取 token
|
|
290
|
-
"""
|
|
291
|
-
cache_key = f"{REDIS_PREFIX_TEMP_CODE}{temp_code}"
|
|
292
|
-
cache_value = await AioRedis.get(cache_key)
|
|
293
|
-
|
|
294
|
-
if not cache_value:
|
|
295
|
-
raise CustomException(msg="临时授权码已过期,请重新授权")
|
|
296
|
-
|
|
297
|
-
# 删除临时码(一次性使用)
|
|
298
|
-
await AioRedis.delete(cache_key)
|
|
299
|
-
|
|
300
|
-
return json.loads(cache_value)
|
|
301
|
-
|
|
302
284
|
# ==================== 私有方法 ====================
|
|
303
285
|
|
|
304
286
|
@classmethod
|
|
@@ -529,22 +511,20 @@ class AuthService:
|
|
|
529
511
|
return user
|
|
530
512
|
|
|
531
513
|
@classmethod
|
|
532
|
-
async def _build_redirect_response(
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
)
|
|
543
|
-
|
|
544
|
-
# 构建重定向 URL
|
|
545
|
-
params = {"temp_code": temp_code}
|
|
514
|
+
async def _build_redirect_response(
|
|
515
|
+
cls, redirect_uri: str, token_data: dict, state: str | None = None
|
|
516
|
+
) -> RedirectResponse:
|
|
517
|
+
"""构建带 token 的重定向响应(使用 URL Fragment)"""
|
|
518
|
+
params = {
|
|
519
|
+
"access_token": token_data["access_token"],
|
|
520
|
+
"refresh_token": token_data["refresh_token"],
|
|
521
|
+
"token_type": token_data["token_type"],
|
|
522
|
+
"expires_in": str(token_data["expires_in"]),
|
|
523
|
+
}
|
|
546
524
|
if state:
|
|
547
525
|
params["state"] = state
|
|
548
526
|
|
|
549
|
-
|
|
527
|
+
# 使用 # fragment 传递 token,避免出现在服务器日志中
|
|
528
|
+
fragment = urlencode(params)
|
|
529
|
+
redirect_url = f"{redirect_uri}#{fragment}"
|
|
550
530
|
return RedirectResponse(url=redirect_url, status_code=302)
|
|
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
|