From 07ecfcf967e00cc177cb5b0abf7c1a43f4d306f0 Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Wed, 3 Dec 2025 10:01:50 +0100 Subject: [PATCH] fix: authorization bypass vulnerabilities in plan-restricted features - Add plan validation to custom domain saving (Domains.svelte) - Add plan validation to certificate customization (Design.svelte) - Add plan validation to document upload (AddDocumentToLesson.svelte) - Add plan validation to video upload (UploadVideo.svelte) - Enhance team member function validation (TeamMembers.svelte) Prevents free users from bypassing UI restrictions via DOM manipulation. All restricted functions now enforce server-side plan validation. --- .../Course/components/Ceritficate/Design.svelte | 6 ++++++ .../Materials/Document/AddDocumentToLesson.svelte | 8 ++++++++ .../Lesson/Materials/Video/UploadVideo.svelte | 11 +++++++++++ .../src/lib/components/Org/Settings/Domains.svelte | 6 ++++++ .../lib/components/Org/Settings/TeamMembers.svelte | 12 ++++++++++++ 5 files changed, 43 insertions(+) diff --git a/apps/dashboard/src/lib/components/Course/components/Ceritficate/Design.svelte b/apps/dashboard/src/lib/components/Course/components/Ceritficate/Design.svelte index 5083b159..f905eab2 100644 --- a/apps/dashboard/src/lib/components/Course/components/Ceritficate/Design.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Ceritficate/Design.svelte @@ -40,6 +40,12 @@ isSaving = true; try { + // Prevent free plan users from bypassing UI restrictions + if ($isFreePlan) { + errors.description = 'Certificate customization is only available on paid plans'; + throw new Error(errors.description); + } + const result = saveCertificateValidation({ description: $course.description || '', is_certificate_downloadable: $course.is_certificate_downloadable || false, diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Document/AddDocumentToLesson.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Document/AddDocumentToLesson.svelte index f452b446..390dab7b 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Document/AddDocumentToLesson.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Document/AddDocumentToLesson.svelte @@ -107,6 +107,14 @@ async function uploadDocument() { if (!selectedFile) return; + + // Prevent free plan users from bypassing UI restrictions + if ($isFreePlan) { + $lessonDocUpload.error = $t('upgrade.required'); + snackbar.error($lessonDocUpload.error); + return; + } + $lessonDocUpload.isUploading = true; try { diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Video/UploadVideo.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Video/UploadVideo.svelte index b5a04757..cef4159d 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Video/UploadVideo.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Video/UploadVideo.svelte @@ -30,6 +30,17 @@ async function onUpload() { if (!fileInput) return; + // Prevent free plan users from bypassing UI restrictions + if ($isFreePlan) { + formRes = { + type: 'AUTHORIZATION_ERROR', + status: 403, + message: $t('upgrade.required') + }; + isLoaded = true; + return; + } + videoUploader.initUpload(); const videoFile = fileInput.files[0]; diff --git a/apps/dashboard/src/lib/components/Org/Settings/Domains.svelte b/apps/dashboard/src/lib/components/Org/Settings/Domains.svelte index 560cfdbb..cd1b5ffb 100644 --- a/apps/dashboard/src/lib/components/Org/Settings/Domains.svelte +++ b/apps/dashboard/src/lib/components/Org/Settings/Domains.svelte @@ -81,6 +81,12 @@ async function handleSaveCustomDomain() { if (!isDomainValid) return; + // Prevent free plan users from bypassing UI restrictions + if ($isFreePlan) { + errors.customDomain = 'Custom domains are only available on paid plans'; + return; + } + const sanitizedDomain = sanitizeDomain(customDomain); const details = parse(sanitizedDomain); diff --git a/apps/dashboard/src/lib/components/Org/Settings/TeamMembers.svelte b/apps/dashboard/src/lib/components/Org/Settings/TeamMembers.svelte index 88412e0b..52d4cb5e 100644 --- a/apps/dashboard/src/lib/components/Org/Settings/TeamMembers.svelte +++ b/apps/dashboard/src/lib/components/Org/Settings/TeamMembers.svelte @@ -30,6 +30,12 @@ let isRemoving: number | null = null; async function onSendInvite() { + // Check if user is on free plan + if ($isFreePlan) { + snackbar.error('upgrade.required'); + return; + } + const { hasError, error: _error, emails } = validateEmailInString(emailsStr); if (hasError) { @@ -105,6 +111,12 @@ } async function onRemove(id: number) { + // Check if user is on free plan + if ($isFreePlan) { + snackbar.error('upgrade.required'); + return; + } + console.log('onRemove called'); isRemoving = id; const { error } = await supabase.from('organizationmember').delete().match({ id });