Skip to content

Commit aba4441

Browse files
committed
owasp top 10 security and performance tests added
1 parent f3ba2cd commit aba4441

File tree

9 files changed

+329
-22
lines changed

9 files changed

+329
-22
lines changed

app/oauth2.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,9 @@ async def get_current_user(db: SessionDep, token: str = Depends(oauth2_scheme)):
4444
token = verify_access_token(token)
4545
user = db.exec(select(User).filter(
4646
User.id == token.id)).first()
47+
if not user:
48+
raise HTTPException(
49+
status_code=status.HTTP_401_UNAUTHORIZED,
50+
detail="User not found"
51+
)
4752
return user

app/routers/post.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from app.models import Post, Vote
77
from app.schemas import PostCreateUpdate, PostSchema, PostOut
88
from sqlalchemy import func
9+
from app.utils import sanitize_input
910

1011
router = APIRouter(prefix="/posts", tags=["Post"])
1112

@@ -37,6 +38,8 @@ async def create_post(
3738
db: SessionDep,
3839
current_user=Depends(oauth2.get_current_user),
3940
):
41+
post_data.title = sanitize_input(post_data.title)
42+
post_data.content = sanitize_input(post_data.content)
4043
new_post = Post(owner_id=current_user.id, **post_data.model_dump())
4144
db.add(new_post)
4245
db.commit()
@@ -58,7 +61,7 @@ async def get_post(
5861
if not post:
5962
raise HTTPException(
6063
status_code=status.HTTP_404_NOT_FOUND,
61-
detail=f"post with id: {id} doesn't exist",
64+
detail=f"post with id: {id} was not exist",
6265
)
6366
return post
6467

@@ -75,14 +78,17 @@ async def update_post(
7578
if not existing_post:
7679
raise HTTPException(
7780
status_code=status.HTTP_404_NOT_FOUND,
78-
detail=f"post with id: {id} doesn't found",
81+
detail=f"post with id: {id} was not found",
7982
)
8083
if existing_post.owner_id != current_user.id:
8184
raise HTTPException(
8285
status_code=status.HTTP_401_UNAUTHORIZED,
8386
detail="Not authorised to perform requsted action",
8487
)
85-
88+
if post.title:
89+
post.title = sanitize_input(post.title)
90+
if post.content:
91+
post.content = sanitize_input(post.content)
8692
post_data = post.model_dump(exclude_unset=True)
8793
existing_post.sqlmodel_update(post_data)
8894
db.add(existing_post)
@@ -100,7 +106,7 @@ async def delete_post(
100106
if not deleted_post:
101107
raise HTTPException(
102108
status_code=status.HTTP_404_NOT_FOUND,
103-
detail=f"post with id: {id} doesn't found",
109+
detail=f"post with id: {id} was not found",
104110
)
105111
if deleted_post.owner_id != current_user.id:
106112
raise HTTPException(
@@ -110,4 +116,4 @@ async def delete_post(
110116

111117
db.delete(deleted_post)
112118
db.commit()
113-
return deleted_post
119+
return

app/routers/user.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from app.models import User
66
from app.schemas import UserBase, UserSchema
77
from app.utils import get_password_hash
8+
from app.utils import sanitize_input
89

910
router = APIRouter(
1011
prefix="/users", tags=["User"]
@@ -19,10 +20,11 @@ async def get_users(db: SessionDep):
1920

2021
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=UserSchema)
2122
async def create_user(user: UserBase, db: SessionDep):
23+
user.email = sanitize_input(user.email)
2224
user_exist = db.exec(select(User).filter(User.email == user.email)).first()
2325
if user_exist:
2426
raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE,
25-
detail=f"User with email: {user.email} is exist, use diffrent email or login with this email")
27+
detail=f"User with email '{user.email}' already exists. Please use a different email or log in.")
2628
hashed_password = get_password_hash(user.password)
2729
user.password = hashed_password
2830
new_user = User(**user.model_dump())
@@ -37,5 +39,5 @@ async def get_user(id: int, db: SessionDep):
3739
user = db.get(User, id)
3840
if not user:
3941
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
40-
detail=f"User with id: {id} doesn't found")
42+
detail=f"User with id: {id} was not found")
4143
return user

app/routers/vote.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ async def create_votes(vote: VoteSchema, db: SessionDep, current_user=Depends(oa
2020
if not post:
2121
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
2222
detail=f"post with id: {vote.post_id} doesn't exist")
23-
2423
already_voted = db.exec(select(Vote).filter(
2524
Vote.post_id == vote.post_id, Vote.user_id == current_user.id)).first()
2625
if vote.dir == 1:
@@ -34,7 +33,7 @@ async def create_votes(vote: VoteSchema, db: SessionDep, current_user=Depends(oa
3433
else:
3534
if not already_voted:
3635
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
37-
detail="Vote doesn't found")
36+
detail="Vote not found")
3837
db.delete(already_voted)
3938
db.commit()
4039
return {"message": "Successfully deleted vote"}

app/utils.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
import bleach
12
from passlib.context import CryptContext
23
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
4+
ALLOWED_TAGS = []
5+
ALLOWED_ATTRIBUTES = {}
36

47

5-
def get_password_hash(password: str):
8+
def get_password_hash(password: str) -> str:
69
return pwd_context.hash(password)
710

811

9-
def verify_password(plain_password, hashed_password):
12+
def verify_password(plain_password: str, hashed_password: str) -> bool:
1013
return pwd_context.verify(plain_password, hashed_password)
14+
15+
16+
def sanitize_input(text: str) -> str:
17+
return bleach.clean(text, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES, strip=True)

requirements.txt

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
alembic==1.15.2
22
annotated-types==0.7.0
33
anyio==4.9.0
4-
bcrypt==4.3.0
4+
Authlib==1.5.2
5+
bcrypt==4.0.1
6+
bleach==6.2.0
57
certifi==2025.4.26
8+
cffi==1.17.1
9+
charset-normalizer==3.4.2
610
click==8.1.8
711
colorama==0.4.6
12+
cryptography==44.0.3
813
dnspython==2.7.0
14+
dparse==0.6.4
915
email_validator==2.2.0
1016
fastapi==0.115.12
1117
fastapi-cli==0.0.7
18+
filelock==3.16.1
1219
greenlet==3.2.1
1320
gunicorn==23.0.0
1421
h11==0.16.0
@@ -19,36 +26,54 @@ idna==3.10
1926
iniconfig==2.1.0
2027
itsdangerous==2.2.0
2128
Jinja2==3.1.6
29+
joblib==1.5.0
2230
Mako==1.3.10
2331
markdown-it-py==3.0.0
2432
MarkupSafe==3.0.2
33+
marshmallow==4.0.0
2534
mdurl==0.1.2
35+
nltk==3.9.1
2636
orjson==3.10.16
2737
packaging==25.0
2838
passlib==1.7.4
2939
pluggy==1.5.0
40+
psutil==6.1.1
3041
psycopg2==2.9.10
31-
pydantic==2.11.3
42+
pycparser==2.22
43+
pydantic==2.9.2
3244
pydantic-extra-types==2.10.3
3345
pydantic-settings==2.9.1
34-
pydantic_core==2.33.1
46+
pydantic_core==2.23.4
3547
Pygments==2.19.1
3648
PyJWT==2.10.1
3749
pytest==8.3.5
3850
python-dotenv==1.1.0
3951
python-multipart==0.0.20
4052
PyYAML==6.0.2
53+
regex==2024.11.6
54+
requests==2.32.3
4155
rich==14.0.0
4256
rich-toolkit==0.14.3
57+
ruamel.yaml==0.18.10
58+
ruamel.yaml.clib==0.2.12
59+
safety==3.4.0
60+
safety-schemas==0.0.14
61+
setuptools==80.3.1
4362
shellingham==1.5.4
4463
sniffio==1.3.1
4564
SQLAlchemy==2.0.40
4665
sqlmodel==0.0.24
4766
starlette==0.46.2
67+
tenacity==9.1.2
68+
tomlkit==0.13.2
69+
tqdm==4.67.1
4870
typer==0.15.2
4971
typing-inspection==0.4.0
5072
typing_extensions==4.13.2
5173
ujson==5.10.0
74+
urllib3==2.4.0
5275
uvicorn==0.34.2
5376
watchfiles==1.0.5
77+
webencodings==0.5.1
5478
websockets==15.0.1
79+
wheel==0.45.1

0 commit comments

Comments
 (0)