Skip to content

Commit

Permalink
feat(auth): add multifactoruser
Browse files Browse the repository at this point in the history
  • Loading branch information
Lyokone committed Jul 7, 2022
1 parent eb29b91 commit 0d5c992
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import 'src/firebase_auth_web_recaptcha_verifier_factory.dart';
import 'src/firebase_auth_web_user.dart';
import 'src/firebase_auth_web_user_credential.dart';
import 'src/interop/auth.dart' as auth_interop;
import 'src/interop/multi_factor.dart' as multi_factor;

/// The web delegate implementation for [FirebaseAuth].
class FirebaseAuthWeb extends FirebaseAuthPlatform {
Expand Down Expand Up @@ -49,7 +50,8 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform {
if (webUser == null) {
return null;
} else {
return UserWeb(this, MultiFactorWeb(this), webUser);
return UserWeb(this,
MultiFactorWeb(this, multi_factor.multiFactor(webUser)), webUser);
}
}).listen((UserWeb? webUser) {
_authStateChangesListeners[app.name]!.add(webUser);
Expand All @@ -61,7 +63,8 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform {
if (webUser == null) {
return null;
} else {
return UserWeb(this, MultiFactorWeb(this), webUser);
return UserWeb(this,
MultiFactorWeb(this, multi_factor.multiFactor(webUser)), webUser);
}
}).listen((UserWeb? webUser) {
_idTokenChangesListeners[app.name]!.add(webUser);
Expand Down Expand Up @@ -121,7 +124,10 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform {
return null;
}

return UserWeb(this, MultiFactorWeb(this), _delegate.currentUser!);
return UserWeb(
this,
MultiFactorWeb(this, multi_factor.multiFactor(_delegate.currentUser!)),
_delegate.currentUser!);
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,74 @@

import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart';

import 'interop/multi_factor.dart' as multi_factor_interop;
import 'utils/web_utils.dart';

/// Web delegate implementation of [UserPlatform].
class MultiFactorWeb extends MultiFactorPlatform {
MultiFactorWeb(FirebaseAuthPlatform auth) : super(auth);
MultiFactorWeb(FirebaseAuthPlatform auth, this._webMultiFactorUser)
: super(auth);

final multi_factor_interop.MultiFactorUser _webMultiFactorUser;

final mapAssertion =
<multi_factor_interop.MultiFactorAssertion, MultiFactorAssertion>{};

@override
Future<MultiFactorSession> getSession() async {
try {
return convertMultiFactorSession(await _webMultiFactorUser.session);
} catch (e) {
throw getFirebaseAuthException(e);
}
}

@override
Future<void> enroll(
MultiFactorAssertion assertion, {
String? displayName,
}) async {
try {
final webAssertion = mapAssertion.keys
.firstWhere((element) => mapAssertion[element] == assertion);
return await _webMultiFactorUser.enroll(
webAssertion,
displayName,
);
} catch (e) {
throw getFirebaseAuthException(e);
}
}

@override
Future<void> unenroll({
String? factorUid,
MultiFactorInfo? multiFactorInfo,
}) {
final uidToUnenroll = factorUid ?? multiFactorInfo?.uid;
if (uidToUnenroll == null) {
throw ArgumentError(
'Either factorUid or multiFactorInfo must not be null',
);
}

return _webMultiFactorUser.unenroll(
uidToUnenroll,
);
}

@override
Future<List<MultiFactorInfo>> getEnrolledFactors() async {
final data = _webMultiFactorUser.enrolledFactors;
return data
.map((e) => MultiFactorInfo(
factorId: e.factorId,
enrollmentTimestamp:
DateTime.parse(e.enrollmentTime).millisecondsSinceEpoch /
1000,
displayName: e.displayName,
uid: e.uid,
))
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:firebase_auth_web/src/firebase_auth_web_multi_factor.dart';

import 'firebase_auth_web_user.dart';
import 'interop/auth.dart' as auth_interop;
import 'interop/multi_factor.dart';
import 'utils/web_utils.dart';

/// Web delegate implementation of [UserCredentialPlatform].
Expand All @@ -22,6 +23,9 @@ class UserCredentialWeb extends UserCredentialPlatform {
webUserCredential.additionalUserInfo,
),
credential: convertWebOAuthCredential(webUserCredential.credential),
user: UserWeb(auth, MultiFactorWeb(auth), webUserCredential.user!),
user: UserWeb(
auth,
MultiFactorWeb(auth, multiFactor(webUserCredential.user!)),
webUserCredential.user!),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
@JS('firebase_auth')
library firebase_interop.auth;

import 'package:js/js.dart';
import 'package:firebase_auth_web/src/interop/auth.dart';
import 'package:firebase_core_web/firebase_core_web_interop.dart';
import 'package:js/js.dart';

@JS()
external AuthJsImpl getAuth([AppJsImpl? app]);
Expand Down Expand Up @@ -233,6 +234,12 @@ external PromiseJsImpl<void> updateProfile(
UserProfile profile,
);

/// https://firebase.google.com/docs/reference/js/auth.md#multifactor
@JS()
external MultiFactorUserJsImpl multiFactor(
UserJsImpl user,
);

@JS('Auth')
abstract class AuthJsImpl {
external AppJsImpl get app;
Expand Down Expand Up @@ -621,7 +628,7 @@ class AndroidSettings {
});
}

/// https://firebase.google.com/docs/reference/js/firebase.auth#.UserCredential
/// https://firebase.google.com/docs/reference/js/auth.usercredential
@JS()
@anonymous
class UserCredentialJsImpl {
Expand Down Expand Up @@ -649,3 +656,92 @@ class AuthSettings {
external set appVerificationDisabledForTesting(bool? b);
// external factory AuthSettings({bool appVerificationDisabledForTesting});
}

/// https://firebase.google.com/docs/reference/js/auth.multifactoruser.md#multifactoruser_interface
@JS()
@anonymous
class MultiFactorUserJsImpl {
external List<MultiFactorInfoJsImpl> get enrolledFactors;
external PromiseJsImpl<void> enroll(
MultiFactorAssertionJsImpl assertion, String? displayName);
external PromiseJsImpl<MultiFactorSessionJsImpl> getSession();
external PromiseJsImpl<void> unenroll(
dynamic /* MultiFactorInfo | string */ option);
}

/// https://firebase.google.com/docs/reference/js/auth.multifactorinfo
@JS()
@anonymous
class MultiFactorInfoJsImpl {
external String? get displayName;
external String get enrollmentTime;
external String get factorId;
external String get uid;
}

/// https://firebase.google.com/docs/reference/js/auth.multifactorassertion
@JS()
@anonymous
class MultiFactorAssertionJsImpl {
external String get factorId;
}

/// https://firebase.google.com/docs/reference/js/auth.multifactorerror
@JS('Error')
@anonymous
class MultiFactorErrorJsImpl extends AuthError {
external dynamic get customData;
}

/// https://firebase.google.com/docs/reference/js/auth.multifactorresolver
@JS()
@anonymous
class MultiFactorResolverJsImpl {
external List<MultiFactorInfoJsImpl> get hints;
external MultiFactorSessionJsImpl get session;
external PromiseJsImpl<UserCredentialJsImpl> resolveSignIn(
MultiFactorAssertionJsImpl assertion);
}

/// https://firebase.google.com/docs/reference/js/auth.multifactorresolver
@JS()
@anonymous
class MultiFactorSessionJsImpl {}

/// https://firebase.google.com/docs/reference/js/auth.phonemultifactorinfo
@JS()
@anonymous
class PhoneMultiFactorInfoJsImpl extends MultiFactorInfoJsImpl {
external String get phoneNumber;
}

/// https://firebase.google.com/docs/reference/js/auth.phonemultifactorenrollinfooptions
@JS()
@anonymous
class PhoneMultiFactorEnrollInfoOptionsJsImpl {
external String get phoneNumber;
external MultiFactorSessionJsImpl? get session;
}

/// https://firebase.google.com/docs/reference/js/auth.phonemultifactorgenerator
@JS()
@anonymous
class PhoneMultiFactorGeneratorJsImpl {
external static String get FACTOR_ID;
external static PhoneMultiFactorAssertionJsImpl? assertion(
PhoneAuthCredentialJsImpl credential);
}

/// https://firebase.google.com/docs/reference/js/auth.phonemultifactorassertion
@JS()
@anonymous
class PhoneMultiFactorAssertionJsImpl extends MultiFactorAssertionJsImpl {}

/// https://firebase.google.com/docs/reference/js/auth.phoneauthcredential
@JS()
@anonymous
class PhoneAuthCredentialJsImpl extends AuthCredential {
external static PhoneAuthCredentialJsImpl fromJSON(
dynamic /*object | string*/ json);
external Object toJSON();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// ignore_for_file: require_trailing_commas
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs

import 'package:firebase_core_web/firebase_core_web_interop.dart'
hide jsify, dartify;

import 'auth.dart' as auth;
import 'auth_interop.dart' as auth_interop;

export 'auth_interop.dart';

/// Given an AppJSImp, return the Auth instance.
MultiFactorUser multiFactor(auth.User user) {
return MultiFactorUser.getInstance(auth_interop.multiFactor(user.jsObject));
}

/// The Firebase MultiFactorUser service class.
///
/// See: https://firebase.google.com/docs/reference/js/auth.md#multifactor.
class MultiFactorUser
extends JsObjectWrapper<auth_interop.MultiFactorUserJsImpl> {
static final _expando = Expando<MultiFactorUser>();

/// Creates a new Auth from a [jsObject].
static MultiFactorUser getInstance(
auth_interop.MultiFactorUserJsImpl jsObject) {
return _expando[jsObject] ??= MultiFactorUser._fromJsObject(jsObject);
}

MultiFactorUser._fromJsObject(auth_interop.MultiFactorUserJsImpl jsObject)
: super.fromJsObject(jsObject);

/// Returns a list of the user's enrolled second factors.
List<MultiFactorInfo> get enrolledFactors =>
jsObject.enrolledFactors.map(MultiFactorInfo.fromJsObject).toList();

/// Returns the session identifier for a second factor enrollment operation.
///
/// This is used to identify the user trying to enroll a second factor.
Future<MultiFactorSession> get session =>
handleThenable(jsObject.getSession())
.then(MultiFactorSession.fromJsObject);

/// Enrolls a second factor as identified by the [MultiFactorAssertion] for the user.
///
/// On resolution, the user tokens are updated to reflect the change in the JWT payload.
/// Accepts an additional display name parameter used to identify the second factor to the end user.
/// Recent re-authentication is required for this operation to succeed. On successful enrollment,
/// existing Firebase sessions (refresh tokens) are revoked. When a new factor is enrolled,
/// an email notification is sent to the user’s email.
Future<void> enroll(MultiFactorAssertion assertion, String? displayName) {
return handleThenable(jsObject.enroll(assertion.jsObject, displayName));
}

/// Unenrolls the specified second factor.
///
/// To specify the factor to remove, pass a [MultiFactorInfo] object
/// (retrieved from [MultiFactorUser.enrolledFactors]) or the factor's UID string.
/// Sessions are not revoked when the account is unenrolled.
/// An email notification is likely to be sent to the user notifying them of the change.
/// Recent re-authentication is required for this operation to succeed.
/// When an existing factor is unenrolled, an email notification is sent to the user’s email.
Future<void> unenroll(String multiFactorInfoId) {
return handleThenable(jsObject.unenroll(multiFactorInfoId));
}
}

/// https://firebase.google.com/docs/reference/js/auth.multifactorinfo
class MultiFactorInfo
extends JsObjectWrapper<auth_interop.MultiFactorInfoJsImpl> {
MultiFactorInfo.fromJsObject(auth.MultiFactorInfoJsImpl jsObject)
: super.fromJsObject(jsObject);

/// The user friendly name of the current second factor.
String? get displayName => jsObject.displayName;

/// The enrollment date of the second factor formatted as a UTC string.
String get enrollmentTime => jsObject.enrollmentTime;

/// The identifier of the second factor.
String get factorId => jsObject.factorId;

/// The multi-factor enrollment ID.
String get uid => jsObject.uid;
}

/// https://firebase.google.com/docs/reference/js/auth.multifactorsession.md#multifactorsession_interface
class MultiFactorSession
extends JsObjectWrapper<auth_interop.MultiFactorSessionJsImpl> {
MultiFactorSession.fromJsObject(auth.MultiFactorSessionJsImpl jsObject)
: super.fromJsObject(jsObject);
}

/// https://firebase.google.com/docs/reference/js/auth.multifactorsession.md#multifactorsession_interface
class MultiFactorAssertion<T extends auth_interop.MultiFactorAssertionJsImpl>
extends JsObjectWrapper<T> {
MultiFactorAssertion.fromJsObject(T jsObject) : super.fromJsObject(jsObject);

String get factorId => jsObject.factorId;
}

/// https://firebase.google.com/docs/reference/js/auth.multifactorsession.md#multifactorsession_interface
class PhoneMultiFactorAssertion
extends MultiFactorAssertion<auth_interop.PhoneMultiFactorAssertionJsImpl> {
PhoneMultiFactorAssertion.fromJsObject(
auth.PhoneMultiFactorAssertionJsImpl jsObject)
: super.fromJsObject(jsObject);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart';
import 'package:firebase_core_web/firebase_core_web_interop.dart'
as core_interop;
import 'package:flutter/material.dart';

import '../interop/auth.dart' as auth_interop;
import '../interop/multi_factor.dart' as multi_factor_interop;

/// Given a web error, an [Exception] is returned.
///
Expand Down Expand Up @@ -290,3 +292,9 @@ String convertRecaptchaVerifierTheme(RecaptchaVerifierTheme theme) {
return 'light';
}
}

/// Converts a [multi_factor_interop.MultiFactorSession] into a [MultiFactorSession].
MultiFactorSession convertMultiFactorSession(
multi_factor_interop.MultiFactorSession multiFactorSession) {
return MultiFactorSession(UniqueKey().toString());
}

0 comments on commit 0d5c992

Please sign in to comment.