Skip to content
Closed
3 changes: 3 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,6 @@
## 2024-08-04 - 숫자 μž…λ ₯ ν•„λ“œ 빈 λ¬Έμžμ—΄ μƒνƒœ μ΄ˆκΈ°ν™” 처리
**ν•™μŠ΅:** 숫자 μž…λ ₯ ν•„λ“œμ—μ„œ 빈 λ¬Έμžμ—΄('')을 μž…λ ₯ν•  λ•Œ λΈŒλΌμš°μ €λŠ” μ΄μ „μ˜ μœ νš¨ν•˜μ§€ μ•Šμ€ μƒνƒœλ₯Ό μ•”μ‹œμ μœΌλ‘œ μœ μ§€ν•˜λ―€λ‘œ, μ‚¬μš©μž μ •μ˜ 검증을 λͺ…μ‹œμ μœΌλ‘œ μ΄ˆκΈ°ν™”ν•˜μ§€ μ•ŠμœΌλ©΄ λ„€μ΄ν‹°λΈŒ HTML5 μœ νš¨μ„± 검사가 정상 μž‘λ™ν•˜μ§€ μ•Šμ„ 수 μžˆμŒμ„ ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€.
**μ‹€ν–‰:** 인라인 검증 슀크립트 μž‘μ„± μ‹œ 빈 λ¬Έμžμ—΄ μƒνƒœλ₯Ό λ³„λ„λ‘œ ν™•μΈν•˜μ—¬ this.setCustomValidity('') 및 this.removeAttribute('aria-invalid')λ₯Ό λͺ…μ‹œμ μœΌλ‘œ ν˜ΈμΆœν•˜λŠ” λ‘œμ§μ„ μΆ”κ°€ν•΄μ•Ό ν•©λ‹ˆλ‹€.
## 2024-08-05 - File type validation for drop zones
**Learning:** The HTML `accept` attribute does not reliably prevent invalid files from being dropped. Always add explicit client-side JavaScript validation against `file.type` and provide inline accessibility feedback using `setCustomValidity` and `aria-invalid` to ensure a smooth, accessible user experience.
**Action:** Add explicit client-side validation against `file.type` for all file inputs and drop zones.
25 changes: 24 additions & 1 deletion saas_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,13 @@ async def add_security_headers(request: Request, call_next):
preview.innerText = '';
return;
}
if (file.type && !file.type.startsWith('audio/') && !file.type.startsWith('video/')) {
input.setCustomValidity('Unsupported file type. Please select an audio or video file.');
input.setAttribute('aria-invalid', 'true');
preview.innerText = 'Unsupported file type (' + file.type + '). Please select an audio or video file.';
preview.style.color = '#dc3545';
return;
}
const text = formatBinaryBytes(file.size);
if (file.size > MAX_UPLOAD_BYTES) {
const limitText = formatBinaryBytes(MAX_UPLOAD_BYTES);
Expand Down Expand Up @@ -329,8 +336,24 @@ async def add_security_headers(request: Request, call_next):
}

let totalSize = 0;
let hasInvalidType = false;
let invalidType = '';
for (let i = 0; i < files.length; i++) {
totalSize += files[i].size;
const f = files[i];
if (f.type && !f.type.startsWith('audio/') && !f.type.startsWith('video/')) {
hasInvalidType = true;
invalidType = f.type;
break;
}
totalSize += f.size;
}

if (hasInvalidType) {
input.setCustomValidity('Unsupported file type. Please select audio or video files only.');
input.setAttribute('aria-invalid', 'true');
preview.innerText = 'Unsupported file type (' + invalidType + ') detected. Please select audio or video files only.';
preview.style.color = '#dc3545';
return;
}

if (files.length > 20) {
Expand Down
Loading