Skip to content

Commit

Permalink
feat(auth, web): add phone mfa (#9031)
Browse files Browse the repository at this point in the history
* feat(auth): add multifactoruser

* feat(auth, web): bridge

* feat(auth, web): solve bridging errors

* feat(auth, web): finish phone web mfa

* feat(auth, web): finish phone web mfa

* feat(auth, web): fix analyze

* feat(auth, web): fix analyze

* feat(auth, web): fix wrong argument
  • Loading branch information
Lyokone committed Jul 18, 2022
1 parent 8d2bfb4 commit 244f5f4
Show file tree
Hide file tree
Showing 21 changed files with 688 additions and 111 deletions.
1 change: 0 additions & 1 deletion packages/firebase_auth/firebase_auth/example/lib/auth.dart
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
@@ -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
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
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);
}
}
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
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
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);
}
}
@@ -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');
}
}
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
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

0 comments on commit 244f5f4

Please sign in to comment.