Skip to content

Commit a7c9164

Browse files
committed
1) Рефакторинг.
2) Авторизация через телеграм.
1 parent 1ddffac commit a7c9164

37 files changed

+874
-719
lines changed

.env.dist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ POSTGRES_PORT=
99
POSTGRES_USER=
1010
POSTGRES_PASSWORD=
1111
POSTGRES_DATABASE=
12+
13+
TELEGRAM_BOT_TOKEN=

Makefile

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,25 @@ help:
99
@echo " migrate Alembic migrate database"
1010
@echo " generate Alembic generate database"
1111

12+
13+
.PHONY: blue
14+
blue:
15+
poetry run blue app/
16+
17+
.PHONY: isort
18+
isort:
19+
poetry run isort app/
20+
21+
.PHONY: ruff
22+
ruff:
23+
poetry run ruff check app/ --fix --respect-gitignore
24+
1225
.PHONY: ref
13-
ref:
14-
poetry run ruff ./app --fix && poetry run black ./app
26+
ref: blue isort ruff
1527

1628
.PHONY: dev
1729
dev:
18-
uvicorn app:app --reload --access-log --log-level debug
19-
30+
poetry run fastapi dev app
2031

2132
.PHONY: generate
2233
generate:

app/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
title=settings.APP_TITLE,
1212
version=settings.APP_VERSION,
1313
description=settings.APP_DESCRIPTION,
14-
openapi_url=f"{settings.APP_API_PREFIX}/openapi.json"
14+
openapi_url=f'{settings.APP_API_PREFIX}/openapi.json',
1515
)
1616

1717
app.include_router(api.api_router, prefix=settings.APP_API_PREFIX)

app/__main__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66

77
from app import app
88

9-
if __name__ == "__main__":
10-
uvicorn.run(app, host="0.0.0.0", port=8000)
9+
if __name__ == '__main__':
10+
uvicorn.run(app, host='0.0.0.0', port=8000)

app/api/__init__.py

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

55
from fastapi import APIRouter
66

7-
from .endpoints import users, tokens
7+
from . import endpoints
88

99
api_router = APIRouter()
10-
api_router.include_router(users.router)
11-
api_router.include_router(tokens.router)
10+
api_router.include_router(endpoints.router)

app/api/deps.py

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,32 @@
22
Dependencies
33
"""
44

5-
from fastapi import Depends, HTTPException, status, Header
5+
from fastapi import Depends, Header
6+
from typing_extensions import Annotated
67

78
from app import models
8-
from app.core import security
9-
from app.core.db import SessionLocal, Database
9+
from app.core import exps, settings
10+
from app.core.db import Database, SessionLocal
11+
from app.core.security import JWTTokenManager
1012

1113

1214
async def get_db() -> Database:
1315
async with SessionLocal() as session:
1416
yield Database(session)
1517

1618

19+
async def get_tkn_manager() -> JWTTokenManager:
20+
return JWTTokenManager(settings.APP_SECRET_KEY)
21+
22+
1723
async def get_current_user(
18-
short_token: str = Header(),
19-
db: Database = Depends(get_db),
24+
access_token: Annotated[str, Header()],
25+
db: Annotated[Database, Depends(get_db)],
26+
tkn_manager: Annotated[JWTTokenManager, Depends(get_tkn_manager)],
2027
) -> models.User:
21-
payload = security.tkn_manager.decode_short_token(short_token)
22-
if not (user := await db.user.read(payload.get("id"))):
23-
raise HTTPException(
24-
status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found"
25-
)
28+
payload = tkn_manager.decode_token(access_token)
29+
if payload.get('type') != 'access':
30+
raise exps.TOKEN_INVALID
31+
if not (user := await db.user.read(payload.get('id'))):
32+
raise exps.USER_NOT_FOUND
2633
return user
27-
28-
29-
async def is_superuser(
30-
user: models.User = Depends(get_current_user),
31-
) -> None:
32-
if not user.is_superuser:
33-
raise HTTPException(
34-
status_code=status.HTTP_403_FORBIDDEN,
35-
detail="You do not have access to this section",
36-
)

app/api/endpoints/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""
2+
Endpoints API Module
3+
"""
4+
5+
from fastapi import APIRouter
6+
7+
from . import tokens, users
8+
9+
router = APIRouter()
10+
router.include_router(users.router)
11+
router.include_router(tokens.router)

app/api/endpoints/tokens/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
from fastapi import APIRouter
66

