Skip to content

Commit 285419f

Browse files
Approve lovable tool use
1 parent 0298ea5 commit 285419f

7 files changed

Lines changed: 702 additions & 76 deletions

File tree

src/components/CampaignGeneralSettings.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,21 @@ export const CampaignGeneralSettings = ({
102102
setShowPaymentSelector(false);
103103

104104
// Stocker les données de réactivation pour le retour de Stripe
105-
localStorage.setItem('campaignReactivationData', JSON.stringify({
105+
const { secureStorage } = await import('@/utils/secureClientStorage');
106+
secureStorage.setCampaignData('campaignReactivationData', {
106107
campaignId: campaign.id,
107108
campaignName: campaign.name,
108-
}));
109+
timestamp: Date.now()
110+
}, 24);
109111
console.log('💾 Données de réactivation stockées pour campagne:', campaign.name);
110112

111113
// Utiliser le système de redirection Stripe existant
112114
await setupPaymentForCampaign(campaign.id, campaign.name);
113115
} catch (error: any) {
114116
console.error('❌ Erreur redirection Stripe:', error);
115117
// Nettoyer les données si erreur
116-
localStorage.removeItem('campaignReactivationData');
118+
const { secureStorage: secureStorageCleanup } = await import('@/utils/secureClientStorage');
119+
secureStorageCleanup.removeSecure('campaign_campaignReactivationData');
117120
}
118121
};
119122

src/components/Dashboard.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,12 @@ export const Dashboard = memo(() => {
275275
setShowSuccessModal(true);
276276

277277
// Nettoyer immédiatement pour éviter les répétitions
278-
localStorage.removeItem('newCampaignCreated');
278+
const { secureStorage } = await import('@/utils/secureClientStorage');
279+
secureStorage.removeSecure('campaign_newCampaignCreated');
279280
} catch (error) {
280281
console.error('❌ DASHBOARD: Erreur parsing newCampaignCreated:', error);
281-
localStorage.removeItem('newCampaignCreated');
282+
const { secureStorage: secureStorageError } = await import('@/utils/secureClientStorage');
283+
secureStorageError.removeSecure('campaign_newCampaignCreated');
282284
}
283285
}
284286
};

