import 'package:flutter/foundation.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../domain/auth_service.dart'; /// Custom URL scheme used to bring the user back into the app after an OAuth /// or magic-link round trip on native platforms. /// /// The scheme mirrors the platform bundle identifiers /// (`com.glpcompanion.glpcompanion`) and must be registered in the iOS/macOS /// `Info.plist`, the Android manifest intent filters, and the Windows runner /// so the operating system can route the redirect callback back to the app. /// [SupabaseAuth] (bundled with `supabase_flutter`) observes the deep link and /// exchanges the returned auth code for a session automatically. const kAuthCallbackRedirectUrl = 'com.glpcompanion.glpcompanion://login-callback/'; /// Resolves the redirect URL embedded in OAuth and magic-link flows. /// /// On web the browser returns to the current origin; native platforms use the /// registered deep-link scheme above. String resolveAuthRedirectUrl() { return kIsWeb ? Uri.base.origin : kAuthCallbackRedirectUrl; } /// [IAuthService] implementation backed by Supabase GoTrue. /// /// Only passwordless sign-in modes are modeled: federated OAuth (Google, /// Apple, Microsoft Identity via Supabase's `azure` provider) and email magic /// links. Direct username/password credentials are intentionally absent in /// line with the platform's security posture. class SupabaseAuthService implements IAuthService { /// Creates the service. Pass [client] in tests to inject a mock; production /// code resolves the global Supabase client lazily on first use so merely /// constructing the service (e.g. while building the login screen before /// `Supabase.initialize` completed, or inside widget tests) never throws. SupabaseAuthService({SupabaseClient? client}) : _clientOverride = client; final SupabaseClient? _clientOverride; SupabaseClient get _client => _clientOverride ?? Supabase.instance.client; @override Future signInWithGoogle() => _signInWithOAuth(OAuthProvider.google); @override Future signInWithApple() => _signInWithOAuth(OAuthProvider.apple); /// Microsoft Identity (Entra ID) is exposed by Supabase Auth as the /// `azure` OAuth provider. @override Future signInWithMicrosoft() => _signInWithOAuth(OAuthProvider.azure); @override Future signInWithMagicLink(String email) async { await _client.auth.signInWithOtp( email: email, emailRedirectTo: resolveAuthRedirectUrl(), ); } @override Future signOut() => _client.auth.signOut(); /// Emits the authenticated user's id on every auth transition and `null` /// once the user is signed out. @override Stream get authStateChanges => _client.auth.onAuthStateChange.map((state) => state.session?.user.id); Future _signInWithOAuth(OAuthProvider provider) async { final launched = await _client.auth.signInWithOAuth( provider, redirectTo: resolveAuthRedirectUrl(), ); if (!launched) { throw AuthException('Could not launch the ${provider.name} sign-in page.'); } } }