Przeglądaj źródła

[auth][mob] Add recovery support for passkey

Neeraj Gupta 1 rok temu
rodzic
commit
27c1b66c08

+ 78 - 0
auth/lib/generated/l10n.dart

@@ -0,0 +1,78 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+import 'package:flutter/material.dart';
+import 'package:intl/intl.dart';
+import 'intl/messages_all.dart';
+
+// **************************************************************************
+// Generator: Flutter Intl IDE plugin
+// Made by Localizely
+// **************************************************************************
+
+// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars
+// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each
+// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes
+
+class S {
+  S();
+
+  static S? _current;
+
+  static S get current {
+    assert(_current != null,
+        'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.');
+    return _current!;
+  }
+
+  static const AppLocalizationDelegate delegate = AppLocalizationDelegate();
+
+  static Future<S> load(Locale locale) {
+    final name = (locale.countryCode?.isEmpty ?? false)
+        ? locale.languageCode
+        : locale.toString();
+    final localeName = Intl.canonicalizedLocale(name);
+    return initializeMessages(localeName).then((_) {
+      Intl.defaultLocale = localeName;
+      final instance = S();
+      S._current = instance;
+
+      return instance;
+    });
+  }
+
+  static S of(BuildContext context) {
+    final instance = S.maybeOf(context);
+    assert(instance != null,
+        'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?');
+    return instance!;
+  }
+
+  static S? maybeOf(BuildContext context) {
+    return Localizations.of<S>(context, S);
+  }
+}
+
+class AppLocalizationDelegate extends LocalizationsDelegate<S> {
+  const AppLocalizationDelegate();
+
+  List<Locale> get supportedLocales {
+    return const <Locale>[
+      Locale.fromSubtags(languageCode: 'en'),
+    ];
+  }
+
+  @override
+  bool isSupported(Locale locale) => _isSupported(locale);
+  @override
+  Future<S> load(Locale locale) => S.load(locale);
+  @override
+  bool shouldReload(AppLocalizationDelegate old) => false;
+
+  bool _isSupported(Locale locale) {
+    for (var supportedLocale in supportedLocales) {
+      if (supportedLocale.languageCode == locale.languageCode) {
+        return true;
+      }
+    }
+    return false;
+  }
+}

+ 3 - 2
auth/lib/l10n/arb/app_en.arb

@@ -144,7 +144,8 @@
   "enterCodeHint": "Enter the 6-digit code from\nyour authenticator app",
   "lostDeviceTitle": "Lost device?",
   "twoFactorAuthTitle": "Two-factor authentication",
-  "passkeyAuthTitle": "Passkey authentication",
+  "passkeyAuthTitle": "Passkey verification",
+  "verifyPasskey": "Verify passkey",
   "recoverAccount": "Recover account",
   "enterRecoveryKeyHint": "Enter your recovery key",
   "recover": "Recover",
@@ -407,7 +408,7 @@
   "hearUsWhereTitle": "How did you hear about Ente? (optional)",
   "hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
   "waitingForBrowserRequest": "Waiting for browser request...",
-  "launchPasskeyUrlAgain": "Launch passkey URL again",
+  "waitingForVerification": "Waiting for verification...",
   "passkey": "Passkey",
   "developerSettingsWarning":"Are you sure that you want to modify Developer settings?",
   "developerSettings": "Developer settings",

+ 13 - 0
auth/lib/models/account/two_factor.dart

@@ -0,0 +1,13 @@
+enum TwoFactorType { totp, passkey }
+
+// ToString for TwoFactorType
+String twoFactorTypeToString(TwoFactorType type) {
+  switch (type) {
+    case TwoFactorType.totp:
+      return "totp";
+    case TwoFactorType.passkey:
+      return "passkey";
+    default:
+      return type.name;
+  }
+}

+ 22 - 0
auth/lib/services/passkey_service.dart

@@ -17,6 +17,28 @@ class PasskeyService {
     return response.data!["accountsToken"] as String;
   }
 