77
from . import auth, pair, refresh
8-
from app.core.structures import Tags
98

10-
router = APIRouter(prefix="/tokens", tags=[Tags.tokens])
9+
router = APIRouter(prefix='/tokens', tags=['tokens'])
1110
router.include_router(auth.router)
1211
router.include_router(pair.router)
1312
router.include_router(refresh.router)

app/api/endpoints/tokens/auth.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,50 @@
1+
"""
2+
User Endpoints Module
3+
"""
4+
import hmac
5+
16
from fastapi import APIRouter, Depends
7+
from typing_extensions import Annotated
28

39
from app import models
410
from app.api import deps
5-
from app.core import exps
11+
from app.core import exps, settings
612
from app.core.db import Database
7-
from app.core.security import tkn_manager, pwd_manager
13+
from app.core.security import JWTTokenManager
814

915
router = APIRouter()
1016

1117

12-
@router.post("/auth/", response_model=models.AuthToken)
13-
async def new_auth_token(data: models.UserCreate, db: Database = Depends(deps.get_db)):
18+
@router.post('/auth/', response_model=models.AuthToken)
19+
async def auth(
20+
user: models.UserCreate,
21+
db: Annotated[Database, Depends(deps.get_db)],
22+
tkn_manager: Annotated[JWTTokenManager, Depends(deps.get_tkn_manager)],
23+
):
1424
"""
15-
Получить токен аутентификации
25+
Get auth token
1626
"""
17-
if not (user := await db.user.get_by_email(data.email)):
18-
raise exps.USER_NOT_REGISTERED
19-
20-
if not pwd_manager.verify_password(data.password, user.password):
21-
raise exps.USER_INCORRECT_PASSWORD
27+
data_check_string = '\n'.join(
28+
sorted(
29+
f'{x}={y}'
30+
for x, y in user.model_dump().items()
31+
if x not in 'hash' and y is not None
32+
)
33+
)
34+
computed_hash = hmac.new(
35+
settings.telegram_bot_token_hash.digest(),
36+
data_check_string.encode(),
37+
'sha256',
38+
).hexdigest()
39+
is_correct = hmac.compare_digest(computed_hash, user.hash)
40+
if not is_correct:
41+
raise exps.USER_IS_CORRECT
2242

23-
auth_token = tkn_manager.create_auth_token({"id": str(user.id)})
43+
model_user = models.User(**user.model_dump())
44+
if cur_user := await db.user.retrieve(user.id):
45+
await db.user.update(cur_user.id, **model_user.model_dump())
46+
else:
47+
await db.user.create(model_user)
48+
await db.session.commit()
49+
auth_token = tkn_manager.encode_token({'id': user.id, 'type': 'auth'}, 15)
2450
return models.AuthToken(auth_token=auth_token)

app/api/endpoints/tokens/pair.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
1-
from fastapi import APIRouter
1+
from fastapi import APIRouter, Depends
2+
from typing_extensions import Annotated
23

34
from app import models
4-
from app.core.security import tkn_manager
5+
from app.api import deps
6+
from app.core import exps
7+
from app.core.security import JWTTokenManager
58

69
router = APIRouter()
710

811

9-
@router.post("/pair/", response_model=models.PairTokens)
10-
async def new_pair_tokens(data: models.AuthToken):
12+
@router.post('/pair/', response_model=models.PairTokens)
13+
async def new_pair_tokens(
14+
data: models.AuthToken,
15+
tkn_manager: Annotated[JWTTokenManager, Depends(deps.get_tkn_manager)],
16+
):
1117
"""
12-
Получить парные токены
18+
Get pair tokens
1319
"""
14-
payload = tkn_manager.decode_auth_token(data.auth_token)
15-
long_token = tkn_manager.create_long_token(payload)
16-
short_token = tkn_manager.create_short_token(payload)
17-
return models.PairTokens(long_token=long_token, short_token=short_token)
20+
21+
payload = tkn_manager.decode_token(data.auth_token)
22+
if payload.get('type') != 'auth':
23+
raise exps.TOKEN_INVALID
24+
payload['type'] = 'access'
25+
access_token = tkn_manager.encode_token(payload, 120)
26+
payload['type'] = 'refresh'
27+
refresh_token = tkn_manager.encode_token(payload, 1440)
28+
return models.PairTokens(
29+
access_token=access_token, refresh_token=refresh_token
30+
)

0 commit comments

Comments
 (0)