Skip to content

Commit

Permalink
feat: 🛂 implement rate limit on api requests
Browse files Browse the repository at this point in the history
  • Loading branch information
nutfdt committed Dec 19, 2024
1 parent 2b104f5 commit cf44561
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 32 deletions.
1 change: 1 addition & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ boto3==1.35.81
autodynatrace==2.1.1
PyJWT==2.10.1
cryptography==44.0.0
slowapi===0.1.9
# ML
basegun-ml==2.0.5
numpy<3.0.0
Expand Down
6 changes: 6 additions & 0 deletions backend/src/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from slowapi.util import get_remote_address

from .config import HEADERS
from .router import router

limiter = Limiter(key_func=get_remote_address)
app = FastAPI(docs_url="/api/docs")
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

app.add_middleware(
CORSMiddleware,
Expand Down
22 changes: 20 additions & 2 deletions backend/src/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
status,
)
from fastapi.responses import PlainTextResponse
from slowapi import Limiter
from slowapi.util import get_remote_address
from user_agents import parse

from .config import (
Expand All @@ -34,22 +36,31 @@
)
from .utils import get_current_user, send_mail, upload_image

DISABLE_RATE_LIMITS = os.environ.get("DISABLE_RATE_LIMITS", "false").lower() == "true"

router = APIRouter(prefix="/api")
limiter = Limiter(
key_func=get_remote_address if not DISABLE_RATE_LIMITS else lambda: None
)


@router.get("/", response_class=PlainTextResponse)
def home():
@limiter.limit("5/minute")
def home(request: Request):
return "Basegun backend"


@router.get("/version", response_class=PlainTextResponse)
def version():
@limiter.limit("5/minute")
def version(request: Request):
return APP_VERSION


