diff options
-rw-r--r-- | backend/constants.py | 9 | ||||
-rw-r--r-- | backend/models/form.py | 87 | ||||
-rw-r--r-- | backend/routes/forms/index.py | 18 |
3 files changed, 81 insertions, 33 deletions
diff --git a/backend/constants.py b/backend/constants.py index f5e4304..bfcf261 100644 --- a/backend/constants.py +++ b/backend/constants.py @@ -62,3 +62,12 @@ class FormFeatures(Enum): COLLECT_EMAIL = "COLLECT_EMAIL" DISABLE_ANTISPAM = "DISABLE_ANTISPAM" WEBHOOK_ENABLED = "WEBHOOK_ENABLED" + + +class WebHook(Enum): + URL = "url" + MESSAGE = "message" + + +class Meta(Enum): + WEB_HOOK = WebHook diff --git a/backend/models/form.py b/backend/models/form.py index 9a3a8a2..96362d4 100644 --- a/backend/models/form.py +++ b/backend/models/form.py @@ -2,8 +2,9 @@ import typing as t import httpx from pydantic import BaseModel, Field, validator +from pydantic.error_wrappers import ErrorWrapper, ValidationError -from backend.constants import FormFeatures +from backend.constants import FormFeatures, Meta, WebHook from .question import Question PUBLIC_FIELDS = ["id", "features", "questions", "name", "description"] @@ -16,40 +17,10 @@ class _WebHook(BaseModel): @validator("url") def validate_url(cls, url: str) -> str: - """Checks if discord webhook urls are valid.""" - if not isinstance(url, str): - raise ValueError("Webhook URL must be a string.") - + """Validates URL parameter.""" if "discord.com/api/webhooks/" not in url: raise ValueError("URL must be a discord webhook.") - # Attempt to connect to URL - try: - httpx.get(url).raise_for_status() - - except httpx.RequestError as e: - # Catch exceptions in request format - raise ValueError( - f"Encountered error while trying to connect to url: `{e}`" - ) - - except httpx.HTTPStatusError as e: - # Catch exceptions in response - status = e.response.status_code - - if status == 401: - raise ValueError( - "Could not authenticate with target. Please check the webhook url." - ) - elif status == 404: - raise ValueError( - "Target could not find webhook url. Please check the webhook url." - ) - else: - raise ValueError( - f"Unknown error ({status}) while connecting to target: {e}" - ) - return url @@ -107,3 +78,55 @@ class Form(BaseModel): class FormList(BaseModel): __root__: t.List[Form] + + +async def validate_hook_url(url: str) -> t.Optional[ValidationError]: + """Validator for discord webhook urls.""" + async def validate(): + if not isinstance(url, str): + raise ValueError("Webhook URL must be a string.") + + if "discord.com/api/webhooks/" not in url: + raise ValueError("URL must be a discord webhook.") + + try: + async with httpx.AsyncClient() as client: + response = await client.get(url) + response.raise_for_status() + + except httpx.RequestError as error: + # Catch exceptions in request format + raise ValueError( + f"Encountered error while trying to connect to url: `{error}`" + ) + + except httpx.HTTPStatusError as error: + # Catch exceptions in response + status = error.response.status_code + + if status == 401: + raise ValueError( + "Could not authenticate with target. Please check the webhook url." + ) + elif status == 404: + raise ValueError( + "Target could not find webhook url. Please check the webhook url." + ) + else: + raise ValueError( + f"Unknown error ({status}) while connecting to target: {error}" + ) + + return url + + # Validate, and return errors, if any + try: + await validate() + except Exception as e: + loc = ( + Meta.__name__.lower(), + WebHook.__name__.lower(), + WebHook.URL.value + ) + + return ValidationError([ErrorWrapper(e, loc=loc)], _WebHook) diff --git a/backend/routes/forms/index.py b/backend/routes/forms/index.py index d1373e4..0e1dee8 100644 --- a/backend/routes/forms/index.py +++ b/backend/routes/forms/index.py @@ -6,8 +6,10 @@ from starlette.authentication import requires from starlette.requests import Request from starlette.responses import JSONResponse -from backend.route import Route +from backend.constants import Meta, WebHook from backend.models import Form, FormList +from backend.models.form import validate_hook_url +from backend.route import Route from backend.validation import ErrorMessage, OkayResponse, api @@ -46,6 +48,20 @@ class FormsList(Route): """Create a new form.""" form_data = await request.json() + # Verify Webhook + try: + # Get url from request + path = (Meta.__name__.lower(), WebHook.__name__.lower(), WebHook.URL.value) + url = form_data[path[0]][path[1]][path[2]] + + # Validate URL + validation = await validate_hook_url(url) + if validation: + return JSONResponse(validation.errors(), status_code=422) + + except KeyError: + pass + form = Form(**form_data) if await request.state.db.forms.find_one({"_id": form.id}): |