Fix onboarding tutorial visibility and apply security remediation tasks (ABV sanitization, i18n hardening, regex escaping)
This commit is contained in:
@@ -5,6 +5,7 @@ import { usePathname } from 'next/navigation';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Scan, GlassWater, Users, Settings, ArrowRight, X, Sparkles } from 'lucide-react';
|
||||
import { useI18n } from '@/i18n/I18nContext';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
|
||||
const ONBOARDING_KEY = 'dramlog_onboarding_complete';
|
||||
|
||||
@@ -50,12 +51,22 @@ const getSteps = (t: (path: string) => string): OnboardingStep[] => [
|
||||
|
||||
export default function OnboardingTutorial() {
|
||||
const { t } = useI18n();
|
||||
const { user, isLoading } = useAuth();
|
||||
const STEPS = getSteps(t);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
// Don't show if auth is still loading
|
||||
if (isLoading) return;
|
||||
|
||||
// Don't show if no user is logged in
|
||||
if (!user) {
|
||||
setIsOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't show on login/auth pages
|
||||
if (pathname === '/login' || pathname === '/auth' || pathname === '/register') {
|
||||
return;
|
||||
|
||||
@@ -43,8 +43,12 @@ export const I18nProvider = ({ children }: { children: ReactNode }) => {
|
||||
let current: any = translations[locale];
|
||||
|
||||
for (const key of keys) {
|
||||
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
||||
console.warn(`Blocked potentially malicious translation key: ${key}`);
|
||||
return path;
|
||||
}
|
||||
if (current[key] === undefined) {
|
||||
console.warn(`Translation missing for key: ${path} in locale: ${locale}`);
|
||||
console.warn(`Translation missing for key: ${key} in path: ${path} in locale: ${locale}`);
|
||||
return path;
|
||||
}
|
||||
current = current[key];
|
||||
|
||||
@@ -30,6 +30,13 @@ const fuse = new Fuse<Distillery>(distilleries as Distillery[], {
|
||||
minMatchCharLength: 4,
|
||||
});
|
||||
|
||||
/**
|
||||
* Escapes special characters for use in a regular expression
|
||||
*/
|
||||
function escapeRegExp(string: string): string {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocess raw distillery name for better matching
|
||||
*/
|
||||
@@ -38,7 +45,7 @@ function preprocessName(raw: string): string {
|
||||
|
||||
// Remove stopwords
|
||||
for (const word of STOPWORDS) {
|
||||
clean = clean.replace(new RegExp(`\\b${word}\\b`, 'gi'), ' ');
|
||||
clean = clean.replace(new RegExp(`\\b${escapeRegExp(word)}\\b`, 'gi'), ' ');
|
||||
}
|
||||
|
||||
// Remove extra whitespace
|
||||
@@ -143,7 +150,7 @@ export function cleanBottleName(bottleName: string, distillery: string): string
|
||||
}
|
||||
|
||||
// Create regex to match distillery at start of name (case-insensitive)
|
||||
const escaped = distillery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const escaped = escapeRegExp(distillery);
|
||||
const regex = new RegExp(`^${escaped}\\s*[-–—:]?\\s*`, 'i');
|
||||
|
||||
let cleaned = bottleName.replace(regex, '').trim();
|
||||
|
||||
@@ -122,7 +122,8 @@ export async function analyzeBottleMistral(input: any): Promise<AnalysisResponse
|
||||
delete jsonData.search_string;
|
||||
|
||||
if (typeof jsonData.abv === 'string') {
|
||||
jsonData.abv = parseFloat(jsonData.abv.replace('%', '').trim());
|
||||
// Fix: Global replace to remove all % signs
|
||||
jsonData.abv = parseFloat(jsonData.abv.replace(/%/g, '').trim());
|
||||
}
|
||||
|
||||
if (jsonData.age) jsonData.age = parseInt(jsonData.age);
|
||||
|
||||
Reference in New Issue
Block a user