security_section_widget.dart 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import 'dart:async';
  2. import 'dart:typed_data';
  3. import 'package:ente_auth/core/configuration.dart';
  4. import 'package:ente_auth/l10n/l10n.dart';
  5. import 'package:ente_auth/models/user_details.dart';
  6. import 'package:ente_auth/services/local_authentication_service.dart';
  7. import 'package:ente_auth/services/user_service.dart';
  8. import 'package:ente_auth/theme/ente_theme.dart';
  9. import 'package:ente_auth/ui/account/recovery_key_page.dart';
  10. import 'package:ente_auth/ui/account/request_pwd_verification_page.dart';
  11. import 'package:ente_auth/ui/account/sessions_page.dart';
  12. import 'package:ente_auth/ui/components/captioned_text_widget.dart';
  13. import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
  14. import 'package:ente_auth/ui/components/menu_item_widget.dart';
  15. import 'package:ente_auth/ui/components/toggle_switch_widget.dart';
  16. import 'package:ente_auth/ui/settings/common_settings.dart';
  17. import 'package:ente_auth/utils/crypto_util.dart';
  18. import 'package:ente_auth/utils/dialog_util.dart';
  19. import 'package:ente_auth/utils/navigation_util.dart';
  20. import 'package:ente_auth/utils/toast_util.dart';
  21. import 'package:flutter/material.dart';
  22. import 'package:flutter_sodium/flutter_sodium.dart';
  23. class SecuritySectionWidget extends StatefulWidget {
  24. const SecuritySectionWidget({Key? key}) : super(key: key);
  25. @override
  26. State<SecuritySectionWidget> createState() => _SecuritySectionWidgetState();
  27. }
  28. class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
  29. final _config = Configuration.instance;
  30. late bool _hasLoggedIn;
  31. @override
  32. void initState() {
  33. _hasLoggedIn = _config.hasConfiguredAccount();
  34. super.initState();
  35. }
  36. @override
  37. void dispose() {
  38. super.dispose();
  39. }
  40. @override
  41. Widget build(BuildContext context) {
  42. final l10n = context.l10n;
  43. return ExpandableMenuItemWidget(
  44. title: l10n.security,
  45. selectionOptionsWidget: _getSectionOptions(context),
  46. leadingIcon: Icons.local_police_outlined,
  47. );
  48. }
  49. Widget _getSectionOptions(BuildContext context) {
  50. final l10n = context.l10n;
  51. final List<Widget> children = [];
  52. if (_hasLoggedIn) {
  53. final bool? canDisableMFA = UserService.instance.canDisableEmailMFA();
  54. if (canDisableMFA == null) {
  55. // We don't know if the user can disable MFA yet, so we fetch the info
  56. UserService.instance.getUserDetailsV2().ignore();
  57. }
  58. children.addAll([
  59. sectionOptionSpacing,
  60. MenuItemWidget(
  61. captionedTextWidget: CaptionedTextWidget(
  62. title: l10n.recoveryKey,
  63. ),
  64. pressedColor: getEnteColorScheme(context).fillFaint,
  65. trailingIcon: Icons.chevron_right_outlined,
  66. trailingIconIsMuted: true,
  67. onTap: () async {
  68. final hasAuthenticated = await LocalAuthenticationService.instance
  69. .requestLocalAuthentication(
  70. context,
  71. l10n.authToViewYourRecoveryKey,
  72. );
  73. if (hasAuthenticated) {
  74. String recoveryKey;
  75. try {
  76. recoveryKey =
  77. Sodium.bin2hex(Configuration.instance.getRecoveryKey());
  78. } catch (e) {
  79. showGenericErrorDialog(context: context);
  80. return;
  81. }
  82. routeToPage(
  83. context,
  84. RecoveryKeyPage(
  85. recoveryKey,
  86. l10n.ok,
  87. showAppBar: true,
  88. onDone: () {},
  89. ),
  90. );
  91. }
  92. },
  93. ),
  94. MenuItemWidget(
  95. captionedTextWidget: CaptionedTextWidget(
  96. title: l10n.emailVerificationToggle,
  97. ),
  98. trailingWidget: ToggleSwitchWidget(
  99. value: () => UserService.instance.hasEmailMFAEnabled(),
  100. onChanged: () async {
  101. final hasAuthenticated = await LocalAuthenticationService.instance
  102. .requestLocalAuthentication(
  103. context,
  104. l10n.authToChangeEmailVerificationSetting,
  105. );
  106. final isEmailMFAEnabled =
  107. UserService.instance.hasEmailMFAEnabled();
  108. if (hasAuthenticated) {
  109. await updateEmailMFA(!isEmailMFAEnabled);
  110. if (mounted) {
  111. setState(() {});
  112. }
  113. }
  114. },
  115. ),
  116. ),
  117. sectionOptionSpacing,
  118. MenuItemWidget(
  119. captionedTextWidget: CaptionedTextWidget(
  120. title: context.l10n.viewActiveSessions,
  121. ),
  122. pressedColor: getEnteColorScheme(context).fillFaint,
  123. trailingIcon: Icons.chevron_right_outlined,
  124. trailingIconIsMuted: true,
  125. onTap: () async {
  126. final hasAuthenticated = await LocalAuthenticationService.instance
  127. .requestLocalAuthentication(
  128. context,
  129. context.l10n.authToViewYourActiveSessions,
  130. );
  131. if (hasAuthenticated) {
  132. Navigator.of(context).push(
  133. MaterialPageRoute(
  134. builder: (BuildContext context) {
  135. return const SessionsPage();
  136. },
  137. ),
  138. );
  139. }
  140. },
  141. ),
  142. ]);
  143. } else {
  144. children.add(sectionOptionSpacing);
  145. }
  146. children.addAll([
  147. MenuItemWidget(
  148. captionedTextWidget: CaptionedTextWidget(
  149. title: l10n.lockscreen,
  150. ),
  151. trailingWidget: ToggleSwitchWidget(
  152. value: () => _config.shouldShowLockScreen(),
  153. onChanged: () async {
  154. final hasAuthenticated = await LocalAuthenticationService.instance
  155. .requestLocalAuthForLockScreen(
  156. context,
  157. !_config.shouldShowLockScreen(),
  158. context.l10n.authToChangeLockscreenSetting,
  159. context.l10n.lockScreenEnablePreSteps,
  160. );
  161. if (hasAuthenticated) {
  162. setState(() {});
  163. }
  164. },
  165. ),
  166. ),
  167. sectionOptionSpacing,
  168. ]);
  169. return Column(
  170. children: children,
  171. );
  172. }
  173. Future<void> updateEmailMFA(bool isEnabled) async {
  174. try {
  175. final UserDetails details =
  176. await UserService.instance.getUserDetailsV2(memoryCount: false);
  177. if (details.profileData?.canDisableEmailMFA == false) {
  178. await routeToPage(
  179. context,
  180. RequestPasswordVerificationPage(
  181. onPasswordVerified: (Uint8List keyEncryptionKey) async {
  182. final Uint8List loginKey =
  183. await CryptoUtil.deriveLoginKey(keyEncryptionKey);
  184. await UserService.instance.registerOrUpdateSrp(loginKey);
  185. },
  186. ),
  187. );
  188. }
  189. await UserService.instance.updateEmailMFA(isEnabled);
  190. } catch (e) {
  191. showToast(context, context.l10n.somethingWentWrongMessage);
  192. }
  193. }
  194. }