Skip to content

Commit 2e77ffc

Browse files
committed
add user, and authentication middleware
1 parent 6203d34 commit 2e77ffc

17 files changed

Lines changed: 461 additions & 131 deletions

.env.sample

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# ==================APP_SETTINGS==================
22
DEBUG=1
3+
SECRET=SOMESECRET
34
# ==================DATA_BASE==================
45
POSTGRES_DB=analizer
56
POSTGRES_PASSWORD=

api_v1/users/exceptions.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from fastapi_users.exceptions import FastAPIUsersException
2+
3+
4+
class PasswordNotValidError(FastAPIUsersException):
5+
"""
6+
Исключение не валидного пароля
7+
"""
8+
9+
pass
10+
11+
12+
class UserNotVerified(FastAPIUsersException):
13+
"""
14+
Исключение не вурифицинованного пользователя
15+
"""
16+
17+
pass

api_v1/users/middlewares.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import Any
2+
from fastapi import Request, Depends
3+
from fastapi_users.db import SQLAlchemyUserDatabase
4+
from starlette.middleware.base import BaseHTTPMiddleware
5+
from sqlalchemy.ext.asyncio import AsyncSession
6+
7+
from .user_manager import UserManager
8+
from config.models import User
9+
from config import db_connection
10+
11+
12+
class AuthenticationMiddleware(BaseHTTPMiddleware):
13+
"""
14+
Прослойка которая добавляет User в Request
15+
"""
16+
def __init__(self, app):
17+
super().__init__(app)
18+
19+
async def dispatch(
20+
self,
21+
request: Request,
22+
call_next: Any,
23+
session: AsyncSession = Depends(db_connection.session_geter),
24+
):
25+
token = request.headers.get('Authorization')
26+
user_db = SQLAlchemyUserDatabase(session, User)
27+
manager = UserManager(user_db=user_db)
28+
user = await manager.authenticate_user(token)
29+
request.scope['user'] = user
30+
response = await call_next(request)
31+
return response

api_v1/users/user_manager.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import jwt
2+
from fastapi_users import BaseUserManager, IntegerIDMixin
3+
from fastapi_users.jwt import decode_jwt
4+
from fastapi_users import exceptions
5+
6+
from .exceptions import PasswordNotValidError, UserNotVerified
7+
from config.models import User
8+
from config import settings
9+
10+
11+
class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
12+
"""
13+
UserManager для работы с пользователем
14+
"""
15+
16+
verification_token_secret = settings.SECRET
17+
18+
async def validate_password(self, password, user):
19+
if not len(password) > 7:
20+
raise PasswordNotValidError('Password is not valid')
21+
22+
async def authenticate_user(self, token: str | None):
23+
if not token:
24+
return
25+
try:
26+
data = decode_jwt(
27+
token,
28+
self.verification_token_secret,
29+
[self.verification_token_audience],
30+
)
31+
except jwt.PyJWTError:
32+
raise exceptions.InvalidVerifyToken()
33+
34+
try:
35+
user_id = data["sub"]
36+
email = data["email"]
37+
except KeyError:
38+
raise exceptions.InvalidVerifyToken()
39+
40+
try:
41+
user = await self.get_by_email(email)
42+
except exceptions.UserNotExists:
43+
return
44+
45+
try:
46+
parsed_id = self.parse_id(user_id)
47+
except exceptions.InvalidID:
48+
raise exceptions.InvalidVerifyToken()
49+
50+
if parsed_id != user.id:
51+
raise exceptions.InvalidVerifyToken()
52+
53+
if not user.is_verified:
54+
raise UserNotVerified()
55+
56+
return user

api_v1/users/views.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from fastapi import APIRouter, Depends, status
2-
1+
from fastapi import APIRouter, Depends, status, Request
32
from sqlalchemy.ext.asyncio import AsyncSession
43

