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.
This commit is contained in:
Rotimi Best
2025-12-03 10:01:50 +01:00
parent 7014757ed5
commit 07ecfcf967
5 changed files with 43 additions and 0 deletions
@@ -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,
@@ -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 {
@@ -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];
@@ -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);
@@ -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 });