security: sanitize all error messages shown to users

Created centralized userFriendlyError() utility that converts raw
exceptions into clean, user-friendly messages. Strips hostnames,
ports, OS error codes, HTTP status codes, stack traces, and Django
field names. Maps network/timeout/auth/server errors to plain
English messages.

Fixed 16 locations across 10 files:
- home_screen, calendar_screen, learn_more_screen (SnackBar/Text)
- login_screen, desktop_login_screen (SnackBar)
- profile_screen, contribute_screen, search_screen (SnackBar)
- review_form, review_section (inline error text)
- gamification_provider (error field)

Also removed double-wrapped exceptions in ReviewService (rethrow
instead of throw Exception('Failed to...: $e')).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 07:15:02 +05:30
parent e63e9daa0c
commit 847577c09d
13 changed files with 111 additions and 27 deletions

View File

@@ -1,5 +1,6 @@
// lib/screens/calendar_screen.dart
import 'package:flutter/material.dart';
import '../core/utils/error_utils.dart';
import 'package:intl/intl.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../features/events/services/events_service.dart';
@@ -94,7 +95,7 @@ class _CalendarScreenState extends State<CalendarScreen> {
}
} catch (e) {
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e))));
} finally {
if (mounted) setState(() => _loadingMonth = false);
}
@@ -117,7 +118,7 @@ class _CalendarScreenState extends State<CalendarScreen> {
final events = await _service.getEventsForDate(yyyyMMdd);
if (mounted) setState(() => _eventsOfDay = events);
} catch (e) {
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e))));
} finally {
if (mounted) setState(() => _loadingDay = false);
}

View File

@@ -5,6 +5,7 @@
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import '../core/utils/error_utils.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';
@@ -1829,7 +1830,7 @@ class _ContributeScreenState extends State<ContributeScreen>
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Failed to pick images: $e')));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e))));
}
}
}
@@ -1868,7 +1869,7 @@ class _ContributeScreenState extends State<ContributeScreen>
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error: $e'), backgroundColor: Colors.red));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e)), backgroundColor: Colors.red));
}
} finally {
if (mounted) setState(() => _submitting = false);
@@ -2652,7 +2653,7 @@ class _ContributeScreenState extends State<ContributeScreen>
);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Redemption failed: $e'), backgroundColor: Colors.red));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e)), backgroundColor: Colors.red));
}
}
}

View File

@@ -1,6 +1,7 @@
// lib/screens/desktop_login_screen.dart
import 'package:flutter/material.dart';
import '../core/utils/error_utils.dart';
import '../features/auth/services/auth_service.dart';
import '../core/auth/auth_guard.dart';
import 'home_desktop_screen.dart';
@@ -101,7 +102,7 @@ class _DesktopLoginScreenState extends State<DesktopLoginScreen> with SingleTick
));
} catch (e) {
if (!mounted) return;
final message = e.toString().replaceAll('Exception: ', '');
final message = userFriendlyError(e);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
setState(() => _isAnimating = false);
} finally {
@@ -335,7 +336,7 @@ class _DesktopRegisterScreenState extends State<DesktopRegisterScreen> {
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const HomeDesktopScreen(skipSidebarEntranceAnimation: true)));
} catch (e) {
if (!mounted) return;
final message = e.toString().replaceAll('Exception: ', '');
final message = userFriendlyError(e);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
} finally {
if (mounted) setState(() => _loading = false);

View File

@@ -1,6 +1,7 @@
// lib/screens/home_screen.dart
import 'dart:async';
import 'dart:ui';
import '../core/utils/error_utils.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../core/auth/auth_guard.dart';
@@ -122,7 +123,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
} catch (e) {
if (mounted) {
setState(() => _loading = false);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e))));
}
}
}

View File

@@ -12,6 +12,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import '../features/events/models/event_models.dart';
import '../features/events/services/events_service.dart';
import '../core/auth/auth_guard.dart';
import '../core/utils/error_utils.dart';
import '../core/constants.dart';
import '../features/reviews/widgets/review_section.dart';
@@ -108,7 +109,7 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
_startAutoScroll();
} catch (e) {
if (!mounted) return;
setState(() => _error = e.toString());
setState(() => _error = userFriendlyError(e));
} finally {
if (mounted) setState(() => _loading = false);
}

View File

@@ -1,6 +1,7 @@
// lib/screens/login_screen.dart
import 'dart:ui';
import '../core/utils/error_utils.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
@@ -106,7 +107,7 @@ class _LoginScreenState extends State<LoginScreen> {
));
} catch (e) {
if (!mounted) return;
final message = e.toString().replaceAll('Exception: ', '');
final message = userFriendlyError(e);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
} finally {
if (mounted) setState(() => _loading = false);
@@ -606,7 +607,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const HomeScreen()));
} catch (e) {
if (!mounted) return;
final message = e.toString().replaceAll('Exception: ', '');
final message = userFriendlyError(e);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
} finally {
if (mounted) setState(() => _loading = false);

View File

@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import '../core/utils/error_utils.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
@@ -211,7 +212,7 @@ class _ProfileScreenState extends State<ProfileScreen>
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Failed to load events: $e')));
.showSnackBar(SnackBar(content: Text(userFriendlyError(e))));
}
} finally {
if (mounted) setState(() => _loadingEvents = false);
@@ -259,7 +260,7 @@ class _ProfileScreenState extends State<ProfileScreen>
} catch (e) {
debugPrint('Image pick error: $e');
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Failed to pick image: $e')));
.showSnackBar(SnackBar(content: Text(userFriendlyError(e))));
}
}

View File

@@ -1,6 +1,7 @@
// lib/screens/search_screen.dart
import 'dart:ui';
import 'package:flutter/material.dart';
import '../core/utils/error_utils.dart';
// Location packages
import 'package:geolocator/geolocator.dart';
@@ -152,7 +153,7 @@ class _SearchScreenState extends State<SearchScreen> {
if (mounted) Navigator.of(context).pop('Current Location');
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Could not determine location: $e')));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e))));
Navigator.of(context).pop('Current Location');
}
} finally {