Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sign in with phone via Firebase Auth #1831

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions modules/auth/lib/signin/auth_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:ensemble/framework/stub/auth_context_manager.dart';
import 'package:ensemble/screen_controller.dart';
import 'package:ensemble/util/utils.dart';
import 'package:ensemble_auth/signin/google_auth_manager.dart';
import 'package:ensemble_auth/signin/sign_in_with_verification_code.dart';
import 'package:ensemble_auth/signin/sign_in_with_server_api_action.dart';
import 'package:ensemble_auth/signin/signin_utils.dart';
import 'package:ensemble_auth/signin/widget/sign_in_with_auth0.dart';
Expand Down Expand Up @@ -427,6 +428,103 @@ class AuthContextManagerImpl with Invokable implements AuthContextManager {
Future<void> signOut() {
return AuthManager().signOut(Utils.globalAppKey.currentContext!);
}

/// Sends a phone verification code to the given phone number.
@override
Future<void> sendVerificationCode({
required String provider,
required String method,
required String phoneNumber,
required Function(String verificationId, int? resendToken) onSuccess,
required Function(String error) onError,
}) async {
try {
final SignInWithVerificationCode _signInWithVerificationCode =
SignInWithVerificationCode();
await _signInWithVerificationCode.sendVerificationCode(
provider: provider,
method: method,
phoneNumber: phoneNumber,
onSuccess: (verificationId, resendToken) {
onSuccess(verificationId, resendToken);
},
onError: (e) {
onError(e.message ?? 'An error occurred while sending code');
},
);
} catch (e) {
onError('Unexpected error occurred: $e');
}
}

/// Verifies a phone code using [smsCode] and [verificationId].
@override
Future<AuthenticatedUser?> validateVerificationCode({
required String provider,
required String method,
required String smsCode,
required String verificationId,
required Function(AuthenticatedUser, String idToken) onSuccess,
required Function(String) onError,
required Function(String) onVerificationFailure,
}) async {
try {
final SignInWithVerificationCode _signInWithVerificationCode =
SignInWithVerificationCode();
final response =
await _signInWithVerificationCode.validateVerificationCode(
provider: provider,
method: method,
smsCode: smsCode,
verificationId: verificationId,
);

if (response != null) {
final user = response['user'];
final idToken = response['idToken'];

await AuthManager()._updateCurrentUser(
Utils.globalAppKey.currentContext!,
user,
);

onSuccess(user, idToken);
} else {
onError('Something went wrong, User not found');
}

return response?['user'];
} catch (e) {
onVerificationFailure('Error verifying phone code: ${e.toString()}');
return null;
}
}

/// Resends the verification code using [resendToken].
@override
Future<void> resendVerificationCode({
required String provider,
required String method,
required String phoneNumber,
required int resendToken,
required Function(String verificationId, int? resendToken) onSuccess,
required Function(String error) onError,
}) {
final SignInWithVerificationCode _signInWithVerificationCode =
SignInWithVerificationCode();
return _signInWithVerificationCode.resendVerificationCode(
provider: provider,
method: method,
phoneNumber: phoneNumber,
resendToken: resendToken,
onSuccess: (verificationId, newResendToken) {
onSuccess(verificationId, newResendToken);
},
onError: (e) {
onError('Error resending phone verification: ${e.message}');
},
);
}
}

class AuthToken {
Expand Down
151 changes: 151 additions & 0 deletions modules/auth/lib/signin/sign_in_with_verification_code.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import 'package:ensemble/framework/stub/auth_context_manager.dart';
import 'package:firebase_auth/firebase_auth.dart';

typedef VerificationSuccessCallback = void Function(
String verificationId, int? resendToken);
typedef VerificationErrorCallback = void Function(FirebaseAuthException error);

class SignInWithVerificationCode {
final FirebaseAuth _auth;

SignInWithVerificationCode({FirebaseAuth? firebaseAuth})
: _auth = firebaseAuth ?? FirebaseAuth.instance;

/// Sends a phone verification code to the specified [phoneNumber].
Future<void> sendVerificationCode({
required String provider,
required String method,
required String phoneNumber,
required VerificationSuccessCallback onSuccess,
required VerificationErrorCallback onError,
}) async {
if (provider == 'firebase') {
if (method != 'phone') {
throw ArgumentError('Unsupported method: $method');
}
await _sendVerificationCode(
phoneNumber: phoneNumber,
onSuccess: onSuccess,
onError: onError,
);
} else {
throw ArgumentError('Unsupported provider: $provider');
}
}

/// Verifies the phone code with the provided [smsCode] and [verificationId].
Future<Map<String, dynamic>?> validateVerificationCode({
required String provider,
required String method,
required String smsCode,
required String verificationId,
}) async {
try {
if (provider == 'firebase') {
if (method != 'phone') {
throw ArgumentError('Unsupported method: $method');
}
return await _validateFirebaseVerificationCode(
smsCode: smsCode,
verificationId: verificationId,
);
} else {
throw ArgumentError('Unsupported provider: $provider');
}
} catch (e) {
rethrow;
}
}

/// Resends a phone verification code using [resendToken].
Future<void> resendVerificationCode({
required String provider,
required String method,
required String phoneNumber,
required int resendToken,
required VerificationSuccessCallback onSuccess,
required VerificationErrorCallback onError,
}) async {
if (provider == 'firebase') {
if (method != 'phone') {
throw ArgumentError('Unsupported method: $method');
}
await _sendVerificationCode(
phoneNumber: phoneNumber,
forceResendingToken: resendToken,
onSuccess: onSuccess,
onError: onError,
);
} else {
throw ArgumentError('Unsupported provider: $provider');
}
}

/// Verify the phone number using Firebase's [verifyPhoneNumber].
Future<void> _sendVerificationCode({
required String phoneNumber,
int? forceResendingToken,
required VerificationSuccessCallback onSuccess,
required VerificationErrorCallback onError,
}) async {
try {
await _auth.verifyPhoneNumber(
phoneNumber: phoneNumber,
forceResendingToken: forceResendingToken,
verificationCompleted: (PhoneAuthCredential credential) {},
verificationFailed: (FirebaseAuthException e) {
_handleFirebaseError(e, onError);
},
codeSent: (String verificationId, int? resendToken) {
onSuccess(verificationId, resendToken);
},
codeAutoRetrievalTimeout: (String verificationId) {},
);
} catch (e) {
rethrow;
}
}

/// Validate the phone code using Firebase's [signInWithCredential].
Future<Map<String, dynamic>?> _validateFirebaseVerificationCode({
required String smsCode,
required String verificationId,
}) async {
try {
final PhoneAuthCredential credential = PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: smsCode,
);
final UserCredential userCredential =
await _auth.signInWithCredential(credential);

if (userCredential.user == null) {
throw FirebaseAuthException(
code: 'user-null',
message: 'User not found.',
);
}

final String? idToken = await userCredential.user!.getIdToken();

final authenticatedUser = AuthenticatedUser(
id: userCredential.user!.uid,
phoneNumber: userCredential.user!.phoneNumber ?? '',
provider: SignInProvider.firebase,
);

return {
'user': authenticatedUser,
'idToken': idToken,
};
} catch (e) {
rethrow;
}
}

/// Log the Firebase error and pass it to [onError].
void _handleFirebaseError(
FirebaseAuthException e, VerificationErrorCallback onError) {
onError(e);
}
}
1 change: 1 addition & 0 deletions modules/auth/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
mockito:

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
Expand Down
Loading
Loading