Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions backend/.env
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,11 @@ LOG_LEVEL="INFO"

DEFAULT_RATE_LIMIT="100/minute"
LOGIN_RATE_LIMIT="5/minute"
REGISTER_RATE_LIMIT="3/minute"
REGISTER_RATE_LIMIT="3/hour"
MESSAGE_RATE_LIMIT="30/minute"
SEARCH_RATE_LIMIT="60/minute"
PROJECT_RATE_LIMIT="100/minute"
PASSWORD_RESET_RATE_LIMIT="3/15minutes"

# ----------------------------------------------------------
# Security Headers
Expand Down Expand Up @@ -155,4 +159,5 @@ ENABLE_NOTIFICATIONS=true
ENABLE_CHAT=true
ENABLE_BUILDER_FLARE=true
ENABLE_PROJECTS=true
ENABLE_APPLICATIONS=true
ENABLE_APPLICATIONS=true

13 changes: 11 additions & 2 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,15 @@ DEFAULT_RATE_LIMIT=100/minute

LOGIN_RATE_LIMIT=5/minute

REGISTER_RATE_LIMIT=3/minute
REGISTER_RATE_LIMIT=3/hour

MESSAGE_RATE_LIMIT=30/minute

SEARCH_RATE_LIMIT=60/minute

PROJECT_RATE_LIMIT=100/minute

PASSWORD_RESET_RATE_LIMIT=3/15minutes

# ----------------------------------------------------------
# Security Headers
Expand Down Expand Up @@ -178,4 +186,5 @@ ENABLE_BUILDER_FLARE=True

ENABLE_PROJECTS=True

ENABLE_APPLICATIONS=True
ENABLE_APPLICATIONS=True

6 changes: 5 additions & 1 deletion backend/app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,11 @@ class Settings(BaseSettings):

DEFAULT_RATE_LIMIT: str = "100/minute"
LOGIN_RATE_LIMIT: str = "5/minute"
REGISTER_RATE_LIMIT: str = "3/minute"
REGISTER_RATE_LIMIT: str = "3/hour"
MESSAGE_RATE_LIMIT: str = "30/minute"
SEARCH_RATE_LIMIT: str = "60/minute"
PROJECT_RATE_LIMIT: str = "100/minute"
PASSWORD_RESET_RATE_LIMIT: str = "3/15minutes"

# ==========================================================
# Security Headers
Expand Down
14 changes: 5 additions & 9 deletions backend/app/middleware/rate_limit.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,17 @@
)

# ------------------------------------------------------------------
# Common Limits
# Common Limits (all configurable via settings)
# ------------------------------------------------------------------

LOGIN_LIMIT = settings.LOGIN_RATE_LIMIT

REGISTER_LIMIT = settings.REGISTER_RATE_LIMIT

PASSWORD_RESET_LIMIT = "3/15minutes"
MESSAGE_LIMIT = settings.MESSAGE_RATE_LIMIT

UPLOAD_LIMIT = "10/hour"
SEARCH_LIMIT = settings.SEARCH_RATE_LIMIT

MESSAGE_LIMIT = "60/minute"
PROJECT_LIMIT = settings.PROJECT_RATE_LIMIT

SEARCH_LIMIT = "120/minute"

PROJECT_LIMIT = "20/hour"

FLARE_LIMIT = "10/hour"
PASSWORD_RESET_LIMIT = settings.PASSWORD_RESET_RATE_LIMIT
27 changes: 27 additions & 0 deletions backend/app/routers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@
APIRouter,
Depends,
HTTPException,
Request,
status,
)
from sqlalchemy.orm import Session

