mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
Add files via upload
This commit is contained in:
@@ -26,6 +26,19 @@ const appSchemes = [
|
||||
|
||||
const COUNTDOWN_SECONDS = 5
|
||||
|
||||
// Validate deep link to prevent javascript: and other dangerous schemes
|
||||
const isValidDeepLink = (url: string): boolean => {
|
||||
if (!url) return false
|
||||
const lowerUrl = url.toLowerCase().trim()
|
||||
// Block dangerous schemes
|
||||
const dangerousSchemes = ['javascript:', 'data:', 'vbscript:', 'file:']
|
||||
if (dangerousSchemes.some(scheme => lowerUrl.startsWith(scheme))) {
|
||||
return false
|
||||
}
|
||||
// Only allow known app schemes
|
||||
return appSchemes.some(app => lowerUrl.startsWith(app.scheme))
|
||||
}
|
||||
|
||||
export default function DeepLinkRedirect() {
|
||||
const { i18n } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
@@ -104,13 +117,13 @@ export default function DeepLinkRedirect() {
|
||||
|
||||
// Open deep link - same as miniapp, just window.location.href
|
||||
const openDeepLink = useCallback(() => {
|
||||
if (!deepLink) return
|
||||
if (!deepLink || !isValidDeepLink(deepLink)) return
|
||||
window.location.href = deepLink
|
||||
}, [deepLink])
|
||||
|
||||
// Countdown timer effect
|
||||
useEffect(() => {
|
||||
if (!deepLink) {
|
||||
if (!deepLink || !isValidDeepLink(deepLink)) {
|
||||
setStatus('error')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import DOMPurify from 'dompurify'
|
||||
import { infoApi, FaqPage } from '../api/info'
|
||||
|
||||
const InfoIcon = () => (
|
||||
@@ -41,6 +42,15 @@ const ChevronIcon = ({ expanded }: { expanded: boolean }) => (
|
||||
|
||||
type TabType = 'faq' | 'rules' | 'privacy' | 'offer'
|
||||
|
||||
// Sanitize HTML content to prevent XSS
|
||||
const sanitizeHtml = (html: string): string => {
|
||||
return DOMPurify.sanitize(html, {
|
||||
ALLOWED_TAGS: ['p', 'br', 'b', 'i', 'u', 'strong', 'em', 'a', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'code', 'pre', 'span', 'div'],
|
||||
ALLOWED_ATTR: ['href', 'target', 'rel', 'class'],
|
||||
ALLOW_DATA_ATTR: false,
|
||||
})
|
||||
}
|
||||
|
||||
// Convert plain text to HTML with proper formatting
|
||||
const formatContent = (content: string): string => {
|
||||
if (!content) return ''
|
||||
@@ -49,11 +59,11 @@ const formatContent = (content: string): string => {
|
||||
const hasHtmlTags = /<[a-z][\s\S]*>/i.test(content)
|
||||
|
||||
if (hasHtmlTags) {
|
||||
return content
|
||||
return sanitizeHtml(content)
|
||||
}
|
||||
|
||||
// Convert plain text to formatted HTML
|
||||
return content
|
||||
const result = content
|
||||
.split(/\n\n+/) // Split by double newlines (paragraphs)
|
||||
.map(paragraph => {
|
||||
// Check if it's a header (starts with # or numeric like "1.")
|
||||
@@ -83,6 +93,8 @@ const formatContent = (content: string): string => {
|
||||
return `<p>${formattedParagraph}</p>`
|
||||
})
|
||||
.join('')
|
||||
|
||||
return sanitizeHtml(result)
|
||||
}
|
||||
|
||||
export default function Info() {
|
||||
|
||||
@@ -5,6 +5,26 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import { useAuthStore } from '../store/auth'
|
||||
import { brandingApi } from '../api/branding'
|
||||
|
||||
// Validate redirect URL to prevent open redirect attacks
|
||||
const getSafeRedirectUrl = (url: string | null): string => {
|
||||
if (!url) return '/'
|
||||
// Only allow relative paths starting with /
|
||||
// Block protocol-relative URLs (//evil.com) and absolute URLs
|
||||
if (!url.startsWith('/') || url.startsWith('//')) {
|
||||
return '/'
|
||||
}
|
||||
// Additional check for encoded characters that could bypass validation
|
||||
try {
|
||||
const decoded = decodeURIComponent(url)
|
||||
if (!decoded.startsWith('/') || decoded.startsWith('//') || decoded.includes('://')) {
|
||||
return '/'
|
||||
}
|
||||
} catch {
|
||||
return '/'
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
export default function TelegramRedirect() {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
@@ -24,8 +44,8 @@ export default function TelegramRedirect() {
|
||||
const logoLetter = branding?.logo_letter || import.meta.env.VITE_APP_LOGO || 'V'
|
||||
const logoUrl = branding ? brandingApi.getLogoUrl(branding) : null
|
||||
|
||||
// Get redirect target from URL params
|
||||
const redirectTo = searchParams.get('redirect') || '/'
|
||||
// Get redirect target from URL params (validated)
|
||||
const redirectTo = getSafeRedirectUrl(searchParams.get('redirect'))
|
||||
|
||||
useEffect(() => {
|
||||
// If already authenticated, redirect immediately
|
||||
|
||||
Reference in New Issue
Block a user