Line data Source code
1 : import 'dart:async'; 2 : import 'dart:convert'; 3 : 4 : import 'package:dartz/dartz.dart'; 5 : import 'package:firebase_auth/firebase_auth.dart'; 6 : import 'package:flutter/foundation.dart'; 7 : import 'package:flutter_firebase_auth_facade/flutter_firebase_auth_facade.dart'; 8 : import 'package:flutter_firebase_auth_facade/src/utils/constants.dart'; 9 : import 'package:google_sign_in/google_sign_in.dart'; 10 : import 'package:http/http.dart' as http; 11 : import 'package:sign_in_with_apple/sign_in_with_apple.dart' as apple; 12 : import 'package:url_launcher/url_launcher.dart'; 13 : import 'package:uni_links2/uni_links.dart'; 14 : 15 : class FirebaseAuthFacade implements IAuthFacade { 16 1 : FirebaseAuthFacade( 17 : this._firebaseAuth, 18 : this._googleSignIn, 19 : ); 20 : 21 : /// This is the clientID in GitHub OAuth app 22 : late String githubClientId; 23 : 24 : /// The secret generated in the github OAuth apps 25 : late String githubSecret; 26 : 27 : ///can be found in signin method of firebase, 28 : ///it's required because it's needed for appleSignin 29 : late String callbackUrl; 30 : 31 : ///Only for ios auth on Android 32 : late String appleClientId; 33 : 34 : /// DI of the firebaseAuth plugin 35 : final FirebaseAuth _firebaseAuth; 36 : 37 : /// DI of the GoogleSignIn plugin 38 : 39 : final GoogleSignIn _googleSignIn; 40 : 41 0 : void call({ 42 : required callbackUrl, 43 : githubClientId = '', 44 : githubSecret = '', 45 : appleClientId = '', 46 : }) { 47 0 : this.githubSecret = githubSecret; 48 0 : this.githubClientId = githubClientId; 49 0 : this.appleClientId = appleClientId; 50 0 : this.callbackUrl = callbackUrl; 51 : } 52 : 53 : /// Deep link stream subscription 54 : StreamSubscription? _streamSubscription; 55 : 56 : StreamController<Either<AuthFailure, Unit>>? githubloginStreamController; 57 : 58 : /// get the current signed in user, 59 : /// return null if unauthenticated 60 1 : @override 61 2 : User? getSignedInUser() => _firebaseAuth.currentUser; 62 : 63 : /// Checking if user is anonym or not 64 1 : @override 65 3 : bool isAnonymous() => _firebaseAuth.currentUser!.isAnonymous; 66 : 67 : ///Register with Email and password 68 : /// return [Future<Either<AuthFailure, Unit>>] 69 : @override 70 1 : Future<Either<AuthFailure, Unit>> registerWithEmailAndPassword( 71 : {required String email, required String password}) async { 72 : try { 73 1 : final previousUser = getSignedInUser(); 74 : if (previousUser != null) { 75 : final authCreds = 76 0 : EmailAuthProvider.credential(email: email, password: password); 77 0 : await previousUser.linkWithCredential(authCreds); 78 : } else { 79 3 : await _firebaseAuth.createUserWithEmailAndPassword( 80 : email: email, 81 : password: password, 82 : ); 83 : } 84 : return const Right(unit); 85 1 : } on FirebaseAuthException catch (e) { 86 2 : if (e.code == kFirebaseCodeEmailAlreadyInUse) { 87 : return const Left(AuthFailure.emailAlreadyInUse()); 88 2 : } else if (e.code == kFirebaseCodeInvalidEmail) { 89 : return const Left(AuthFailure.invalidEmail()); 90 2 : } else if (e.code == kFirebaseCodeWeakPassword) { 91 : return const Left(AuthFailure.weakPassword()); 92 2 : } else if (e.code == kFirebaseCodeOperationNotAllowed) { 93 : return const Left(AuthFailure.operationNotAllowed()); 94 0 : } else if (e.code == kFirebasecodeInvalidCredentials) { 95 : return const Left(AuthFailure.invalidCredentials()); 96 : } else { 97 : return const Left(AuthFailure.serverError()); 98 : } 99 : } 100 : } 101 : 102 : /// Send an email to reset the password 103 0 : @override 104 : Future<void> resetPassword({required String email}) => 105 0 : _firebaseAuth.sendPasswordResetEmail(email: email); 106 : 107 : /// Login out 108 0 : @override 109 : Future<void> signedOut() => 110 0 : Future.wait([_googleSignIn.signOut(), _firebaseAuth.signOut()]); 111 : 112 : /// Sign in as anonymous 113 : /// return [Future<Either<AuthFailure, Unit>>] 114 : @override 115 0 : Future<Either<AuthFailure, Unit>> signInWithAnon() async { 116 : try { 117 0 : await _firebaseAuth.signInAnonymously(); 118 0 : return right(unit); 119 0 : } on FirebaseAuthException catch (e) { 120 0 : if (e.code == kFirebaseCodeOperationNotAllowed) { 121 : return const Left(AuthFailure.operationNotAllowed()); 122 : } 123 : return const Left(AuthFailure.serverError()); 124 : } 125 : } 126 : 127 : /// Sign in with Apple 128 : /// return [Future<Either<AuthFailure, Unit>>] 129 : @override 130 0 : Future<Either<AuthFailure, Unit>> signInWithApple() async { 131 : try { 132 0 : final oAuthProvider = OAuthProvider(appleProvider); 133 : if (kIsWeb) { 134 0 : final result = await _firebaseAuth.signInWithPopup(oAuthProvider); 135 0 : var credential = result.credential; 136 0 : await _firebaseAuth.signInWithCredential(credential!); 137 0 : return right(unit); 138 : } else { 139 0 : final credential = await apple.SignInWithApple.getAppleIDCredential( 140 0 : scopes: [ 141 : apple.AppleIDAuthorizationScopes.email, 142 : apple.AppleIDAuthorizationScopes.fullName, 143 : ], 144 0 : webAuthenticationOptions: apple.WebAuthenticationOptions( 145 0 : clientId: appleClientId, 146 0 : redirectUri: Uri.parse(callbackUrl), 147 : ), 148 : ); 149 0 : final newCredentials = oAuthProvider.credential( 150 0 : idToken: credential.identityToken, 151 0 : accessToken: credential.authorizationCode, 152 : ); 153 0 : final previousUser = getSignedInUser(); 154 : if (previousUser != null) { 155 0 : await previousUser.linkWithCredential(newCredentials); 156 : } else { 157 0 : await _firebaseAuth.signInWithCredential(newCredentials); 158 : } 159 0 : return right(unit); 160 : } 161 0 : } on apple.SignInWithAppleException catch (e) { 162 0 : print(e); 163 0 : if (e is apple.SignInWithAppleNotSupportedException) { 164 : return const Left(AuthFailure.wrongIosVersion()); 165 : } 166 : return const Left(AuthFailure.serverError()); 167 0 : } on FirebaseAuthException catch (e) { 168 0 : if (e.code.contains('credential-already-in-use')) { 169 : return const Left(AuthFailure.emailAlreadyInUse()); 170 : } 171 : return const Left(AuthFailure.serverError()); 172 : } 173 : } 174 : 175 : /// Sign in with user and password 176 : /// return [Future<Either<AuthFailure, Unit>>] 177 : @override 178 0 : Future<Either<AuthFailure, Unit>> signInWithEmailAndPassword( 179 : {required String email, required String password}) async { 180 : try { 181 0 : await _firebaseAuth.signInWithEmailAndPassword( 182 : email: email, 183 : password: password, 184 : ); 185 : return const Right(unit); 186 0 : } on FirebaseAuthException catch (e) { 187 0 : if (e.code == 'user-not-found' || e.code == 'wrong-password') { 188 : return const Left(AuthFailure.invalidEmailAndPasswordCombination()); 189 : } else { 190 : return const Left(AuthFailure.serverError()); 191 : } 192 : } 193 : } 194 : 195 : /// Sign in with GitHub 196 : /// return [Future<Either<AuthFailure, Unit>>] 197 : @override 198 0 : Future<Either<AuthFailure, Unit>> signInWithGitHub() async { 199 : try { 200 0 : githubloginStreamController = 201 0 : StreamController<Either<AuthFailure, Unit>>(); 202 0 : var provider = GithubAuthProvider() 203 0 : ..addScope('repo') 204 0 : ..setCustomParameters({'allow_signup': false}); 205 : 206 : if (kIsWeb) { 207 0 : final result = await _firebaseAuth.signInWithPopup(provider); 208 0 : var credential = result.credential; 209 0 : await _firebaseAuth.signInWithCredential(credential!); 210 0 : return right(unit); 211 : } else { 212 0 : final url = '$githubAuthUrl' 213 0 : '$githubClientId&$githubScope'; 214 0 : await _streamSubscription?.cancel(); 215 0 : _streamSubscription = linkStream.listen((deeplink) async { 216 0 : final code = _getCodeFromGitHubLink(deeplink!); 217 0 : await _loginWithGitHub(code); 218 : }); 219 0 : if (await canLaunch(url)) { 220 0 : print('Launchunbg url'); 221 0 : await launch(url); 222 : } else { 223 0 : return left(const AuthFailure.serverError()); 224 : } 225 : final failureOrSuccess = 226 0 : await githubloginStreamController!.stream.first; 227 0 : await githubloginStreamController!.close(); 228 0 : print(failureOrSuccess); 229 : return failureOrSuccess; 230 : } 231 : } catch (e) { 232 0 : print(e); 233 0 : return left(const AuthFailure.serverError()); 234 : } 235 : } 236 : 237 : /// Sign in with google 238 : /// return [Future<Either<AuthFailure, Unit>>] 239 : @override 240 0 : Future<Either<AuthFailure, Unit>> signInWithGoogle() async { 241 : try { 242 0 : final googleUser = await _googleSignIn.signIn(); 243 : if (googleUser == null) { 244 : return const Left(AuthFailure.cancelledByUser()); 245 : } 246 0 : final googleAuthentication = await googleUser.authentication; 247 0 : final authCredential = GoogleAuthProvider.credential( 248 0 : idToken: googleAuthentication.idToken, 249 0 : accessToken: googleAuthentication.accessToken); 250 0 : final previousUser = getSignedInUser(); 251 : if (previousUser != null) { 252 0 : await previousUser.linkWithCredential(authCredential); 253 : } else { 254 0 : await _firebaseAuth.signInWithCredential(authCredential); 255 : } 256 0 : return right(unit); 257 0 : } on FirebaseAuthException catch (e) { 258 0 : print(e); 259 0 : if (e.code.contains('credential-already-in-use')) { 260 : return const Left(AuthFailure.emailAlreadyInUse()); 261 : } 262 : return const Left(AuthFailure.serverError()); 263 : } 264 : } 265 : 266 : //// Stream of the user current state 267 : @override 268 0 : Stream<User?> userState() async* { 269 0 : yield* _firebaseAuth.authStateChanges(); 270 : } 271 : 272 0 : String _getCodeFromGitHubLink(String link) => 273 0 : link.substring(link.indexOf(RegExp('code=')) + 5); 274 : 275 0 : Future<void> _loginWithGitHub(String code) async { 276 : try { 277 0 : final response = await http.post( 278 0 : Uri.parse('https://github.com/login/oauth/access_token'), 279 0 : headers: { 280 : 'Content-Type': 'application/json', 281 : 'Accept': 'application/json' 282 : }, 283 0 : body: '{"client_id":"$githubClientId",' 284 0 : '"client_secret":"$githubSecret","code":"$code"}'); 285 0 : final body = response.body; 286 0 : final Map<String, dynamic> bodymap = json.decode(body); 287 0 : final token = bodymap['access_token']; 288 0 : final AuthCredential credential = GithubAuthProvider.credential( 289 : token, 290 : ); 291 0 : await _firebaseAuth.signInWithCredential(credential); 292 0 : print(getSignedInUser()); 293 0 : githubloginStreamController!.add(right(unit)); 294 0 : await _streamSubscription?.cancel(); 295 0 : } on FirebaseAuthException catch (e) { 296 0 : if (e.code == kFirebaseCodeEmailAlreadyInUse) { 297 0 : githubloginStreamController! 298 0 : .add(const Left(AuthFailure.emailAlreadyInUse())); 299 0 : } else if (e.code == kFirebaseCodeInvalidEmail) { 300 0 : githubloginStreamController! 301 0 : .add(const Left(AuthFailure.invalidEmail())); 302 0 : } else if (e.code == kFirebaseCodeWeakPassword) { 303 0 : githubloginStreamController! 304 0 : .add(const Left(AuthFailure.weakPassword())); 305 0 : } else if (e.code == kFirebaseCodeOperationNotAllowed) { 306 0 : githubloginStreamController! 307 0 : .add(const Left(AuthFailure.operationNotAllowed())); 308 0 : } else if (e.code == kFirebasecodeInvalidCredentials) { 309 0 : githubloginStreamController! 310 0 : .add(const Left(AuthFailure.invalidCredentials())); 311 : } else { 312 0 : githubloginStreamController!.add(const Left(AuthFailure.serverError())); 313 : } 314 : } 315 : } 316 : }