nc-user-terminator 0.1.0__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.

Potentially problematic release.


This version of nc-user-terminator might be problematic. Click here for more details.

@@ -0,0 +1,10 @@
1
+ from client import OAuthClient
2
+ from exceptions import OAuthError
3
+ from models import UserResponse, OAuth2AuthorizeResponse
4
+
5
+ __all__ = [
6
+ "OAuthClient",
7
+ "OAuthError",
8
+ "UserResponse",
9
+ "OAuth2AuthorizeResponse",
10
+ ]
@@ -0,0 +1,180 @@
1
+ import asyncio
2
+ from typing import Optional, Dict, Any
3
+
4
+ from models import UserResponse, OAuth2AuthorizeResponse, CallbackResponse
5
+ from exceptions import OAuthError
6
+ from utils import request
7
+
8
+
9
+ TENANT_HEADER = "X-Tenant-Code"
10
+
11
+
12
+ class OAuthClient:
13
+ def __init__(
14
+ self,
15
+ base_url: str,
16
+ tenant_code: str,
17
+ redirect_url: Optional[str] = None,
18
+ single_session: bool = False,
19
+ ):
20
+ """
21
+ OAuth 客户端
22
+
23
+ :param base_url: 服务端基础地址 (例如 http://localhost:8000)
24
+ :param tenant_code: 租户编码
25
+ :param redirect_url: 可选,重定向地址
26
+ :param single_session: 是否单会话登录
27
+ """
28
+ self._base_url = base_url.rstrip("/")
29
+ self._tenant_code = tenant_code
30
+ self._redirect_url = redirect_url
31
+ self._single_session = single_session
32
+
33
+ # ----------------------
34
+ # 内部异步包装
35
+ # ----------------------
36
+ async def _arequest(self, *args, **kwargs) -> dict:
37
+ return await asyncio.to_thread(request, *args, **kwargs)
38
+
39
+ def _headers(self, extra: Optional[Dict[str, str]] = None) -> Dict[str, str]:
40
+ headers = {TENANT_HEADER: self._tenant_code}
41
+ if extra:
42
+ headers.update(extra)
43
+ return headers
44
+
45
+ # ----------------------
46
+ # 授权
47
+ # ----------------------
48
+ def authorize(self, platform: str) -> OAuth2AuthorizeResponse:
49
+ """同步获取授权地址"""
50
+ params = {}
51
+ if self._redirect_url:
52
+ params["redirect_url"] = self._redirect_url
53
+ res_dict = request(
54
+ "GET",
55
+ f"{self._base_url}/api/oauth/{platform}/authorize",
56
+ params=params,
57
+ headers=self._headers(),
58
+ )
59
+ return OAuth2AuthorizeResponse(res_dict)
60
+
61
+ async def authorize_async(self, platform: str) -> OAuth2AuthorizeResponse:
62
+ """异步获取授权地址"""
63
+ params = {}
64
+ if self._redirect_url:
65
+ params["redirect_url"] = self._redirect_url
66
+ res_dict = await self._arequest(
67
+ "GET",
68
+ f"{self._base_url}/api/oauth/{platform}/authorize",
69
+ params=params,
70
+ headers=self._headers(),
71
+ )
72
+ return OAuth2AuthorizeResponse(res_dict)
73
+
74
+ # ----------------------
75
+ # 普通回调
76
+ # ----------------------
77
+ def callback(self, platform: str, query_params: Dict[str, Any]) -> CallbackResponse:
78
+ """同步处理 OAuth2 回调 (非 One Tap)"""
79
+ if query_params.get("credential"):
80
+ raise OAuthError("Use google_one_tap() for One Tap login")
81
+
82
+ params = {"single_session": str(self._single_session).lower()}
83
+ if self._redirect_url:
84
+ params["redirect_url"] = self._redirect_url
85
+ params.update(query_params or {})
86
+ res_dict = request(
87
+ "GET",
88
+ f"{self._base_url}/api/oauth/{platform}/callback",
89
+ params=params,
90
+ headers=self._headers(),
91
+ )
92
+ return CallbackResponse(res_dict)
93
+
94
+ async def callback_async(self, platform: str, query_params: Dict[str, Any]) -> CallbackResponse:
95
+ """异步处理 OAuth2 回调 (非 One Tap)"""
96
+ if query_params.get("credential"):
97
+ raise OAuthError("Use google_one_tap_async() for One Tap login")
98
+
99
+ params = {"single_session": str(self._single_session).lower()}
100
+ if self._redirect_url:
101
+ params["redirect_url"] = self._redirect_url
102
+ params.update(query_params or {})
103
+ res_dict = await self._arequest(
104
+ "GET",
105
+ f"{self._base_url}/api/oauth/{platform}/callback",
106
+ params=params,
107
+ headers=self._headers(),
108
+ )
109
+ return CallbackResponse(res_dict)
110
+
111
+ # ----------------------
112
+ # Google One Tap
113
+ # ----------------------
114
+ def google_one_tap(self, credential: str) -> CallbackResponse:
115
+ """同步 Google One Tap 登录"""
116
+ params = {"credential": credential}
117
+ if self._redirect_url:
118
+ params["redirect_url"] = self._redirect_url
119
+ res_dict = request(
120
+ "GET",
121
+ f"{self._base_url}/api/oauth/google/callback",
122
+ params=params,
123
+ headers=self._headers(),
124
+ )
125
+ return CallbackResponse(res_dict)
126
+
127
+ async def google_one_tap_async(self, credential: str) -> CallbackResponse:
128
+ """异步 Google One Tap 登录"""
129
+ params = {"credential": credential}
130
+ if self._redirect_url:
131
+ params["redirect_url"] = self._redirect_url
132
+ res_dict = await self._arequest(
133
+ "GET",
134
+ f"{self._base_url}/api/oauth/google/callback",
135
+ params=params,
136
+ headers=self._headers(),
137
+ )
138
+ return CallbackResponse(res_dict)
139
+
140
+ # ----------------------
141
+ # 获取用户信息
142
+ # ----------------------
143
+ def say_my_name(self, token: str) -> UserResponse:
144
+ """同步获取当前用户"""
145
+ headers = self._headers({"Authorization": f"Bearer {token}"})
146
+ res_dict = request("GET", f"{self._base_url}/api/me", headers=headers)
147
+ return UserResponse(res_dict)
148
+
149
+ async def say_my_name_async(self, token: str) -> UserResponse:
150
+ """异步获取当前用户"""
151
+ headers = self._headers({"Authorization": f"Bearer {token}"})
152
+ res_dict = await self._arequest("GET", f"{self._base_url}/api/me", headers=headers)
153
+ return UserResponse(res_dict)
154
+
155
+ # 刷新过期时间
156
+ def reborn(self, token: str, extend_seconds: Optional[int] = None) -> dict:
157
+ """重新设置过期时间"""
158
+ headers = self._headers({"Authorization": f"Bearer {token}"})
159
+ if extend_seconds:
160
+ return request("POST", f"{self._base_url}/api/refresh-me", headers=headers, json_body={"extend_seconds": extend_seconds})
161
+ else:
162
+ return request("POST", f"{self._base_url}/api/refresh-me", headers=headers)
163
+
164
+ async def reborn_async(self, token: str, extend_seconds: Optional[int] = None) -> dict:
165
+ """异步重新设置过期时间"""
166
+ headers = self._headers({"Authorization": f"Bearer {token}"})
167
+ if extend_seconds:
168
+ return await self._arequest("POST", f"{self._base_url}/api/refresh-me", headers=headers, json_body={"extend_seconds": extend_seconds})
169
+ else:
170
+ return await self._arequest("POST", f"{self._base_url}/api/refresh-me", headers=headers)
171
+
172
+ # 登出
173
+ def logout(self, token: str):
174
+ headers = self._headers({"Authorization": f"Bearer {token}"})
175
+ return request("POST", f"{self._base_url}/api/auth/logout", headers=headers)
176
+
177
+ async def logout_async(self, token: str) -> dict:
178
+ """异步获取当前用户"""
179
+ headers = self._headers({"Authorization": f"Bearer {token}"})
180
+ return await self._arequest("POST", f"{self._base_url}/api/auth/logout", headers=headers)
@@ -0,0 +1,8 @@
1
+ class OAuthError(Exception):
2
+ """统一封装 OAuth 调用错误"""
3
+
4
+ def __init__(self, message: str, status_code: int = None, response: str = None):
5
+ self.message = message
6
+ self.status_code = status_code
7
+ self.response = response
8
+ super().__init__(f"[{status_code}] {message}" if status_code else message)
@@ -0,0 +1,48 @@
1
+ from typing import Optional
2
+
3
+ class OAuth2AuthorizeResponse(dict):
4
+ @property
5
+ def url(self) -> str:
6
+ return self.get("authorization_url")
7
+
8
+ class UserResponse(dict):
9
+ @property
10
+ def id(self) -> str:
11
+ return self.get("id")
12
+
13
+ @property
14
+ def email(self) -> str:
15
+ return self.get("email")
16
+
17
+ @property
18
+ def picture(self) -> Optional[str]:
19
+ return self.get("picture")
20
+
21
+ @property
22
+ def full_name(self) -> Optional[str]:
23
+ return self.get("full_name")
24
+
25
+ @property
26
+ def role(self) -> Optional[str]:
27
+ return self.get("role")
28
+
29
+ class CallbackResponse(dict):
30
+ @property
31
+ def token(self) -> str:
32
+ return self.get("access_token")
33
+
34
+ @property
35
+ def token_type(self) -> str:
36
+ return self.get("token_type")
37
+
38
+ @property
39
+ def user_name(self) -> str:
40
+ return self.get("user_name")
41
+
42
+ @property
43
+ def email(self) -> str:
44
+ return self.get("email")
45
+
46
+ @property
47
+ def user_picture(self) -> str:
48
+ return self.get("user_picture")
@@ -0,0 +1,39 @@
1
+ import json
2
+ import urllib.parse
3
+ import urllib.request
4
+ import urllib.error
5
+
6
+ from exceptions import OAuthError
7
+
8
+
9
+ def request(method: str, url: str, params=None, headers=None, json_body=None) -> dict:
10
+ # 拼接 GET 参数
11
+ if params:
12
+ query = urllib.parse.urlencode(params)
13
+ url = f"{url}?{query}"
14
+
15
+ data = None
16
+ if json_body is not None:
17
+ data = json.dumps(json_body).encode("utf-8")
18
+ if headers is None:
19
+ headers = {}
20
+ headers["Content-Type"] = "application/json"
21
+
22
+ req = urllib.request.Request(url, method=method.upper(), data=data)
23
+
24
+ if headers:
25
+ for k, v in headers.items():
26
+ req.add_header(k, v)
27
+
28
+ try:
29
+ with urllib.request.urlopen(req) as resp:
30
+ body = resp.read().decode()
31
+ try:
32
+ return json.loads(body)
33
+ except json.JSONDecodeError:
34
+ raise OAuthError("Invalid JSON response", resp.getcode(), body)
35
+ except urllib.error.HTTPError as e:
36
+ body = e.read().decode() if e.fp else None
37
+ raise OAuthError("HTTP request failed", e.code, body)
38
+ except urllib.error.URLError as e:
39
+ raise OAuthError(f"Network error: {e.reason}")
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: nc-user-terminator
3
+ Version: 0.1.0
4
+ Summary: OAuth client wrapper for multi-tenant projects
5
+ Author: bw_song
6
+ Author-email: m132777096902@gmail.com
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+ Dynamic: author-email
10
+ Dynamic: description-content-type
11
+ Dynamic: requires-python
@@ -0,0 +1,9 @@
1
+ nc-user-manager/__init__.py,sha256=VHYhkgEcaHYUlOK9z0bJNiOWCBRDpBe29lTUIktZ1Ng,235
2
+ nc-user-manager/client.py,sha256=urNJFx2ssQc5KxmW6SshXfdAJw_Hq4Sm2x208oV20kM,7074
3
+ nc-user-manager/exceptions.py,sha256=yUMDrh1HHZF36UUadQNHvJlDgEYSYoYOObs8Q11fjrE,351
4
+ nc-user-manager/models.py,sha256=mDK7zskIcaThxvUUTWVf6eMasw7YaA3hDGVVSd1ZJgo,1088
5
+ nc-user-manager/utils.py,sha256=0QmJ9s3mzuYQSlwkcS8BW5XCe23lcoKskEeuqfdZHck,1245
6
+ nc_user_terminator-0.1.0.dist-info/METADATA,sha256=rJsRtdiFbiY7LMsH6aBhdm4ntLqQjJQhroUGcqy7LIc,327
7
+ nc_user_terminator-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ nc_user_terminator-0.1.0.dist-info/top_level.txt,sha256=VJF5yCswS1mXdkOMLPZqRGrvVORiO5V3mxE5UkEJAIo,16
9
+ nc_user_terminator-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ nc-user-manager