shaapi 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.
Files changed (141) hide show
  1. shaapi/__init__.py +3 -0
  2. shaapi/cli.py +97 -0
  3. shaapi/generator.py +114 -0
  4. shaapi/template/.dockerignore +37 -0
  5. shaapi/template/.env.template +59 -0
  6. shaapi/template/.gitattributes +12 -0
  7. shaapi/template/.gitignore +170 -0
  8. shaapi/template/.gitlab-ci.yml +89 -0
  9. shaapi/template/Dockerfile +59 -0
  10. shaapi/template/LICENSE +21 -0
  11. shaapi/template/README.md +206 -0
  12. shaapi/template/backend/.gitignore +164 -0
  13. shaapi/template/backend/__init__.py +0 -0
  14. shaapi/template/backend/alembic/README +1 -0
  15. shaapi/template/backend/alembic/env.py +102 -0
  16. shaapi/template/backend/alembic/script.py.mako +26 -0
  17. shaapi/template/backend/alembic/versions/2026_06_08_1024-64524c63b666_initial.py +143 -0
  18. shaapi/template/backend/alembic.ini +117 -0
  19. shaapi/template/backend/app/__init__.py +55 -0
  20. shaapi/template/backend/app/admin/__init__.py +1 -0
  21. shaapi/template/backend/app/admin/api/v1/__init__.py +0 -0
  22. shaapi/template/backend/app/admin/api/v1/auth.py +59 -0
  23. shaapi/template/backend/app/admin/api/v1/casbin.py +218 -0
  24. shaapi/template/backend/app/admin/api/v1/login_log.py +63 -0
  25. shaapi/template/backend/app/admin/api/v1/opera_log.py +61 -0
  26. shaapi/template/backend/app/admin/api/v1/role.py +108 -0
  27. shaapi/template/backend/app/admin/api/v1/user.py +47 -0
  28. shaapi/template/backend/app/admin/schema/casbin_rule.py +45 -0
  29. shaapi/template/backend/app/admin/schema/login_log.py +36 -0
  30. shaapi/template/backend/app/admin/schema/opera_log.py +43 -0
  31. shaapi/template/backend/app/admin/schema/role.py +36 -0
  32. shaapi/template/backend/app/admin/schema/sso.py +37 -0
  33. shaapi/template/backend/app/admin/schema/token.py +74 -0
  34. shaapi/template/backend/app/admin/schema/user.py +93 -0
  35. shaapi/template/backend/app/admin/service/auth_service.py +233 -0
  36. shaapi/template/backend/app/admin/service/casbin_service.py +135 -0
  37. shaapi/template/backend/app/admin/service/login_log_service.py +62 -0
  38. shaapi/template/backend/app/admin/service/opera_log_service.py +31 -0
  39. shaapi/template/backend/app/admin/service/role_service.py +79 -0
  40. shaapi/template/backend/app/admin/service/secure_token_service.py +60 -0
  41. shaapi/template/backend/app/admin/service/user_service.py +153 -0
  42. shaapi/template/backend/app/api.py +11 -0
  43. shaapi/template/backend/common/__init__.py +0 -0
  44. shaapi/template/backend/common/cloud_storage/__init__.py +11 -0
  45. shaapi/template/backend/common/cloud_storage/cloud_storage.py +180 -0
  46. shaapi/template/backend/common/dataclasses.py +52 -0
  47. shaapi/template/backend/common/email_conf/email.py +105 -0
  48. shaapi/template/backend/common/enums.py +144 -0
  49. shaapi/template/backend/common/exception/__init__.py +0 -0
  50. shaapi/template/backend/common/exception/errors.py +87 -0
  51. shaapi/template/backend/common/exception/exception_handler.py +280 -0
  52. shaapi/template/backend/common/log.py +123 -0
  53. shaapi/template/backend/common/model.py +68 -0
  54. shaapi/template/backend/common/pagination.py +83 -0
  55. shaapi/template/backend/common/response/__init__.py +0 -0
  56. shaapi/template/backend/common/response/response_code.py +158 -0
  57. shaapi/template/backend/common/response/response_schema.py +110 -0
  58. shaapi/template/backend/common/schema.py +144 -0
  59. shaapi/template/backend/common/security/jwt.py +203 -0
  60. shaapi/template/backend/common/security/rbac.py +98 -0
  61. shaapi/template/backend/common/security/sec_token.py +6 -0
  62. shaapi/template/backend/common/socketio/action.py +11 -0
  63. shaapi/template/backend/common/socketio/server.py +50 -0
  64. shaapi/template/backend/common/sso/base.py +69 -0
  65. shaapi/template/backend/common/sso/google.py +127 -0
  66. shaapi/template/backend/core/conf.py +208 -0
  67. shaapi/template/backend/core/path_conf.py +24 -0
  68. shaapi/template/backend/core/registrar.py +195 -0
  69. shaapi/template/backend/crud/__init__.py +1 -0
  70. shaapi/template/backend/crud/crud_base.py +35 -0
  71. shaapi/template/backend/crud/crud_casbin.py +46 -0
  72. shaapi/template/backend/crud/crud_login_log.py +58 -0
  73. shaapi/template/backend/crud/crud_opera_log.py +58 -0
  74. shaapi/template/backend/crud/crud_role.py +128 -0
  75. shaapi/template/backend/crud/crud_user.py +267 -0
  76. shaapi/template/backend/database/__init__.py +0 -0
  77. shaapi/template/backend/database/db_postgres.py +125 -0
  78. shaapi/template/backend/database/db_redis.py +62 -0
  79. shaapi/template/backend/entrypoint-api.sh +19 -0
  80. shaapi/template/backend/lang/en/app.py +18 -0
  81. shaapi/template/backend/lang/en/auth.py +10 -0
  82. shaapi/template/backend/lang/fr/app.py +18 -0
  83. shaapi/template/backend/lang/fr/auth.py +10 -0
  84. shaapi/template/backend/main.py +54 -0
  85. shaapi/template/backend/middleware/__init__.py +1 -0
  86. shaapi/template/backend/middleware/access_middleware.py +19 -0
  87. shaapi/template/backend/middleware/i18n_middleware.py +19 -0
  88. shaapi/template/backend/middleware/jwt_auth_middleware.py +73 -0
  89. shaapi/template/backend/middleware/opera_log_middleware.py +179 -0
  90. shaapi/template/backend/middleware/state_middleware.py +26 -0
  91. shaapi/template/backend/models/__init__.py +10 -0
  92. shaapi/template/backend/models/associations.py +20 -0
  93. shaapi/template/backend/models/casbin_rule.py +30 -0
  94. shaapi/template/backend/models/login_log.py +28 -0
  95. shaapi/template/backend/models/opera_log.py +36 -0
  96. shaapi/template/backend/models/role.py +27 -0
  97. shaapi/template/backend/models/user.py +30 -0
  98. shaapi/template/backend/seeder/json/admin.json +15 -0
  99. shaapi/template/backend/seeder/json/user.json +15 -0
  100. shaapi/template/backend/seeder/run.py +34 -0
  101. shaapi/template/backend/static/ip2region.xdb +0 -0
  102. shaapi/template/backend/templates/build/meet.html +169 -0
  103. shaapi/template/backend/templates/build/new_account.html +373 -0
  104. shaapi/template/backend/templates/build/reset-password.html +170 -0
  105. shaapi/template/backend/templates/build/test_email.html +25 -0
  106. shaapi/template/backend/templates/build/welcome-one-1.html +160 -0
  107. shaapi/template/backend/templates/build/welcome-one.html +178 -0
  108. shaapi/template/backend/templates/build/welcome-two.html +234 -0
  109. shaapi/template/backend/templates/index.html +0 -0
  110. shaapi/template/backend/templates/src/new_account.mjml +15 -0
  111. shaapi/template/backend/templates/src/reset_password.mjml +19 -0
  112. shaapi/template/backend/templates/src/test_email.mjml +11 -0
  113. shaapi/template/backend/templates/ws/ws.html +70 -0
  114. shaapi/template/backend/utils/demo_site.py +18 -0
  115. shaapi/template/backend/utils/encrypt.py +108 -0
  116. shaapi/template/backend/utils/health_check.py +34 -0
  117. shaapi/template/backend/utils/prometheus.py +135 -0
  118. shaapi/template/backend/utils/request_parse.py +110 -0
  119. shaapi/template/backend/utils/serializers.py +75 -0
  120. shaapi/template/backend/utils/timezone.py +51 -0
  121. shaapi/template/backend/utils/trace_id.py +7 -0
  122. shaapi/template/backend/utils/translator.py +28 -0
  123. shaapi/template/devops/scripts/deploy.sh +7 -0
  124. shaapi/template/devops/scripts/setup_env.sh +62 -0
  125. shaapi/template/docker-compose.monitoring.yml +63 -0
  126. shaapi/template/docker-compose.override.yml +12 -0
  127. shaapi/template/docker-compose.yml +90 -0
  128. shaapi/template/docker-run.sh +99 -0
  129. shaapi/template/etc/dashboards/fastapi-observability.json +1044 -0
  130. shaapi/template/etc/dashboards.yaml +10 -0
  131. shaapi/template/etc/grafana/datasource.yml +79 -0
  132. shaapi/template/etc/prometheus/prometheus.yml +52 -0
  133. shaapi/template/package-lock.json +2102 -0
  134. shaapi/template/package.json +16 -0
  135. shaapi/template/pyproject.toml +78 -0
  136. shaapi/template/uv.lock +2866 -0
  137. shaapi-0.1.0.dist-info/METADATA +92 -0
  138. shaapi-0.1.0.dist-info/RECORD +141 -0
  139. shaapi-0.1.0.dist-info/WHEEL +4 -0
  140. shaapi-0.1.0.dist-info/entry_points.txt +2 -0
  141. shaapi-0.1.0.dist-info/licenses/LICENCE +21 -0
