🔒 CRITICAL SECURITY FIX: Remove password storage in localStorage
SECURITY VULNERABILITY FIXED: Registration form was storing passwords in plain text in localStorage, creating a severe XSS vulnerability where attackers could steal credentials. Changes Made: 1. **RegisterForm.tsx:** - REMOVED localStorage persistence of registration_progress (lines 110-146) - Password, email, and all form data now kept in memory only - Added cleanup effect to remove any existing registration_progress data - Form data is submitted directly to backend via secure API calls 2. **WizardContext.tsx:** - REMOVED localStorage persistence of wizard state (lines 98-116) - All onboarding progress now tracked exclusively via backend API - Added cleanup effect to remove any existing wizardState data - Updated resetWizard to not reference localStorage 3. **Architecture Change:** - All user data and progress tracking now uses backend APIs exclusively - Backend APIs already exist: /api/v1/auth/register, onboarding_progress.py - No sensitive data stored in browser localStorage Impact: - Prevents credential theft via XSS attacks - Ensures data security and consistency across sessions - Aligns with security best practices (OWASP guidelines) Backend Support: - services/auth/app/api/auth_operations.py handles registration - services/auth/app/api/onboarding_progress.py tracks wizard progress - All data persisted securely in PostgreSQL database
This commit is contained in:
@@ -108,43 +108,20 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
|||||||
loadPlanMetadata();
|
loadPlanMetadata();
|
||||||
}, [selectedPlan]);
|
}, [selectedPlan]);
|
||||||
|
|
||||||
// Save form progress to localStorage
|
// SECURITY: Removed localStorage usage for registration progress
|
||||||
|
// Registration form data (including passwords) should NEVER be stored in localStorage
|
||||||
|
// due to XSS vulnerability risks. Form state is kept in memory only and submitted
|
||||||
|
// directly to backend via secure API calls.
|
||||||
|
|
||||||
|
// Clean up any old registration_progress data on mount (security fix)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const formState = {
|
try {
|
||||||
formData,
|
localStorage.removeItem('registration_progress');
|
||||||
selectedPlan,
|
localStorage.removeItem('wizardState'); // Clean up wizard state too
|
||||||
useTrial,
|
} catch (err) {
|
||||||
currentStep,
|
console.error('Error cleaning up old localStorage data:', err);
|
||||||
timestamp: Date.now(),
|
|
||||||
};
|
|
||||||
localStorage.setItem('registration_progress', JSON.stringify(formState));
|
|
||||||
}, [formData, selectedPlan, useTrial, currentStep]);
|
|
||||||
|
|
||||||
// Recover form state on mount (if less than 24 hours old)
|
|
||||||
useEffect(() => {
|
|
||||||
// Only recover if not coming from a direct link with plan pre-selected
|
|
||||||
if (preSelectedPlan) return;
|
|
||||||
|
|
||||||
const saved = localStorage.getItem('registration_progress');
|
|
||||||
if (saved) {
|
|
||||||
try {
|
|
||||||
const state = JSON.parse(saved);
|
|
||||||
const age = Date.now() - state.timestamp;
|
|
||||||
const maxAge = 24 * 60 * 60 * 1000; // 24 hours
|
|
||||||
|
|
||||||
if (age < maxAge) {
|
|
||||||
// Optionally restore state (for now, just log it exists)
|
|
||||||
console.log('Found saved registration progress');
|
|
||||||
} else {
|
|
||||||
// Clear old state
|
|
||||||
localStorage.removeItem('registration_progress');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to parse saved registration state:', err);
|
|
||||||
localStorage.removeItem('registration_progress');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [preSelectedPlan]);
|
}, []);
|
||||||
|
|
||||||
const validateForm = (): boolean => {
|
const validateForm = (): boolean => {
|
||||||
const newErrors: Partial<SimpleUserRegistration> = {};
|
const newErrors: Partial<SimpleUserRegistration> = {};
|
||||||
|
|||||||
@@ -95,23 +95,18 @@ export const WizardProvider: React.FC<WizardProviderProps> = ({
|
|||||||
startedAt: providedInitialState?.startedAt || new Date().toISOString(),
|
startedAt: providedInitialState?.startedAt || new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Persist state to localStorage
|
// SECURITY: Removed localStorage persistence for wizard state
|
||||||
useEffect(() => {
|
// All onboarding progress is now tracked exclusively via backend API
|
||||||
if (state.startedAt) {
|
// (services/auth/app/api/onboarding_progress.py) to ensure data security
|
||||||
localStorage.setItem('wizardState', JSON.stringify(state));
|
// and consistency across sessions. No wizard data is stored locally.
|
||||||
}
|
|
||||||
}, [state]);
|
|
||||||
|
|
||||||
// Load persisted state on mount
|
// Clean up any old wizardState data on mount (security fix)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const persistedState = localStorage.getItem('wizardState');
|
try {
|
||||||
if (persistedState) {
|
localStorage.removeItem('wizardState');
|
||||||
try {
|
localStorage.removeItem('registration_progress');
|
||||||
const parsed = JSON.parse(persistedState);
|
} catch (err) {
|
||||||
setState(prev => ({ ...prev, ...parsed }));
|
console.error('Error cleaning up old localStorage data:', err);
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to parse persisted wizard state:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -224,7 +219,7 @@ export const WizardProvider: React.FC<WizardProviderProps> = ({
|
|||||||
...initialState,
|
...initialState,
|
||||||
startedAt: new Date().toISOString(),
|
startedAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
localStorage.removeItem('wizardState');
|
// No localStorage cleanup needed - we don't store wizard state locally anymore
|
||||||
};
|
};
|
||||||
|
|
||||||
const value: WizardContextValue = {
|
const value: WizardContextValue = {
|
||||||
|
|||||||
Reference in New Issue
Block a user