1212from pydantic import BaseModel , ConfigDict , HttpUrl
1313from sqlalchemy .ext .asyncio import AsyncSession
1414from sqlalchemy .orm import selectinload
15+ import validators
1516
1617from api .auth import require_auth # type: ignore
1718from db import get_db # , engine
1819from models .user import User , UserProject
1920
21+ CDN_HOST = "hc-cdn.hel1.your-objectstorage.com"
22+
2023
2124class CreateProjectRequest (BaseModel ):
2225 """Create project request from client"""
@@ -69,11 +72,26 @@ def from_model(cls, project: UserProject) -> "ProjectResponse":
6972
7073
7174router = APIRouter ()
72-
7375# @protect
7476# async def create_project(): ...
7577
7678
79+ def validate_repo (repo : HttpUrl | None ):
80+ """Validate repository URL against security criteria"""
81+ if not repo :
82+ raise HTTPException (status_code = 400 , detail = "repo url is missing" )
83+ if not repo .host :
84+ raise HTTPException (status_code = 400 , detail = "repo url is missing host" )
85+ if not validators .url (str (repo ), private = False ):
86+ raise HTTPException (
87+ status_code = 400 , detail = "repo url is not valid or is local/private"
88+ )
89+ if len (repo .host ) > 256 :
90+ raise HTTPException (
91+ status_code = 400 , detail = "repo url host exceeds the length limit"
92+ )
93+
94+
7795# @protect
7896@router .post ("/api/projects/update" )
7997@require_auth
@@ -100,11 +118,15 @@ async def update_project(
100118
101119 # Validate preview image if being updated
102120 if project_request .preview_image is not None :
103- if project_request .preview_image .host != "hc-cdn.hel1.your-objectstorage.com" :
121+ if project_request .preview_image .host != CDN_HOST :
104122 raise HTTPException (
105- status_code = 400 , detail = "image must be hosted on hc cdn "
123+ status_code = 400 , detail = "image must be hosted on the Hack Club CDN "
106124 )
107125
126+ # Validate repo URL if being updated
127+ if project_request .repo is not None :
128+ validate_repo (project_request .repo )
129+
108130 update_data = project_request .model_dump (
109131 exclude_unset = True , exclude = {"project_id" }, mode = "python"
110132 )
@@ -218,11 +240,13 @@ async def create_project(
218240 ) # if the user hasn't been created yet they shouldn't be authed
219241
220242 # Validate preview image
221- if (
222- project_create_request .preview_image .host
223- != "hc-cdn.hel1.your-objectstorage.com"
224- ):
225- raise HTTPException (status_code = 400 , detail = "image must be hosted on hc cdn" )
243+ if project_create_request .preview_image .host != CDN_HOST :
244+ raise HTTPException (
245+ status_code = 400 , detail = "image must be hosted on the Hack Club CDN"
246+ )
247+
248+ # Validate repo URL
249+ validate_repo (project_create_request .repo )
226250
227251 new_project = UserProject (
228252 name = project_create_request .project_name ,
0 commit comments