from app.database.session import get_db
from app.middleware.rate_limit import (
limiter,
LOGIN_LIMIT,
PASSWORD_RESET_LIMIT,
REGISTER_LIMIT,
)
from app.schemas.auth import (
AuthResponse,
LoginRequest,
Expand All @@ -34,7 +41,9 @@
status_code=status.HTTP_201_CREATED,
summary="Register a new user",
)
@limiter.limit(REGISTER_LIMIT)
def register(
request: Request,
payload: RegisterRequest,
db: Session = Depends(get_db),
):
Expand All @@ -59,7 +68,9 @@ def register(
response_model=AuthResponse,
summary="Login",
)
@limiter.limit(LOGIN_LIMIT)
def login(
request: Request,
payload: LoginRequest,
db: Session = Depends(get_db),
):
Expand Down Expand Up @@ -121,7 +132,9 @@ def get_current_user_id(
response_model=CurrentUserResponse,
summary="Current authenticated user",
)
@limiter.limit("30/minute")
def me(
request: Request,
user_id: str = Depends(get_current_user_id),
db: Session = Depends(get_db),
):
Expand All @@ -141,7 +154,9 @@ def me(
response_model=AuthResponse,
summary="Refresh JWT",
)
@limiter.limit("10/minute")
def refresh(
request: Request,
payload: RefreshTokenRequest,
db: Session = Depends(get_db),
):
Expand Down Expand Up @@ -176,7 +191,9 @@ def refresh(
response_model=LogoutResponse,
summary="Logout",
)
@limiter.limit("10/minute")
def logout(
request: Request,
user_id: str = Depends(get_current_user_id),
db: Session = Depends(get_db),
):
Expand Down Expand Up @@ -207,7 +224,9 @@ def logout(
response_model=SuccessResponse,
summary="Change Password",
)
@limiter.limit("5/minute")
def change_password(
request: Request,
payload: ChangePasswordRequest,
user_id: str = Depends(get_current_user_id),
db: Session = Depends(get_db),
Expand All @@ -232,7 +251,9 @@ def change_password(
response_model=ForgotPasswordResponse,
summary="Forgot Password",
)
@limiter.limit(PASSWORD_RESET_LIMIT)
def forgot_password(
request: Request,
payload: ForgotPasswordRequest,
db: Session = Depends(get_db),
):
Expand All @@ -254,7 +275,9 @@ def forgot_password(
response_model=SuccessResponse,
summary="Reset Password",
)
@limiter.limit(PASSWORD_RESET_LIMIT)
def reset_password(
request: Request,
payload: ResetPasswordRequest,
db: Session = Depends(get_db),
):
Expand Down Expand Up @@ -295,7 +318,9 @@ def reset_password(
response_model=VerifyEmailResponse,
summary="Verify Email",
)
@limiter.limit("5/minute")
def verify_email(
request: Request,
payload: VerifyEmailRequest,
db: Session = Depends(get_db),
):
Expand Down Expand Up @@ -326,7 +351,9 @@ def verify_email(
response_model=SuccessResponse,
summary="Resend Verification Email",
)
@limiter.limit("3/hour")
def resend_verification(
request: Request,
payload: ResendVerificationEmailRequest,
db: Session = Depends(get_db),
):
Expand Down
13 changes: 12 additions & 1 deletion backend/app/routers/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import uuid

from fastapi import APIRouter, Depends, HTTPException, Query, status
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from sqlalchemy.orm import Session

from app.database.session import get_db
from app.dependencies import get_current_user
from app.middleware.rate_limit import limiter, MESSAGE_LIMIT, SEARCH_LIMIT
from app.models.user import User
from app.schemas.message import (
MessageCreate,
Expand All @@ -26,7 +27,9 @@
response_model=MessageResponse,
status_code=status.HTTP_201_CREATED,
)
@limiter.limit(MESSAGE_LIMIT)
def send_message(
request: Request,
message: MessageCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
Expand Down Expand Up @@ -99,7 +102,9 @@ def my_messages(
"/search/{conversation_id}",
response_model=list[MessageResponse],
)
@limiter.limit(SEARCH_LIMIT)
def search_messages(
request: Request,
conversation_id: uuid.UUID,
keyword: str = Query(...),
db: Session = Depends(get_db),
Expand All @@ -116,7 +121,9 @@ def search_messages(
"/{message_id}",
response_model=MessageResponse,
)
@limiter.limit("20/minute")
def update_message(
request: Request,
message_id: uuid.UUID,
message: MessageUpdate,
db: Session = Depends(get_db),
Expand Down Expand Up @@ -144,7 +151,9 @@ def update_message(
"/{message_id}/restore",
response_model=MessageResponse,
)
@limiter.limit("10/minute")
def restore_message(
request: Request,
message_id: uuid.UUID,
db: Session = Depends(get_db),
):
Expand All @@ -170,7 +179,9 @@ def restore_message(
"/{message_id}",
response_model=MessageResponse,
)
@limiter.limit("10/minute")
def delete_message(
request: Request,
message_id: uuid.UUID,
db: Session = Depends(get_db),
):
Expand Down
9 changes: 8 additions & 1 deletion backend/app/routers/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import uuid

from fastapi import APIRouter, Depends, HTTPException, Query, status
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from sqlalchemy.orm import Session

from app.database.session import get_db
from app.dependencies import get_current_user
from app.middleware.rate_limit import limiter, SEARCH_LIMIT
from app.models.user import User
from app.schemas.organization import (
OrganizationCreate,
Expand Down Expand Up @@ -95,7 +96,9 @@ def get_organization_by_slug(
"/",
response_model=list[OrganizationResponse],
)
@limiter.limit(SEARCH_LIMIT)
def list_organizations(
request: Request,
skip: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
db: Session = Depends(get_db),
Expand All @@ -112,7 +115,9 @@ def list_organizations(
"/me",
response_model=list[OrganizationResponse],
)
@limiter.limit(SEARCH_LIMIT)
def my_organizations(
request: Request,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
Expand All @@ -127,7 +132,9 @@ def my_organizations(
"/search/{keyword}",
response_model=list[OrganizationResponse],
)
@limiter.limit(SEARCH_LIMIT)
def search_organizations(
request: Request,
keyword: str,
db: Session = Depends(get_db),
):
Expand Down
Loading
Loading