Bläddra i källkod

Show option to enable/disable email MFA

Neeraj Gupta 2 år sedan
förälder
incheckning
553a6ce732

+ 37 - 0
lib/models/user_details.dart

@@ -1,3 +1,4 @@
+import 'dart:convert';
 import 'dart:math';
 
 import 'package:collection/collection.dart';
@@ -10,6 +11,7 @@ class UserDetails {
   final int sharedCollectionsCount;
   final Subscription subscription;
   final FamilyData? familyData;
+  final ProfileData? profileData;
 
   UserDetails(
     this.email,
@@ -18,6 +20,7 @@ class UserDetails {
     this.sharedCollectionsCount,
     this.subscription,
     this.familyData,
+      this.profileData,
   );
 
   bool isPartOfFamily() {
@@ -59,8 +62,10 @@ class UserDetails {
       (map['sharedCollectionsCount'] ?? 0) as int,
       Subscription.fromMap(map['subscription']),
       FamilyData.fromMap(map['familyData']),
+      ProfileData.fromJson(map['profileData']),
     );
   }
+
 }
 
 class FamilyMember {
@@ -80,7 +85,39 @@ class FamilyMember {
     );
   }
 }
+class ProfileData {
+  bool canDisableEmailMFA;
+  bool isEmailMFAEnabled;
+  bool isTwoFactorEnabled;
+
+  // Constructor with default values
+  ProfileData({
+    this.canDisableEmailMFA = false,
+    this.isEmailMFAEnabled = false,
+    this.isTwoFactorEnabled = false,
+  });
+
+  // Factory method to create ProfileData instance from JSON
+  factory ProfileData.fromJson(Map<String, dynamic>? json) {
+    if (json == null) null;
+
+    return ProfileData(
+      canDisableEmailMFA: json!['canDisableEmailMFA'] ?? false,
+      isEmailMFAEnabled: json['isEmailMFAEnabled'] ?? false,
+      isTwoFactorEnabled: json['isTwoFactorEnabled'] ?? false,
+    );
+  }
 
+  // Method to convert ProfileData instance to JSON
+  Map<String, dynamic> toJson() {
+    return {
+      'canDisableEmailMFA': canDisableEmailMFA,
+      'isEmailMFAEnabled': isEmailMFAEnabled,
+      'isTwoFactorEnabled': isTwoFactorEnabled,
+    };
+  }
+  String toJsonString() => json.encode(toJson());
+}
 class FamilyData {
   final List<FamilyMember>? members;
 

+ 31 - 6
lib/services/user_service.dart

@@ -45,6 +45,8 @@ import "package:uuid/uuid.dart";
 class UserService {
   static const keyHasEnabledTwoFactor = "has_enabled_two_factor";
   static const keyUserDetails = "user_details";
+  static const kCanDisableEmailMFA = "can_disable_email_mfa";
+  static const kIsEmailMFAEnabled = "is_email_mfa_enabled";
   final  SRP6GroupParameters kDefaultSrpGroup = SRP6StandardGroups.rfc5054_4096;
   final _dio = Network.instance.getDio();
   final _enteDio = Network.instance.enteDio;
@@ -131,10 +133,9 @@ class UserService {
 
 
   Future<UserDetails> getUserDetailsV2({
-    bool memoryCount = true,
-    bool shouldCache = false,
+    bool memoryCount = false,
+    bool shouldCache = true,
   }) async {
-    _logger.info("Fetching user details");
     try {
       final response = await _enteDio.get(
         "/users/details/v2",
@@ -144,14 +145,19 @@ class UserService {
       );
       final userDetails = UserDetails.fromMap(response.data);
       if (shouldCache) {
+        if(userDetails.profileData != null) {
+          _preferences.setBool(kIsEmailMFAEnabled, userDetails.profileData!.isEmailMFAEnabled);
+          _preferences.setBool(kCanDisableEmailMFA, userDetails.profileData!.canDisableEmailMFA);
+        }
         // handle email change from different client
         if (userDetails.email != _config.getEmail()) {
           setEmail(userDetails.email);
         }
       }
+      _logger.info("Successfully fetched user details");
       return userDetails;
-    } on DioError catch (e) {
-      _logger.info(e);
+    } catch(e) {
+      _logger.warning("Failed to fetch", e);
       rethrow;
     }
   }
@@ -890,7 +896,26 @@ class UserService {
     }
   }
 
+  bool canDisableEmailMFA() {
+    return _preferences.getBool(kCanDisableEmailMFA)  ?? false;
+  }
+  bool hasEmailMFAEnabled() {
+    return _preferences.getBool(kIsEmailMFAEnabled) ?? true;
+  }
 
-
+  Future<void> updateEmailMFA(bool isEnabled) async {
+    try {
+      await _enteDio.put(
+        "/users/email-mfa",
+        data: {
+          "isEnabled": isEnabled,
+        },
+      );
+      _preferences.setBool(kIsEmailMFAEnabled, isEnabled);
+    } catch (e) {
+      _logger.severe("Failed to update email mfa",e);
+      rethrow;
+    }
+  }
 }
 

+ 41 - 0
lib/ui/settings/security_section_widget.dart

@@ -1,6 +1,9 @@
+import 'dart:async';
+
 import 'package:ente_auth/core/configuration.dart';
 import 'package:ente_auth/l10n/l10n.dart';
 import 'package:ente_auth/services/local_authentication_service.dart';
+import 'package:ente_auth/services/user_service.dart';
 import 'package:ente_auth/theme/ente_theme.dart';
 import 'package:ente_auth/ui/account/sessions_page.dart';
 import 'package:ente_auth/ui/components/captioned_text_widget.dart';
@@ -8,6 +11,7 @@ import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
 import 'package:ente_auth/ui/components/menu_item_widget.dart';
 import 'package:ente_auth/ui/components/toggle_switch_widget.dart';
 import 'package:ente_auth/ui/settings/common_settings.dart';
+import 'package:ente_auth/utils/toast_util.dart';
 import 'package:flutter/material.dart';
 
 class SecuritySectionWidget extends StatefulWidget {
@@ -41,6 +45,8 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
   }
 
   Widget _getSectionOptions(BuildContext context) {
+    final bool canDisableMFA = UserService.instance.canDisableEmailMFA();
+
     final l10n = context.l10n;
     final List<Widget> children = [];
     children.addAll([
@@ -64,6 +70,33 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
           },
         ),
       ),
+      if(canDisableMFA)
+        sectionOptionSpacing,
+      if(canDisableMFA)
+        MenuItemWidget(
+          captionedTextWidget: const CaptionedTextWidget(
+            title: "Email MFA",
+          ),
+          trailingWidget: ToggleSwitchWidget(
+            value: () => UserService.instance.hasEmailMFAEnabled(),
+            onChanged: () async {
+              final hasAuthenticated = await LocalAuthenticationService
+                  .instance
+                  .requestLocalAuthentication(
+                context,
+                "Authenticate to change your email MFA setting",
+              );
+              final isEmailMFAEnabled =
+              UserService.instance.hasEmailMFAEnabled();
+              if (hasAuthenticated) {
+                await updateEmailMFA(!isEmailMFAEnabled);
+                if(mounted){
+                  setState(() {});
+                }
+              }
+            },
+          ),
+        ),
       sectionOptionSpacing,
       MenuItemWidget(
         captionedTextWidget: CaptionedTextWidget(
@@ -95,4 +128,12 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
       children: children,
     );
   }
+
+  Future<void> updateEmailMFA(bool isEnabled) async {
+    try {
+      await UserService.instance.updateEmailMFA(isEnabled);
+    } catch (e) {
+     showToast(context, "Error updating email MFA");
+    }
+  }
 }

+ 3 - 0
lib/ui/settings_page.dart

@@ -1,5 +1,6 @@
 import 'dart:io';
 
+import 'package:ente_auth/services/user_service.dart';
 import 'package:ente_auth/theme/colors.dart';
 import 'package:ente_auth/theme/ente_theme.dart';
 import 'package:ente_auth/ui/settings/about_section_widget.dart';
@@ -20,8 +21,10 @@ class SettingsPage extends StatelessWidget {
   final ValueNotifier<String?> emailNotifier;
   const SettingsPage({Key? key, required this.emailNotifier}) : super(key: key);
 
+
   @override
   Widget build(BuildContext context) {
+    UserService.instance.getUserDetailsV2().ignore();
     final enteColorScheme = getEnteColorScheme(context);
     return Scaffold(
       body: Container(