pixelarraythirdparty 1.2.0__py3-none-any.whl → 1.2.1__py3-none-any.whl
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.
- pixelarraythirdparty/__init__.py +1 -1
- pixelarraythirdparty/unified_login/__init__.py +2 -0
- pixelarraythirdparty/unified_login/unified_login.py +257 -0
- {pixelarraythirdparty-1.2.0.dist-info → pixelarraythirdparty-1.2.1.dist-info}/METADATA +1 -1
- {pixelarraythirdparty-1.2.0.dist-info → pixelarraythirdparty-1.2.1.dist-info}/RECORD +8 -8
- {pixelarraythirdparty-1.2.0.dist-info → pixelarraythirdparty-1.2.1.dist-info}/WHEEL +0 -0
- {pixelarraythirdparty-1.2.0.dist-info → pixelarraythirdparty-1.2.1.dist-info}/licenses/LICENSE +0 -0
- {pixelarraythirdparty-1.2.0.dist-info → pixelarraythirdparty-1.2.1.dist-info}/top_level.txt +0 -0
pixelarraythirdparty/__init__.py
CHANGED
|
@@ -6,6 +6,244 @@ from typing import Dict, Optional, Tuple
|
|
|
6
6
|
from pixelarraythirdparty.client import AsyncClient
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
class OAuth2Login(AsyncClient):
|
|
10
|
+
"""
|
|
11
|
+
统一的 OAuth2 登录客户端基类
|
|
12
|
+
|
|
13
|
+
支持所有基于 OAuth2 的第三方登录(Google、微信、GitHub、GitLab、抖音等)
|
|
14
|
+
通过配置不同的端点来支持不同的提供商
|
|
15
|
+
|
|
16
|
+
使用示例:
|
|
17
|
+
```
|
|
18
|
+
# Google登录
|
|
19
|
+
google = OAuth2Login(
|
|
20
|
+
api_key="your_api_key",
|
|
21
|
+
provider="google"
|
|
22
|
+
)
|
|
23
|
+
user_info, success = await google.login()
|
|
24
|
+
|
|
25
|
+
# 微信PC端扫码登录
|
|
26
|
+
wechat = OAuth2Login(
|
|
27
|
+
api_key="your_api_key",
|
|
28
|
+
provider="wechat",
|
|
29
|
+
login_type="desktop"
|
|
30
|
+
)
|
|
31
|
+
user_info, success = await wechat.login()
|
|
32
|
+
|
|
33
|
+
# 微信手机端登录
|
|
34
|
+
wechat_mobile = OAuth2Login(
|
|
35
|
+
api_key="your_api_key",
|
|
36
|
+
provider="wechat",
|
|
37
|
+
login_type="mobile"
|
|
38
|
+
)
|
|
39
|
+
user_info, success = await wechat_mobile.login()
|
|
40
|
+
|
|
41
|
+
# 支持refresh_token的提供商(如Google、GitLab)
|
|
42
|
+
if user_info.get("refresh_token"):
|
|
43
|
+
token_data, success = await google.refresh_access_token(
|
|
44
|
+
user_info.get("refresh_token")
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# 提供商端点映射
|
|
50
|
+
PROVIDER_ENDPOINTS = {
|
|
51
|
+
"google": {
|
|
52
|
+
"auth_url": "/api/unified-login/google/auth-url",
|
|
53
|
+
"wait_login": "/api/unified-login/google/wait-login",
|
|
54
|
+
"refresh_token": "/api/unified-login/google/refresh-token",
|
|
55
|
+
},
|
|
56
|
+
"wechat": {
|
|
57
|
+
"auth_url": "/api/unified-login/wechat/auth-url",
|
|
58
|
+
"wait_login": "/api/unified-login/wechat/wait-login",
|
|
59
|
+
},
|
|
60
|
+
"wechat-official": {
|
|
61
|
+
"auth_url": "/api/unified-login/wechat-official/auth-url",
|
|
62
|
+
"wait_login": "/api/unified-login/wechat-official/wait-login",
|
|
63
|
+
},
|
|
64
|
+
"github": {
|
|
65
|
+
"auth_url": "/api/unified-login/github/auth-url",
|
|
66
|
+
"wait_login": "/api/unified-login/github/wait-login",
|
|
67
|
+
},
|
|
68
|
+
"gitlab": {
|
|
69
|
+
"auth_url": "/api/unified-login/gitlab/auth-url",
|
|
70
|
+
"wait_login": "/api/unified-login/gitlab/wait-login",
|
|
71
|
+
"refresh_token": "/api/unified-login/gitlab/refresh-token",
|
|
72
|
+
},
|
|
73
|
+
"douyin": {
|
|
74
|
+
"auth_url": "/api/unified-login/douyin/auth-url",
|
|
75
|
+
"wait_login": "/api/unified-login/douyin/wait-login",
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
api_key: str,
|
|
82
|
+
provider: str,
|
|
83
|
+
login_type: Optional[str] = None
|
|
84
|
+
):
|
|
85
|
+
"""
|
|
86
|
+
description:
|
|
87
|
+
初始化OAuth2登录客户端
|
|
88
|
+
parameters:
|
|
89
|
+
api_key(str): API密钥
|
|
90
|
+
provider(str): 提供商名称,可选值:google, wechat, github, gitlab, douyin
|
|
91
|
+
login_type(str, optional): 登录类型,仅对微信有效,可选值:desktop(PC端扫码), mobile(手机端)
|
|
92
|
+
"""
|
|
93
|
+
super().__init__(api_key)
|
|
94
|
+
self.provider = provider.lower()
|
|
95
|
+
self.login_type = login_type
|
|
96
|
+
|
|
97
|
+
# 验证提供商是否支持
|
|
98
|
+
if self.provider not in self.PROVIDER_ENDPOINTS:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"不支持的提供商: {provider}。"
|
|
101
|
+
f"支持的提供商: {', '.join(self.PROVIDER_ENDPOINTS.keys())}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# 微信特殊处理:根据login_type选择不同的端点
|
|
105
|
+
if self.provider == "wechat":
|
|
106
|
+
if login_type == "mobile":
|
|
107
|
+
self.provider = "wechat-official"
|
|
108
|
+
|
|
109
|
+
def _get_endpoint(self, endpoint_type: str) -> str:
|
|
110
|
+
"""
|
|
111
|
+
description:
|
|
112
|
+
获取指定类型的端点URL
|
|
113
|
+
parameters:
|
|
114
|
+
endpoint_type(str): 端点类型,可选值:auth_url, wait_login, refresh_token
|
|
115
|
+
return:
|
|
116
|
+
endpoint(str): 端点URL
|
|
117
|
+
"""
|
|
118
|
+
endpoints = self.PROVIDER_ENDPOINTS.get(self.provider, {})
|
|
119
|
+
endpoint = endpoints.get(endpoint_type)
|
|
120
|
+
if not endpoint:
|
|
121
|
+
raise ValueError(
|
|
122
|
+
f"提供商 {self.provider} 不支持 {endpoint_type} 端点"
|
|
123
|
+
)
|
|
124
|
+
return endpoint
|
|
125
|
+
|
|
126
|
+
async def _get_auth_url(self) -> Tuple[Optional[Dict[str, str]], bool]:
|
|
127
|
+
"""
|
|
128
|
+
description:
|
|
129
|
+
获取OAuth授权URL
|
|
130
|
+
return:
|
|
131
|
+
auth_data(dict, optional): 授权数据字典,包含auth_url和state
|
|
132
|
+
success(bool): 是否成功
|
|
133
|
+
"""
|
|
134
|
+
endpoint = self._get_endpoint("auth_url")
|
|
135
|
+
data, success = await self._request("POST", endpoint)
|
|
136
|
+
if not success:
|
|
137
|
+
return None, False
|
|
138
|
+
auth_url = data.get("auth_url")
|
|
139
|
+
if not auth_url:
|
|
140
|
+
return None, False
|
|
141
|
+
return data, True
|
|
142
|
+
|
|
143
|
+
def _extract_state(self, auth_url: Optional[str]) -> Optional[str]:
|
|
144
|
+
"""
|
|
145
|
+
description:
|
|
146
|
+
从授权URL中提取state参数
|
|
147
|
+
parameters:
|
|
148
|
+
auth_url(str, optional): 授权URL
|
|
149
|
+
return:
|
|
150
|
+
state(str, optional): state参数值,如果提取失败则返回None
|
|
151
|
+
"""
|
|
152
|
+
if not auth_url:
|
|
153
|
+
return None
|
|
154
|
+
try:
|
|
155
|
+
parsed = urllib.parse.urlparse(auth_url)
|
|
156
|
+
query = urllib.parse.parse_qs(parsed.query)
|
|
157
|
+
values = query.get("state")
|
|
158
|
+
if values:
|
|
159
|
+
return values[0]
|
|
160
|
+
except Exception:
|
|
161
|
+
return None
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
async def _wait_for_login(
|
|
165
|
+
self, state: str, timeout: int
|
|
166
|
+
) -> Tuple[Dict, bool]:
|
|
167
|
+
"""
|
|
168
|
+
description:
|
|
169
|
+
等待登录结果,轮询检查登录状态
|
|
170
|
+
parameters:
|
|
171
|
+
state(str): 登录状态标识
|
|
172
|
+
timeout(int): 超时时间(秒)
|
|
173
|
+
return:
|
|
174
|
+
user_info(dict): 用户信息字典
|
|
175
|
+
success(bool): 是否成功
|
|
176
|
+
"""
|
|
177
|
+
interval = 2
|
|
178
|
+
total_checks = max(1, timeout // interval) if timeout > 0 else 1
|
|
179
|
+
endpoint = self._get_endpoint("wait_login")
|
|
180
|
+
|
|
181
|
+
for _ in range(total_checks):
|
|
182
|
+
status, response = await self._request_raw(
|
|
183
|
+
"POST",
|
|
184
|
+
endpoint,
|
|
185
|
+
json={"state": state},
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if status == 200 and response.get("success") is True:
|
|
189
|
+
return response.get("data", {}), True
|
|
190
|
+
|
|
191
|
+
if status in (400, 408):
|
|
192
|
+
break
|
|
193
|
+
|
|
194
|
+
await asyncio.sleep(interval)
|
|
195
|
+
|
|
196
|
+
return {}, False
|
|
197
|
+
|
|
198
|
+
async def login(self, timeout: int = 180) -> Tuple[Dict, bool]:
|
|
199
|
+
"""
|
|
200
|
+
description:
|
|
201
|
+
仿 Supabase CLI 的一键登录流程:打开浏览器完成授权,终端端轮询等待登录结果
|
|
202
|
+
parameters:
|
|
203
|
+
timeout(int, optional): 等待用户完成授权的超时时间(秒),默认为180
|
|
204
|
+
return:
|
|
205
|
+
user_info(dict): 用户信息字典
|
|
206
|
+
success(bool): 是否成功
|
|
207
|
+
"""
|
|
208
|
+
auth_data, success = await self._get_auth_url()
|
|
209
|
+
if not success or not auth_data:
|
|
210
|
+
return {}, False
|
|
211
|
+
|
|
212
|
+
auth_url = auth_data.get("auth_url")
|
|
213
|
+
state = auth_data.get("state") or self._extract_state(auth_url)
|
|
214
|
+
|
|
215
|
+
if not auth_url or not state:
|
|
216
|
+
return {}, False
|
|
217
|
+
|
|
218
|
+
webbrowser.open(auth_url, new=2)
|
|
219
|
+
|
|
220
|
+
return await self._wait_for_login(state, timeout)
|
|
221
|
+
|
|
222
|
+
async def refresh_access_token(self, refresh_token: str) -> Tuple[Dict, bool]:
|
|
223
|
+
"""
|
|
224
|
+
description:
|
|
225
|
+
使用refresh_token刷新access_token(仅支持Google和GitLab)
|
|
226
|
+
parameters:
|
|
227
|
+
refresh_token(str): OAuth refresh_token
|
|
228
|
+
return:
|
|
229
|
+
token_data(dict): 包含新的access_token和可能的refresh_token的字典
|
|
230
|
+
success(bool): 是否成功
|
|
231
|
+
"""
|
|
232
|
+
try:
|
|
233
|
+
endpoint = self._get_endpoint("refresh_token")
|
|
234
|
+
except ValueError:
|
|
235
|
+
return {}, False
|
|
236
|
+
|
|
237
|
+
data, success = await self._request(
|
|
238
|
+
"POST",
|
|
239
|
+
endpoint,
|
|
240
|
+
json={"refresh_token": refresh_token},
|
|
241
|
+
)
|
|
242
|
+
if not success:
|
|
243
|
+
return {}, False
|
|
244
|
+
return data, True
|
|
245
|
+
|
|
246
|
+
|
|
9
247
|
class GoogleLogin(AsyncClient):
|
|
10
248
|
"""
|
|
11
249
|
Google OAuth2 登录客户端
|
|
@@ -574,6 +812,25 @@ class GitLabLogin(AsyncClient):
|
|
|
574
812
|
|
|
575
813
|
return {}, False
|
|
576
814
|
|
|
815
|
+
async def refresh_access_token(self, refresh_token: str) -> Tuple[Dict, bool]:
|
|
816
|
+
"""
|
|
817
|
+
description:
|
|
818
|
+
使用refresh_token刷新access_token
|
|
819
|
+
parameters:
|
|
820
|
+
refresh_token(str): GitLab OAuth refresh_token
|
|
821
|
+
return:
|
|
822
|
+
token_data(dict): 包含新的access_token和可能的refresh_token的字典
|
|
823
|
+
success(bool): 是否成功
|
|
824
|
+
"""
|
|
825
|
+
data, success = await self._request(
|
|
826
|
+
"POST",
|
|
827
|
+
"/api/unified-login/gitlab/refresh-token",
|
|
828
|
+
json={"refresh_token": refresh_token},
|
|
829
|
+
)
|
|
830
|
+
if not success:
|
|
831
|
+
return {}, False
|
|
832
|
+
return data, True
|
|
833
|
+
|
|
577
834
|
|
|
578
835
|
class SMSLogin(AsyncClient):
|
|
579
836
|
"""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
pixelarraythirdparty/__init__.py,sha256=
|
|
1
|
+
pixelarraythirdparty/__init__.py,sha256=DF1GG4u4U8LMZ4FJ4WCnNYP04vDO5y2JPLoVN-jT72M,582
|
|
2
2
|
pixelarraythirdparty/client.py,sha256=Ym_IZ6cdoZJoMKW61ZRTfQ81-Hay5dpLTgExoANZwI4,3171
|
|
3
3
|
pixelarraythirdparty/cron/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
pixelarraythirdparty/cron/cron.py,sha256=nv4e2hX_UkEJ-kbEARbInU2J6aREyYZ61dZ-4b9UWJI,3146
|
|
@@ -10,12 +10,12 @@ pixelarraythirdparty/product/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
|
10
10
|
pixelarraythirdparty/product/product.py,sha256=5fgv2Ck860epYXxipY83vePziubCIlocFu43mGt_bhM,7497
|
|
11
11
|
pixelarraythirdparty/project/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
pixelarraythirdparty/project/project.py,sha256=a8swjckyn4y3NlIx0-tMbcRwDY9woOijGi7lb1wN7Ok,2455
|
|
13
|
-
pixelarraythirdparty/unified_login/__init__.py,sha256=
|
|
14
|
-
pixelarraythirdparty/unified_login/unified_login.py,sha256=
|
|
13
|
+
pixelarraythirdparty/unified_login/__init__.py,sha256=tzy3nmRv-qZID-6kYRFarqQVrmkUsEI7D6de_PFu7Tg,327
|
|
14
|
+
pixelarraythirdparty/unified_login/unified_login.py,sha256=zP63uZgmzZhskZwCrwIruJ6H1ftq9thTYcVyMADpo2s,32144
|
|
15
15
|
pixelarraythirdparty/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
pixelarraythirdparty/user/user.py,sha256=SqufSAVMxQElz-NqtlOZs_dG7UtbuE-ygLAyml9oKpY,5761
|
|
17
|
-
pixelarraythirdparty-1.2.
|
|
18
|
-
pixelarraythirdparty-1.2.
|
|
19
|
-
pixelarraythirdparty-1.2.
|
|
20
|
-
pixelarraythirdparty-1.2.
|
|
21
|
-
pixelarraythirdparty-1.2.
|
|
17
|
+
pixelarraythirdparty-1.2.1.dist-info/licenses/LICENSE,sha256=O-g1dUr0U50rSIvmWE9toiVkSgFpVt72_MHITbWvAqA,1067
|
|
18
|
+
pixelarraythirdparty-1.2.1.dist-info/METADATA,sha256=uK5VrxfZlyhAVTr6qYF2_Jk9lTuTQYMCsaSXFPrGyqc,993
|
|
19
|
+
pixelarraythirdparty-1.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
20
|
+
pixelarraythirdparty-1.2.1.dist-info/top_level.txt,sha256=dzG2Ut8j7noUqj_0ZQjcIDAeHYCh_9WtlxjAxtoyufo,21
|
|
21
|
+
pixelarraythirdparty-1.2.1.dist-info/RECORD,,
|
|
File without changes
|
{pixelarraythirdparty-1.2.0.dist-info → pixelarraythirdparty-1.2.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|