diff --git a/packages/wyatt_authentication_bloc/example/android/app/google-services.json b/packages/wyatt_authentication_bloc/example/android/app/google-services.json
index 93ee728e..cd75a682 100644
--- a/packages/wyatt_authentication_bloc/example/android/app/google-services.json
+++ b/packages/wyatt_authentication_bloc/example/android/app/google-services.json
@@ -42,6 +42,42 @@
}
}
},
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:136771801992:android:8482c9b90bc29de697203d",
+ "android_client_info": {
+ "package_name": "com.example.crud_bloc_example"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": [
+ {
+ "client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
+ "client_type": 3
+ },
+ {
+ "client_id": "136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com",
+ "client_type": 2,
+ "ios_info": {
+ "bundle_id": "com.example.example"
+ }
+ }
+ ]
+ }
+ }
+ },
{
"client_info": {
"mobilesdk_app_id": "1:136771801992:android:d20e0361057e815197203d",
diff --git a/packages/wyatt_authentication_bloc/example/lib/app/app.dart b/packages/wyatt_authentication_bloc/example/lib/app/app.dart
index 1c87d2ed..2886a69d 100644
--- a/packages/wyatt_authentication_bloc/example/lib/app/app.dart
+++ b/packages/wyatt_authentication_bloc/example/lib/app/app.dart
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+import 'dart:developer';
+
import 'package:authentication_bloc_example/constants.dart';
import 'package:authentication_bloc_example/home/home_page.dart';
import 'package:authentication_bloc_example/login/login_page.dart';
@@ -55,14 +57,32 @@ class App extends StatelessWidget {
if (user.isNotEmpty && !user.isAnonymous) {
// Check if user is register in Firesore.
DocumentSnapshot firestoreUser = await FirebaseFirestore.instance
- .collection('users')
+ .collection(firestoreCollectionUsers)
.doc(user.uid)
.get();
- return {
- 'user':
- UserFirestore.fromMap(firestoreUser.data() as Map),
- ...firestoreUser.data() as Map? ?? {}
- };
+
+ if (!firestoreUser.exists) {
+ // Register user in Firestore when sign in with social account.
+ final uid = user.uid;
+ final u = {'uid': uid, 'email': user.email};
+ await FirebaseFirestore.instance
+ .collection(firestoreCollectionUsers)
+ .doc(uid)
+ .set(u);
+ return {
+ 'user': UserFirestore(
+ uid: uid,
+ email: user.email ?? '',
+ name: user.displayName ?? '',
+ phone: user.phoneNumber ?? ''),
+ };
+ } else {
+ return {
+ 'user': UserFirestore.fromMap(
+ firestoreUser.data() as Map),
+ ...firestoreUser.data() as Map? ?? {}
+ };
+ }
} else {
return {};
}
@@ -73,7 +93,11 @@ class App extends StatelessWidget {
if (uid != null) {
final data = state.data.toMap();
final user = {'uid': uid, 'email': state.email.value, ...data};
- await FirebaseFirestore.instance.collection('users').doc(uid).set(user);
+ log('onSignUpSuccess: $user');
+ await FirebaseFirestore.instance
+ .collection(firestoreCollectionUsers)
+ .doc(uid)
+ .set(user);
}
}
diff --git a/packages/wyatt_authentication_bloc/example/lib/constants.dart b/packages/wyatt_authentication_bloc/example/lib/constants.dart
index 953b8602..03422520 100644
--- a/packages/wyatt_authentication_bloc/example/lib/constants.dart
+++ b/packages/wyatt_authentication_bloc/example/lib/constants.dart
@@ -19,4 +19,6 @@ const String formFieldPhone = 'phone';
const String formFieldPro = 'isPro';
const String formFieldConfirmedPassword = 'confirmedPassword';
const String formFieldSiren = 'siren';
-const String formFieldIban = 'iban';
\ No newline at end of file
+const String formFieldIban = 'iban';
+
+const String firestoreCollectionUsers = 'authentication_bloc_users';
\ No newline at end of file
diff --git a/packages/wyatt_authentication_bloc/example/lib/login/widgets/login_form.dart b/packages/wyatt_authentication_bloc/example/lib/login/widgets/login_form.dart
index e552f8d6..e5c9f55d 100644
--- a/packages/wyatt_authentication_bloc/example/lib/login/widgets/login_form.dart
+++ b/packages/wyatt_authentication_bloc/example/lib/login/widgets/login_form.dart
@@ -102,6 +102,24 @@ class _LoginWithPasswordButton extends StatelessWidget {
}
}
+class _LoginWithGoogleButton extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return BlocBuilder(
+ buildWhen: (previous, current) => previous.status != current.status,
+ builder: (context, state) {
+ return state.status.isSubmissionInProgress
+ ? const CircularProgressIndicator()
+ : ElevatedButton(
+ onPressed: () =>
+ context.read().signInWithGoogle(),
+ child: const Text('LOGIN GOOGLE'),
+ );
+ },
+ );
+ }
+}
+
class _SignUpButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
@@ -182,6 +200,8 @@ class LoginForm extends StatelessWidget {
const SizedBox(height: 8),
_LoginAnonButton(),
const SizedBox(height: 8),
+ _LoginWithGoogleButton(),
+ const SizedBox(height: 8),
_SignUpButton(),
const SizedBox(height: 8),
_SignUpAsProButton(),
diff --git a/packages/wyatt_authentication_bloc/example/lib/sign_up/widgets/sign_up_form.dart b/packages/wyatt_authentication_bloc/example/lib/sign_up/widgets/sign_up_form.dart
index 6e48d10a..a44c0d0f 100644
--- a/packages/wyatt_authentication_bloc/example/lib/sign_up/widgets/sign_up_form.dart
+++ b/packages/wyatt_authentication_bloc/example/lib/sign_up/widgets/sign_up_form.dart
@@ -255,7 +255,8 @@ class _DebugButton extends StatelessWidget {
builder: (context, state) {
return ElevatedButton(
onPressed: () {
- log(state.toString());
+ // log(state.toString());
+ log(state.data.toMap().toString());
},
child: const Text('DEBUG'),
);
diff --git a/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_firebase.dart b/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_firebase.dart
index 9a842cbc..eade1e10 100644
--- a/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_firebase.dart
+++ b/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_firebase.dart
@@ -106,10 +106,12 @@ class SignInAnonymouslyFailureFirebase
}
}
-class SignInWithGoogleFailureFirebase extends SignInWithGoogleFailureInterface {
- SignInWithGoogleFailureFirebase([String? code, String? message])
+class SignInWithCredentialFailureFirebase
+ extends SignInWithCredentialFailureInterface {
+ SignInWithCredentialFailureFirebase([String? code, String? message])
: super(code ?? 'unknown', message ?? 'An unknown error occurred.');
- SignInWithGoogleFailureFirebase.fromCode(String code) : super.fromCode(code) {
+ SignInWithCredentialFailureFirebase.fromCode(String code)
+ : super.fromCode(code) {
switch (code) {
case 'account-exists-with-different-credential':
message = 'Account exists with different credentials.';
@@ -143,6 +145,38 @@ class SignInWithGoogleFailureFirebase extends SignInWithGoogleFailureInterface {
}
}
+class SignInWithGoogleFailureFirebase
+ extends SignInWithCredentialFailureFirebase
+ implements SignInWithGoogleFailureInterface {
+ SignInWithGoogleFailureFirebase([String? code, String? message])
+ : super(code, message);
+ SignInWithGoogleFailureFirebase.fromCode(String code) : super.fromCode(code);
+}
+
+class SignInWithFacebookFailureFirebase
+ extends SignInWithCredentialFailureFirebase
+ implements SignInWithFacebookFailureInterface {
+ SignInWithFacebookFailureFirebase([String? code, String? message])
+ : super(code, message);
+ SignInWithFacebookFailureFirebase.fromCode(String code)
+ : super.fromCode(code);
+}
+
+class SignInWithAppleFailureFirebase extends SignInWithCredentialFailureFirebase
+ implements SignInWithAppleFailureInterface {
+ SignInWithAppleFailureFirebase([String? code, String? message])
+ : super(code, message);
+ SignInWithAppleFailureFirebase.fromCode(String code) : super.fromCode(code);
+}
+
+class SignInWithTwitterFailureFirebase extends SignInWithCredentialFailureFirebase
+ implements SignInWithAppleFailureInterface {
+ SignInWithTwitterFailureFirebase([String? code, String? message])
+ : super(code, message);
+ SignInWithTwitterFailureFirebase.fromCode(String code) : super.fromCode(code);
+}
+
+
class SignInWithEmailLinkFailureFirebase
extends SignInWithEmailLinkFailureInterface {
SignInWithEmailLinkFailureFirebase([String? code, String? message])
diff --git a/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_interface.dart b/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_interface.dart
index bb3e9b44..4a1d26b8 100644
--- a/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_interface.dart
+++ b/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_interface.dart
@@ -64,6 +64,20 @@ abstract class FetchSignInMethodsForEmailFailureInterface
: super.fromCode(code);
}
+/// {@template sign_in_with_credential_failure}
+/// Thrown during the sign in process if a failure occurs.
+/// {@endtemplate}
+abstract class SignInWithCredentialFailureInterface
+ extends AuthenticationFailureInterface {
+ /// {@macro sign_in_with_credential_failure}
+ SignInWithCredentialFailureInterface(String code, String message)
+ : super(code, message);
+
+ /// {@macro sign_in_with_credential_failure}
+ SignInWithCredentialFailureInterface.fromCode(String code)
+ : super.fromCode(code);
+}
+
/// {@template sign_in_anonymously_failure}
/// Thrown during the sign in process if a failure occurs.
/// {@endtemplate}
@@ -91,6 +105,47 @@ abstract class SignInWithGoogleFailureInterface
SignInWithGoogleFailureInterface.fromCode(String code) : super.fromCode(code);
}
+/// {@template sign_in_with_facebook_failure}
+/// Thrown during the sign in process if a failure occurs.
+/// {@endtemplate}
+abstract class SignInWithFacebookFailureInterface
+ extends AuthenticationFailureInterface {
+ /// {@macro sign_in_with_facebook_failure}
+ SignInWithFacebookFailureInterface(String code, String message)
+ : super(code, message);
+
+ /// {@macro sign_in_with_facebook_failure}
+ SignInWithFacebookFailureInterface.fromCode(String code)
+ : super.fromCode(code);
+}
+
+/// {@template sign_in_with_apple_failure}
+/// Thrown during the sign in process if a failure occurs.
+/// {@endtemplate}
+abstract class SignInWithAppleFailureInterface
+ extends AuthenticationFailureInterface {
+ /// {@macro sign_in_with_apple_failure}
+ SignInWithAppleFailureInterface(String code, String message)
+ : super(code, message);
+
+ /// {@macro sign_in_with_apple_failure}
+ SignInWithAppleFailureInterface.fromCode(String code) : super.fromCode(code);
+}
+
+/// {@template sign_in_with_twitter_failure}
+/// Thrown during the sign in process if a failure occurs.
+/// {@endtemplate}
+abstract class SignInWithTwitterFailureInterface
+ extends AuthenticationFailureInterface {
+ /// {@macro sign_in_with_twitter_failure}
+ SignInWithTwitterFailureInterface(String code, String message)
+ : super(code, message);
+
+ /// {@macro sign_in_with_twitter_failure}
+ SignInWithTwitterFailureInterface.fromCode(String code)
+ : super.fromCode(code);
+}
+
/// {@template sign_in_with_email_link_failure}
/// Thrown during the sign in process if a failure occurs.
/// {@endtemplate}
diff --git a/packages/wyatt_authentication_bloc/lib/src/models/user/user_firebase.dart b/packages/wyatt_authentication_bloc/lib/src/models/user/user_firebase.dart
index df1b78bc..53908ab2 100644
--- a/packages/wyatt_authentication_bloc/lib/src/models/user/user_firebase.dart
+++ b/packages/wyatt_authentication_bloc/lib/src/models/user/user_firebase.dart
@@ -64,6 +64,9 @@ class UserFirebase implements UserInterface {
@override
String get uid => _user?.uid ?? '';
+ @override
+ String? get providerId => _user?.providerData.first.providerId;
+
@override
bool? get isNewUser {
if (_user?.metadata.lastSignInTime == null ||
diff --git a/packages/wyatt_authentication_bloc/lib/src/models/user/user_interface.dart b/packages/wyatt_authentication_bloc/lib/src/models/user/user_interface.dart
index 5bbcc3f7..43b926bf 100644
--- a/packages/wyatt_authentication_bloc/lib/src/models/user/user_interface.dart
+++ b/packages/wyatt_authentication_bloc/lib/src/models/user/user_interface.dart
@@ -71,6 +71,9 @@ abstract class UserInterface {
/// The user's unique ID.
String get uid;
+ /// The provider ID for the user.
+ String? get providerId;
+
/// Whether the user account has been recently created.
bool? get isNewUser;
diff --git a/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_firebase.dart b/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_firebase.dart
index 20f6e2e4..f2838122 100644
--- a/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_firebase.dart
+++ b/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_firebase.dart
@@ -15,19 +15,28 @@
// along with this program. If not, see .
import 'package:firebase_auth/firebase_auth.dart';
+import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
+import 'package:google_sign_in/google_sign_in.dart';
+import 'package:sign_in_with_apple/sign_in_with_apple.dart';
+import 'package:twitter_login/twitter_login.dart';
import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_firebase.dart';
import 'package:wyatt_authentication_bloc/src/models/user/user_firebase.dart';
import 'package:wyatt_authentication_bloc/src/models/user/user_interface.dart';
import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart';
+import 'package:wyatt_authentication_bloc/src/utils/cryptography.dart';
class AuthenticationRepositoryFirebase
implements AuthenticationRepositoryInterface {
final FirebaseAuth _firebaseAuth;
+ final TwitterLogin? _twitterLogin;
UserFirebase _userCache = const UserFirebase.empty();
- AuthenticationRepositoryFirebase({FirebaseAuth? firebaseAuth})
- : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance;
+ AuthenticationRepositoryFirebase({
+ FirebaseAuth? firebaseAuth,
+ TwitterLogin? twitterLogin,
+ }) : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance,
+ _twitterLogin = twitterLogin;
@override
Stream get user {
@@ -99,9 +108,106 @@ class AuthenticationRepositoryFirebase
}
@override
- Future signInWithGoogle() {
- // TODO(hpcl): implement signInWithGoogle
- throw UnimplementedError();
+ Future signInWithGoogle() async {
+ // Trigger the authentication flow
+ final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
+
+ // Obtain the auth details from the request
+ final GoogleSignInAuthentication? googleAuth =
+ await googleUser?.authentication;
+
+ // Create a new credential
+ final credential = GoogleAuthProvider.credential(
+ accessToken: googleAuth?.accessToken,
+ idToken: googleAuth?.idToken,
+ );
+
+ try {
+ await _firebaseAuth.signInWithCredential(credential);
+ } on FirebaseAuthException catch (e) {
+ throw SignInWithGoogleFailureFirebase.fromCode(e.code);
+ } catch (_) {
+ throw SignInWithGoogleFailureFirebase();
+ }
+ }
+
+ @override
+ Future signInWithFacebook() async {
+ // Trigger the sign-in flow
+ final LoginResult loginResult = await FacebookAuth.instance.login();
+
+ // Create a credential from the access token
+ final OAuthCredential credential =
+ FacebookAuthProvider.credential(loginResult.accessToken?.token ?? '');
+
+ try {
+ await _firebaseAuth.signInWithCredential(credential);
+ } on FirebaseAuthException catch (e) {
+ throw SignInWithFacebookFailureFirebase.fromCode(e.code);
+ } catch (_) {
+ throw SignInWithFacebookFailureFirebase();
+ }
+ }
+
+ @override
+ Future signInWithApple() async {
+ // To prevent replay attacks with the credential returned from Apple, we
+ // include a nonce in the credential request. When signing in with
+ // Firebase, the nonce in the id token returned by Apple, is expected to
+ // match the sha256 hash of `rawNonce`.
+ final rawNonce = Cryptography.generateNonce();
+ final nonce = Cryptography.sha256ofString(rawNonce);
+
+ // Request credential for the currently signed in Apple account.
+ final appleCredential = await SignInWithApple.getAppleIDCredential(
+ scopes: [
+ AppleIDAuthorizationScopes.email,
+ AppleIDAuthorizationScopes.fullName,
+ ],
+ nonce: nonce,
+ );
+
+ // Create an `OAuthCredential` from the credential returned by Apple.
+ final credential = OAuthProvider('apple.com').credential(
+ idToken: appleCredential.identityToken,
+ rawNonce: rawNonce,
+ );
+
+ // Sign in the user with Firebase. If the nonce we generated earlier does
+ // not match the nonce in `appleCredential.identityToken`,
+ // sign in will fail.
+ try {
+ await _firebaseAuth.signInWithCredential(credential);
+ } on FirebaseAuthException catch (e) {
+ throw SignInWithAppleFailureFirebase.fromCode(e.code);
+ } catch (_) {
+ throw SignInWithAppleFailureFirebase();
+ }
+ }
+
+ @override
+ Future signInWithTwitter() async {
+ final twitterLogin = _twitterLogin;
+ if (twitterLogin == null) {
+ throw SignInWithTwitterFailureFirebase();
+ }
+
+ // Trigger the sign-in flow
+ final authResult = await twitterLogin.login();
+
+ // Create a credential from the access token
+ final credential = TwitterAuthProvider.credential(
+ accessToken: authResult.authToken!,
+ secret: authResult.authTokenSecret!,
+ );
+
+ try {
+ await _firebaseAuth.signInWithCredential(credential);
+ } on FirebaseAuthException catch (e) {
+ throw SignInWithCredentialFailureFirebase.fromCode(e.code);
+ } catch (_) {
+ throw SignInWithCredentialFailureFirebase();
+ }
}
@override
diff --git a/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_interface.dart b/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_interface.dart
index ecdcd03b..4a2dc295 100644
--- a/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_interface.dart
+++ b/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_interface.dart
@@ -61,6 +61,21 @@ abstract class AuthenticationRepositoryInterface {
/// Throws a [SignInWithGoogleFailureInterface] if an exception occurs.
Future signInWithGoogle();
+ /// Starts the Sign In with Facebook Flow.
+ ///
+ /// Throws a [SignInWithFacebookFailureInterface] if an exception occurs.
+ Future signInWithFacebook();
+
+ /// Starts the Sign In with Apple Flow.
+ ///
+ /// Throws a [SignInWithAppleFailureInterface] if an exception occurs.
+ Future signInWithApple();
+
+ /// Starts the Sign In with Twitter Flow.
+ ///
+ /// Throws a [SignInWithTwitterFailureInterface] if an exception occurs.
+ Future signInWithTwitter();
+
/// Signs in using an email address and email sign-in link.
///
/// Throws a [SignInWithEmailLinkFailureInterface] if an exception occurs.
diff --git a/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_cubit.dart b/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_cubit.dart
index b357d347..f90b0ca5 100644
--- a/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_cubit.dart
+++ b/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_cubit.dart
@@ -76,6 +76,28 @@ class SignInCubit extends Cubit {
}
}
+ Future signInWithGoogle() async {
+ if (state.status.isSubmissionInProgress) {
+ return;
+ }
+
+ emit(state.copyWith(status: FormStatus.submissionInProgress));
+ try {
+ await _authenticationRepository.signInWithGoogle();
+ _authenticationCubit.start();
+ emit(state.copyWith(status: FormStatus.submissionSuccess));
+ } on SignInWithGoogleFailureInterface catch (e) {
+ emit(
+ state.copyWith(
+ errorMessage: e.message,
+ status: FormStatus.submissionFailure,
+ ),
+ );
+ } catch (_) {
+ emit(state.copyWith(status: FormStatus.submissionFailure));
+ }
+ }
+
Future signInWithEmailAndPassword() async {
if (!state.status.isValidated) return;
emit(state.copyWith(status: FormStatus.submissionInProgress));
diff --git a/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_state.dart b/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_state.dart
index f6cfb863..f59c536c 100644
--- a/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_state.dart
+++ b/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_state.dart
@@ -45,4 +45,14 @@ class SignInState extends Equatable {
@override
List