+  Future<bool> isPasskeyRecoveryEnabled() async {
+    final response = await _enteDio.get(
+      "/users/two-factor/recovery-status",
+    );
+    return response.data!["isPasskeyRecoveryEnabled"] as bool;
+  }
+
+  Future<void> configurePasskeyRecovery(
+    String secret,
+    String userEncryptedSecret,
+    String userSecretNonce,
+  ) async {
+    await _enteDio.post(
+      "/users/two-factor/passkeys/configure-recovery",
+      data: {
+        "secret": secret,
+        "userSecretCipher": userEncryptedSecret,
+        "userSecretNonce": userSecretNonce,
+      },
+    );
+  }
+
   Future<void> openPasskeyPage(BuildContext context) async {
     try {
       final jwtToken = await getJwtToken();

+ 18 - 2
auth/lib/services/user_service.dart

@@ -11,6 +11,7 @@ import 'package:ente_auth/core/event_bus.dart';
 import 'package:ente_auth/core/network.dart';
 import 'package:ente_auth/events/user_details_changed_event.dart';
 import 'package:ente_auth/l10n/l10n.dart';
+import 'package:ente_auth/models/account/two_factor.dart';
 import 'package:ente_auth/models/api/user/srp.dart';
 import 'package:ente_auth/models/delete_account.dart';
 import 'package:ente_auth/models/key_attributes.dart';
@@ -762,7 +763,11 @@ class UserService {
     }
   }
 
-  Future<void> recoverTwoFactor(BuildContext context, String sessionID) async {
+  Future<void> recoverTwoFactor(
+    BuildContext context,
+    String sessionID,
+    TwoFactorType type,
+  ) async {
     final dialog = createProgressDialog(context, context.l10n.pleaseWait);
     await dialog.show();
     try {
@@ -770,6 +775,7 @@ class UserService {
         _config.getHttpEndpoint() + "/users/two-factor/recover",
         queryParameters: {
           "sessionID": sessionID,
+          "twoFactorType": twoFactorTypeToString(type),
         },
       );
       if (response.statusCode == 200) {
@@ -778,6 +784,7 @@ class UserService {
           MaterialPageRoute(
             builder: (BuildContext context) {
               return TwoFactorRecoveryPage(
+                type,
                 sessionID,
                 response.data["encryptedSecret"],
                 response.data["secretDecryptionNonce"],
@@ -788,6 +795,7 @@ class UserService {
         );
       }
     } on DioError catch (e) {
+      await dialog.hide();
       _logger.severe(e);
       if (e.response != null && e.response!.statusCode == 404) {
         showToast(context, context.l10n.sessionExpired);
@@ -809,6 +817,7 @@ class UserService {
         );
       }
     } catch (e) {
+      await dialog.hide();
       _logger.severe(e);
       // ignore: unawaited_futures
       showErrorDialog(
@@ -823,6 +832,7 @@ class UserService {
 
   Future<void> removeTwoFactor(
     BuildContext context,
+    TwoFactorType type,
     String sessionID,
     String recoveryKey,
     String encryptedSecret,
@@ -857,11 +867,15 @@ class UserService {
       return;
     }
     try {
+      final secretValue = type == TwoFactorType.passkey
+          ? utf8.decode(base64.decode(secret))
+          : secret;
       final response = await _dio.post(
         _config.getHttpEndpoint() + "/users/two-factor/remove",
         data: {
           "sessionID": sessionID,
-          "secret": secret,
+          "secret": secretValue,
+          "twoFactorType": twoFactorTypeToString(type),
         },
       );
       if (response.statusCode == 200) {
@@ -881,6 +895,7 @@ class UserService {
         );
       }
     } on DioError catch (e) {
+      await dialog.hide();
       _logger.severe(e);
       if (e.response != null && e.response!.statusCode == 404) {
         showToast(context, "Session expired");
@@ -902,6 +917,7 @@ class UserService {
         );
       }
     } catch (e) {
+      await dialog.hide();
       _logger.severe(e);
       // ignore: unawaited_futures
       showErrorDialog(

+ 44 - 22
auth/lib/ui/passkey_page.dart

@@ -1,9 +1,11 @@
 import 'dart:convert';
 
 import 'package:ente_auth/core/configuration.dart';
-import 'package:ente_auth/ente_theme_data.dart';
 import 'package:ente_auth/l10n/l10n.dart';
+import 'package:ente_auth/models/account/two_factor.dart';
 import 'package:ente_auth/services/user_service.dart';
+import 'package:ente_auth/ui/components/buttons/button_widget.dart';
+import 'package:ente_auth/ui/components/models/button_type.dart';
 import 'package:ente_auth/utils/dialog_util.dart';
 import 'package:flutter/material.dart';
 import 'package:logging/logging.dart';
@@ -99,30 +101,50 @@ class _PasskeyPageState extends State<PasskeyPage> {
   }
 
   Widget _getBody() {
-    final l10n = context.l10n;
-
     return Center(
-      child: Column(
-        mainAxisAlignment: MainAxisAlignment.center,
-        children: [
-          Text(
-            l10n.waitingForBrowserRequest,
-            style: const TextStyle(
-              height: 1.4,
-              fontSize: 16,
+      child: Padding(
+        padding: const EdgeInsets.symmetric(horizontal: 32),
+        child: Column(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            Text(
+              context.l10n.waitingForVerification,
+              style: const TextStyle(
+                height: 1.4,
+                fontSize: 16,
+              ),
             ),
-          ),
-          const SizedBox(height: 16),
-          Container(
-            width: double.infinity,
-            padding: const EdgeInsets.symmetric(horizontal: 32),
-            child: ElevatedButton(
-              style: Theme.of(context).colorScheme.optionalActionButtonStyle,
-              onPressed: launchPasskey,
-              child: Text(l10n.launchPasskeyUrlAgain),
+            const SizedBox(height: 16),
+            ButtonWidget(
+              buttonType: ButtonType.primary,
+              labelText: context.l10n.verifyPasskey,
+              onTap: () => launchPasskey(),
             ),
-          ),
-        ],
+            const Padding(padding: EdgeInsets.all(30)),
+            GestureDetector(
+              behavior: HitTestBehavior.opaque,
+              onTap: () {
+                UserService.instance.recoverTwoFactor(
+                  context,
+                  widget.sessionID,
+                  TwoFactorType.passkey,
+                );
+              },
+              child: Container(
+                padding: const EdgeInsets.all(10),
+                child: Center(
+                  child: Text(
+                    context.l10n.recoverAccount,
+                    style: const TextStyle(
+                      decoration: TextDecoration.underline,
+                      fontSize: 12,
+                    ),
+                  ),
+                ),
+              ),
+            ),
+          ],
+        ),
       ),
     );
   }

+ 31 - 1
auth/lib/ui/settings/security_section_widget.dart

@@ -1,4 +1,5 @@
 import 'dart:async';
+import 'dart:convert';
 import 'dart:typed_data';
 
 import 'package:ente_auth/core/configuration.dart';
@@ -21,6 +22,8 @@ import 'package:ente_auth/utils/dialog_util.dart';
 import 'package:ente_auth/utils/navigation_util.dart';
 import 'package:ente_auth/utils/toast_util.dart';
 import 'package:flutter/material.dart';
+import 'package:logging/logging.dart';
+import 'package:uuid/uuid.dart';
 
 class SecuritySectionWidget extends StatefulWidget {
   const SecuritySectionWidget({Key? key}) : super(key: key);
@@ -32,6 +35,7 @@ class SecuritySectionWidget extends StatefulWidget {
 class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
   final _config = Configuration.instance;
   late bool _hasLoggedIn;
+  final Logger _logger = Logger('SecuritySectionWidget');
 
   @override
   void initState() {
@@ -75,7 +79,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
             pressedColor: getEnteColorScheme(context).fillFaint,
             trailingIcon: Icons.chevron_right_outlined,
             trailingIconIsMuted: true,
-            onTap: () => PasskeyService.instance.openPasskeyPage(context),
+            onTap: () async => await onPasskeyClick(context),
           ),
         sectionOptionSpacing,
         MenuItemWidget(
@@ -159,6 +163,32 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
     );
   }
 
+  Future<void> onPasskeyClick(BuildContext buildContext) async {
+    try {
+      final isPassKeyResetEnabled =
+          await PasskeyService.instance.isPasskeyRecoveryEnabled();
+      if (!isPassKeyResetEnabled) {
+        final Uint8List recoveryKey = Configuration.instance.getRecoveryKey();
+        final resetSecret = const Uuid().v4().toString();
+        final bytes = utf8.encode(resetSecret);
+        final base64Str = base64.encode(bytes);
+        final encryptionResult = CryptoUtil.encryptSync(
+          CryptoUtil.base642bin(base64Str),
+          recoveryKey,
+        );
+        await PasskeyService.instance.configurePasskeyRecovery(
+          resetSecret,
+          CryptoUtil.bin2base64(encryptionResult.encryptedData!),
+          CryptoUtil.bin2base64(encryptionResult.nonce!),
+        );
+      }
+      PasskeyService.instance.openPasskeyPage(buildContext).ignore();
+    } catch (e, s) {
+      _logger.severe("failed to open passkey page", e, s);
+      await showGenericErrorDialog(context: context);
+    }
+  }
+
   Future<void> updateEmailMFA(bool enableEmailMFA) async {
     try {
       final UserDetails details =

+ 6 - 1
auth/lib/ui/two_factor_authentication_page.dart

@@ -1,4 +1,5 @@
 import 'package:ente_auth/l10n/l10n.dart';
+import 'package:ente_auth/models/account/two_factor.dart';
 import 'package:ente_auth/services/user_service.dart';
 import 'package:ente_auth/ui/lifecycle_event_handler.dart';
 import 'package:flutter/material.dart';
@@ -129,7 +130,11 @@ class _TwoFactorAuthenticationPageState
         GestureDetector(
           behavior: HitTestBehavior.opaque,
           onTap: () {
-            UserService.instance.recoverTwoFactor(context, widget.sessionID);
+            UserService.instance.recoverTwoFactor(
+              context,
+              widget.sessionID,
+              TwoFactorType.totp,
+            );
           },
           child: Container(
             padding: const EdgeInsets.all(10),

+ 4 - 0
auth/lib/ui/two_factor_recovery_page.dart

@@ -1,6 +1,7 @@
 import 'dart:ui';
 
 import 'package:ente_auth/l10n/l10n.dart';
+import 'package:ente_auth/models/account/two_factor.dart';
 import 'package:ente_auth/services/user_service.dart';
 import 'package:ente_auth/utils/dialog_util.dart';
 import 'package:flutter/material.dart';
@@ -9,8 +10,10 @@ class TwoFactorRecoveryPage extends StatefulWidget {
   final String sessionID;
   final String encryptedSecret;
   final String secretDecryptionNonce;
+  final TwoFactorType type;
 
   const TwoFactorRecoveryPage(
+    this.type,
     this.sessionID,
     this.encryptedSecret,
     this.secretDecryptionNonce, {
@@ -72,6 +75,7 @@ class _TwoFactorRecoveryPageState extends State<TwoFactorRecoveryPage> {
                   ? () async {
                       await UserService.instance.removeTwoFactor(
                         context,
+                        widget.type,
                         widget.sessionID,
                         _recoveryKey.text,
                         widget.encryptedSecret,

+ 2 - 0
mobile/lib/generated/intl/messages_cs.dart

@@ -29,12 +29,14 @@ class MessageLookup extends MessageLookupByLibrary {
         "contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
         "deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
             "This account is linked to other ente apps, if you use any.\\n\\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted."),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "editLocation": MessageLookupByLibrary.simpleMessage("Edit location"),
         "editsToLocationWillOnlyBeSeenWithinEnte":
             MessageLookupByLibrary.simpleMessage(
                 "Edits to location will only be seen within Ente"),
         "fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "modifyYourQueryOrTrySearchingFor":
             MessageLookupByLibrary.simpleMessage(
                 "Modify your query, or try searching for"),

+ 2 - 0
mobile/lib/generated/intl/messages_de.dart

@@ -562,6 +562,7 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("Geteiltes Album löschen?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
             "Dieses Album wird für alle gelöscht\n\nDu wirst den Zugriff auf geteilte Fotos in diesem Album, die anderen gehören, verlieren"),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "deselectAll": MessageLookupByLibrary.simpleMessage("Alle abwählen"),
         "designedToOutlive":
             MessageLookupByLibrary.simpleMessage("Entwickelt um zu bewahren"),
@@ -872,6 +873,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "locationName": MessageLookupByLibrary.simpleMessage("Standortname"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "Ein Standort-Tag gruppiert alle Fotos, die in einem Radius eines Fotos aufgenommen wurden"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Sperren"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
             "Um den Sperrbildschirm zu aktivieren, legen Sie bitte den Geräte-Passcode oder die Bildschirmsperre in den Systemeinstellungen fest."),

+ 2 - 2
mobile/lib/generated/intl/messages_en.dart

@@ -548,6 +548,7 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("Delete shared album?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
             "The album will be deleted for everyone\n\nYou will lose access to shared photos in this album that are owned by others"),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "deselectAll": MessageLookupByLibrary.simpleMessage("Deselect all"),
         "designedToOutlive":
             MessageLookupByLibrary.simpleMessage("Designed to outlive"),
@@ -797,8 +798,6 @@ class MessageLookup extends MessageLookupByLibrary {
             "Kindly help us with this information"),
         "language": MessageLookupByLibrary.simpleMessage("Language"),
         "lastUpdated": MessageLookupByLibrary.simpleMessage("Last updated"),
-        "launchPasskeyUrlAgain":
-            MessageLookupByLibrary.simpleMessage("Launch passkey URL again"),
         "leave": MessageLookupByLibrary.simpleMessage("Leave"),
         "leaveAlbum": MessageLookupByLibrary.simpleMessage("Leave album"),
         "leaveFamily": MessageLookupByLibrary.simpleMessage("Leave family"),
@@ -848,6 +847,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "locationName": MessageLookupByLibrary.simpleMessage("Location name"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "A location tag groups all photos that were taken within some radius of a photo"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Lock"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
             "To enable lockscreen, please setup device passcode or screen lock in your system settings."),

+ 2 - 0
mobile/lib/generated/intl/messages_es.dart

@@ -487,6 +487,7 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("¿Borrar álbum compartido?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
             "El álbum se eliminará para todos\n\nPerderás el acceso a las fotos compartidas en este álbum que son propiedad de otros"),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "deselectAll":
             MessageLookupByLibrary.simpleMessage("Deseleccionar todo"),
         "designedToOutlive":
@@ -761,6 +762,7 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("Nombre de la ubicación"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "Una etiqueta de ubicación agrupa todas las fotos que fueron tomadas dentro de un radio de una foto"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Bloquear"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
             "Para activar la pantalla de bloqueo, por favor configure el código de acceso del dispositivo o el bloqueo de pantalla en los ajustes de su sistema."),

+ 2 - 0
mobile/lib/generated/intl/messages_fr.dart

@@ -562,6 +562,7 @@ class MessageLookup extends MessageLookupByLibrary {
             "Supprimer l\'album partagé ?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
             "L\'album sera supprimé pour tout le monde\n\nVous perdrez l\'accès aux photos partagées dans cet album qui sont détenues par d\'autres personnes"),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "deselectAll":
             MessageLookupByLibrary.simpleMessage("Tout déselectionner"),
         "designedToOutlive":
@@ -870,6 +871,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "locationName": MessageLookupByLibrary.simpleMessage("Nom du lieu"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "Un tag d\'emplacement regroupe toutes les photos qui ont été prises dans un certain rayon d\'une photo"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Verrouiller"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
             "Pour activer l\'écran de verrouillage, veuillez configurer le code d\'accès de l\'appareil ou le verrouillage de l\'écran dans les paramètres de votre système."),

+ 2 - 0
mobile/lib/generated/intl/messages_it.dart

@@ -542,6 +542,7 @@ class MessageLookup extends MessageLookupByLibrary {
             "Eliminare l\'album condiviso?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
             "L\'album verrà eliminato per tutti\n\nPerderai l\'accesso alle foto condivise in questo album che sono di proprietà di altri"),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "deselectAll":
             MessageLookupByLibrary.simpleMessage("Deseleziona tutti"),
         "designedToOutlive":
@@ -838,6 +839,7 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("Nome della località"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "Un tag di localizzazione raggruppa tutte le foto scattate entro il raggio di una foto"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Blocca"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
             "Per abilitare la schermata di blocco, configura il codice di accesso del dispositivo o il blocco schermo nelle impostazioni di sistema."),

+ 2 - 0
mobile/lib/generated/intl/messages_ko.dart

@@ -29,12 +29,14 @@ class MessageLookup extends MessageLookupByLibrary {
         "contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
         "deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
             "This account is linked to other ente apps, if you use any.\\n\\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted."),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "editLocation": MessageLookupByLibrary.simpleMessage("Edit location"),
         "editsToLocationWillOnlyBeSeenWithinEnte":
             MessageLookupByLibrary.simpleMessage(
                 "Edits to location will only be seen within Ente"),
         "fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "modifyYourQueryOrTrySearchingFor":
             MessageLookupByLibrary.simpleMessage(
                 "Modify your query, or try searching for"),

+ 2 - 0
mobile/lib/generated/intl/messages_nl.dart

@@ -564,6 +564,7 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("Gedeeld album verwijderen?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
             "Het album wordt verwijderd voor iedereen\n\nJe verliest de toegang tot gedeelde foto\'s in dit album die eigendom zijn van anderen"),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "deselectAll":
             MessageLookupByLibrary.simpleMessage("Alles deselecteren"),
         "designedToOutlive": MessageLookupByLibrary.simpleMessage(
@@ -880,6 +881,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "locationName": MessageLookupByLibrary.simpleMessage("Locatie naam"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "Een locatie tag groept alle foto\'s die binnen een bepaalde straal van een foto zijn genomen"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Vergrendel"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
             "Om vergrendelscherm in te schakelen, moet u een toegangscode of schermvergrendeling instellen in uw systeeminstellingen."),

+ 2 - 0
mobile/lib/generated/intl/messages_no.dart

@@ -41,6 +41,7 @@ class MessageLookup extends MessageLookupByLibrary {
             "Vi er lei oss for at du forlater oss. Gi oss gjerne en tilbakemelding så vi kan forbedre oss."),
         "deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
             "This account is linked to other ente apps, if you use any.\\n\\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted."),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "editLocation": MessageLookupByLibrary.simpleMessage("Edit location"),
         "editsToLocationWillOnlyBeSeenWithinEnte":
             MessageLookupByLibrary.simpleMessage(
@@ -57,6 +58,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
         "kindlyHelpUsWithThisInformation": MessageLookupByLibrary.simpleMessage(
             "Vær vennlig og hjelp oss med denne informasjonen"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "modifyYourQueryOrTrySearchingFor":
             MessageLookupByLibrary.simpleMessage(
                 "Modify your query, or try searching for"),

+ 2 - 0
mobile/lib/generated/intl/messages_pl.dart

@@ -78,6 +78,7 @@ class MessageLookup extends MessageLookupByLibrary {
             "Inna, niewymieniona wyżej przyczyna"),
         "deleteRequestSLAText": MessageLookupByLibrary.simpleMessage(
             "Twoje żądanie zostanie przetworzone w ciągu 72 godzin."),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "doThisLater": MessageLookupByLibrary.simpleMessage("Spróbuj później"),
         "editLocation": MessageLookupByLibrary.simpleMessage("Edit location"),
         "editsToLocationWillOnlyBeSeenWithinEnte":
@@ -116,6 +117,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
         "kindlyHelpUsWithThisInformation":
             MessageLookupByLibrary.simpleMessage("Pomóż nam z tą informacją"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "logInLabel": MessageLookupByLibrary.simpleMessage("Zaloguj się"),
         "moderateStrength": MessageLookupByLibrary.simpleMessage("Umiarkowana"),
         "modifyYourQueryOrTrySearchingFor":

+ 2 - 2
mobile/lib/generated/intl/messages_pt.dart

@@ -558,6 +558,7 @@ class MessageLookup extends MessageLookupByLibrary {
             "Excluir álbum compartilhado?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
             "O álbum será apagado para todos\n\nVocê perderá o acesso a fotos compartilhadas neste álbum que pertencem aos outros"),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "deselectAll": MessageLookupByLibrary.simpleMessage("Desmarcar todos"),
         "designedToOutlive":
             MessageLookupByLibrary.simpleMessage("Feito para ter logenvidade"),
@@ -820,8 +821,6 @@ class MessageLookup extends MessageLookupByLibrary {
         "language": MessageLookupByLibrary.simpleMessage("Idioma"),
         "lastUpdated":
             MessageLookupByLibrary.simpleMessage("Última atualização"),
-        "launchPasskeyUrlAgain": MessageLookupByLibrary.simpleMessage(
-            "Iniciar a URL de chave de acesso novamente"),
         "leave": MessageLookupByLibrary.simpleMessage("Sair"),
         "leaveAlbum": MessageLookupByLibrary.simpleMessage("Sair do álbum"),
         "leaveFamily": MessageLookupByLibrary.simpleMessage("Sair da família"),
@@ -873,6 +872,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "locationName": MessageLookupByLibrary.simpleMessage("Nome do Local"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "Uma tag em grupo de todas as fotos que foram tiradas dentro de algum raio de uma foto"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Bloquear"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
             "Para ativar o bloqueio de tela, por favor ative um método de autenticação nas configurações do sistema do seu dispositivo."),

+ 2 - 2
mobile/lib/generated/intl/messages_zh.dart

@@ -465,6 +465,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "deleteSharedAlbum": MessageLookupByLibrary.simpleMessage("要删除共享相册吗?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
             "将为所有人删除相册\n\n您将无法访问此相册中他人拥有的共享照片"),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "deselectAll": MessageLookupByLibrary.simpleMessage("取消全选"),
         "designedToOutlive": MessageLookupByLibrary.simpleMessage("经久耐用"),
         "details": MessageLookupByLibrary.simpleMessage("详情"),
@@ -673,8 +674,6 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("请帮助我们了解这个信息"),
         "language": MessageLookupByLibrary.simpleMessage("语言"),
         "lastUpdated": MessageLookupByLibrary.simpleMessage("最后更新"),
-        "launchPasskeyUrlAgain":
-            MessageLookupByLibrary.simpleMessage("再次启动 通行密钥 URL"),
         "leave": MessageLookupByLibrary.simpleMessage("离开"),
         "leaveAlbum": MessageLookupByLibrary.simpleMessage("离开相册"),
         "leaveFamily": MessageLookupByLibrary.simpleMessage("离开家庭计划"),
@@ -717,6 +716,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "locationName": MessageLookupByLibrary.simpleMessage("地点名称"),
         "locationTagFeatureDescription":
             MessageLookupByLibrary.simpleMessage("位置标签将在照片的某个半径范围内拍摄的所有照片进行分组"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("锁定"),
         "lockScreenEnablePreSteps":
             MessageLookupByLibrary.simpleMessage("要启用锁屏,请在系统设置中设置设备密码或屏幕锁定。"),

+ 20 - 30
mobile/lib/generated/l10n.dart

@@ -6919,16 +6919,6 @@ class S {
     );
   }
 
-  /// `Descriptions`
-  String get descriptions {
-    return Intl.message(
-      'Descriptions',
-      name: 'descriptions',
-      desc: '',
-      args: [],
-    );
-  }
-
   /// `File types and names`
   String get fileTypesAndNames {
     return Intl.message(
@@ -6949,16 +6939,6 @@ class S {
     );
   }
 
-  /// `Locations`
-  String get locations {
-    return Intl.message(
-      'Locations',
-      name: 'locations',
-      desc: '',
-      args: [],
-    );
-  }
-
   /// `Moments`
   String get moments {
     return Intl.message(
@@ -8328,16 +8308,6 @@ class S {
     );
   }
 
-  /// `Launch passkey URL again`
-  String get launchPasskeyUrlAgain {
-    return Intl.message(
-      'Launch passkey URL again',
-      name: 'launchPasskeyUrlAgain',
-      desc: '',
-      args: [],
-    );
-  }
-
   /// `Passkey`
   String get passkey {
     return Intl.message(
@@ -8427,6 +8397,26 @@ class S {
       args: [],
     );
   }
+
+  /// `Locations`
+  String get locations {
+    return Intl.message(
+      'Locations',
+      name: 'locations',
+      desc: '',
+      args: [],
+    );
+  }
+
+  /// `Descriptions`
+  String get descriptions {
+    return Intl.message(
+      'Descriptions',
+      name: 'descriptions',
+      desc: '',
+      args: [],
+    );
+  }
 }
 
 class AppLocalizationDelegate extends LocalizationsDelegate<S> {

+ 0 - 1
mobile/lib/l10n/intl_en.arb

@@ -1188,7 +1188,6 @@
   "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente",
   "cleanUncategorized": "Clean Uncategorized",
   "waitingForVerification": "Waiting for verification...",
-  "launchPasskeyUrlAgain": "Launch passkey URL again",
   "passkey": "Passkey",
   "passkeyAuthTitle": "Passkey verification",
   "verifyPasskey": "Verify passkey",

+ 4 - 1
mobile/lib/services/user_service.dart

@@ -912,11 +912,14 @@ class UserService {
       return;
     }
     try {
+      final secretValue = type == TwoFactorType.passkey
+          ? utf8.decode(base64.decode(secret))
+          : secret;
       final response = await _dio.post(
         _config.getHttpEndpoint() + "/users/two-factor/remove",
         data: {
           "sessionID": sessionID,
-          "secret": utf8.decode(base64.decode(secret)),
+          "secret": secretValue,
           "twoFactorType": twoFactorTypeToString(type),
         },
       );