Skip to content

Commit 5416527

Browse files
authored
Merge pull request #17 from jnnabugwu/dev
Dev
2 parents 4c83e36 + 0bd4e0d commit 5416527

File tree

9 files changed

+870
-61
lines changed

9 files changed

+870
-61
lines changed

β€Žlib/core/config/supabase_config.dartβ€Ž

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
33
import 'package:supabase_flutter/supabase_flutter.dart';
44
import 'dart:js' as js;
55
import '../../utils/environment.dart';
6+
import '../../features/auth/data/services/google_auth_service.dart';
67

78
class SupabaseConfig {
89
static String get supabaseUrl {
@@ -64,13 +65,47 @@ class SupabaseConfig {
6465
debugPrint('⚠️ Connection test failed: $e');
6566
}
6667

68+
// Handle OAuth redirects for web (if we came from an OAuth flow)
69+
if (kIsWeb) {
70+
debugPrint('πŸ”„ Checking for OAuth redirects...');
71+
72+
// Delay slightly to ensure the app is fully loaded
73+
await Future.delayed(Duration(milliseconds: 500));
74+
75+
_handlePossibleOAuthRedirect(client);
76+
}
77+
6778
debugPrint('βœ… Supabase initialized successfully');
6879
} catch (e, stackTrace) {
6980
debugPrint('❌ Error initializing Supabase: $e');
7081
debugPrint('πŸ“š Stack trace: $stackTrace');
7182
rethrow;
7283
}
7384
}
85+
86+
/// Handle OAuth redirects for web platforms
87+
static void _handlePossibleOAuthRedirect(SupabaseClient client) {
88+
if (!kIsWeb) return;
89+
90+
try {
91+
// Create a GoogleAuthService instance to handle the redirect
92+
final googleAuthService = GoogleAuthService();
93+
94+
// Use the improved method that properly refreshes the session
95+
googleAuthService.checkForRedirectSession().then((success) {
96+
if (success) {
97+
debugPrint('βœ… Successfully processed OAuth redirect and established session');
98+
// The Google auth service has already refreshed the session
99+
} else {
100+
debugPrint('ℹ️ No OAuth redirect detected or session couldn\'t be established');
101+
}
102+
}).catchError((error) {
103+
debugPrint('❌ Error handling OAuth redirect: $error');
104+
});
105+
} catch (e) {
106+
debugPrint('❌ Error handling OAuth redirect: $e');
107+
}
108+
}
74109

75110
static SupabaseClient get client => Supabase.instance.client;
76111
}

β€Žlib/features/auth/data/services/google_auth_service.dartβ€Ž

Lines changed: 221 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,142 @@
11
import 'package:google_sign_in/google_sign_in.dart';
22
import 'package:supabase_flutter/supabase_flutter.dart';
33
import 'package:flutter/foundation.dart';
4+
import 'dart:js' as js;
5+
import 'dart:async';
6+
7+
// NOTE: To fix the redirect_uri_mismatch error:
8+
// 1. Go to Google Cloud Console: https://console.cloud.google.com/
9+
// 2. Navigate to APIs & Services > Credentials
10+
// 3. Find your OAuth 2.0 Client ID and click Edit
11+
// 4. Under "Authorized redirect URIs", add these URIs:
12+
// - http://localhost
13+
// - http://localhost:3000
14+
// - https://your-domain.com (for production)
15+
// - The exact URI printed in the debug console when attempting sign-in
16+
// 5. Click Save and wait a few minutes for changes to propagate
417