@@ -0,0 +1,47 @@
1
+ from typing import Annotated, Union
2
+ from fastapi import APIRouter, Path, Query, Request
3
+ from backend.common.enums import Role
4
+ from backend.common.pagination import DependsPagination, paging_data
5
+ from backend.database.db_postgres import CurrentSession
6
+ from backend.utils.serializers import select_as_dict
7
+
8
+ from backend.app.admin.schema.user import GetUserInfoListDetails, UserUpdate
9
+ from backend.app.admin.service.user_service import user_service
10
+ from backend.common.response.response_schema import ResponseModel, response_base
11
+ from backend.common.security.jwt import DependsJwtAuth
12
+
13
+ router = APIRouter(prefix="/user", tags=["User"])
14
+
15
+
16
+
17
+ # @router.get(
18
+ # "/{pk}",
19
+ # summary="Get user by ID",
20
+ # )
21
+ # async def get_user(request: Request, pk: Annotated[int, Path(...)]) -> ResponseModel:
22
+ # user = await user_service.get_by_id(user_id=pk)
23
+ # data = GetUserInfoListDetails(**select_as_dict(user))
24
+ # return response_base.success(request=request, data=data)
25
+
26
+
27
+ @router.get(
28
+ "/",
29
+ summary="Get users pagination",
30
+ dependencies=[
31
+ DependsJwtAuth,
32
+ DependsPagination,
33
+ ],
34
+ )
35
+ async def get_pagination_users(
36
+ request: Request,
37
+ db: CurrentSession,
38
+ query: Annotated[str | None, Query()] = None,
39
+ role: Annotated[Union[Role, str] | None, Query()] = None,
40
+ status: Annotated[bool | None, Query()] = None,
41
+ ) -> ResponseModel:
42
+ user_select = await user_service.get_select(
43
+ q=query, role=role, status=status
44
+ )
45
+ page_data = await paging_data(db, user_select, GetUserInfoListDetails)
46
+ return response_base.success(request=request, data=page_data)
47
+
@@ -0,0 +1,45 @@
1
+ from pydantic import ConfigDict, Field
2
+
3
+ from backend.common.enums import MethodType
4
+ from backend.common.schema import SchemaBase
5
+
6
+
7
+ class CreatePolicyParam(SchemaBase):
8
+ sub: str = Field(..., description='User x_id / Role x_id')
9
+ path: str = Field(..., description='api path')
10
+ method: MethodType = Field(default=MethodType.GET, description='Request method')
11
+
12
+
13
+ class UpdatePolicyParam(CreatePolicyParam):
14
+ pass
15
+
16
+
17
+ class DeletePolicyParam(CreatePolicyParam):
18
+ pass
19
+
20
+
21
+ class DeleteAllPoliciesParam(SchemaBase):
22
+ uuid: str | None = None
23
+ role: str
24
+
25
+
26
+ class CreateUserRoleParam(SchemaBase):
27
+ uuid: str = Field(..., description='User x_id')
28
+ role: str = Field(..., description='role')
29
+
30
+
31
+ class DeleteUserRoleParam(CreateUserRoleParam):
32
+ pass
33
+
34
+
35
+ class GetPolicyListDetails(SchemaBase):
36
+ model_config = ConfigDict(from_attributes=True)
37
+
38
+ id: int
39
+ ptype: str = Field(..., description='Rule type, p / g')
40
+ v0: str = Field(..., description='User uuid / Role')
41
+ v1: str = Field(..., description='api path / roles')
42
+ v2: str | None = None
43
+ v3: str | None = None
44
+ v4: str | None = None
45
+ v5: str | None = None
@@ -0,0 +1,36 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import ConfigDict
4
+
5
+ from backend.common.schema import SchemaBase
6
+
7
+
8
+ class LoginLogSchemaBase(SchemaBase):
9
+ user_x_id: str
10
+ email: str
11
+ status: int
12
+ ip: str
13
+ country: str | None
14
+ region: str | None
15
+ city: str | None
16
+ user_agent: str
17
+ browser: str | None
18
+ os: str | None
19
+ device: str | None
20
+ msg: str
21
+ login_time: datetime
22
+
23
+
24
+ class CreateLoginLogParam(LoginLogSchemaBase):
25
+ pass
26
+
27
+
28
+ class UpdateLoginLogParam(LoginLogSchemaBase):
29
+ pass
30
+
31
+
32
+ class GetLoginLogListDetails(LoginLogSchemaBase):
33
+ model_config = ConfigDict(from_attributes=True)
34
+
35
+ id: int
36
+ created_time: datetime
@@ -0,0 +1,43 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import ConfigDict, Field
4
+
5
+ from backend.common.enums import StatusType
6
+ from backend.common.schema import SchemaBase
7
+
8
+
9
+ class OperaLogSchemaBase(SchemaBase):
10
+ trace_id: str
11
+ user_email: str | None = None
12
+ method: str
13
+ title: str
14
+ path: str
15
+ ip: str
16
+ country: str | None = None
17
+ region: str | None = None
18
+ city: str | None = None
19
+ user_agent: str
20
+ os: str | None = None
21
+ browser: str | None = None
22
+ device: str | None = None
23
+ args: dict | None = None
24
+ status: StatusType = Field(default=StatusType.enable)
25
+ code: str
26
+ msg: str | None = None
27
+ cost_time: float
28
+ opera_time: datetime
29
+
30
+
31
+ class CreateOperaLogParam(OperaLogSchemaBase):
32
+ pass
33
+
34
+
35
+ class UpdateOperaLogParam(OperaLogSchemaBase):
36
+ pass
37
+
38
+
39
+ class GetOperaLogListDetails(OperaLogSchemaBase):
40
+ model_config = ConfigDict(from_attributes=True)
41
+
42
+ id: int
43
+ created_time: datetime
@@ -0,0 +1,36 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import ConfigDict, Field
4
+
5
+ from backend.common.enums import RoleDataScopeType, StatusType
6
+ from backend.common.schema import SchemaBase
7
+
8
+
9
+ class RoleSchemaBase(SchemaBase):
10
+ name: str
11
+ data_scope: RoleDataScopeType = Field(
12
+ default=RoleDataScopeType.custom, description='Permission ranges (1: all data permissions 2: custom data permissions)'
13
+ )
14
+ status: StatusType = Field(default=StatusType.enable)
15
+ remark: str | None = None
16
+
17
+
18
+ class CreateRoleParam(RoleSchemaBase):
19
+ pass
20
+
21
+
22
+ class UpdateRoleParam(RoleSchemaBase):
23
+ pass
24
+
25
+
26
+ class UpdateRoleMenuParam(SchemaBase):
27
+ menus: list[int]
28
+
29
+
30
+ class GetRoleListDetails(RoleSchemaBase):
31
+ model_config = ConfigDict(from_attributes=True)
32
+
33
+ id: int
34
+ x_id: str
35
+ created_time: datetime
36
+ updated_time: datetime | None = None
@@ -0,0 +1,37 @@
1
+ from enum import Enum
2
+ from typing import Optional
3
+
4
+ from pydantic import AnyHttpUrl
5
+ from pydantic import BaseModel
6
+ from pydantic import EmailStr
7
+
8
+
9
+ class SocialTypes(Enum):
10
+ facebook = "facebook"
11
+ google = "google"
12
+ msal = "msal"
13
+ linkedin = "linkedin"
14
+ github = "github"
15
+ gitlab = "gitlab"
16
+
17
+
18
+ class OAuthRedirectLink(BaseModel):
19
+
20
+ url: AnyHttpUrl
21
+
22
+
23
+ class OAuthCodeResponseSchema(BaseModel):
24
+ code: str
25
+
26
+
27
+ class OAuthTokenResponseSchema(BaseModel):
28
+ token: str
29
+
30
+
31
+ class OAuthUserDataResponseSchema(BaseModel):
32
+ external_id: str
33
+ email: EmailStr
34
+ social_type: SocialTypes
35
+ img: Optional[AnyHttpUrl]
36
+ firstname: Optional[str]
37
+ lastname: Optional[str]
@@ -0,0 +1,74 @@
1
+ from datetime import datetime
2
+ from typing import Any
3
+
4
+
5
+ from backend.common.schema import SchemaBase
6
+ from backend.common.enums import Secure_token_type
7
+
8
+
9
+ class GetRoleDetails(SchemaBase):
10
+ name: str
11
+ class Config:
12
+ from_attributes = True
13
+
14
+ class ConnectedUserInfo(SchemaBase):
15
+
16
+ id: int
17
+ x_id: str
18
+ profile_image: dict | None = None
19
+ firstname: str | None = None
20
+ lastname: str | None = None
21
+ phone: str | None = None
22
+ email: str
23
+ roles: list[GetRoleDetails] | None = None
24
+ country_code: str | None = None
25
+
26
+ class Config:
27
+ from_attributes = True
28
+
29
+ class ConnectedCompanyInfo(SchemaBase):
30
+ id: int
31
+ x_id: str
32
+ name: str | None = None
33
+ email: str
34
+ logo: Any | None = None
35
+ min_members: int | None = None
36
+ max_members: int | None = None
37
+ email_verified: bool | None = None
38
+ country_code: str | None = None
39
+ role: GetRoleDetails | None = None
40
+
41
+ class Config:
42
+ from_attributes = True
43
+
44
+ class GetSwaggerToken(SchemaBase):
45
+ access_token: str
46
+ token_type: str = 'Bearer'
47
+ user: Any
48
+
49
+
50
+ class AccessTokenBase(SchemaBase):
51
+ access_token: str
52
+ access_token_type: str = 'Bearer'
53
+ access_token_expire_time: datetime
54
+
55
+
56
+ class GetNewToken(AccessTokenBase):
57
+ pass
58
+
59
+
60
+ class GetLoginToken(AccessTokenBase):
61
+ user: ConnectedUserInfo
62
+ pass
63
+
64
+
65
+ class GetCompanyLoginToken(AccessTokenBase):
66
+ company: ConnectedCompanyInfo
67
+ pass
68
+
69
+ class Secure_token(SchemaBase):
70
+ token_type: Secure_token_type
71
+ token: str
72
+ user_x_id: str
73
+ used: bool = False
74
+ expiration: datetime | None = None
@@ -0,0 +1,93 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import ConfigDict, EmailStr, Field, HttpUrl, model_validator
4
+ from typing_extensions import Self
5
+
6
+ from backend.common.enums import StatusType
7
+ from backend.common.schema import SchemaBase
8
+ from backend.app.admin.schema.role import GetRoleListDetails
9
+
10
+
11
+
12
+ class UserBase(SchemaBase):
13
+ firstname: str | None = None
14
+ lastname: str | None = None
15
+ fullname: str | None = None
16
+ phone: str | None = None
17
+ pseudo: str | None = None
18
+ email_verified: bool = False
19
+ country_code: str | None = None
20
+ profile_image: dict | None = None
21
+
22
+
23
+ class UserLoginSchema(SchemaBase):
24
+ email: EmailStr
25
+ password: str
26
+
27
+
28
+ class UserRegister(SchemaBase):
29
+ password: str
30
+ email: EmailStr
31
+
32
+
33
+ class UserUpdate(UserBase):
34
+ pass
35
+
36
+
37
+ class UpdateUserParam(SchemaBase):
38
+ firstname: str | None = None
39
+ lastname: str | None = None
40
+ email: EmailStr | None = None
41
+ phone: str | None = None
42
+
43
+
44
+ class UserResetPassword(SchemaBase):
45
+ new_password: str
46
+ confirm_password: str
47
+
48
+
49
+ class UserInfoSchemaBase(UserBase):
50
+ email: EmailStr
51
+
52
+
53
+
54
+ class GetUserInfoNoRelationDetail(UserInfoSchemaBase):
55
+ model_config = ConfigDict(from_attributes=True)
56
+
57
+ id: int
58
+ x_id: str
59
+ profile_image: dict | None = None
60
+ status: bool
61
+ join_time: datetime = None
62
+ last_login_time: datetime | None = None
63
+
64
+
65
+
66
+ class GetUserInfoListDetails(GetUserInfoNoRelationDetail):
67
+ model_config = ConfigDict(from_attributes=True)
68
+
69
+ roles: list[GetRoleListDetails] | None = []
70
+
71
+
72
+ class GetCurrentUserInfoDetail(GetUserInfoListDetails):
73
+ model_config = ConfigDict(from_attributes=True)
74
+
75
+ roles: list[GetRoleListDetails] | list[str] | None = []
76
+
77
+ @model_validator(mode='after')
78
+ def handel(self) -> Self:
79
+ """Dealing with sectors and roles"""
80
+ roles = self.roles
81
+ if roles:
82
+ self.roles = [{'name': role.name} for role in roles] # type: ignore
83
+ return self
84
+
85
+
86
+ class CurrentUserIns(GetUserInfoListDetails):
87
+ model_config = ConfigDict(from_attributes=True)
88
+
89
+
90
+ class UpdatePasswordParam(SchemaBase):
91
+ old_password: str
92
+ new_password: str
93
+ confirm_password: str
@@ -0,0 +1,233 @@
1
+ from fastapi import Request, Response
2
+ from fastapi.security import HTTPBasicCredentials
3
+ from datetime import datetime
4
+ from starlette.background import BackgroundTask, BackgroundTasks
5
+
6
+ from backend.crud.crud_user import user_dao
7
+ from backend.models.user import User
8
+ from backend.app.admin.schema.token import GetLoginToken, GetNewToken
9
+ from backend.app.admin.schema.user import UserLoginSchema, UserRegister
10
+ from backend.app.admin.service.login_log_service import LoginLogService
11
+ from backend.common.enums import LoginLogStatusType
12
+ from backend.common.exception import errors
13
+ from backend.common.security.jwt import (
14
+ create_access_token,
15
+ create_new_token,
16
+ create_refresh_token,
17
+ get_token,
18
+ jwt_decode,
19
+ password_verify,
20
+ )
21
+ from backend.core.conf import settings
22
+ from backend.database.db_postgres import async_db_session
23
+ from backend.database.db_redis import redis_client
24
+ from backend.utils.timezone import timezone
25
+ from backend.utils.translator import Translator
26
+
27
+
28
+ class AuthService:
29
+ @staticmethod
30
+ async def swagger_login(*, obj: HTTPBasicCredentials) -> tuple[str, User]:
31
+ async with async_db_session.begin() as db:
32
+ current_user = await user_dao.get_by_email(db, obj.username)
33
+ if not current_user:
34
+ raise errors.NotFoundError(msg='Incorrect email or password')
35
+ elif not password_verify(f'{obj.password}{current_user.salt}', current_user.password):
36
+ raise errors.AuthorizationError(msg='Incorrect email or password')
37
+ elif not current_user.status:
38
+ raise errors.AuthorizationError(msg='The user has been locked out. Please contact the system useristrator.')
39
+ access_token = await create_access_token(str(current_user.x_id), False)
40
+ await user_dao.update_login_time(db, obj.username)
41
+ return access_token.access_token, current_user
42
+
43
+ @staticmethod
44
+ async def register(
45
+ *, request: Request, response: Response, obj: UserRegister, background_tasks: BackgroundTasks
46
+ ) -> GetLoginToken:
47
+ translator = Translator(request.state.locale)
48
+
49
+ async with async_db_session.begin() as db:
50
+ if not obj.password:
51
+ raise errors.ForbiddenError(msg=translator.t('auth.enpty_password'))
52
+
53
+ email = await user_dao.get_by_email(db, obj.email)
54
+ if email:
55
+ raise errors.ForbiddenError(msg=translator.t('auth.exist'))
56
+
57
+ new_user = await user_dao.add(db, obj)
58
+
59
+ # current_user_id = new_user.x_id
60
+ # is_multi_login = new_user.is_multi_login
61
+ user_x_id = new_user.x_id
62
+ email = new_user.email
63
+
64
+ access_token = await create_access_token(str(user_x_id), False)
65
+ refresh_token = await create_refresh_token(str(user_x_id), False)
66
+
67
+ n_user = await user_dao.get_with_relation(db, x_id=user_x_id, populates=['roles'])
68
+
69
+ background_tasks.add_task(
70
+ LoginLogService.create,
71
+ **dict(
72
+ db=db,
73
+ request=request,
74
+ user_x_id=user_x_id,
75
+ email=email,
76
+ login_time=datetime.now(),
77
+ status=LoginLogStatusType.success.value,
78
+ msg=translator.t('auth.successful'),
79
+ )
80
+ )
81
+
82
+ # await user_dao.update_login_time(db, obj.email)
83
+ response.set_cookie(
84
+ key=settings.COOKIE_REFRESH_TOKEN_KEY,
85
+ value=refresh_token.refresh_token,
86
+ max_age=settings.COOKIE_REFRESH_TOKEN_EXPIRE_SECONDS,
87
+ expires=timezone.f_utc(refresh_token.refresh_token_expire_time),
88
+ httponly=True,
89
+ samesite='none'
90
+ )
91
+
92
+ data = GetLoginToken(
93
+ access_token=access_token.access_token,
94
+ access_token_expire_time=access_token.access_token_expire_time,
95
+ user=n_user, # type: ignore
96
+ )
97
+ return data
98
+
99
+
100
+ @staticmethod
101
+ async def login(
102
+ *, request: Request, response: Response, obj: UserLoginSchema, background_tasks: BackgroundTasks
103
+ ) -> GetLoginToken:
104
+ translator = Translator(request.state.locale)
105
+
106
+ async with async_db_session.begin() as db:
107
+ try:
108
+ current_user = await user_dao.get_by_email(db, email=obj.email, populates=['roles'])
109
+ if not current_user:
110
+ raise errors.NotFoundError(msg=translator.t('auth.incorrect_credential'))
111
+
112
+ user_x_id = current_user.x_id
113
+ email = current_user.email
114
+
115
+ if not password_verify(obj.password + current_user.salt, current_user.password):
116
+ raise errors.AuthorizationError(msg=translator.t('auth.incorrect_credential'))
117
+ elif not current_user.status:
118
+ raise errors.AuthorizationError(msg=translator.t('auth.account_locked'))
119
+
120
+ access_token = await create_access_token(str(user_x_id), False)
121
+ refresh_token = await create_refresh_token(str(user_x_id), False)
122
+
123
+ except errors.NotFoundError as e:
124
+ raise errors.NotFoundError(msg=e.msg)
125
+ except (errors.AuthorizationError, errors.CustomError) as e:
126
+ task = BackgroundTask(
127
+ LoginLogService.create,
128
+ **dict(
129
+ db=db,
130
+ request=request,
131
+ user_x_id=user_x_id,
132
+ email=email,
133
+ login_time=datetime.now(),
134
+ status=LoginLogStatusType.fail.value,
135
+ msg=e.msg,
136
+ ),
137
+ )
138
+ raise errors.AuthorizationError(msg=e.msg, background=task)
139
+ except Exception as e:
140
+ raise e
141
+ else:
142
+ background_tasks.add_task(
143
+ LoginLogService.create,
144
+ **dict(
145
+ db=db,
146
+ request=request,
147
+ user_x_id=user_x_id,
148
+ email=email,
149
+ login_time=datetime.now(),
150
+ status=LoginLogStatusType.success.value,
151
+ msg=translator.t('auth.successful'),
152
+ ),
153
+ )
154
+ # await redis_client.delete(f'{user_settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{request.state.ip}')
155
+
156
+ await user_dao.update_login_time(db, obj.email)
157
+ response.set_cookie(
158
+ key=settings.COOKIE_REFRESH_TOKEN_KEY,
159
+ value=refresh_token.refresh_token,
160
+ max_age=settings.COOKIE_REFRESH_TOKEN_EXPIRE_SECONDS,
161
+ expires=timezone.f_utc(refresh_token.refresh_token_expire_time),
162
+ httponly=True,
163
+ samesite='none'
164
+ )
165
+ await db.refresh(current_user)
166
+ data = GetLoginToken(
167
+ access_token=access_token.access_token,
168
+ access_token_expire_time=access_token.access_token_expire_time,
169
+ user=current_user, # type: ignore
170
+ )
171
+ return data
172
+
173
+
174
+ @staticmethod
175
+ async def new_token(*, request: Request, response: Response) -> GetNewToken:
176
+ translator = Translator(request.state.locale)
177
+ refresh_token = request.cookies.get(settings.COOKIE_REFRESH_TOKEN_KEY)
178
+
179
+ if not refresh_token:
180
+ raise errors.TokenError(msg=translator.t('auth.refresh_token_not_found'))
181
+ try:
182
+ user_x_id = jwt_decode(refresh_token)
183
+ except Exception:
184
+ raise errors.TokenError(msg=translator.t('auth.invalid_refresh_token'))
185
+ # if request.user.id != user_id:
186
+ # raise errors.TokenError(msg='Refresh Token is invalid')
187
+ async with async_db_session() as db:
188
+ current_user = await user_dao.get_by_x_id(db, user_x_id)
189
+ if not current_user:
190
+ raise errors.NotFoundError(msg=translator.t('auth.incorrect_credential'))
191
+ elif not current_user.status:
192
+ raise errors.AuthorizationError(msg=translator.t('auth.account_locked'))
193
+ current_token = get_token(request)
194
+ new_token = await create_new_token(
195
+ sub=str(current_user.x_id),
196
+ token=current_token,
197
+ refresh_token=refresh_token,
198
+ multi_login=False,
199
+ )
200
+ response.set_cookie(
201
+ key=settings.COOKIE_REFRESH_TOKEN_KEY,
202
+ value=new_token.new_refresh_token,
203
+ max_age=settings.COOKIE_REFRESH_TOKEN_EXPIRE_SECONDS,
204
+ expires=timezone.f_utc(new_token.new_refresh_token_expire_time),
205
+ httponly=True,
206
+ samesite='none'
207
+ )
208
+ data = GetNewToken(
209
+ access_token=new_token.new_access_token,
210
+ access_token_expire_time=new_token.new_access_token_expire_time,
211
+ )
212
+ return data
213
+
214
+
215
+ @staticmethod
216
+ async def logout(*, request: Request, response: Response) -> None:
217
+ token = get_token(request)
218
+ refresh_token = request.cookies.get(settings.COOKIE_REFRESH_TOKEN_KEY)
219
+ response.delete_cookie(settings.COOKIE_REFRESH_TOKEN_KEY)
220
+ # if request.user.is_multi_login:
221
+ # key = f'{settings.TOKEN_REDIS_PREFIX}:{request.user.id}:{token}'
222
+ # await redis_client.delete(key)
223
+ # if refresh_token:
224
+ # key = f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{request.user.id}:{refresh_token}'
225
+ # await redis_client.delete(key)
226
+ # else:
227
+ key_prefix = f'{settings.TOKEN_REDIS_PREFIX}:{request.user.x_id}:'
228
+ await redis_client.delete_prefix(key_prefix)
229
+ key_prefix = f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{request.user.x_id}:'
230
+ await redis_client.delete_prefix(key_prefix)
231
+
232
+
233
+ auth_service = AuthService()