54
from config import db_connection
@@ -14,9 +13,5 @@
1413
@router.get(path='/get',
1514
description='Test end point',
1615
)
17-
async def get_user(
18-
session: AsyncSession = Depends(db_connection.session_geter),
19-
):
20-
session = session
21-
raise ValidationError(status_code=status.HTTP_400_BAD_REQUEST,
22-
detail=dict(some='Some is wrong'))
16+
async def get_user(request: Request):
17+
return request.user

app_includes/logs_errors.py

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,22 @@
22
from fastapi.responses import JSONResponse
33
from fastapi.exceptions import HTTPException
44
from starlette.exceptions import HTTPException as StarletteHTTPException
5+
from fastapi_users.exceptions import (
6+
InvalidID,
7+
UserAlreadyExists,
8+
UserNotExists,
9+
UserInactive,
10+
UserAlreadyVerified,
11+
InvalidVerifyToken,
12+
InvalidResetPasswordToken,
13+
InvalidPasswordException,
14+
)
515

616
from http import HTTPStatus
717

818
from config.setup_logs.logging import logger
919
from api_v1.exeptions import ValidationError
20+
from api_v1.users.exceptions import PasswordNotValidError, UserNotVerified
1021

1122

1223
def register_errors(app: FastAPI) -> None:
@@ -59,6 +70,165 @@ async def file_not_found_error_handler(
5970
return JSONResponse(response)
6071
```
6172
"""
73+
@app.exception_handler(UserNotVerified)
74+
async def user_not_verify_error_handler(
75+
request: Request,
76+
exc: UserNotVerified,
77+
):
78+
"""
79+
Логирование всех UserNotVerified
80+
"""
81+
logger.opt(exception=True).warning(exc)
82+
response = dict(
83+
status=False,
84+
error_code=exc.status_code,
85+
message=exc.detail,
86+
)
87+
return JSONResponse(response)
88+
89+
@app.exception_handler(InvalidPasswordException)
90+
async def password_invalid_error_handler(
91+
request: Request,
92+
exc: InvalidPasswordException,
93+
):
94+
"""
95+
Логирование всех InvalidPasswordException
96+
"""
97+
logger.opt(exception=True).warning(exc)
98+
response = dict(
99+
status=False,
100+
error_code=exc.status_code,
101+
message=exc.detail,
102+
)
103+
return JSONResponse(response)
104+
105+
@app.exception_handler(InvalidResetPasswordToken)
106+
async def password_token_error_handler(
107+
request: Request,
108+
exc: InvalidResetPasswordToken,
109+
):
110+
"""
111+
Логирование всех InvalidResetPasswordToken
112+
"""
113+
logger.opt(exception=True).warning(exc)
114+
response = dict(
115+
status=False,
116+
error_code=exc.status_code,
117+
message=exc.detail,
118+
)
119+
return JSONResponse(response)
120+
121+
@app.exception_handler(InvalidVerifyToken)
122+
async def verify_token_error_handler(
123+
request: Request,
124+
exc: InvalidVerifyToken,
125+
):
126+
"""
127+
Логирование всех InvalidVerifyToken
128+
"""
129+
logger.opt(exception=True).warning(exc)
130+
response = dict(
131+
status=False,
132+
error_code=exc.status_code,
133+
message=exc.detail,
134+
)
135+
return JSONResponse(response)
136+
137+
@app.exception_handler(UserAlreadyVerified)
138+
async def user_exists_error_handler(
139+
request: Request,
140+
exc: UserAlreadyVerified,
141+
):
142+
"""
143+
Логирование всех UserAlreadyVerified
144+
"""
145+
logger.opt(exception=True).warning(exc)
146+
response = dict(
147+
status=False,
148+
error_code=exc.status_code,
149+
message=exc.detail,
150+
)
151+
return JSONResponse(response)
152+
153+
@app.exception_handler(UserInactive)
154+
async def user_activity_error_handler(
155+
request: Request,
156+
exc: UserInactive,
157+
):
158+
"""
159+
Логирование всех UserInactive
160+
"""
161+
logger.opt(exception=True).warning(exc)
162+
response = dict(
163+
status=False,
164+
error_code=exc.status_code,
165+
message=exc.detail,
166+
)
167+
return JSONResponse(response)
168+
169+
@app.exception_handler(UserNotExists)
170+
async def user_not_exists_error_handler(
171+
request: Request,
172+
exc: UserNotExists,
173+
):
174+
"""
175+
Логирование всех UserNotExists
176+
"""
177+
logger.opt(exception=True).warning(exc)
178+
response = dict(
179+
status=False,
180+
error_code=exc.status_code,
181+
message=exc.detail,
182+
)
183+
return JSONResponse(response)
184+
185+
@app.exception_handler(UserAlreadyExists)
186+
async def user_already_exists_error_handler(
187+
request: Request,
188+
exc: UserAlreadyExists,
189+
):
190+
"""
191+
Логирование всех UserAlreadyExists
192+
"""
193+
logger.opt(exception=True).warning(exc)
194+
response = dict(
195+
status=False,
196+
error_code=exc.status_code,
197+
message=exc.detail,
198+
)
199+
return JSONResponse(response)
200+
201+
@app.exception_handler(InvalidID)
202+
async def invalid_id_error_handler(
203+
request: Request,
204+
exc: InvalidID,
205+
):
206+
"""
207+
Логирование всех InvalidID
208+
"""
209+
logger.opt(exception=True).warning(exc)
210+
response = dict(
211+
status=False,
212+
error_code=exc.status_code,
213+
message=exc.detail,
214+
)
215+
return JSONResponse(response)
216+
217+
@app.exception_handler(PasswordNotValidError)
218+
async def password_validator_error_handler(
219+
request: Request,
220+
exc: PasswordNotValidError,
221+
):
222+
"""
223+
Логирование всех PasswordNotValidError
224+
"""
225+
logger.opt(exception=True).warning(exc)
226+
response = dict(
227+
status=False,
228+
error_code=exc.status_code,
229+
message=exc.detail,
230+
)
231+
return JSONResponse(response)
62232

63233
@app.exception_handler(ValidationError)
64234
async def validation_error_handler(
@@ -109,7 +279,7 @@ async def error_handler(
109279
return JSONResponse(response)
110280

111281
@app.exception_handler(StarletteHTTPException)
112-
async def validation_error_handler(
282+
async def validation_starlette_error_handler(
113283
request: Request,
114284
exc: StarletteHTTPException,
115285
):

app_includes/middlewares.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from fastapi import FastAPI
33

44
from config import settings
5+
from api_v1.users.middlewares import AuthenticationMiddleware
56

67

78
def register_middlewares(app: FastAPI) -> None:
@@ -50,3 +51,6 @@ def register_middlewares(app: FastAPI) -> None:
5051
allow_methods=['*'],
5152
allow_headers=['*'],
5253
)
54+
app.add_middleware(
55+
AuthenticationMiddleware,
56+
)

async_alembic/env.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
from alembic import context
1010

11-
from config import BaseModel, settings
11+
from config import settings
12+
from config.models.base import Base
1213

1314

1415
# this is the Alembic Config object, which provides
@@ -22,7 +23,7 @@
2223

2324
# add your model's MetaData object here
2425
# for 'autogenerate' support
25-
target_metadata = BaseModel.metadata
26+
target_metadata = Base.metadata
2627

2728
# other values from the config, defined by the needs of env.py,
2829
# can be acquired:
@@ -91,7 +92,7 @@ def run_migrations_online() -> None:
9192
poolclass=pool.NullPool,
9293
future=True,
9394
)
94-
)
95+
)
9596
if isinstance(connectable, AsyncEngine):
9697
asyncio.run(run_async_migrations(connectable))
9798
else:

0 commit comments

Comments
 (0)