// MIT licence import 'dart:async'; import 'dart:io'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:fluent_academy_app/blocs/initializable_cubit.dart'; import 'package:fluent_academy_app/widgets/user/auth/signin/sign_in_providers.dart'; import 'package:flutter/widgets.dart'; import 'package:firebase_auth/firebase_auth.dart' as auth; import 'package:google_sign_in/google_sign_in.dart'; import 'package:sign_in_with_apple/sign_in_with_apple.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'dart:math'; import 'dart:convert'; import 'dart:io'; import 'package:crypto/crypto.dart'; import 'package:sign_in_with_apple/sign_in_with_apple.dart'; enum SignInProviders { Google, Email, Twitter, Apple } Map signInLabels = { SignInProviders.Apple: "Apple", SignInProviders.Google: "Google", SignInProviders.Twitter: "Twitter", SignInProviders.Email: "Email", }; Map signInProviderIds = { "password": SignInProviders.Email, "twitter.com": SignInProviders.Twitter, "google.com": SignInProviders.Google, "apple": SignInProviders.Apple }; class AuthenticationBlocState with InitializableState { bool isAuthenticated = false; bool authenticating = false; bool hasConnection = false; bool checkingConnection = false; String? uid; @override AuthenticationBlocState copy() { return AuthenticationBlocState() ..authenticating = authenticating ..hasConnection = hasConnection ..isAuthenticated = isAuthenticated ..initializer = initializer; } } class AuthenticationBloc extends InitializableCubit { auth.User? get user => auth.FirebaseAuth.instance.currentUser; AuthenticationBloc() : super(AuthenticationBlocState()..isAuthenticated = false); Future initialize(BuildContext context) async { await Firebase.initializeApp(); auth.FirebaseAuth.instance.authStateChanges().listen((user) { emit(state.copy() ..uid = user?.uid ..isAuthenticated = user != null); }); if (user != null) { emit(state.copy() ..isAuthenticated = true ..uid = user!.uid); } else { try { emit(state.copy()..checkingConnection = true); final result = await InternetAddress.lookup('google.com'); if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) { emit(state.copy() ..checkingConnection = false ..hasConnection = true); } } on SocketException catch (_) { emit(state.copy() ..checkingConnection = false ..hasConnection = false); } } return super.initialize(context); } String _createNonce(int length) { final random = Random(); final charCodes = List.generate(length, (_) { late int codeUnit; switch (random.nextInt(3)) { case 0: codeUnit = random.nextInt(10) + 48; break; case 1: codeUnit = random.nextInt(26) + 65; break; case 2: codeUnit = random.nextInt(26) + 97; break; } return codeUnit; }); return String.fromCharCodes(charCodes); } Future _createAppleOAuthCred() async { final nonce = _createNonce(32); final nativeAppleCred = Platform.isIOS ? await SignInWithApple.getAppleIDCredential( scopes: [ AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName, ], nonce: sha256.convert(utf8.encode(nonce)).toString(), ) : await SignInWithApple.getAppleIDCredential( scopes: [ AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName, ], webAuthenticationOptions: WebAuthenticationOptions( redirectUri: Uri.parse( WEB_HANDLER), clientId: CLIENT_ID, ), nonce: sha256.convert(utf8.encode(nonce)).toString(), ); return new OAuthCredential( providerId: "apple.com", // MUST be "apple.com" signInMethod: "oauth", // MUST be "oauth" accessToken: nativeAppleCred .identityToken, // propagate Apple ID token to BOTH accessToken and idToken parameters idToken: nativeAppleCred.identityToken, rawNonce: nonce, ); } Future signInWithApple() async { final oauthCred = await _createAppleOAuthCred(); await FirebaseAuth.instance.signInWithCredential(oauthCred); return true; } Future signInWithGoogle() async { final GoogleSignIn _googleSignIn = GoogleSignIn(); GoogleSignInAccount? googleUser = await _googleSignIn.signIn(); if(googleUser == null) return false; GoogleSignInAuthentication googleAuth = await googleUser!.authentication; AuthCredential credential = GoogleAuthProvider.credential( idToken: googleAuth.idToken, accessToken: googleAuth.accessToken); await FirebaseAuth.instance.signInWithCredential(credential); return true; } Future signInWithProvider(SignInProviders provider) async { switch (provider) { case SignInProviders.Apple: return signInWithApple(); case SignInProviders.Google: return signInWithGoogle(); default: throw UnsupportedError("Provider $provider not yet supported"); } } Future logout() async { await auth.FirebaseAuth.instance.signOut(); emit(state.copy()..isAuthenticated = false); } }