glp-companion/lib/features/auth/data/supabase_auth_service.dart

78 lines
2.9 KiB
Dart

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.
SupabaseAuthService({SupabaseClient? client})
: _client = client ?? Supabase.instance.client;
final SupabaseClient _client;
@override
Future<void> signInWithGoogle() => _signInWithOAuth(OAuthProvider.google);
@override
Future<void> signInWithApple() => _signInWithOAuth(OAuthProvider.apple);
/// Microsoft Identity (Entra ID) is exposed by Supabase Auth as the
/// `azure` OAuth provider.
@override
Future<void> signInWithMicrosoft() => _signInWithOAuth(OAuthProvider.azure);
@override
Future<void> signInWithMagicLink(String email) async {
await _client.auth.signInWithOtp(
email: email,
emailRedirectTo: resolveAuthRedirectUrl(),
);
}
@override
Future<void> signOut() => _client.auth.signOut();
/// Emits the authenticated user's id on every auth transition and `null`
/// once the user is signed out.
@override
Stream<String?> get authStateChanges =>
_client.auth.onAuthStateChange.map((state) => state.session?.user.id);
Future<void> _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.');
}
}
}