nc-user-terminator 0.1.2__py3-none-any.whl → 0.1.4__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.
- nc_user_manager/cache.py +88 -0
- nc_user_manager/client.py +90 -4
- {nc_user_terminator-0.1.2.dist-info → nc_user_terminator-0.1.4.dist-info}/METADATA +2 -1
- nc_user_terminator-0.1.4.dist-info/RECORD +10 -0
- nc_user_terminator-0.1.2.dist-info/RECORD +0 -9
- {nc_user_terminator-0.1.2.dist-info → nc_user_terminator-0.1.4.dist-info}/WHEEL +0 -0
- {nc_user_terminator-0.1.2.dist-info → nc_user_terminator-0.1.4.dist-info}/top_level.txt +0 -0
nc_user_manager/cache.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
from cachetools import TTLCache
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
DEFAULT_TTL = 3600
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseCache:
|
|
10
|
+
"""缓存接口"""
|
|
11
|
+
is_async = False
|
|
12
|
+
|
|
13
|
+
def set(self, key: str, value: Any, ttl: Optional[int] = None):
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
|
|
16
|
+
def get(self, key: str) -> Any:
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
|
|
19
|
+
def delete(self, key: str):
|
|
20
|
+
raise NotImplementedError
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ---------------------
|
|
24
|
+
# 内存缓存
|
|
25
|
+
# ---------------------
|
|
26
|
+
class MemoryCache(BaseCache):
|
|
27
|
+
is_async = False
|
|
28
|
+
|
|
29
|
+
def __init__(self, maxsize: int = 1000, ttl: int = DEFAULT_TTL):
|
|
30
|
+
self._cache = TTLCache(maxsize=maxsize, ttl=ttl)
|
|
31
|
+
|
|
32
|
+
def set(self, key: str, value: Any, ttl: Optional[int] = None):
|
|
33
|
+
# cachetools TTLCache 不支持 per-key TTL, 所以只使用全局 TTL
|
|
34
|
+
self._cache[key] = value
|
|
35
|
+
|
|
36
|
+
def get(self, key: str) -> Any:
|
|
37
|
+
return self._cache.get(key)
|
|
38
|
+
|
|
39
|
+
def delete(self, key: str):
|
|
40
|
+
self._cache.pop(key, None)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ---------------------
|
|
44
|
+
# Redis 缓存
|
|
45
|
+
# ---------------------
|
|
46
|
+
class RedisCache(BaseCache):
|
|
47
|
+
"""同步 Redis"""
|
|
48
|
+
is_async = False
|
|
49
|
+
|
|
50
|
+
def __init__(self, redis_client, ttl: int = DEFAULT_TTL):
|
|
51
|
+
self.redis = redis_client
|
|
52
|
+
self.ttl = ttl
|
|
53
|
+
|
|
54
|
+
def set(self, key: str, value: Any, ttl: Optional[int] = None):
|
|
55
|
+
ttl = ttl or self.ttl
|
|
56
|
+
self.redis.setex(key, ttl, json.dumps(value))
|
|
57
|
+
|
|
58
|
+
def get(self, key: str) -> Any:
|
|
59
|
+
data = self.redis.get(key)
|
|
60
|
+
if data:
|
|
61
|
+
return json.loads(data)
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
def delete(self, key: str):
|
|
65
|
+
self.redis.delete(key)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AsyncRedisCache(BaseCache):
|
|
69
|
+
"""异步 Redis"""
|
|
70
|
+
is_async = True
|
|
71
|
+
|
|
72
|
+
def __init__(self, redis_client, ttl: int = DEFAULT_TTL):
|
|
73
|
+
self.redis = redis_client
|
|
74
|
+
self.ttl = ttl
|
|
75
|
+
|
|
76
|
+
async def set(self, key: str, value: Any, ttl: Optional[int] = None):
|
|
77
|
+
ttl = ttl or self.ttl
|
|
78
|
+
await self.redis.setex(key, ttl, json.dumps(value))
|
|
79
|
+
|
|
80
|
+
async def get(self, key: str) -> Any:
|
|
81
|
+
data = await self.redis.get(key)
|
|
82
|
+
if data:
|
|
83
|
+
return json.loads(data)
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
async def delete(self, key: str):
|
|
87
|
+
await self.redis.delete(key)
|
|
88
|
+
|
nc_user_manager/client.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from typing import Optional, Dict, Any
|
|
3
3
|
|
|
4
|
+
from cache import MemoryCache, BaseCache, AsyncRedisCache
|
|
4
5
|
from models import UserResponse, OAuth2AuthorizeResponse, CallbackResponse
|
|
5
6
|
from exceptions import OAuthError
|
|
6
7
|
from utils import request
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
TENANT_HEADER = "X-Tenant-Code"
|
|
11
|
+
DEFAULT_TTL = 3600
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class OAuthClient:
|
|
@@ -16,6 +18,7 @@ class OAuthClient:
|
|
|
16
18
|
tenant_code: str,
|
|
17
19
|
redirect_url: Optional[str] = None,
|
|
18
20
|
single_session: bool = False,
|
|
21
|
+
cache: Optional[BaseCache] = None,
|
|
19
22
|
):
|
|
20
23
|
"""
|
|
21
24
|
OAuth 客户端
|
|
@@ -29,6 +32,9 @@ class OAuthClient:
|
|
|
29
32
|
self._tenant_code = tenant_code
|
|
30
33
|
self._redirect_url = redirect_url
|
|
31
34
|
self._single_session = single_session
|
|
35
|
+
self._cache = cache or MemoryCache(maxsize=10000, ttl=DEFAULT_TTL)
|
|
36
|
+
# 异步缓存,仅允许异步 redis
|
|
37
|
+
self._async_cache = self._cache.is_async
|
|
32
38
|
|
|
33
39
|
# ----------------------
|
|
34
40
|
# 内部异步包装
|
|
@@ -113,7 +119,10 @@ class OAuthClient:
|
|
|
113
119
|
# ----------------------
|
|
114
120
|
def google_one_tap(self, credential: str) -> CallbackResponse:
|
|
115
121
|
"""同步 Google One Tap 登录"""
|
|
116
|
-
params = {
|
|
122
|
+
params = {
|
|
123
|
+
"credential": credential,
|
|
124
|
+
"single_session": str(self._single_session).lower()
|
|
125
|
+
}
|
|
117
126
|
if self._redirect_url:
|
|
118
127
|
params["redirect_url"] = self._redirect_url
|
|
119
128
|
res_dict = request(
|
|
@@ -126,7 +135,10 @@ class OAuthClient:
|
|
|
126
135
|
|
|
127
136
|
async def google_one_tap_async(self, credential: str) -> CallbackResponse:
|
|
128
137
|
"""异步 Google One Tap 登录"""
|
|
129
|
-
params = {
|
|
138
|
+
params = {
|
|
139
|
+
"credential": credential,
|
|
140
|
+
"single_session": str(self._single_session).lower()
|
|
141
|
+
}
|
|
130
142
|
if self._redirect_url:
|
|
131
143
|
params["redirect_url"] = self._redirect_url
|
|
132
144
|
res_dict = await self._arequest(
|
|
@@ -137,19 +149,83 @@ class OAuthClient:
|
|
|
137
149
|
)
|
|
138
150
|
return CallbackResponse(res_dict)
|
|
139
151
|
|
|
152
|
+
# ----------------------
|
|
153
|
+
# 缓存工具
|
|
154
|
+
# ----------------------
|
|
155
|
+
def _cache_user(self, token: str, user_dict: dict, ttl=DEFAULT_TTL):
|
|
156
|
+
key = f"user:{token}"
|
|
157
|
+
self._cache.set(key, user_dict, ttl)
|
|
158
|
+
|
|
159
|
+
if self._single_session:
|
|
160
|
+
user_id = user_dict.get("id")
|
|
161
|
+
if user_id:
|
|
162
|
+
old_token = self._cache.get(f"user_token:{user_id}")
|
|
163
|
+
if old_token and old_token != token:
|
|
164
|
+
self._cache.delete(f"user:{old_token}")
|
|
165
|
+
self._cache.set(f"user_token:{user_id}", token, ttl)
|
|
166
|
+
|
|
167
|
+
async def _cache_user_async(self, token: str, user_dict: dict, ttl=DEFAULT_TTL):
|
|
168
|
+
if not self._async_cache:
|
|
169
|
+
raise RuntimeError("异步缓存未配置")
|
|
170
|
+
|
|
171
|
+
key = f"user:{token}"
|
|
172
|
+
await self._cache.set(key, user_dict, ttl)
|
|
173
|
+
|
|
174
|
+
if self._single_session:
|
|
175
|
+
user_id = user_dict.get("id")
|
|
176
|
+
if user_id:
|
|
177
|
+
old_token = await self._cache.get(f"user_token:{user_id}")
|
|
178
|
+
if old_token and old_token != token:
|
|
179
|
+
await self._cache.delete(f"user:{old_token}")
|
|
180
|
+
await self._cache.set(f"user_token:{user_id}", token, ttl)
|
|
181
|
+
|
|
182
|
+
def _uncache_user(self, token: str, user_dict: Optional[dict] = None):
|
|
183
|
+
self._cache.delete(f"user:{token}")
|
|
184
|
+
if self._single_session and user_dict:
|
|
185
|
+
user_id = user_dict.get("id")
|
|
186
|
+
if user_id:
|
|
187
|
+
self._cache.delete(f"user_token:{user_id}")
|
|
188
|
+
|
|
189
|
+
async def _uncache_user_async(self, token: str, user_dict: Optional[dict] = None):
|
|
190
|
+
await self._cache.delete(f"user:{token}")
|
|
191
|
+
if self._single_session and user_dict:
|
|
192
|
+
user_id = user_dict.get("id")
|
|
193
|
+
if user_id:
|
|
194
|
+
await self._cache.delete(f"user_token:{user_id}")
|
|
195
|
+
|
|
140
196
|
# ----------------------
|
|
141
197
|
# 获取用户信息
|
|
142
198
|
# ----------------------
|
|
143
199
|
def say_my_name(self, token: str) -> UserResponse:
|
|
144
200
|
"""同步获取当前用户"""
|
|
201
|
+
if self._async_cache:
|
|
202
|
+
raise RuntimeError("同步缓存未配置")
|
|
203
|
+
|
|
204
|
+
key = f"user:{token}"
|
|
205
|
+
data = self._cache.get(key)
|
|
206
|
+
if data:
|
|
207
|
+
return UserResponse(data)
|
|
208
|
+
|
|
145
209
|
headers = self._headers({"Authorization": f"Bearer {token}"})
|
|
146
210
|
res_dict = request("GET", f"{self._base_url}/api/me", headers=headers)
|
|
211
|
+
|
|
212
|
+
self._cache_user(token, res_dict)
|
|
147
213
|
return UserResponse(res_dict)
|
|
148
214
|
|
|
149
215
|
async def say_my_name_async(self, token: str) -> UserResponse:
|
|
216
|
+
if not self._async_cache:
|
|
217
|
+
raise RuntimeError("异步方法只支持异步缓存,请传入 async_cache")
|
|
218
|
+
|
|
150
219
|
"""异步获取当前用户"""
|
|
220
|
+
key = f"user:{token}"
|
|
221
|
+
data = await self._cache.get(key)
|
|
222
|
+
if data:
|
|
223
|
+
return UserResponse(data)
|
|
224
|
+
|
|
151
225
|
headers = self._headers({"Authorization": f"Bearer {token}"})
|
|
152
226
|
res_dict = await self._arequest("GET", f"{self._base_url}/api/me", headers=headers)
|
|
227
|
+
|
|
228
|
+
await self._cache_user_async(token, res_dict)
|
|
153
229
|
return UserResponse(res_dict)
|
|
154
230
|
|
|
155
231
|
# 刷新过期时间
|
|
@@ -172,9 +248,19 @@ class OAuthClient:
|
|
|
172
248
|
# 登出
|
|
173
249
|
def logout(self, token: str):
|
|
174
250
|
headers = self._headers({"Authorization": f"Bearer {token}"})
|
|
175
|
-
|
|
251
|
+
resp = request("POST", f"{self._base_url}/api/auth/logout", headers=headers)
|
|
252
|
+
|
|
253
|
+
data = self._cache.get(f"user:{token}")
|
|
254
|
+
self._uncache_user(token, data)
|
|
255
|
+
return resp
|
|
256
|
+
|
|
176
257
|
|
|
177
258
|
async def logout_async(self, token: str) -> dict:
|
|
178
259
|
"""异步获取当前用户"""
|
|
179
260
|
headers = self._headers({"Authorization": f"Bearer {token}"})
|
|
180
|
-
|
|
261
|
+
resp = await self._arequest("POST", f"{self._base_url}/api/auth/logout", headers=headers)
|
|
262
|
+
|
|
263
|
+
data = await self._cache.get(f"user:{token}")
|
|
264
|
+
await self._uncache_user_async(token, data)
|
|
265
|
+
return resp
|
|
266
|
+
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nc-user-terminator
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: OAuth client wrapper for multi-tenant projects
|
|
5
5
|
Author: bw_song
|
|
6
6
|
Author-email: m132777096902@gmail.com
|
|
7
7
|
Requires-Python: >=3.8
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: cachetools==6.2.0
|
|
9
10
|
Dynamic: author-email
|
|
10
11
|
Dynamic: description-content-type
|
|
11
12
|
Dynamic: requires-python
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
nc_user_manager/__init__.py,sha256=fF3FZD0XUW5YCfTbBeJs3RK-Mnm3IQ7lns6Eb_tMqPU,238
|
|
2
|
+
nc_user_manager/cache.py,sha256=u9ioXDwHmEJHRB4VKI4JU_pp-8Y5O4bLPm8-pwqiMVc,2273
|
|
3
|
+
nc_user_manager/client.py,sha256=jCtHW65Hk3AV3VQaKj7QapCyLgKqDoHKuX3FKVtwZn4,10241
|
|
4
|
+
nc_user_manager/exceptions.py,sha256=yUMDrh1HHZF36UUadQNHvJlDgEYSYoYOObs8Q11fjrE,351
|
|
5
|
+
nc_user_manager/models.py,sha256=mDK7zskIcaThxvUUTWVf6eMasw7YaA3hDGVVSd1ZJgo,1088
|
|
6
|
+
nc_user_manager/utils.py,sha256=0QmJ9s3mzuYQSlwkcS8BW5XCe23lcoKskEeuqfdZHck,1245
|
|
7
|
+
nc_user_terminator-0.1.4.dist-info/METADATA,sha256=-fUx3iRGBFFT3AqVd69tATCupipDXL7dVzHTQ6R_oFw,361
|
|
8
|
+
nc_user_terminator-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
nc_user_terminator-0.1.4.dist-info/top_level.txt,sha256=kOAUtl6RYo-x3vMJL8It3KCJLoIFPvMUiAAyXjPQTYA,16
|
|
10
|
+
nc_user_terminator-0.1.4.dist-info/RECORD,,
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
nc_user_manager/__init__.py,sha256=fF3FZD0XUW5YCfTbBeJs3RK-Mnm3IQ7lns6Eb_tMqPU,238
|
|
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.2.dist-info/METADATA,sha256=iXTOp_tLVmJPR7LZug3BUkcZzixcdn2_gQgy13Bxfrc,327
|
|
7
|
-
nc_user_terminator-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
-
nc_user_terminator-0.1.2.dist-info/top_level.txt,sha256=kOAUtl6RYo-x3vMJL8It3KCJLoIFPvMUiAAyXjPQTYA,16
|
|
9
|
-
nc_user_terminator-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|