4
4
5
5
import binascii
6
6
import hashlib
7
+ import uuid
7
8
8
- import jwt
9
+ import httpx
10
+ import pydnsbl
11
+ from pydantic import ValidationError
9
12
from starlette .requests import Request
10
13
11
14
from starlette .responses import JSONResponse
12
15
13
- from backend .constants import SECRET_KEY
16
+ from backend .constants import HCAPTCHA_API_SECRET , FormFeatures
17
+ from backend .models import Form , FormResponse
14
18
from backend .route import Route
15
19
20
+ HCAPTCHA_VERIFY_URL = "https://hcaptcha.com/siteverify"
21
+ HCAPTCHA_HEADERS = {
22
+ "Content-Type" : "application/x-www-form-urlencoded"
23
+ }
24
+
16
25
17
26
class SubmitForm (Route ):
18
27
"""
@@ -28,40 +37,78 @@ async def post(self, request: Request) -> JSONResponse:
28
37
if form := await request .state .db .forms .find_one (
29
38
{"_id" : request .path_params ["form_id" ], "features" : "OPEN" }
30
39
):
31
- response_obj = {}
40
+ form = Form (** form )
41
+ response = data .copy ()
42
+ response ["id" ] = str (uuid .uuid4 ())
43
+ response ["form_id" ] = form .id
32
44
33
- if " DISABLE_ANTISPAM" not in form [ " features" ] :
45
+ if FormFeatures . DISABLE_ANTISPAM . value not in form . features :
34
46
ip_hash_ctx = hashlib .md5 ()
35
47
ip_hash_ctx .update (request .client .host .encode ())
36
48
ip_hash = binascii .hexlify (ip_hash_ctx .digest ())
49
+ user_agent_hash_ctx = hashlib .md5 ()
50
+ user_agent_hash_ctx .update (request .headers ["User-Agent" ].encode ())
51
+ user_agent_hash = binascii .hexlify (user_agent_hash_ctx .digest ())
37
52
38
- response_obj ["antispam" ] = {
39
- "ip" : ip_hash .decode ()
40
- }
53
+ dsn_checker = pydnsbl .DNSBLIpChecker ()
54
+ dsn_blacklist = await dsn_checker .check_async (request .client .host )
41
55
42
- if "REQUIRES_LOGIN" in form ["features" ]:
43
- if token := data .get ("token" ):
44
- data = jwt .decode (token , SECRET_KEY , algorithms = ['HS256' ])
45
- response_obj ["user" ] = {
46
- "user" : f"{ data ['username' ]} #{ data ['discriminator' ]} " ,
47
- "id" : data ["id" ]
56
+ async with httpx .AsyncClient () as client :
57
+ query_params = {
58
+ "secret" : HCAPTCHA_API_SECRET ,
59
+ "response" : data .get ("captcha" )
48
60
}
61
+ r = await client .post (
62
+ HCAPTCHA_VERIFY_URL ,
63
+ params = query_params ,
64
+ headers = HCAPTCHA_HEADERS
65
+ )
66
+ r .raise_for_status ()
67
+ captcha_data = r .json ()
68
+
69
+ response ["antispam" ] = {
70
+ "ip_hash" : ip_hash .decode (),
71
+ "user_agent_hash" : user_agent_hash .decode (),
72
+ "captcha_pass" : captcha_data ["success" ],
73
+ "dns_blacklisted" : dsn_blacklist .blacklisted ,
74
+ }
75
+
76
+ if FormFeatures .REQUIRES_LOGIN .value in form .features :
77
+ if request .user .is_authenticated :
78
+ response ["user" ] = request .user .payload
49
79
50
- if "COLLECT_EMAIL" in form ["features" ]:
51
- if data .get ("email" ):
52
- response_obj ["user" ]["email" ] = data ["email" ]
53
- else :
54
- return JSONResponse ({
55
- "error" : "User data did not include email information"
56
- })
80
+ if FormFeatures .COLLECT_EMAIL .value in form .features and "email" not in response ["user" ]: # noqa
81
+ return JSONResponse ({
82
+ "error" : "email_required"
83
+ }, status_code = 400 )
57
84
else :
58
85
return JSONResponse ({
59
- "error" : "Missing Discord user data"
60
- })
86
+ "error" : "missing_discord_data"
87
+ }, status_code = 400 )
88
+
89
+ missing_fields = []
90
+ for question in form .questions :
91
+ if question .id not in response ["response" ]:
92
+ missing_fields .append (question .id )
93
+
94
+ if missing_fields :
95
+ return JSONResponse ({
96
+ "error" : "missing_fields" ,
97
+ "fields" : missing_fields
98
+ }, status_code = 400 )
99
+
100
+ try :
101
+ response_obj = FormResponse (** response )
102
+ except ValidationError as e :
103
+ return JSONResponse (e .errors ())
104
+
105
+ await request .state .db .responses .insert_one (
106
+ response_obj .dict (by_alias = True )
107
+ )
61
108
62
109
return JSONResponse ({
63
- "form" : form ,
64
- "response" : response_obj
110
+ "form" : form . dict () ,
111
+ "response" : response_obj . dict ()
65
112
})
66
113
else :
67
114
return JSONResponse ({
0 commit comments