🔒 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:
Claude
2025-11-07 09:27:28 +00:00
parent 9b8d3f0db6
commit 29106aa45e
2 changed files with 23 additions and 51 deletions

View File

@@ -108,43 +108,20 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
loadPlanMetadata();
}, [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(() => {
const formState = {
formData,
selectedPlan,
useTrial,
currentStep,
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');
}
try {
localStorage.removeItem('registration_progress');
localStorage.removeItem('wizardState'); // Clean up wizard state too
} catch (err) {
console.error('Error cleaning up old localStorage data:', err);
}
}, [preSelectedPlan]);
}, []);
const validateForm = (): boolean => {
const newErrors: Partial<SimpleUserRegistration> = {};

View File

@@ -95,23 +95,18 @@ export const WizardProvider: React.FC<WizardProviderProps> = ({
startedAt: providedInitialState?.startedAt || new Date().toISOString(),
});
// Persist state to localStorage
useEffect(() => {
if (state.startedAt) {
localStorage.setItem('wizardState', JSON.stringify(state));
}
}, [state]);
// SECURITY: Removed localStorage persistence for wizard state
// All onboarding progress is now tracked exclusively via backend API
// (services/auth/app/api/onboarding_progress.py) to ensure data security
// and consistency across sessions. No wizard data is stored locally.
// Load persisted state on mount
// Clean up any old wizardState data on mount (security fix)
useEffect(() => {
const persistedState = localStorage.getItem('wizardState');
if (persistedState) {
try {
const parsed = JSON.parse(persistedState);
setState(prev => ({ ...prev, ...parsed }));
} catch (error) {
console.error('Failed to parse persisted wizard state:', error);
}
try {
localStorage.removeItem('wizardState');
localStorage.removeItem('registration_progress');
} catch (err) {
console.error('Error cleaning up old localStorage data:', err);
}
}, []);
@@ -224,7 +219,7 @@ export const WizardProvider: React.FC<WizardProviderProps> = ({
...initialState,
startedAt: new Date().toISOString(),
});
localStorage.removeItem('wizardState');
// No localStorage cleanup needed - we don't store wizard state locally anymore
};
const value: WizardContextValue = {