reyserver 1.1.93__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.
- reyserver/__init__.py +26 -0
- reyserver/rall.py +21 -0
- reyserver/rauth.py +471 -0
- reyserver/rbase.py +74 -0
- reyserver/rbind.py +335 -0
- reyserver/rcache.py +116 -0
- reyserver/rclient.py +267 -0
- reyserver/rfile.py +316 -0
- reyserver/rpublic.py +62 -0
- reyserver/rredirect.py +48 -0
- reyserver/rserver.py +432 -0
- reyserver/rtest.py +80 -0
- reyserver-1.1.93.dist-info/METADATA +33 -0
- reyserver-1.1.93.dist-info/RECORD +16 -0
- reyserver-1.1.93.dist-info/WHEEL +4 -0
- reyserver-1.1.93.dist-info/licenses/LICENSE +7 -0
reyserver/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@Time : 2023-02-19
|
|
6
|
+
@Author : Rey
|
|
7
|
+
@Contact : reyxbo@163.com
|
|
8
|
+
@Explain : Backend server method set.
|
|
9
|
+
|
|
10
|
+
Modules
|
|
11
|
+
-------
|
|
12
|
+
rall : All methods.
|
|
13
|
+
rauth : Authentication methods.
|
|
14
|
+
rbase : Base methods.
|
|
15
|
+
rbind : Dependency bind methods.
|
|
16
|
+
rcache : Cache methods.
|
|
17
|
+
rclient : Client methods.
|
|
18
|
+
rfile : File methods.
|
|
19
|
+
rpublic : Public methods.
|
|
20
|
+
rredirect : Redirect methods.
|
|
21
|
+
rserver : Server methods.
|
|
22
|
+
rtest : Test methods.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
from .rserver import Server
|
reyserver/rall.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@Time : 2024-01-11
|
|
6
|
+
@Author : Rey
|
|
7
|
+
@Contact : reyxbo@163.com
|
|
8
|
+
@Explain : All methods.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from .rauth import *
|
|
13
|
+
from .rbase import *
|
|
14
|
+
from .rbind import *
|
|
15
|
+
from .rcache import *
|
|
16
|
+
from .rclient import *
|
|
17
|
+
from .rfile import *
|
|
18
|
+
from .rpublic import *
|
|
19
|
+
from .rredirect import *
|
|
20
|
+
from .rserver import *
|
|
21
|
+
from .rtest import *
|
reyserver/rauth.py
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@Time : 2025-10-10
|
|
6
|
+
@Author : Rey
|
|
7
|
+
@Contact : reyxbo@163.com
|
|
8
|
+
@Explain : Authentication methods.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from typing import Any, TypedDict, NotRequired, Literal
|
|
13
|
+
from datetime import datetime as Datetime
|
|
14
|
+
from fastapi import APIRouter, Request
|
|
15
|
+
from fastapi.security import OAuth2PasswordBearer
|
|
16
|
+
from reydb import rorm, DatabaseEngine, DatabaseEngineAsync
|
|
17
|
+
from reykit.rdata import encode_jwt, decode_jwt, is_hash_bcrypt
|
|
18
|
+
from reykit.rre import search_batch
|
|
19
|
+
from reykit.rtime import now
|
|
20
|
+
|
|
21
|
+
from .rbase import exit_api
|
|
22
|
+
from .rbind import Bind
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = (
|
|
26
|
+
'DatabaseORMTableUser',
|
|
27
|
+
'DatabaseORMTableRole',
|
|
28
|
+
'DatabaseORMTablePerm',
|
|
29
|
+
'DatabaseORMTableUserRole',
|
|
30
|
+
'DatabaseORMTableRolePerm',
|
|
31
|
+
'build_db_auth',
|
|
32
|
+
'router_auth'
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
UserData = TypedDict(
|
|
37
|
+
'UserData',
|
|
38
|
+
{
|
|
39
|
+
'create_time': float,
|
|
40
|
+
'udpate_time': float,
|
|
41
|
+
'user_id': int,
|
|
42
|
+
'user_name': str,
|
|
43
|
+
'role_names': list[str],
|
|
44
|
+
'perm_names': list[str],
|
|
45
|
+
'perm_apis': list[str],
|
|
46
|
+
'email': str | None,
|
|
47
|
+
'phone': str | None,
|
|
48
|
+
'avatar': int | None,
|
|
49
|
+
'password': NotRequired[str]
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
TokenData = TypedDict(
|
|
53
|
+
'TokenData',
|
|
54
|
+
{
|
|
55
|
+
'sub': int,
|
|
56
|
+
'iat': int,
|
|
57
|
+
'nbf': int,
|
|
58
|
+
'exp': int,
|
|
59
|
+
'user': UserData
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
Token = str
|
|
63
|
+
JSONToken = TypedDict(
|
|
64
|
+
'JSONToken',
|
|
65
|
+
{
|
|
66
|
+
'access_token': Token,
|
|
67
|
+
'token_type': Literal['Bearer']
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class DatabaseORMTableUser(rorm.Table):
|
|
73
|
+
"""
|
|
74
|
+
Database "user" table ORM model.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
__name__ = 'user'
|
|
78
|
+
__comment__ = 'User information table.'
|
|
79
|
+
create_time: rorm.Datetime = rorm.Field(field_default=':time', not_null=True, index_n=True, comment='Record create time.')
|
|
80
|
+
update_time: rorm.Datetime = rorm.Field(field_default=':time', not_null=True, index_n=True, comment='Record update time.')
|
|
81
|
+
user_id: int = rorm.Field(key_auto=True, comment='User ID.')
|
|
82
|
+
name: str = rorm.Field(rorm.types.VARCHAR(50), not_null=True, index_u=True, comment='User name, use lowercase letters.')
|
|
83
|
+
password: str = rorm.Field(rorm.types.CHAR(60), not_null=True, comment='User password, encrypted with "bcrypt".')
|
|
84
|
+
email: rorm.Email = rorm.Field(rorm.types.VARCHAR(255), index_u=True, comment='User email.')
|
|
85
|
+
phone: str = rorm.Field(rorm.types.CHAR(11), index_u=True, comment='User phone.')
|
|
86
|
+
avatar: int = rorm.Field(comment='User avatar file ID.')
|
|
87
|
+
is_valid: bool = rorm.Field(field_default='TRUE', not_null=True, comment='Is the valid.')
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class DatabaseORMTableRole(rorm.Table):
|
|
91
|
+
"""
|
|
92
|
+
Database "role" table ORM model.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
__name__ = 'role'
|
|
96
|
+
__comment__ = 'Role information table.'
|
|
97
|
+
create_time: rorm.Datetime = rorm.Field(field_default=':time', not_null=True, index_n=True, comment='Record create time.')
|
|
98
|
+
update_time: rorm.Datetime = rorm.Field(field_default=':time', arg_default=now, not_null=True, index_n=True, comment='Record update time.')
|
|
99
|
+
role_id: int = rorm.Field(rorm.types.SMALLINT, key_auto=True, comment='Role ID.')
|
|
100
|
+
name: str = rorm.Field(rorm.types.VARCHAR(50), not_null=True, index_u=True, comment='Role name.')
|
|
101
|
+
desc: str = rorm.Field(rorm.types.VARCHAR(500), comment='Role description.')
|
|
102
|
+
is_valid: bool = rorm.Field(field_default='TRUE', not_null=True, comment='Is the valid.')
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class DatabaseORMTablePerm(rorm.Table):
|
|
106
|
+
"""
|
|
107
|
+
Database "perm" table ORM model.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
__name__ = 'perm'
|
|
111
|
+
__comment__ = 'API permission information table.'
|
|
112
|
+
create_time: rorm.Datetime = rorm.Field(field_default=':time', not_null=True, index_n=True, comment='Record create time.')
|
|
113
|
+
update_time: rorm.Datetime = rorm.Field(field_default=':time', arg_default=now, not_null=True, index_n=True, comment='Record update time.')
|
|
114
|
+
perm_id: int = rorm.Field(rorm.types.SMALLINT, key_auto=True, comment='Permission ID.')
|
|
115
|
+
name: str = rorm.Field(rorm.types.VARCHAR(50), not_null=True, index_u=True, comment='Permission name.')
|
|
116
|
+
desc: str = rorm.Field(rorm.types.VARCHAR(500), comment='Permission description.')
|
|
117
|
+
api: str = rorm.Field(
|
|
118
|
+
rorm.types.VARCHAR(1000),
|
|
119
|
+
comment=r'API method and resource path regular expression "match" pattern, case insensitive, format is "{method} {path}" (e.g. "GET /users").'
|
|
120
|
+
)
|
|
121
|
+
is_valid: bool = rorm.Field(field_default='TRUE', not_null=True, comment='Is the valid.')
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class DatabaseORMTableUserRole(rorm.Table):
|
|
125
|
+
"""
|
|
126
|
+
Database "user_role" table ORM model.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
__name__ = 'user_role'
|
|
130
|
+
__comment__ = 'User and role association table.'
|
|
131
|
+
create_time: rorm.Datetime = rorm.Field(field_default=':time', not_null=True, index_n=True, comment='Record create time.')
|
|
132
|
+
update_time: rorm.Datetime = rorm.Field(field_default=':time', arg_default=now, not_null=True, index_n=True, comment='Record update time.')
|
|
133
|
+
user_id: int = rorm.Field(key=True, comment='User ID.')
|
|
134
|
+
role_id: int = rorm.Field(rorm.types.SMALLINT, key=True, comment='Role ID.')
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class DatabaseORMTableRolePerm(rorm.Table):
|
|
138
|
+
"""
|
|
139
|
+
Database "role_perm" table ORM model.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
__name__ = 'role_perm'
|
|
143
|
+
__comment__ = 'role and permission association table.'
|
|
144
|
+
create_time: rorm.Datetime = rorm.Field(field_default=':time', not_null=True, index_n=True, comment='Record create time.')
|
|
145
|
+
update_time: rorm.Datetime = rorm.Field(field_default=':time', arg_default=now, not_null=True, index_n=True, comment='Record update time.')
|
|
146
|
+
role_id: int = rorm.Field(rorm.types.SMALLINT, key=True, comment='Role ID.')
|
|
147
|
+
perm_id: int = rorm.Field(rorm.types.SMALLINT, key=True, comment='Permission ID.')
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def build_db_auth(engine: DatabaseEngine | DatabaseEngineAsync) -> None:
|
|
151
|
+
"""
|
|
152
|
+
Check and build "auth" database tables.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
db : Database engine instance.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
# Set parameter.
|
|
160
|
+
|
|
161
|
+
## Table.
|
|
162
|
+
tables = [
|
|
163
|
+
DatabaseORMTableUser,
|
|
164
|
+
DatabaseORMTableRole,
|
|
165
|
+
DatabaseORMTablePerm,
|
|
166
|
+
DatabaseORMTableUserRole,
|
|
167
|
+
DatabaseORMTableRolePerm
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
## View stats.
|
|
171
|
+
views_stats = [
|
|
172
|
+
{
|
|
173
|
+
'table': 'stats',
|
|
174
|
+
'items': [
|
|
175
|
+
{
|
|
176
|
+
'name': 'user_count',
|
|
177
|
+
'select': (
|
|
178
|
+
'SELECT COUNT(1)\n'
|
|
179
|
+
'FROM "user"'
|
|
180
|
+
),
|
|
181
|
+
'comment': 'User information count.'
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
'name': 'role_count',
|
|
185
|
+
'select': (
|
|
186
|
+
'SELECT COUNT(1)\n'
|
|
187
|
+
'FROM "role"'
|
|
188
|
+
),
|
|
189
|
+
'comment': 'Role information count.'
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
'name': 'perm_count',
|
|
193
|
+
'select': (
|
|
194
|
+
'SELECT COUNT(1)\n'
|
|
195
|
+
'FROM "perm"'
|
|
196
|
+
),
|
|
197
|
+
'comment': 'Permission information count.'
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
'name': 'user_day_count',
|
|
201
|
+
'select': (
|
|
202
|
+
'SELECT COUNT(1)\n'
|
|
203
|
+
'FROM "user"\n'
|
|
204
|
+
'WHERE DATE_PART(\'day\', NOW() - "create_time") = 0'
|
|
205
|
+
),
|
|
206
|
+
'comment': 'User information count in the past day.'
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
'name': 'user_week_count',
|
|
210
|
+
'select': (
|
|
211
|
+
'SELECT COUNT(1)\n'
|
|
212
|
+
'FROM "user"\n'
|
|
213
|
+
'WHERE DATE_PART(\'day\', NOW() - "create_time") <= 6'
|
|
214
|
+
),
|
|
215
|
+
'comment': 'User information count in the past week.'
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
'name': 'user_month_count',
|
|
219
|
+
'select': (
|
|
220
|
+
'SELECT COUNT(1)\n'
|
|
221
|
+
'FROM "user"\n'
|
|
222
|
+
'WHERE DATE_PART(\'day\', NOW() - "create_time") <= 29'
|
|
223
|
+
),
|
|
224
|
+
'comment': 'User information count in the past month.'
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
'name': 'user_last_time',
|
|
228
|
+
'select': (
|
|
229
|
+
'SELECT MAX("create_time")\n'
|
|
230
|
+
'FROM "user"'
|
|
231
|
+
),
|
|
232
|
+
'comment': 'User last record create time.'
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
# Build.
|
|
239
|
+
engine.sync_engine.build.build(tables=tables, views_stats=views_stats, skip=True)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
bearer = OAuth2PasswordBearer(
|
|
243
|
+
tokenUrl='/token',
|
|
244
|
+
scheme_name='OAuth2Password',
|
|
245
|
+
description='Authentication of OAuth2 password model.',
|
|
246
|
+
auto_error=False
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
async def depend_token(
|
|
251
|
+
request: Request,
|
|
252
|
+
server: Bind.Server = Bind.server,
|
|
253
|
+
token: Token | None = Bind.Depend(bearer)
|
|
254
|
+
) -> TokenData:
|
|
255
|
+
"""
|
|
256
|
+
Dependencie function of authentication token.
|
|
257
|
+
If the verification fails, then response status code is 401 or 403.
|
|
258
|
+
|
|
259
|
+
Parameters
|
|
260
|
+
----------
|
|
261
|
+
request : Request.
|
|
262
|
+
server : Server.
|
|
263
|
+
token : Authentication token.
|
|
264
|
+
|
|
265
|
+
Returns
|
|
266
|
+
-------
|
|
267
|
+
Token data.
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
# Check.
|
|
271
|
+
if not server.is_started_auth:
|
|
272
|
+
return
|
|
273
|
+
if bearer is None:
|
|
274
|
+
exit_api(401)
|
|
275
|
+
|
|
276
|
+
# Parameter.
|
|
277
|
+
key = server.api_auth_key
|
|
278
|
+
api_path = f'{request.method} {request.url.path}'
|
|
279
|
+
|
|
280
|
+
# Cache.
|
|
281
|
+
token_data: UserData | None = getattr(request.state, 'token_data', None)
|
|
282
|
+
|
|
283
|
+
# Decode.
|
|
284
|
+
if token_data is None:
|
|
285
|
+
token_data: TokenData | None = decode_jwt(token, key)
|
|
286
|
+
if token_data is None:
|
|
287
|
+
exit_api(401)
|
|
288
|
+
request.state.token_data = token_data
|
|
289
|
+
|
|
290
|
+
# Authentication.
|
|
291
|
+
perm_apis = token_data['user']['perm_apis']
|
|
292
|
+
perm_apis = [
|
|
293
|
+
f'^{pattern}$'
|
|
294
|
+
for pattern in perm_apis
|
|
295
|
+
]
|
|
296
|
+
result = search_batch(api_path, *perm_apis)
|
|
297
|
+
if result is None:
|
|
298
|
+
exit_api(403)
|
|
299
|
+
|
|
300
|
+
return token_data
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
Bind.token = Bind.Depend(depend_token)
|
|
304
|
+
|
|
305
|
+
router_auth = APIRouter()
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
async def get_user_data(
|
|
309
|
+
conn: Bind.Conn,
|
|
310
|
+
account: str,
|
|
311
|
+
account_type: Literal['user_id', 'name', 'email', 'phone'] = 'name',
|
|
312
|
+
filter_invalid: bool = True
|
|
313
|
+
) -> UserData | None:
|
|
314
|
+
"""
|
|
315
|
+
Get user data.
|
|
316
|
+
|
|
317
|
+
Parameters
|
|
318
|
+
----------
|
|
319
|
+
conn: Asyncronous database connection.
|
|
320
|
+
account : User account.
|
|
321
|
+
account_type : User account type.
|
|
322
|
+
- "Literal['name']": User name.
|
|
323
|
+
- "Literal['email']": User Email.
|
|
324
|
+
- "Literal['phone']": User phone mumber.
|
|
325
|
+
filter_invalid : Whether filter invalid user.
|
|
326
|
+
|
|
327
|
+
Returns
|
|
328
|
+
-------
|
|
329
|
+
User data or null.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
# Parameters.
|
|
333
|
+
if filter_invalid:
|
|
334
|
+
sql_where_user = (
|
|
335
|
+
' WHERE (\n'
|
|
336
|
+
f' "{account_type}" = :account\n'
|
|
337
|
+
' AND "is_valid" = TRUE\n'
|
|
338
|
+
' )\n'
|
|
339
|
+
)
|
|
340
|
+
sql_where_role = sql_where_perm = ' WHERE "is_valid" = TRUE\n'
|
|
341
|
+
else:
|
|
342
|
+
sql_where_user = ' WHERE "{account_type}" = :account\n'
|
|
343
|
+
sql_where_role = sql_where_perm = ''
|
|
344
|
+
|
|
345
|
+
# Get.
|
|
346
|
+
sql = (
|
|
347
|
+
'SELECT ANY_VALUE("create_time") AS "create_time",\n'
|
|
348
|
+
' ANY_VALUE("phone") AS "phone",\n'
|
|
349
|
+
' ANY_VALUE("update_time") AS "update_time",\n'
|
|
350
|
+
' ANY_VALUE("user"."user_id") AS "user_id",\n'
|
|
351
|
+
' ANY_VALUE("user"."name") AS "user_name",\n'
|
|
352
|
+
' ANY_VALUE("password") AS "password",\n'
|
|
353
|
+
' ANY_VALUE("email") AS "email",\n'
|
|
354
|
+
' ANY_VALUE("avatar") AS "avatar",\n'
|
|
355
|
+
' STRING_AGG(DISTINCT "role"."name", \';\') AS "role_names",\n'
|
|
356
|
+
' STRING_AGG(DISTINCT "perm"."name", \';\') AS "perm_names",\n'
|
|
357
|
+
' STRING_AGG(DISTINCT "perm"."api", \';\') AS "perm_apis"\n'
|
|
358
|
+
'FROM (\n'
|
|
359
|
+
' SELECT "create_time", "update_time", "user_id", "password", "name", "email", "phone", "avatar"\n'
|
|
360
|
+
' FROM "user"\n'
|
|
361
|
+
f'{sql_where_user}'
|
|
362
|
+
' LIMIT 1\n'
|
|
363
|
+
') as "user"\n'
|
|
364
|
+
'LEFT JOIN (\n'
|
|
365
|
+
' SELECT "user_id", "role_id"\n'
|
|
366
|
+
' FROM "user_role"\n'
|
|
367
|
+
') as "user_role"\n'
|
|
368
|
+
'ON "user_role"."user_id" = "user"."user_id"\n'
|
|
369
|
+
'LEFT JOIN (\n'
|
|
370
|
+
' SELECT "role_id", "name"\n'
|
|
371
|
+
' FROM "role"\n'
|
|
372
|
+
f'{sql_where_role}'
|
|
373
|
+
') AS "role"\n'
|
|
374
|
+
'ON "user_role"."role_id" = "role"."role_id"\n'
|
|
375
|
+
'LEFT JOIN (\n'
|
|
376
|
+
' SELECT "role_id", "perm_id"\n'
|
|
377
|
+
' FROM "role_perm"\n'
|
|
378
|
+
') as "role_perm"\n'
|
|
379
|
+
'ON "role_perm"."role_id" = "role"."role_id"\n'
|
|
380
|
+
'LEFT JOIN (\n'
|
|
381
|
+
' SELECT "perm_id", "name", "api"\n'
|
|
382
|
+
' FROM "perm"\n'
|
|
383
|
+
f'{sql_where_perm}'
|
|
384
|
+
') AS "perm"\n'
|
|
385
|
+
'ON "role_perm"."perm_id" = "perm"."perm_id"\n'
|
|
386
|
+
'GROUP BY "user"."user_id"'
|
|
387
|
+
)
|
|
388
|
+
result = await conn.execute(
|
|
389
|
+
sql,
|
|
390
|
+
account=account
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# Extract.
|
|
394
|
+
if result.empty:
|
|
395
|
+
info = None
|
|
396
|
+
else:
|
|
397
|
+
row: dict[str, Datetime | Any] = result.to_row()
|
|
398
|
+
if row['role_names'] is None:
|
|
399
|
+
row['role_names'] = ''
|
|
400
|
+
if row['perm_names'] is None:
|
|
401
|
+
row['perm_names'] = ''
|
|
402
|
+
if row['perm_apis'] is None:
|
|
403
|
+
row['perm_apis'] = ''
|
|
404
|
+
info: UserData = {
|
|
405
|
+
'create_time': row['create_time'].timestamp(),
|
|
406
|
+
'udpate_time': row['update_time'].timestamp(),
|
|
407
|
+
'user_id': row['user_id'],
|
|
408
|
+
'user_name': row['user_name'],
|
|
409
|
+
'role_names': row['role_names'].split(';'),
|
|
410
|
+
'perm_names': row['perm_names'].split(';'),
|
|
411
|
+
'perm_apis': row['perm_apis'].split(';'),
|
|
412
|
+
'email': row['email'],
|
|
413
|
+
'phone': row['phone'],
|
|
414
|
+
'avatar': row['avatar'],
|
|
415
|
+
'password': row['password']
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return info
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@router_auth.post('/token')
|
|
422
|
+
async def create_token(
|
|
423
|
+
username: str = Bind.i.form,
|
|
424
|
+
password: str = Bind.i.form,
|
|
425
|
+
conn: Bind.Conn = Bind.conn.auth,
|
|
426
|
+
server: Bind.Server = Bind.server
|
|
427
|
+
) -> JSONToken:
|
|
428
|
+
"""
|
|
429
|
+
Create token.
|
|
430
|
+
|
|
431
|
+
Parameters
|
|
432
|
+
----------
|
|
433
|
+
username : User name.
|
|
434
|
+
password : User password.
|
|
435
|
+
|
|
436
|
+
Returns
|
|
437
|
+
-------
|
|
438
|
+
JSON with "token".
|
|
439
|
+
"""
|
|
440
|
+
|
|
441
|
+
# Parameter.
|
|
442
|
+
key = server.api_auth_key
|
|
443
|
+
sess_seconds = server.api_auth_sess_seconds
|
|
444
|
+
|
|
445
|
+
# User data.
|
|
446
|
+
user_data = await get_user_data(conn, username)
|
|
447
|
+
|
|
448
|
+
# Check.
|
|
449
|
+
if user_data is None:
|
|
450
|
+
exit_api(401)
|
|
451
|
+
password_hash = user_data.pop('password')
|
|
452
|
+
if not is_hash_bcrypt(password, password_hash):
|
|
453
|
+
exit_api(401)
|
|
454
|
+
|
|
455
|
+
# Response.
|
|
456
|
+
now_timestamp_s = now('timestamp_s')
|
|
457
|
+
user_id = user_data.pop('user_id')
|
|
458
|
+
data: TokenData = {
|
|
459
|
+
'sub': str(user_id),
|
|
460
|
+
'iat': now_timestamp_s,
|
|
461
|
+
'nbf': now_timestamp_s,
|
|
462
|
+
'exp': now_timestamp_s + sess_seconds,
|
|
463
|
+
'user': user_data
|
|
464
|
+
}
|
|
465
|
+
token = encode_jwt(data, key)
|
|
466
|
+
response = {
|
|
467
|
+
'access_token': token,
|
|
468
|
+
'token_type': 'Bearer'
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return response
|
reyserver/rbase.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@Time : 2025-07-17
|
|
6
|
+
@Author : Rey
|
|
7
|
+
@Contact : reyxbo@163.com
|
|
8
|
+
@Explain : Base methods.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from typing import NoReturn
|
|
13
|
+
from http import HTTPStatus
|
|
14
|
+
from fastapi import HTTPException
|
|
15
|
+
from fastapi.params import Depends
|
|
16
|
+
from reykit.rbase import Base, Exit, throw
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = (
|
|
20
|
+
'ServerBase',
|
|
21
|
+
'ServerExit',
|
|
22
|
+
'ServerExitAPI',
|
|
23
|
+
'exit_api',
|
|
24
|
+
'depend_pass'
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ServerBase(Base):
|
|
29
|
+
"""
|
|
30
|
+
Server base type.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ServerExit(ServerBase, Exit):
|
|
35
|
+
"""
|
|
36
|
+
Server exit type.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ServerExitAPI(ServerExit, HTTPException):
|
|
41
|
+
"""
|
|
42
|
+
Server exit API type.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def exit_api(code: int = 400, text: str | None = None) -> NoReturn:
|
|
47
|
+
"""
|
|
48
|
+
Throw exception to exit API.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
code : Response status code.
|
|
53
|
+
text : Explain text.
|
|
54
|
+
`None`: Use Default text.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# Parameter.
|
|
58
|
+
if not 400 <= code <= 499:
|
|
59
|
+
throw(ValueError, code)
|
|
60
|
+
if text is None:
|
|
61
|
+
status = HTTPStatus(code)
|
|
62
|
+
text = status.description
|
|
63
|
+
|
|
64
|
+
# Throw exception.
|
|
65
|
+
raise ServerExitAPI(code, text)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def depend_pass_func() -> None:
|
|
69
|
+
"""
|
|
70
|
+
Depend pass.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
depend_pass = Depends(depend_pass_func)
|