src/hooks/useCampaignsSupabase.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,13 @@ export const useCampaignsSupabase = () => {
127127

128128
console.log('✅ SUPABASE - Campagne créée:', newCampaign.id);
129129

130-
// Store campaign data for success modal
131-
import('@/utils/secureClientStorage').then(({ secureStorage }) => {
132-
secureStorage.setSecure('newCampaignCreated', {
130+
// Store campaign data for success modal using dynamic import
131+
const { secureStorage } = await import('@/utils/secureClientStorage');
132+
secureStorage.setCampaignData('newCampaignCreated', {
133133
id: newCampaign.id,
134-
name: newCampaign.name
135-
}));
134+
name: newCampaign.name,
135+
timestamp: Date.now()
136+
}, 1);
136137

137138
// Reload campaigns
138139
await loadCampaigns();

src/hooks/useSecureAuth.ts

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
/**
2+
* 🔐 Secure Authentication Hook
3+
* Enhanced authentication with security monitoring and rate limiting
4+
*/
5+
6+
import { useState, useCallback, useEffect } from 'react';
7+
import { supabase } from '@/integrations/supabase/client';
8+
import { User, Session } from '@supabase/supabase-js';
9+
import { secureStorage } from '@/utils/secureClientStorage';
10+
import { rateLimiters } from '@/utils/inputValidation';
11+
import { securityMonitoring } from '@/utils/security';
12+
import { toast } from 'sonner';
13+
14+
interface SecureAuthState {
15+
user: User | null;
16+
session: Session | null;
17+
loading: boolean;
18+
authenticated: boolean;
19+
}
20+
21+
interface AuthAttemptLog {
22+
timestamp: number;
23+
success: boolean;
24+
method: string;
25+
ip?: string;
26+
}
27+
28+
export const useSecureAuth = () => {
29+
const [authState, setAuthState] = useState<SecureAuthState>({
30+
user: null,
31+
session: null,
32+
loading: true,
33+
authenticated: false
34+
});
35+
36+
// Monitor authentication attempts
37+
const logAuthAttempt = useCallback((method: string, success: boolean, details?: any) => {
38+
const attemptLog: AuthAttemptLog = {
39+
timestamp: Date.now(),
40+
success,
41+
method
42+
};
43+
44+
// Store in secure storage for monitoring
45+
secureStorage.setAuthData(`auth_attempt_${Date.now()}`, attemptLog, 1);
46+
47+
// Log security event
48+
securityMonitoring.logSuspiciousActivity({
49+
type: success ? 'successful_authentication' : 'failed_authentication',
50+
details: {
51+
method,
52+
...details,
53+
timestamp: attemptLog.timestamp
54+
}
55+
});
56+
57+
if (!success) {
58+
console.warn('🔐 AUTH: Authentication attempt failed', { method, details });
59+
}
60+
}, []);
61+
62+
// Check for rate limiting on auth attempts
63+
const checkAuthRateLimit = useCallback((ip: string = 'unknown') => {
64+
const isAllowed = rateLimiters.auth.isAllowed({ ip });
65+
66+
if (!isAllowed) {
67+
securityMonitoring.logSuspiciousActivity({
68+
type: 'auth_rate_limit_exceeded',
69+
ip,
70+
details: {
71+
timestamp: Date.now(),
72+
maxRequests: 10,
73+
windowMs: 15 * 60 * 1000
74+
}
75+
});
76+
77+
toast.error('Too many authentication attempts. Please wait before trying again.');
78+
throw new Error('Rate limit exceeded');
79+
}
80+
81+
return true;
82+
}, []);
83+
84+
// Secure sign up with comprehensive validation
85+
const signUp = useCallback(async (email: string, password: string) => {
86+
try {
87+
// Rate limiting check
88+
checkAuthRateLimit();
89+
90+
// Input validation
91+
if (!email || !password) {
92+
throw new Error('Email and password are required');
93+
}
94+
95+
if (password.length < 8) {
96+
throw new Error('Password must be at least 8 characters long');
97+
}
98+
99+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
100+
if (!emailRegex.test(email)) {
101+
throw new Error('Invalid email format');
102+
}
103+
104+
// Check for common weak passwords
105+
const weakPasswords = ['password', '12345678', 'qwerty123', 'password123'];
106+
if (weakPasswords.includes(password.toLowerCase())) {
107+
throw new Error('Password is too weak. Please choose a stronger password.');
108+
}
109+
110+
const redirectUrl = `${window.location.origin}/`;
111+
112+
const { data, error } = await supabase.auth.signUp({
113+
email: email.toLowerCase().trim(),
114+
password,
115+
options: {
116+
emailRedirectTo: redirectUrl
117+
}
118+
});
119+
120+
if (error) {
121+
logAuthAttempt('signup', false, { error: error.message, email });
122+
throw error;
123+
}
124+
125+
logAuthAttempt('signup', true, { email, userId: data.user?.id });
126+
127+
return { data, error: null };
128+
129+
} catch (error: any) {
130+
logAuthAttempt('signup', false, { error: error.message, email });
131+
console.error('🔐 AUTH: Sign up error:', error);
132+
return { data: null, error };
133+
}
134+
}, [checkAuthRateLimit, logAuthAttempt]);
135+
136+
// Secure sign in with monitoring
137+
const signIn = useCallback(async (email: string, password: string) => {
138+
try {
139+
// Rate limiting check
140+
checkAuthRateLimit();
141+
142+
// Input validation
143+
if (!email || !password) {
144+
throw new Error('Email and password are required');
145+
}
146+
147+
const { data, error } = await supabase.auth.signInWithPassword({
148+
email: email.toLowerCase().trim(),
149+
password
150+
});
151+
152+
if (error) {
153+
logAuthAttempt('signin', false, { error: error.message, email });
154+
throw error;
155+
}
156+
157+
// Store secure session data
158+
if (data.session) {
159+
secureStorage.setAuthData('last_signin', {
160+
timestamp: Date.now(),
161+
userId: data.user?.id,
162+
email: data.user?.email
163+
}, 24);
164+
}
165+
166+
logAuthAttempt('signin', true, { email, userId: data.user?.id });
167+
168+
return { data, error: null };
169+
170+
} catch (error: any) {
171+
logAuthAttempt('signin', false, { error: error.message, email });
172+
console.error('🔐 AUTH: Sign in error:', error);
173+
return { data: null, error };
174+
}
175+
}, [checkAuthRateLimit, logAuthAttempt]);
176+
177+
// Secure sign out with cleanup
178+
const signOut = useCallback(async () => {
179+
try {
180+
const { error } = await supabase.auth.signOut();
181+
182+
if (error) {
183+
console.error('🔐 AUTH: Sign out error:', error);
184+
throw error;
185+
}
186+
187+
// Clear sensitive data from secure storage
188+
const currentUser = authState.user;
189+
if (currentUser) {
190+
logAuthAttempt('signout', true, { userId: currentUser.id });
191+
192+
// Clean up user-specific secure storage
193+
secureStorage.removeSecure('auth_last_signin');
194+
secureStorage.cleanExpiredData();
195+
}
196+
197+
return { error: null };
198+
199+
} catch (error: any) {
200+
logAuthAttempt('signout', false, { error: error.message });
201+
return { error };
202+
}
203+
}, [authState.user, logAuthAttempt]);
204+
205+
// Initialize auth state and monitoring
206+
useEffect(() => {
207+
let mounted = true;
208+
209+
// Set up auth state listener
210+
const { data: { subscription } } = supabase.auth.onAuthStateChange(
211+
async (event, session) => {
212+
if (!mounted) return;
213+
214+
console.log('🔐 AUTH: State change event:', event);
215+
216+
// Security monitoring for auth events
217+
if (event === 'SIGNED_IN' && session?.user) {
218+
securityMonitoring.logSuspiciousActivity({
219+
type: 'user_session_started',
220+
userId: session.user.id,
221+
details: {
222+
event,
223+
timestamp: Date.now(),
224+
userAgent: navigator.userAgent
225+
}
226+
});
227+
}
228+
229+
if (event === 'SIGNED_OUT') {
230+
securityMonitoring.logSuspiciousActivity({
231+
type: 'user_session_ended',
232+
details: {
233+
event,
234+
timestamp: Date.now()
235+
}
236+
});
237+
}
238+
239+
setAuthState({
240+
user: session?.user ?? null,
241+
session: session,
242+
loading: false,
243+
authenticated: !!session?.user
244+
});
245+
}
246+
);
247+
248+
// Check for existing session
249+
supabase.auth.getSession().then(({ data: { session } }) => {
250+
if (mounted) {
251+
setAuthState({
252+
user: session?.user ?? null,
253+
session: session,
254+
loading: false,
255+
authenticated: !!session?.user
256+
});
257+
}
258+
});
259+
260+
return () => {
261+
mounted = false;
262+
subscription.unsubscribe();
263+
};
264+
}, []);
265+
266+
// Session validation
267+
const validateSession = useCallback(async () => {
268+
try {
269+
const { data: { session }, error } = await supabase.auth.getSession();
270+
271+
if (error || !session) {
272+
console.warn('🔐 AUTH: Session validation failed');
273+
return false;
274+
}
275+
276+
// Check if session is close to expiring (refresh if less than 5 minutes left)
277+
const expiresAt = session.expires_at ? session.expires_at * 1000 : 0;
278+
const fiveMinutes = 5 * 60 * 1000;
279+
280+
if (expiresAt - Date.now() < fiveMinutes) {
281+
console.log('🔐 AUTH: Refreshing session');
282+
const { error: refreshError } = await supabase.auth.refreshSession();
283+
284+
if (refreshError) {
285+
console.error('🔐 AUTH: Session refresh failed:', refreshError);
286+
return false;
287+
}
288+
}
289+
290+
return true;
291+
292+
} catch (error) {
293+
console.error('🔐 AUTH: Session validation error:', error);
294+
return false;
295+
}
296+
}, []);
297+
298+
return {
299+
...authState,
300+
signUp,
301+
signIn,
302+
signOut,
303+
validateSession,
304+
isLoading: authState.loading
305+
};
306+
};

0 commit comments

Comments
 (0)