518
class GoogleAuthService {
6-
final GoogleSignIn _googleSignIn = GoogleSignIn(
7-
// The CLIENT_ID from the Google Cloud Console
8-
clientId: kIsWeb ? '735463188812-jk93h4mn6l9pmphkm9vvggg7q4egpaai.apps.googleusercontent.com' : null,
9-
scopes: ['email', 'profile'],
10-
);
19+
// Use a lazily initialized singleton pattern for GoogleSignIn
20+
static GoogleSignIn? _instance;
21+
22+
// Get the GoogleSignIn instance, creating it if it doesn't exist
23+
GoogleSignIn get _googleSignIn {
24+
_instance ??= GoogleSignIn(
25+
// The CLIENT_ID from the Google Cloud Console
26+
clientId: kIsWeb ? '735463188812-jk93h4mn6l9pmphkm9vvggg7q4egpaai.apps.googleusercontent.com' : null,
27+
scopes: ['email', 'profile', 'openid'],
28+
);
29+
return _instance!;
30+
}
1131

1232
final SupabaseClient _supabaseClient = Supabase.instance.client;
1333

34+
/// Check if we were redirected from OAuth and handle the session
35+
Future<bool> checkForRedirectSession() async {
36+
if (kIsWeb) {
37+
try {
38+
debugPrint('πŸ§ͺ Checking for OAuth redirect...');
39+
40+
// Get the current URL
41+
final url = Uri.base.toString();
42+
43+
// Check for auth-related parameters in the URL
44+
final hasAuthParams = url.contains('access_token=') ||
45+
url.contains('refresh_token=') ||
46+
url.contains('code=');
47+
48+
if (hasAuthParams) {
49+
debugPrint('πŸ”— Found URL with auth parameters: $url');
50+
51+
// Simple approach: Just let Supabase handle the auth parameters
52+
try {
53+
// 1. Let Supabase handle the URL parsing
54+
await _supabaseClient.auth.getSessionFromUrl(Uri.parse(url));
55+
56+
// 2. Clear the URL
57+
_cleanUrl();
58+
59+
// 3. Verify we have a session
60+
final hasSession = _supabaseClient.auth.currentSession != null;
61+
62+
if (hasSession) {
63+
final user = _supabaseClient.auth.currentUser;
64+
debugPrint('βœ… Successfully established auth session. User: ${user?.email}');
65+
return true;
66+
} else {
67+
debugPrint('❌ Failed to establish auth session despite valid URL parameters');
68+
return false;
69+
}
70+
} catch (e) {
71+
debugPrint('❌ Error handling auth URL: $e');
72+
return false;
73+
}
74+
}
75+
76+
// No auth parameters in URL, check if we have a session anyway
77+
final currentSession = _supabaseClient.auth.currentSession;
78+
if (currentSession != null) {
79+
debugPrint('βœ… No redirect detected, but found existing session for: ${currentSession.user.email}');
80+
return true;
81+
}
82+
83+
debugPrint('❌ No auth parameters in URL and no existing session');
84+
return false;
85+
} catch (e) {
86+
debugPrint('❌ Error in checkForRedirectSession: $e');
87+
return false;
88+
}
89+
}
90+
return false;
91+
}
92+
93+
/// Clean the URL by removing auth parameters
94+
void _cleanUrl() {
95+
if (kIsWeb) {
96+
try {
97+
// Get just the path portion of the current URL
98+
final cleanUrl = Uri.base.origin + Uri.base.path;
99+
js.context['history'].callMethod('replaceState', [null, '', cleanUrl]);
100+
debugPrint('🧹 URL cleaned: $cleanUrl');
101+
} catch (e) {
102+
debugPrint('❌ Error cleaning URL: $e');
103+
}
104+
}
105+
}
106+
107+
/// Utility method to extract access token from URL
108+
String _extractTokenFromUrl(String url) {
109+
final uri = Uri.parse(url);
110+
final accessToken = uri.queryParameters['access_token'];
111+
final refreshToken = uri.queryParameters['refresh_token'];
112+
113+
debugPrint('πŸ”‘ URL contains access_token: ${accessToken != null}');
114+
debugPrint('πŸ”‘ URL contains refresh_token: ${refreshToken != null}');
115+
116+
if (accessToken != null) {
117+
return accessToken;
118+
}
119+
120+
// For OAuth redirects, the token might be in the fragment
121+
final fragment = uri.fragment;
122+
if (fragment.isNotEmpty) {
123+
final params = Uri.splitQueryString(fragment);
124+
return params['access_token'] ?? '';
125+
}
126+
127+
return '';
128+
}
129+
14130
Future<AuthResponse?> signInWithGoogle() async {
15131
try {
16132
debugPrint('Starting Google Sign In process');
17133

18-
// Trigger Google Sign In flow
134+
// For Web platform, use a different approach since signIn() is being deprecated
135+
if (kIsWeb) {
136+
return await _webSignIn();
137+
}
138+
139+
// For mobile platforms, use the standard approach
19140
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
20141
if (googleUser == null) {
21142
debugPrint('User canceled the sign-in flow');
@@ -31,9 +152,12 @@ class GoogleAuthService {
31152
final idToken = googleAuth.idToken;
32153
final accessToken = googleAuth.accessToken;
33154

155+
debugPrint('ID Token: ${idToken != null ? 'Present' : 'Not present'}');
156+
debugPrint('Access Token: ${accessToken != null ? 'Present' : 'Not present'}');
157+
34158
if (idToken == null) {
35-
debugPrint('No ID Token found');
36-
throw Exception('No ID Token found');
159+
debugPrint('No ID Token found - this is likely due to browser cookie settings or missing gapi client');
160+
throw Exception('No ID Token found. Please ensure third-party cookies are enabled in your browser settings.');
37161
}
38162

39163
debugPrint('ID Token obtained, signing in with Supabase');
@@ -54,6 +178,53 @@ class GoogleAuthService {
54178
}
55179
}
56180

181+
/// Web-specific Google Sign In approach using Supabase's OAuth flow
182+
Future<AuthResponse?> _webSignIn() async {
183+
try {
184+
debugPrint('Using Web-specific Google Sign In approach');
185+
186+
// Get the current origin for our app to redirect back to
187+
String redirectUrl;
188+
final origin = Uri.base.origin;
189+
190+
// For production environments, we should use the actual deployed URL
191+
// This checks if we're running on localhost
192+
if (origin.contains('localhost')) {
193+
// Development environment
194+
redirectUrl = '$origin/';
195+
debugPrint('πŸ“± DEVELOPMENT: Using localhost redirect: $redirectUrl');
196+
} else {
197+
// Production environment - use explicit production URL
198+
// Replace this with your actual production URL
199+
final productionUrl = origin;
200+
redirectUrl = '$productionUrl/';
201+
debugPrint('🌎 PRODUCTION: Using production redirect: $redirectUrl');
202+
}
203+
204+
debugPrint('Will redirect back to: $redirectUrl');
205+
206+
// Use Supabase's built-in OAuth flow for Google
207+
final response = await _supabaseClient.auth.signInWithOAuth(
208+
OAuthProvider.google,
209+
// Specify where to redirect after Supabase completes the authentication
210+
redirectTo: redirectUrl,
211+
queryParams: {
212+
'access_type': 'offline',
213+
'prompt': 'consent',
214+
},
215+
);
216+
217+
debugPrint('Supabase OAuth initiated successfully');
218+
219+
// At this point, the user will be redirected to Google's login page,
220+
// and then back to your app via Supabase. The session will be handled by Supabase.
221+
return null;
222+
} catch (e) {
223+
debugPrint('Error in web sign-in: $e');
224+
rethrow;
225+
}
226+
}
227+
57228
Future<void> signOut() async {
58229
try {
59230
await _googleSignIn.signOut();
@@ -63,4 +234,46 @@ class GoogleAuthService {
63234
rethrow;
64235
}
65236
}
237+
238+
/// Utility method to diagnose authentication issues
239+
Future<void> debugAuthState() async {
240+
try {
241+
debugPrint('πŸ” AUTH DIAGNOSTICS πŸ”');
242+
243+
// Check current session
244+
final currentSession = _supabaseClient.auth.currentSession;
245+
debugPrint('πŸ‘€ Has current session: ${currentSession != null}');
246+
247+
if (currentSession != null) {
248+
debugPrint('πŸ“§ User email: ${currentSession.user.email ?? 'Not available'}');
249+
debugPrint('πŸ†” User ID: ${currentSession.user.id}');
250+
debugPrint('⏰ Session expires at: ${currentSession.expiresAt != null ? DateTime.fromMillisecondsSinceEpoch(currentSession.expiresAt! * 1000) : 'Unknown'}');
251+
252+
// Check for access and refresh tokens
253+
debugPrint('πŸ”‘ Access token present: ${currentSession.accessToken.isNotEmpty}');
254+
debugPrint('πŸ”„ Refresh token present: ${currentSession.refreshToken?.isNotEmpty ?? false}');
255+
}
256+
257+
// Check current user
258+
final currentUser = _supabaseClient.auth.currentUser;
259+
debugPrint('πŸ‘€ Has current user: ${currentUser != null}');
260+
261+
if (currentUser != null) {
262+
debugPrint('πŸ“§ User email: ${currentUser.email ?? 'Not available'}');
263+
debugPrint('πŸ†” User ID: ${currentUser.id}');
264+
debugPrint('βœ… Email confirmed: ${currentUser.emailConfirmedAt != null}');
265+
debugPrint('πŸ“± Phone confirmed: ${currentUser.phoneConfirmedAt != null}');
266+
}
267+
268+
// Check if these match
269+
if (currentSession != null && currentUser != null) {
270+
final sessionMatchesUser = currentSession.user.id == currentUser.id;
271+
debugPrint('πŸ”„ Session user matches current user: $sessionMatchesUser');
272+
}
273+
274+
debugPrint('πŸ” END AUTH DIAGNOSTICS πŸ”');
275+
} catch (e) {
276+
debugPrint('❌ Error in debugAuthState: $e');
277+
}
278+
}
66279
}

0 commit comments

Comments
Β (0)