🔒 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();
|
||||
}, [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> = {};
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user