jknife 0.0.1__py2.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.
- jknife/__init__.py +0 -0
- jknife/commands/__init__.py +0 -0
- jknife/commands/jknife.py +376 -0
- jknife/db/__init__.py +142 -0
- jknife/db/models/__init__.py +0 -0
- jknife/db/models/mongo/__init__.py +57 -0
- jknife/db/models/mongo/network.py +27 -0
- jknife/db/models/mongo/personnel_info.py +108 -0
- jknife/db/models/mongo/settings.py +58 -0
- jknife/db/models/mongo/token.py +62 -0
- jknife/db/models/mongo/users.py +91 -0
- jknife/db/models/rdbms/__init__.py +53 -0
- jknife/db/models/rdbms/network.py +24 -0
- jknife/db/models/rdbms/personnel_info.py +88 -0
- jknife/db/models/rdbms/settings.py +39 -0
- jknife/db/models/rdbms/token.py +53 -0
- jknife/db/models/rdbms/users.py +94 -0
- jknife/dependencies/__init__.py +0 -0
- jknife/dependencies/token.py +202 -0
- jknife/dependencies/users.py +76 -0
- jknife/logging.py +69 -0
- jknife/views/__init__.py +23 -0
- jknife/views/error_message.py +38 -0
- jknife/views/personnel_info.py +42 -0
- jknife/views/tokens.py +22 -0
- jknife/views/users.py +70 -0
- jknife-0.0.1.dist-info/METADATA +17 -0
- jknife-0.0.1.dist-info/RECORD +31 -0
- jknife-0.0.1.dist-info/WHEEL +5 -0
- jknife-0.0.1.dist-info/entry_points.txt +2 -0
- jknife-0.0.1.dist-info/licenses/LICENSE +7 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# import packages from default or pip library
|
|
2
|
+
import jwt
|
|
3
|
+
from redis.exceptions import ConnectionError
|
|
4
|
+
from random import randint
|
|
5
|
+
from fastapi import Depends, HTTPException, status
|
|
6
|
+
from fastapi.security import HTTPBasic, HTTPBearer, HTTPAuthorizationCredentials
|
|
7
|
+
from datetime import datetime, timedelta, timezone
|
|
8
|
+
|
|
9
|
+
# import packages from this framework
|
|
10
|
+
from settings import AUTHENTICATION
|
|
11
|
+
from src.jknife.db import logger
|
|
12
|
+
from src.jknife.db import redis_connector
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# create secret key in this file.
|
|
16
|
+
def create_secretkey() -> str:
|
|
17
|
+
return "".join([chr(randint(65, 122)) for _ in range(256)])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# SETTINGS
|
|
21
|
+
_DEFAULT_TOKEN_VALID_TIME: int = 20
|
|
22
|
+
_DEFAULT_TOKEN_REFRESH_TIME: int = 24 * 60 * 30
|
|
23
|
+
_TOKEN_SETTING: dict = AUTHENTICATION.get("token")
|
|
24
|
+
_TOKEN_SECRET_KEY: str = _TOKEN_SETTING.get("secret_key") or create_secretkey()
|
|
25
|
+
_TOKEN_ISSUER: str = _TOKEN_SETTING.get("token_issuer") or "127.0.0.1"
|
|
26
|
+
_TOKEN_AUDIENCE: str = _TOKEN_SETTING.get("token_audience") or "http://127.0.0.1"
|
|
27
|
+
_TOKEN_EXP_ACCESS: int = _TOKEN_SETTING.get("token_valid_time") or _DEFAULT_TOKEN_VALID_TIME
|
|
28
|
+
_TOKEN_EXP_REFRESH: int = _TOKEN_SETTING.get("token_refresh_time") or _DEFAULT_TOKEN_REFRESH_TIME
|
|
29
|
+
_TOKEN_ALGORITHM: str = _TOKEN_SETTING.get("algorithm") or "HS256"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Bearer Token Window
|
|
33
|
+
oauth2_scheme = HTTPBearer(scheme_name="bearer",
|
|
34
|
+
description="Authentication related with JWT token.\nInput access token got after signing in.",
|
|
35
|
+
auto_error=False)
|
|
36
|
+
basic_scheme = HTTPBasic(scheme_name="basic",
|
|
37
|
+
description="Authentication with username and password.\nInput username and password.",
|
|
38
|
+
auto_error=False)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# define function for tokens
|
|
42
|
+
def _check_blacklist_access_token(token: str) -> str:
|
|
43
|
+
"""
|
|
44
|
+
check whether the access token is registered as blacklist in redis.
|
|
45
|
+
|
|
46
|
+
:param token: access_token in string
|
|
47
|
+
:return: access_token, if it is not registered in redis
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
user_unique = decode_jwt_token(token=token).get("sub")
|
|
51
|
+
|
|
52
|
+
if redis_connector.get(name=f"access_token:{token}"):
|
|
53
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
|
54
|
+
detail={"msg": "invalid access token"})
|
|
55
|
+
|
|
56
|
+
elif not redis_connector.get(name=f"refresh_token:{user_unique}"):
|
|
57
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
|
58
|
+
detail={"msg": "you have to sign in first."})
|
|
59
|
+
|
|
60
|
+
return token
|
|
61
|
+
|
|
62
|
+
def check_whitelist_refresh_token(user_unique: str, token: str):
|
|
63
|
+
"""
|
|
64
|
+
check whether the refresh token is registered as whitelist in redis.
|
|
65
|
+
|
|
66
|
+
:param token: refresh_token in string
|
|
67
|
+
:return: refresh_token, if it is registered in redis
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
registered_token: bytes = redis_connector.get(name=f"refresh_token:{user_unique}")
|
|
71
|
+
if registered_token is None:
|
|
72
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
|
73
|
+
detail={"msg": "you have to sign in first."})
|
|
74
|
+
|
|
75
|
+
if registered_token.decode("utf-8") != token:
|
|
76
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
|
77
|
+
detail={"msg": "invalid refresh token"})
|
|
78
|
+
return token
|
|
79
|
+
|
|
80
|
+
def create_jwt_token(user_unique: str, user_data: dict, is_access: bool=True) -> str:
|
|
81
|
+
"""
|
|
82
|
+
create jwt token(access and refresh) with user data.
|
|
83
|
+
|
|
84
|
+
:param user_unique: primary key to distinguish specific object among the users.
|
|
85
|
+
:param user_data: user data that is customised by developer.
|
|
86
|
+
:param is_access: set token type as bool for access(default) or refresh(False).
|
|
87
|
+
:return: encrypted string token.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
payload: dict = {
|
|
91
|
+
"iss": _TOKEN_ISSUER,
|
|
92
|
+
"sub": str(user_unique),
|
|
93
|
+
"aud": _TOKEN_AUDIENCE,
|
|
94
|
+
"exp": datetime.now(timezone.utc) + timedelta(minutes=_TOKEN_EXP_ACCESS if is_access else _TOKEN_EXP_REFRESH),
|
|
95
|
+
"user_data": user_data,
|
|
96
|
+
}
|
|
97
|
+
return jwt.encode(payload=payload,
|
|
98
|
+
key=_TOKEN_SECRET_KEY,
|
|
99
|
+
algorithm=_TOKEN_ALGORITHM)
|
|
100
|
+
|
|
101
|
+
def decode_jwt_token(token:str, is_access:bool=True) -> dict | None:
|
|
102
|
+
"""
|
|
103
|
+
decode jwt token information that encrypted in token string.
|
|
104
|
+
|
|
105
|
+
:param token: token value in string
|
|
106
|
+
:param is_access: set token type as bool for access(default) or refresh(False).
|
|
107
|
+
:return:
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
return jwt.decode(jwt=token,
|
|
112
|
+
key=_TOKEN_SECRET_KEY,
|
|
113
|
+
audience=_TOKEN_AUDIENCE,
|
|
114
|
+
algorithms=_TOKEN_ALGORITHM)
|
|
115
|
+
|
|
116
|
+
except jwt.exceptions.ExpiredSignatureError:
|
|
117
|
+
token_type: str = "access" if is_access else "refresh"
|
|
118
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
|
119
|
+
detail={"msg": f"{token_type} token was expired"})
|
|
120
|
+
|
|
121
|
+
except jwt.exceptions.DecodeError:
|
|
122
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
|
123
|
+
detail={"msg": "invalid access token. can not decode token information."})
|
|
124
|
+
|
|
125
|
+
def get_bearer_access_token(user_cred: HTTPAuthorizationCredentials = Depends(oauth2_scheme)) -> str:
|
|
126
|
+
"""
|
|
127
|
+
get access token from HTTPBearer credential in RestAPI Docs Page.
|
|
128
|
+
|
|
129
|
+
:param user_cred: get access_token information from HTTPBearer credential
|
|
130
|
+
:return: return access_token
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
if user_cred is None:
|
|
134
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
|
135
|
+
detail={"msg": "missing access token in headers."})
|
|
136
|
+
|
|
137
|
+
if user_cred.scheme.lower() != "bearer":
|
|
138
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
|
139
|
+
detail={"msg": "invalid access token. token type is not match."})
|
|
140
|
+
|
|
141
|
+
return _check_blacklist_access_token(token=user_cred.credentials)
|
|
142
|
+
|
|
143
|
+
def get_token_info(token: str, is_access: bool = True) -> dict:
|
|
144
|
+
"""
|
|
145
|
+
decrypt JWT token and return its data as dict type.
|
|
146
|
+
|
|
147
|
+
:param token: JWT token(access or refresh)
|
|
148
|
+
:param is_access: set token type as bool for access(default) or refresh(False).
|
|
149
|
+
:return: token information in dict type.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
payload: dict = decode_jwt_token(token=token, is_access=is_access)
|
|
154
|
+
return payload
|
|
155
|
+
|
|
156
|
+
def register_blacklist_access_token(token: str, ex: int=_TOKEN_EXP_ACCESS) -> None:
|
|
157
|
+
"""
|
|
158
|
+
register JWT access token in redis after signout or changing password
|
|
159
|
+
template: access_token: {ACCESS_TOKEN}
|
|
160
|
+
|
|
161
|
+
:param token: access_token that will be registered in redis.
|
|
162
|
+
:param ex: token expiration time in second.
|
|
163
|
+
:return: None
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
redis_connector.set(name=f"access_token:{token}", value="1", ex=ex * 60)
|
|
168
|
+
except ConnectionError:
|
|
169
|
+
logger.warning(msg="fail to register access_token as a blacklist. can not connect to redis db.")
|
|
170
|
+
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
def register_whitelist_refresh_token(user_unique: str, token: str) -> None:
|
|
174
|
+
"""
|
|
175
|
+
register JWT refresh token in redis after signin.
|
|
176
|
+
template: refresh_token:{USER_UNIQUE}
|
|
177
|
+
|
|
178
|
+
:param user_unique: primary key for each user.
|
|
179
|
+
:param token: refresh token that was issued after signin.
|
|
180
|
+
:return: None
|
|
181
|
+
"""
|
|
182
|
+
try:
|
|
183
|
+
redis_connector.set(name=f"refresh_token:{user_unique}", value=token, ex=_TOKEN_EXP_REFRESH * 60)
|
|
184
|
+
except ConnectionError:
|
|
185
|
+
logger.warning(msg="fail to register refresh_token as a whitelist. can not connect to redis db.")
|
|
186
|
+
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def remove_whitelist_refresh_token(user_unique: str) -> None:
|
|
190
|
+
"""
|
|
191
|
+
remove registered refresh token in redis after signout or changing password.
|
|
192
|
+
|
|
193
|
+
:param user_unique: primary key for each user.
|
|
194
|
+
:return: None
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
redis_connector.delete(f"refresh_token:{user_unique}")
|
|
199
|
+
except ConnectionError:
|
|
200
|
+
logger.warning(msg="fail to remove refresh token from redis. can not connect to redis db.")
|
|
201
|
+
|
|
202
|
+
return None
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# import packages from default or pip library
|
|
2
|
+
from fastapi.exceptions import RequestValidationError
|
|
3
|
+
|
|
4
|
+
# import packages from this framework
|
|
5
|
+
from settings import PASSWORD_POLICIES
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# settings
|
|
9
|
+
MIN_LENGTH: int = PASSWORD_POLICIES.get("min_length")
|
|
10
|
+
SPECIAL_CHARS: str = "!@#$%^&*()_-+=~`\'\";:\\|<>,.?/"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# define password policy functions
|
|
14
|
+
def password_minlen(password: str, field: str = "password"):
|
|
15
|
+
if len(password) >= MIN_LENGTH:
|
|
16
|
+
return password
|
|
17
|
+
|
|
18
|
+
raise RequestValidationError(errors={"input": field,
|
|
19
|
+
"msg": f"{field} must be at least 8 chars"})
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def password_upchar(password: str, field: str = "password") -> str:
|
|
23
|
+
for c in password:
|
|
24
|
+
if c.isupper():
|
|
25
|
+
return password
|
|
26
|
+
|
|
27
|
+
raise RequestValidationError(errors={"input": field,
|
|
28
|
+
"msg": f"{field} must contain at least one upper character"})
|
|
29
|
+
|
|
30
|
+
def password_lowchar(password: str, field: str = "password") -> str:
|
|
31
|
+
for c in password:
|
|
32
|
+
if c.islower():
|
|
33
|
+
return password
|
|
34
|
+
|
|
35
|
+
raise RequestValidationError(errors={"input": field,
|
|
36
|
+
"msg": f"{field} must contain at least one lower character"})
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def password_number(password: str, field: str = "password") -> str:
|
|
40
|
+
for c in password:
|
|
41
|
+
if c.isnumeric():
|
|
42
|
+
return password
|
|
43
|
+
|
|
44
|
+
raise RequestValidationError(errors={"input": field,
|
|
45
|
+
"msg": f"{field} must contain at least one number"})
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def password_special(password: str, field: str = "password") -> str:
|
|
49
|
+
for c in password:
|
|
50
|
+
if c in SPECIAL_CHARS:
|
|
51
|
+
return password
|
|
52
|
+
|
|
53
|
+
raise RequestValidationError(errors={"input":field,
|
|
54
|
+
"msg":f"{field} must contain at least one special character"})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# define dependencies functions
|
|
58
|
+
def validate_password_policy(password: str, field: str = "password") -> str:
|
|
59
|
+
policy_mapping: dict = {
|
|
60
|
+
"PASSWORD_MINLEN": password_minlen,
|
|
61
|
+
"PASSWORD_UPCHAR": password_upchar,
|
|
62
|
+
"PASSWORD_LOWCHAR": password_lowchar,
|
|
63
|
+
"PASSWORD_NUMBER": password_number,
|
|
64
|
+
"PASSWORD_SPECIAL": password_special,
|
|
65
|
+
}
|
|
66
|
+
validate_list: list = PASSWORD_POLICIES.get("compliance")
|
|
67
|
+
|
|
68
|
+
if len(validate_list) == 0:
|
|
69
|
+
return password
|
|
70
|
+
|
|
71
|
+
for func in validate_list:
|
|
72
|
+
tmp = policy_mapping.get(func)
|
|
73
|
+
if tmp is not None and callable(tmp):
|
|
74
|
+
tmp(password=password, field=field)
|
|
75
|
+
|
|
76
|
+
return password
|
jknife/logging.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import logging.config
|
|
3
|
+
from os import mkdir, listdir
|
|
4
|
+
from typing_extensions import Annotated, Doc
|
|
5
|
+
from settings import LOG_SETTINGS, DEBUG_MODE
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# create logs folder to store log files.
|
|
9
|
+
if "logs" not in listdir(""):
|
|
10
|
+
mkdir("logs")
|
|
11
|
+
|
|
12
|
+
# set logging config from settings.py
|
|
13
|
+
# apply default key:value for dictConfig
|
|
14
|
+
LOG_SETTINGS.update({"version": 1, "disable_existing_loggers": False})
|
|
15
|
+
logging.config.dictConfig(config=LOG_SETTINGS)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# define LoggerMgmt for multiple logging
|
|
19
|
+
class LoggerMgmt:
|
|
20
|
+
"""
|
|
21
|
+
This class is designed to implement multiple logging system in FastAPI.
|
|
22
|
+
|
|
23
|
+
:param logger_names: assign the logger name that you want to use in specific area.
|
|
24
|
+
you can define it in settings.py with LOG_SETTINGS and *_LOGGER_LIST
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self,
|
|
28
|
+
logger_names: Annotated[list[str],
|
|
29
|
+
Doc("assign the name of loggers to record activities")]):
|
|
30
|
+
self.__loggers = [logging.getLogger(name=name) for name in logger_names]
|
|
31
|
+
if DEBUG_MODE:
|
|
32
|
+
uvicorn_logger = logging.getLogger(name="uvicorn")
|
|
33
|
+
uvicorn_logger.setLevel(level="DEBUG")
|
|
34
|
+
self.__loggers.append(uvicorn_logger)
|
|
35
|
+
|
|
36
|
+
def critical(self, msg: Annotated[str,
|
|
37
|
+
Doc("log message for critical level")]) -> None:
|
|
38
|
+
for logger in self.__loggers:
|
|
39
|
+
logger.critical(msg=msg)
|
|
40
|
+
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
def error(self, msg: Annotated[str,
|
|
44
|
+
Doc("log message for error level")]) -> None:
|
|
45
|
+
for logger in self.__loggers:
|
|
46
|
+
logger.error(msg=msg)
|
|
47
|
+
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def warning(self, msg: Annotated[str,
|
|
51
|
+
Doc("log message for warning level")]) -> None:
|
|
52
|
+
for logger in self.__loggers:
|
|
53
|
+
logger.warning(msg=msg)
|
|
54
|
+
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def info(self, msg: Annotated[str,
|
|
58
|
+
Doc("log message for info level")]) -> None:
|
|
59
|
+
for logger in self.__loggers:
|
|
60
|
+
logger.info(msg=msg)
|
|
61
|
+
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
def debug(self, msg: Annotated[str,
|
|
65
|
+
Doc("log message for debug level")]) -> None:
|
|
66
|
+
for logger in self.__loggers:
|
|
67
|
+
logger.debug(msg=msg)
|
|
68
|
+
|
|
69
|
+
return None
|
jknife/views/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# import packages from default or pip library
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from typing_extensions import Annotated, Doc, Optional
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
# import packages from this framework
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# define mixin class
|
|
11
|
+
class IdViewMixin(BaseModel):
|
|
12
|
+
id: Annotated[int,
|
|
13
|
+
Doc("Default integer id for each table row.")]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UUIDViewMixin(BaseModel):
|
|
17
|
+
id: Annotated[UUID,
|
|
18
|
+
Doc("UUID format id for each table row.")]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RegisterDateTimeViewMixin(BaseModel):
|
|
22
|
+
register_dt: Annotated[Optional[datetime],
|
|
23
|
+
Doc("Datetime that the row was added at.")]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# import packages from default or pip library
|
|
2
|
+
from typing_extensions import Annotated, Doc
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
# import packages from this framework
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Error Mixin Class
|
|
9
|
+
class ErrMsgMixin(BaseModel):
|
|
10
|
+
msg: Annotated[str,
|
|
11
|
+
Doc("message for error")]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ErrMsgTypeMixin(BaseModel):
|
|
15
|
+
type: Annotated[str,
|
|
16
|
+
Doc("customised message type. for example, 'password_mismatch' or 'unauthorised access'.")]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ErrMsgBoolResultMixin(BaseModel):
|
|
20
|
+
result: Annotated[bool, Doc("result of calling API in bool type.")] = False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FieldValidationErrMixin(BaseModel):
|
|
24
|
+
field: Annotated[str,
|
|
25
|
+
Doc("field name which has an error.")]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Error Model
|
|
29
|
+
class DefaultErrorMsgView(ErrMsgMixin):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ErrMsgWithTypeView(ErrMsgTypeMixin, DefaultErrorMsgView):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ErrMsgWithTypeAndResultView(ErrMsgBoolResultMixin, ErrMsgWithTypeView):
|
|
38
|
+
pass
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# import packages from default or pip library
|
|
2
|
+
from datetime import date
|
|
3
|
+
from typing_extensions import Annotated, Doc
|
|
4
|
+
from pydantic import BaseModel, EmailStr
|
|
5
|
+
|
|
6
|
+
# import packages from this framework
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# define mixin class
|
|
10
|
+
class BirthdateViewMixin(BaseModel):
|
|
11
|
+
birthdate: Annotated[date,
|
|
12
|
+
Doc("user's birthdate")]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CPNumViewMixin(BaseModel):
|
|
16
|
+
cp_num: Annotated[str,
|
|
17
|
+
Doc("cellphone number.")]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EmailInputViewMixin(BaseModel):
|
|
21
|
+
email: Annotated[EmailStr,
|
|
22
|
+
Doc("email address for user. it can be replaced username.")]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FirstNameViewMixin(BaseModel):
|
|
26
|
+
firstname: Annotated[str,
|
|
27
|
+
Doc("firstname of user")]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LastNameViewMixin(BaseModel):
|
|
31
|
+
lastname: Annotated[str,
|
|
32
|
+
Doc("lastname of user")]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class NationViewMixin(BaseModel):
|
|
36
|
+
nation: Annotated[str,
|
|
37
|
+
Doc("the nation that user came from.")]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PostalCodeViewMixin(BaseModel):
|
|
41
|
+
postal_code: Annotated[str,
|
|
42
|
+
Doc("postal number of address.")]
|
jknife/views/tokens.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# import packages from default or pip library
|
|
2
|
+
from typing_extensions import Annotated, Doc
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# import packages from this framework
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# define Class for Common SQLModel
|
|
11
|
+
class DefaultJWTTokenView(BaseModel):
|
|
12
|
+
"""
|
|
13
|
+
This class is a view template for returning JWT tokens information.
|
|
14
|
+
Read Only.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
access_token: Annotated[str,
|
|
18
|
+
Doc("This is an access token string for authenticated user")]
|
|
19
|
+
refresh_token: Annotated[str,
|
|
20
|
+
Doc("This is a token string for reissuing access token")]
|
|
21
|
+
token_type: Annotated[str,
|
|
22
|
+
Doc("Token type that used in this class. Default is 'Bearer'")] = "Bearer"
|
jknife/views/users.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# import packages from default or pip library
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing_extensions import Annotated, Doc
|
|
4
|
+
from pydantic import BaseModel, field_validator
|
|
5
|
+
|
|
6
|
+
# import packages from this framework
|
|
7
|
+
from src.jknife.views.personnel_info import EmailInputViewMixin
|
|
8
|
+
from src.jknife.dependencies.users import validate_password_policy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# define mixin class
|
|
12
|
+
class UsernameInputViewMixin(BaseModel):
|
|
13
|
+
username: Annotated[str,
|
|
14
|
+
Doc("username")]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class InputPasswordViewMixin(BaseModel):
|
|
18
|
+
password: Annotated[str,
|
|
19
|
+
Doc("password for user")]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LastSigninDateTimeViewMixin(BaseModel):
|
|
23
|
+
last_signin_dt: Annotated[datetime | None,
|
|
24
|
+
Doc("User's last signin datetime.")] = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SigninFailMixin(BaseModel):
|
|
28
|
+
is_active: Annotated[bool,
|
|
29
|
+
Doc("show whether the user is activated or not.")]
|
|
30
|
+
signin_fail: Annotated[int,
|
|
31
|
+
Doc("If the user fail to login, this value will be incremented.")]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# define common view class
|
|
35
|
+
class ChangePasswordView(InputPasswordViewMixin):
|
|
36
|
+
new_password: Annotated[str,
|
|
37
|
+
Doc("New password for changing password")]
|
|
38
|
+
|
|
39
|
+
@field_validator('new_password', mode='before')
|
|
40
|
+
@classmethod
|
|
41
|
+
def check_new_password(cls, value) -> str:
|
|
42
|
+
return validate_password_policy(password=value, field="new_password")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DefaultSignupView(InputPasswordViewMixin, UsernameInputViewMixin):
|
|
46
|
+
password: Annotated[str,
|
|
47
|
+
Doc("password for user")]
|
|
48
|
+
|
|
49
|
+
@field_validator('password', mode='before')
|
|
50
|
+
@classmethod
|
|
51
|
+
def check_password(cls, value) -> str:
|
|
52
|
+
return validate_password_policy(password=value)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class DefaultEmailSignupView(InputPasswordViewMixin, EmailInputViewMixin):
|
|
56
|
+
password: Annotated[str,
|
|
57
|
+
Doc("password for user")]
|
|
58
|
+
|
|
59
|
+
@field_validator('password', mode='before')
|
|
60
|
+
@classmethod
|
|
61
|
+
def check_password(cls, value) -> str:
|
|
62
|
+
return validate_password_policy(password=value)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class UsernameSigninView(InputPasswordViewMixin, UsernameInputViewMixin):
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class EmailSigninView(InputPasswordViewMixin, EmailInputViewMixin):
|
|
70
|
+
pass
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jknife
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Custom FastAPI for luna-negra
|
|
5
|
+
Author: luna-negra
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: Backend,Customised,FastAPI,Python,pypi
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Dist: blinker>=1.9.0
|
|
12
|
+
Requires-Dist: fastapi[standard]>=0.166.1
|
|
13
|
+
Requires-Dist: mongoengine
|
|
14
|
+
Requires-Dist: pycountry>=24.6.1
|
|
15
|
+
Requires-Dist: pyjwt>=2.10.1
|
|
16
|
+
Requires-Dist: redis>=6.4.0
|
|
17
|
+
Requires-Dist: sqlmodel>=0.0.24
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
jknife/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
jknife/logging.py,sha256=0MVIdLEYE2hpaRHhjbS20hFKIFXVJVVDzqyJmUU8_HQ,2314
|
|
3
|
+
jknife/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
jknife/commands/jknife.py,sha256=Mu-oiYMjdHT0rD3jKjum6_9K_RjbxuqB2C7gr-_Qukc,11635
|
|
5
|
+
jknife/db/__init__.py,sha256=weR-h-z2xd4wW64kc5rCrG7zz3toe-bwggoBka_g0nA,6245
|
|
6
|
+
jknife/db/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
jknife/db/models/mongo/__init__.py,sha256=bDj6rFEtNudrbtPbk4iORlWwXyMv6V_PCeixyGufxP4,1984
|
|
8
|
+
jknife/db/models/mongo/network.py,sha256=MfFq05k_42uw4mMfcd62K7MOhuk0E5OuqfxmHD_ONR0,1106
|
|
9
|
+
jknife/db/models/mongo/personnel_info.py,sha256=wIalbvVI1bqCTOsShrSvy29I6OrdqaY6nYGhSxRFTf0,3273
|
|
10
|
+
jknife/db/models/mongo/settings.py,sha256=mFJQAAeoyWEY1J6y5_l1p4JevOYHyX27wL5A-gO35bg,2219
|
|
11
|
+
jknife/db/models/mongo/token.py,sha256=Xfw6LlAZ8Qu2iA9vYxyTk52aq4KoMLZ9P0KBRDiOBLI,2410
|
|
12
|
+
jknife/db/models/mongo/users.py,sha256=xTlkK5BiF5oSaeFyF1aTh7JDpu2K6Jr0MI2ALt0BoSA,2931
|
|
13
|
+
jknife/db/models/rdbms/__init__.py,sha256=0r22xz8J_kENMiPNVr_t3P5iILGXsll38wXXwnREABc,1847
|
|
14
|
+
jknife/db/models/rdbms/network.py,sha256=lk3p4UBbsnPRuwr5Q4CGXDL9bSAlZAEaUz-GVfEWc0U,1040
|
|
15
|
+
jknife/db/models/rdbms/personnel_info.py,sha256=OBwMvg8K_3XqKSSaeJzDir_pIIHNd3cxdL-2IsQFuFQ,2976
|
|
16
|
+
jknife/db/models/rdbms/settings.py,sha256=p_typKsxO6aCh-HNL_vlj9GIT7gm-eYkyA-vvo0wsmY,1520
|
|
17
|
+
jknife/db/models/rdbms/token.py,sha256=4JP3sdspM-nsBXM9v1zbM2JqI56FzKPsP7nNowZDPGo,2179
|
|
18
|
+
jknife/db/models/rdbms/users.py,sha256=5vldUYl3pGZjWjuWGvn6M0SeHvj869omcMS6TkBVSa4,3189
|
|
19
|
+
jknife/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
jknife/dependencies/token.py,sha256=uBQ_zIU56HBOZH_mQWwdQeEFv7VXdKAJn7MAFqWODsU,7765
|
|
21
|
+
jknife/dependencies/users.py,sha256=G6bJ-vHz7hlHXn2OpNEAoVB1H8DJhwc4XBMBmKW1v6Q,2502
|
|
22
|
+
jknife/views/__init__.py,sha256=uXUiBzToN6gmb7zcHUyPbniNjCtzbyN31FrIVs7PpI0,596
|
|
23
|
+
jknife/views/error_message.py,sha256=g3xS8Z0R1BeP9XpmETgLirDdghM789iGXBfU-K8wwWM,887
|
|
24
|
+
jknife/views/personnel_info.py,sha256=JL_GqerZjhgs4AJQsQ2eeUZxCh17G8zXuWsDqaEI2tk,957
|
|
25
|
+
jknife/views/tokens.py,sha256=PJh_fGEwK3T6Kq0aY-CuJPWCTdcHFrCSd2NhJMZqYqU,647
|
|
26
|
+
jknife/views/users.py,sha256=ndbSTMlEeiwazfejHrGj37Jnw-e40Y6YoZwzyyyM4ns,2041
|
|
27
|
+
jknife-0.0.1.dist-info/METADATA,sha256=VPDim94DKvnv8t2T4RQrpcWOaOcBP7j1aRoQ6tWGa9I,519
|
|
28
|
+
jknife-0.0.1.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
|
29
|
+
jknife-0.0.1.dist-info/entry_points.txt,sha256=08tkSuEw5jKr7Jz0cFlcKr8b2fjsQIf4Mzmb0cB8XOM,55
|
|
30
|
+
jknife-0.0.1.dist-info/licenses/LICENSE,sha256=myY0xrsHJcytQadTeGVqmIo6QlskAAf5epHL-WRQe30,1067
|
|
31
|
+
jknife-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright <YEAR> <COPYRIGHT HOLDER>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|