Skip to content

Commit a26a0bb

Browse files
committed
Refactor: Remove obsolete Alembic migration script and add input validation utilities
- Deleted the migration script `7fc823789534_remove_extra_line.py` as it was no longer needed. - Introduced a new file `validation.py` containing input validation utilities to ensure data integrity and security across various fields, including username, email, contest name, project name, and more.
1 parent 7da6a63 commit a26a0bb

File tree

2 files changed

+178
-29
lines changed

2 files changed

+178
-29
lines changed

backend/alembic/versions/7fc823789534_remove_extra_line.py

Lines changed: 0 additions & 29 deletions
This file was deleted.

backend/app/utils/validation.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
"""Input validation utilities for security and data integrity."""
2+
3+
MAX_USERNAME_LENGTH = 50
4+
MAX_EMAIL_LENGTH = 255
5+
MAX_CONTEST_NAME_LENGTH = 200
6+
MAX_PROJECT_NAME_LENGTH = 100
7+
MAX_DESCRIPTION_LENGTH = 5000
8+
MAX_URL_LENGTH = 2048
9+
MAX_COMMENT_LENGTH = 2000
10+
MAX_CATEGORY_COUNT = 50
11+
12+
13+
def validate_string_length(value, field_name, max_length, min_length=0):
14+
"""
15+
Validate that a string is within allowed length limits.
16+
17+
Args:
18+
value: String value to validate
19+
field_name: Name of the field (for error messages)
20+
max_length: Maximum allowed length
21+
min_length: Minimum allowed length (default: 0)
22+
23+
Returns:
24+
tuple: (is_valid: bool, error_message: str or None)
25+
"""
26+
if not isinstance(value, str):
27+
return False, f'{field_name} must be a string'
28+
29+
if len(value) < min_length:
30+
return False, f'{field_name} must be at least {min_length} characters'
31+
32+
if len(value) > max_length:
33+
return False, f'{field_name} must be at most {max_length} characters'
34+
35+
return True, None
36+
37+
38+
def validate_username_length(username):
39+
"""
40+
Validate username length.
41+
42+
Args:
43+
username: Username string to validate
44+
45+
Returns:
46+
tuple: (is_valid: bool, error_message: str or None)
47+
"""
48+
return validate_string_length(
49+
username, 'Username', MAX_USERNAME_LENGTH, min_length=3
50+
)
51+
52+
53+
def validate_email_length(email):
54+
"""
55+
Validate email length.
56+
57+
Args:
58+
email: Email string to validate
59+
60+
Returns:
61+
tuple: (is_valid: bool, error_message: str or None)
62+
"""
63+
return validate_string_length(
64+
email, 'Email', MAX_EMAIL_LENGTH, min_length=5
65+
)
66+
67+
68+
# REMOVED: validate_password_length - password-based authentication removed
69+
# Authentication is now exclusively via MediaWiki OAuth
70+
71+
72+
def validate_contest_name_length(name):
73+
"""
74+
Validate contest name length.
75+
76+
Args:
77+
name: Contest name string to validate
78+
79+
Returns:
80+
tuple: (is_valid: bool, error_message: str or None)
81+
"""
82+
return validate_string_length(
83+
name, 'Contest name', MAX_CONTEST_NAME_LENGTH, min_length=1
84+
)
85+
86+
87+
def validate_project_name_length(project_name):
88+
"""
89+
Validate project name length.
90+
91+
Args:
92+
project_name: Project name string to validate
93+
94+
Returns:
95+
tuple: (is_valid: bool, error_message: str or None)
96+
"""
97+
return validate_string_length(
98+
project_name, 'Project name', MAX_PROJECT_NAME_LENGTH, min_length=1
99+
)
100+
101+
102+
def validate_description_length(description):
103+
"""
104+
Validate description length.
105+
106+
Args:
107+
description: Description string to validate
108+
109+
Returns:
110+
tuple: (is_valid: bool, error_message: str or None)
111+
"""
112+
if description is None:
113+
return True, None
114+
115+
return validate_string_length(
116+
description, 'Description', MAX_DESCRIPTION_LENGTH, min_length=0
117+
)
118+
119+
120+
def validate_url_length(url):
121+
"""
122+
Validate URL length.
123+
124+
Args:
125+
url: URL string to validate
126+
127+
Returns:
128+
tuple: (is_valid: bool, error_message: str or None)
129+
"""
130+
return validate_string_length(
131+
url, 'URL', MAX_URL_LENGTH, min_length=1
132+
)
133+
134+
135+
def validate_comment_length(comment):
136+
"""
137+
Validate comment/review comment length.
138+
139+
Args:
140+
comment: Comment string to validate
141+
142+
Returns:
143+
tuple: (is_valid: bool, error_message: str or None)
144+
"""
145+
if comment is None:
146+
return True, None
147+
148+
return validate_string_length(
149+
comment, 'Comment', MAX_COMMENT_LENGTH, min_length=0
150+
)
151+
152+
153+
def validate_category_list(categories):
154+
"""
155+
Validate category list length and individual URLs.
156+
157+
Args:
158+
categories: List of category URLs
159+
160+
Returns:
161+
tuple: (is_valid: bool, error_message: str or None)
162+
"""
163+
if not isinstance(categories, list):
164+
return False, 'Categories must be a list'
165+
166+
if len(categories) > MAX_CATEGORY_COUNT:
167+
return False, f'Maximum {MAX_CATEGORY_COUNT} categories allowed'
168+
169+
# Validate each category URL length
170+
for i, category in enumerate(categories):
171+
if not isinstance(category, str):
172+
return False, f'Category {i+1} must be a string'
173+
174+
is_valid, error = validate_url_length(category)
175+
if not is_valid:
176+
return False, f'Category {i+1}: {error}'
177+
178+
return True, None

0 commit comments

Comments
 (0)