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

feat(auth, web): add phone mfa #9031

Merged
merged 8 commits into from
Jul 18, 2022
Merged
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
1 change: 0 additions & 1 deletion packages/firebase_auth/firebase_auth/example/lib/auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'dart:io';

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_auth_example/config.dart';
import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'dart:developer';

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart';
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ class FirebaseAuth extends FirebasePluginPlatform {
// If we add a recaptcha to the page by creating a new instance, we must
// also clear that instance before proceeding.
bool mustClear = verifier == null;
verifier ??= RecaptchaVerifier();
verifier ??= RecaptchaVerifier(auth: _delegate);
final result =
await _delegate.signInWithPhoneNumber(phoneNumber, verifier.delegate);
if (mustClear) {
Expand Down
13 changes: 12 additions & 1 deletion packages/firebase_auth/firebase_auth/lib/src/multi_factor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class MultiFactor {
///
/// [displayName] can be used to provide a display name for the second factor.
Future<void> enroll(
MultiFactorAssertion assertion, {
MultiFactorAssertionPlatform assertion, {
String? displayName,
}) async {
return _delegate.enroll(assertion, displayName: displayName);
Expand All @@ -40,3 +40,14 @@ class MultiFactor {
return _delegate.getEnrolledFactors();
}
}

/// Provider for generating a PhoneMultiFactorAssertion.
class PhoneMultiFactorGenerator {
/// Transforms a PhoneAuthCredential into a [MultiFactorAssertion]
/// which can be used to confirm ownership of a phone second factor.
static MultiFactorAssertionPlatform getAssertion(
PhoneAuthCredential credential,
) {
return PhoneMultiFactorGeneratorPlatform.instance.getAssertion(credential);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class RecaptchaVerifier {
///
/// [onExpired] An optional callback which is called when the reCAPTCHA expires.
factory RecaptchaVerifier({
required FirebaseAuthPlatform auth,
String? container,
RecaptchaVerifierSize size = RecaptchaVerifierSize.normal,
RecaptchaVerifierTheme theme = RecaptchaVerifierTheme.light,
Expand All @@ -55,6 +56,7 @@ class RecaptchaVerifier {
}) {
return RecaptchaVerifier._(
_factory.delegateFor(
auth: auth,
container: container,
size: size,
theme: theme,
Expand Down
2 changes: 1 addition & 1 deletion packages/firebase_auth/firebase_auth/lib/src/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ class User {
// If we add a recaptcha to the page by creating a new instance, we must
// also clear that instance before proceeding.
bool mustClear = verifier == null;
verifier ??= RecaptchaVerifier();
verifier ??= RecaptchaVerifier(auth: _delegate.auth);
final result =
await _delegate.linkWithPhoneNumber(phoneNumber, verifier.delegate);
if (mustClear) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ class MethodChannelMultiFactor extends MultiFactorPlatform {

@override
Future<void> enroll(
MultiFactorAssertion assertion, {
MultiFactorAssertionPlatform assertion, {
String? displayName,
}) async {
if (assertion.credential is PhoneAuthCredential) {
final credential = assertion.credential as PhoneAuthCredential;
final _assertion = assertion as MultiFactorAssertion;

if (_assertion.credential is PhoneAuthCredential) {
final credential = _assertion.credential as PhoneAuthCredential;
final verificationId = credential.verificationId;
final verificationCode = credential.smsCode;

Expand All @@ -43,7 +45,7 @@ class MethodChannelMultiFactor extends MultiFactorPlatform {
);
} else {
throw UnimplementedError(
'Credential type ${assertion.credential} is not supported yet',
'Credential type ${_assertion.credential} is not supported yet',
);
}
}
Expand Down Expand Up @@ -90,10 +92,12 @@ class MethodChannelMultiFactorResolver extends MultiFactorResolverPlatform {

@override
Future<UserCredentialPlatform> resolveSignIn(
MultiFactorAssertion assertion,
MultiFactorAssertionPlatform assertion,
) async {
if (assertion.credential is PhoneAuthCredential) {
final credential = assertion.credential as PhoneAuthCredential;
final _assertion = assertion as MultiFactorAssertion;

if (_assertion.credential is PhoneAuthCredential) {
final credential = _assertion.credential as PhoneAuthCredential;
final verificationId = credential.verificationId;
final verificationCode = credential.smsCode;

Expand All @@ -118,8 +122,30 @@ class MethodChannelMultiFactorResolver extends MultiFactorResolverPlatform {
return userCredential;
} else {
throw UnimplementedError(
'Credential type ${assertion.credential} is not supported yet',
'Credential type ${_assertion.credential} is not supported yet',
);
}
}
}

/// Represents an assertion that the Firebase Authentication server
/// can use to authenticate a user as part of a multi-factor flow.
class MultiFactorAssertion extends MultiFactorAssertionPlatform {
MultiFactorAssertion(this.credential) : super();

/// Associated credential to the assertion
final AuthCredential credential;
}

/// Helper class used to generate PhoneMultiFactorAssertions.
class MethodChannelPhoneMultiFactorGenerator
extends PhoneMultiFactorGeneratorPlatform {
/// Transforms a PhoneAuthCredential into a [MultiFactorAssertion]
/// which can be used to confirm ownership of a phone second factor.
@override
MultiFactorAssertionPlatform getAssertion(
PhoneAuthCredential credential,
) {
return MultiFactorAssertion(credential);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart';
import 'package:firebase_auth_platform_interface/src/method_channel/method_channel_multi_factor.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';

/// {@template .platformInterfaceMultiFactor}
Expand All @@ -19,7 +20,10 @@ abstract class MultiFactorPlatform extends PlatformInterface {
/// Enrolls a second factor as identified by the [MultiFactorAssertion] parameter for the current user.
///
/// [displayName] can be used to provide a display name for the second factor.
Future<void> enroll(MultiFactorAssertion assertion, {String? displayName}) {
Future<void> enroll(
MultiFactorAssertionPlatform assertion, {
String? displayName,
}) {
throw UnimplementedError('enroll() is not implemented');
}

Expand Down Expand Up @@ -54,21 +58,7 @@ class MultiFactorSession {

/// Represents an assertion that the Firebase Authentication server
/// can use to authenticate a user as part of a multi-factor flow.
class MultiFactorAssertion {
const MultiFactorAssertion._(this.credential);

/// Associated credential to the assertion
final AuthCredential credential;
}

/// Helper class used to generate PhoneMultiFactorAssertions.
class PhoneMultiFactorGenerator {
/// Transforms a PhoneAuthCredential into a [MultiFactorAssertion]
/// which can be used to confirm ownership of a phone second factor.
static MultiFactorAssertion getAssertion(PhoneAuthCredential credential) {
return MultiFactorAssertion._(credential);
}
}
class MultiFactorAssertionPlatform {}

/// {@macro .platformInterfaceMultiFactorResolverPlatform}
/// Utility class that contains methods to resolve second factor
Expand All @@ -91,7 +81,7 @@ class MultiFactorResolverPlatform {
/// Completes sign in with a second factor using an MultiFactorAssertion which
/// confirms that the user has successfully completed the second factor challenge.
Future<UserCredentialPlatform> resolveSignIn(
MultiFactorAssertion assertion,
MultiFactorAssertionPlatform assertion,
) {
throw UnimplementedError('resolveSignIn() is not implemented');
}
Expand Down Expand Up @@ -139,3 +129,35 @@ class PhoneMultiFactorInfo extends MultiFactorInfo {
/// The phone number associated with this second factor verification method.
final String phoneNumber;
}

/// Helper class used to generate PhoneMultiFactorAssertions.
class PhoneMultiFactorGeneratorPlatform extends PlatformInterface {
static PhoneMultiFactorGeneratorPlatform? _instance;

static final Object _token = Object();

PhoneMultiFactorGeneratorPlatform() : super(token: _token);

/// The current default [PhoneMultiFactorGeneratorPlatform] instance.
///
/// It will always default to [MethodChannelPhoneMultiFactorGenerator]
/// if no other implementation was provided.
static PhoneMultiFactorGeneratorPlatform get instance {
_instance ??= MethodChannelPhoneMultiFactorGenerator();
return _instance!;
}

/// Sets the [PhoneMultiFactorGeneratorPlatform.instance]
static set instance(PhoneMultiFactorGeneratorPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}

/// Transforms a PhoneAuthCredential into a [MultiFactorAssertion]
/// which can be used to confirm ownership of a phone second factor.
MultiFactorAssertionPlatform getAssertion(
PhoneAuthCredential credential,
) {
throw UnimplementedError('getAssertion() is not implemented');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ abstract class RecaptchaVerifierFactoryPlatform extends PlatformInterface {
/// Underlying implementations can use this method to create the underlying
/// implementation of a Recaptcha Verifier.
RecaptchaVerifierFactoryPlatform delegateFor({
required FirebaseAuthPlatform auth,
String? container,
RecaptchaVerifierSize size = RecaptchaVerifierSize.normal,
RecaptchaVerifierTheme theme = RecaptchaVerifierTheme.light,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ void main() {
group('delegateFor()', () {
test('throws UnimplementedError error', () async {
try {
recaptchaVerifierFactoryPlatform.delegateFor();
recaptchaVerifierFactoryPlatform.delegateFor(
auth: FirebaseAuthPlatform.instance,
);
} on UnimplementedError catch (e) {
expect(e.message, equals('delegateFor() is not implemented'));
return;
Expand Down
Loading