@router.get("/contact-details")
@limiter.limit("5/minute")
async def phone_number(
current_user: Annotated[dict, Depends(get_current_user)],
request: Request,
):
if current_user.get("idp") != "proxyma":
raise HTTPException(
Expand All @@ -63,6 +74,7 @@ async def phone_number(


@router.post("/upload")
@limiter.limit("5/minute")
async def imageupload(
request: Request,
response: Response,
Expand Down Expand Up @@ -141,6 +153,7 @@ async def imageupload(


@router.post("/identification-feedback")
@limiter.limit("5/minute")
async def log_feedback(request: Request, user_id: Union[str, None] = Cookie(None)):
res = await request.json()

Expand All @@ -155,6 +168,7 @@ async def log_feedback(request: Request, user_id: Union[str, None] = Cookie(None


@router.post("/tutorial-feedback")
@limiter.limit("5/minute")
async def log_tutorial_feedback(
request: Request, user_id: Union[str, None] = Cookie(None)
):
Expand All @@ -178,7 +192,9 @@ async def log_tutorial_feedback(


@router.post("/expert-contact")
@limiter.limit("5/minute")
async def expert_contact(
request: Request,
firstname: Annotated[str, Form()],
lastname: Annotated[str, Form()],
nigend: Annotated[str, Form()],
Expand Down Expand Up @@ -219,7 +235,9 @@ async def expert_contact(


@router.post("/identification-alarm-gun")
@limiter.limit("5/minute")
async def image_alarm_gun(
request: Request,
image: UploadFile = File(...),
):
try:
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.ci.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
services:
backend:
image: ${IMAGE_TAG}
environment:
- DISABLE_RATE_LIMITS=true
1 change: 1 addition & 0 deletions frontend/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ declare module 'vue' {
DsfrAlert: typeof import('@gouvminint/vue-dsfr')['DsfrAlert']
DsfrButton: typeof import('@gouvminint/vue-dsfr')['DsfrButton']
DsfrCheckbox: typeof import('@gouvminint/vue-dsfr')['DsfrCheckbox']
DsfrErrorPage: typeof import('@gouvminint/vue-dsfr')['DsfrErrorPage']
DsfrFileUpload: typeof import('@gouvminint/vue-dsfr')['DsfrFileUpload']
DsfrHeader: typeof import('@gouvminint/vue-dsfr')['DsfrHeader']
DsfrInput: typeof import('@gouvminint/vue-dsfr')['DsfrInput']
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
h2 {
font-size: 1.3rem;
}

.error-img {
width: 120%;
}
}
.text-blue {
color: var(--blue-france-sun-113-625);
Expand Down
17 changes: 6 additions & 11 deletions frontend/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const HomePage = () => import("@/views/HomePage.vue");
const StartPage = () => import("@/views/StartPage.vue");
const InstructionsPage = () => import("@/views/InstructionsPage.vue");
const ErrorPage = () => import("@/views/ErrorPage.vue");
const PageNotFound = () => import("@/views/PageNotFound.vue");
const AboutPage = () => import("@/views/AboutPage.vue");
const LegalPage = () => import("@/views/LegalPage.vue");
const ContactPage = () => import("@/views/ContactPage.vue");
Expand Down Expand Up @@ -173,6 +172,7 @@ const routes: RouteRecordRaw[] = [
},
{
path: "/erreur",
alias: "/:pathMach(.*)*",
name: "ErrorPage",
component: ErrorPage,
meta: {
Expand All @@ -190,15 +190,6 @@ const routes: RouteRecordRaw[] = [
title: "Accessibilité",
},
},
{
path: "/:pathMach(.*)*",
name: "PageNotFound",
component: PageNotFound,
meta: {
wholeLogo: true,
title: "Page non trouvée",
},
},
{
path: "/guide-contact",
name: "ExpertContact",
Expand Down Expand Up @@ -269,7 +260,11 @@ const router = createRouter({
router.beforeEach((to, from, next) => {
console.log(to);
document.title = "Basegun | " + to.meta.title;
next();
if (to.name === "ErrorPage" && !to.query.status) {
next({ ...to, query: { ...to.query, status: 404 } });
} else {
next();
}
});

export default router;
66 changes: 59 additions & 7 deletions frontend/src/views/ErrorPage.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,67 @@
<script lang="ts" setup>
import { useRoute } from "vue-router";
const route = useRoute();
const errorStatus = route.query.status || 0o0;
const errorMessage = computed(() => {
if (errorStatus == 429) {
return {
title: " - Trop de requêtes",
description:
"Il semblerait que vous ayez effectué plusieurs requêtes en quelques minutes. Veuillez réessayer plus tard.",
};
} else if (errorStatus == 500) {
return {
title: " - Erreur interne du serveur",
description:
"Une erreur interne lié au serveur est survenue. Veuillez réessayer plus tard.",
};
} else if (errorStatus == 401) {
return {
title: " - Non autorisé",
description:
"Vous n'êtes pas autorisé à accéder à cette page. Veuillez vous authentifier.",
};
} else if (errorStatus == 404) {
return {
title: " - Page non trouvée",
description:
"Vous avez saisi ou suivi un lien vers une page qui n'existe pas.",
};
} else {
return {
title: " - Erreur inconnue",
description: "Une erreur est survenue. Veuillez réessayer plus tard.",
};
}
});
</script>

<template>
<div class="fr-container">
<div class="text-center relative top-1/6 m-4">
<h1>Erreur</h1>
<p>Une erreur est survenue dans le traitement de votre requête.</p>
<div class="text-center relative top-1/6 <md:top-4 m-4">
<DsfrErrorPage
:title="'Erreur ' + errorStatus.concat('', errorMessage.title)"
subtitle=""
:description="errorMessage.description"
help="Si vous êtes sur cette page, c'est que vous avez rencontré un problème. Réessayer plus tard et si le problème persiste, contactez-nous."
/>

<p>
Veuillez réessayer ou
<router-link :to="{ name: 'ContactPage' }"> nous contacter </router-link
>.
Vous pouvez retourner vers la page d'accueil ou
<strong
><router-link :to="{ name: 'ContactPage' }">
nous contacter.
</router-link></strong
>
</p>
<router-link v-slot="{ navigate }" :to="{ name: 'StartPage' }">
<DsfrButton label="Retour" @click="navigate()" />
<DsfrButton
icon="ri-home-4-line"
label="Retour vers l'accueil"
@click="navigate()"
/>
</router-link>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/views/InstructionsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ async function uploadImage(base64: string, fileName: string) {
router.push(nextRoute);
} catch (error) {
console.log(error);
router.push({ name: "ErrorPage" });
const status = error.status;
router.push({ name: "ErrorPage", query: { status } });
}
try {
Expand Down
11 changes: 0 additions & 11 deletions frontend/src/views/PageNotFound.vue

This file was deleted.

0 comments on commit cf44561

Please sign in to comment.