Bladeren bron

[auth][mob] Add recovery support for passkey

Neeraj Gupta 1 jaar geleden
bovenliggende
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",
   "enterCodeHint": "Enter the 6-digit code from\nyour authenticator app",
   "lostDeviceTitle": "Lost device?",
   "lostDeviceTitle": "Lost device?",
   "twoFactorAuthTitle": "Two-factor authentication",
   "twoFactorAuthTitle": "Two-factor authentication",
-  "passkeyAuthTitle": "Passkey authentication",
+  "passkeyAuthTitle": "Passkey verification",
+  "verifyPasskey": "Verify passkey",
   "recoverAccount": "Recover account",
   "recoverAccount": "Recover account",
   "enterRecoveryKeyHint": "Enter your recovery key",
   "enterRecoveryKeyHint": "Enter your recovery key",
   "recover": "Recover",
   "recover": "Recover",
@@ -407,7 +408,7 @@
   "hearUsWhereTitle": "How did you hear about Ente? (optional)",
   "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!",
   "hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
   "waitingForBrowserRequest": "Waiting for browser request...",
   "waitingForBrowserRequest": "Waiting for browser request...",
-  "launchPasskeyUrlAgain": "Launch passkey URL again",
+  "waitingForVerification": "Waiting for verification...",
   "passkey": "Passkey",
   "passkey": "Passkey",
   "developerSettingsWarning":"Are you sure that you want to modify Developer settings?",
   "developerSettingsWarning":"Are you sure that you want to modify Developer settings?",
   "developerSettings": "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;
     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 {
   Future<void> openPasskeyPage(BuildContext context) async {
     try {
     try {
       final jwtToken = await getJwtToken();
       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/core/network.dart';
 import 'package:ente_auth/events/user_details_changed_event.dart';
 import 'package:ente_auth/events/user_details_changed_event.dart';
 import 'package:ente_auth/l10n/l10n.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/api/user/srp.dart';
 import 'package:ente_auth/models/delete_account.dart';
 import 'package:ente_auth/models/delete_account.dart';
 import 'package:ente_auth/models/key_attributes.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);
     final dialog = createProgressDialog(context, context.l10n.pleaseWait);
     await dialog.show();
     await dialog.show();
     try {
     try {
@@ -770,6 +775,7 @@ class UserService {
         _config.getHttpEndpoint() + "/users/two-factor/recover",
         _config.getHttpEndpoint() + "/users/two-factor/recover",
         queryParameters: {
         queryParameters: {
           "sessionID": sessionID,
           "sessionID": sessionID,
+          "twoFactorType": twoFactorTypeToString(type),
         },
         },
       );
       );
       if (response.statusCode == 200) {
       if (response.statusCode == 200) {
@@ -778,6 +784,7 @@ class UserService {
           MaterialPageRoute(
           MaterialPageRoute(
             builder: (BuildContext context) {
             builder: (BuildContext context) {
               return TwoFactorRecoveryPage(
               return TwoFactorRecoveryPage(
+                type,
                 sessionID,
                 sessionID,
                 response.data["encryptedSecret"],
                 response.data["encryptedSecret"],
                 response.data["secretDecryptionNonce"],
                 response.data["secretDecryptionNonce"],
@@ -788,6 +795,7 @@ class UserService {
         );
         );
       }
       }
     } on DioError catch (e) {
     } on DioError catch (e) {
+      await dialog.hide();
       _logger.severe(e);
       _logger.severe(e);
       if (e.response != null && e.response!.statusCode == 404) {
       if (e.response != null && e.response!.statusCode == 404) {
         showToast(context, context.l10n.sessionExpired);
         showToast(context, context.l10n.sessionExpired);
@@ -809,6 +817,7 @@ class UserService {
         );
         );
       }
       }
     } catch (e) {
     } catch (e) {
+      await dialog.hide();
       _logger.severe(e);
       _logger.severe(e);
       // ignore: unawaited_futures
       // ignore: unawaited_futures
       showErrorDialog(
       showErrorDialog(
@@ -823,6 +832,7 @@ class UserService {
 
 
   Future<void> removeTwoFactor(
   Future<void> removeTwoFactor(
     BuildContext context,
     BuildContext context,
+    TwoFactorType type,
     String sessionID,
     String sessionID,
     String recoveryKey,
     String recoveryKey,
     String encryptedSecret,
     String encryptedSecret,
@@ -857,11 +867,15 @@ class UserService {
       return;
       return;
     }
     }
     try {
     try {
+      final secretValue = type == TwoFactorType.passkey
+          ? utf8.decode(base64.decode(secret))
+          : secret;
       final response = await _dio.post(
       final response = await _dio.post(
         _config.getHttpEndpoint() + "/users/two-factor/remove",
         _config.getHttpEndpoint() + "/users/two-factor/remove",
         data: {
         data: {
           "sessionID": sessionID,
           "sessionID": sessionID,
-          "secret": secret,
+          "secret": secretValue,
+          "twoFactorType": twoFactorTypeToString(type),
         },
         },
       );
       );
       if (response.statusCode == 200) {
       if (response.statusCode == 200) {
@@ -881,6 +895,7 @@ class UserService {
         );
         );
       }
       }
     } on DioError catch (e) {
     } on DioError catch (e) {
+      await dialog.hide();
       _logger.severe(e);
       _logger.severe(e);
       if (e.response != null && e.response!.statusCode == 404) {
       if (e.response != null && e.response!.statusCode == 404) {
         showToast(context, "Session expired");
         showToast(context, "Session expired");
@@ -902,6 +917,7 @@ class UserService {
         );
         );
       }
       }
     } catch (e) {
     } catch (e) {
+      await dialog.hide();
       _logger.severe(e);
       _logger.severe(e);
       // ignore: unawaited_futures
       // ignore: unawaited_futures
       showErrorDialog(
       showErrorDialog(

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

@@ -1,9 +1,11 @@
 import 'dart:convert';
 import 'dart:convert';
 
 
 import 'package:ente_auth/core/configuration.dart';
 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/l10n/l10n.dart';
+import 'package:ente_auth/models/account/two_factor.dart';
 import 'package:ente_auth/services/user_service.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:ente_auth/utils/dialog_util.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
@@ -99,30 +101,50 @@ class _PasskeyPageState extends State<PasskeyPage> {
   }
   }
 
 
   Widget _getBody() {
   Widget _getBody() {
-    final l10n = context.l10n;
-
     return Center(
     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:async';
+import 'dart:convert';
 import 'dart:typed_data';
 import 'dart:typed_data';
 
 
 import 'package:ente_auth/core/configuration.dart';
 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/navigation_util.dart';
 import 'package:ente_auth/utils/toast_util.dart';
 import 'package:ente_auth/utils/toast_util.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
+import 'package:logging/logging.dart';
+import 'package:uuid/uuid.dart';
 
 
 class SecuritySectionWidget extends StatefulWidget {
 class SecuritySectionWidget extends StatefulWidget {
   const SecuritySectionWidget({Key? key}) : super(key: key);
   const SecuritySectionWidget({Key? key}) : super(key: key);
@@ -32,6 +35,7 @@ class SecuritySectionWidget extends StatefulWidget {
 class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
 class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
   final _config = Configuration.instance;
   final _config = Configuration.instance;
   late bool _hasLoggedIn;
   late bool _hasLoggedIn;
+  final Logger _logger = Logger('SecuritySectionWidget');
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -75,7 +79,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
             pressedColor: getEnteColorScheme(context).fillFaint,
             pressedColor: getEnteColorScheme(context).fillFaint,
             trailingIcon: Icons.chevron_right_outlined,
             trailingIcon: Icons.chevron_right_outlined,
             trailingIconIsMuted: true,
             trailingIconIsMuted: true,
-            onTap: () => PasskeyService.instance.openPasskeyPage(context),
+            onTap: () async => await onPasskeyClick(context),
           ),
           ),
         sectionOptionSpacing,
         sectionOptionSpacing,
         MenuItemWidget(
         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 {
   Future<void> updateEmailMFA(bool enableEmailMFA) async {
     try {
     try {
       final UserDetails details =
       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/l10n/l10n.dart';
+import 'package:ente_auth/models/account/two_factor.dart';
 import 'package:ente_auth/services/user_service.dart';
 import 'package:ente_auth/services/user_service.dart';
 import 'package:ente_auth/ui/lifecycle_event_handler.dart';
 import 'package:ente_auth/ui/lifecycle_event_handler.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
@@ -129,7 +130,11 @@ class _TwoFactorAuthenticationPageState
         GestureDetector(
         GestureDetector(
           behavior: HitTestBehavior.opaque,
           behavior: HitTestBehavior.opaque,
           onTap: () {
           onTap: () {
-            UserService.instance.recoverTwoFactor(context, widget.sessionID);
+            UserService.instance.recoverTwoFactor(
+              context,
+              widget.sessionID,
+              TwoFactorType.totp,
+            );
           },
           },
           child: Container(
           child: Container(
             padding: const EdgeInsets.all(10),
             padding: const EdgeInsets.all(10),

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

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

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

@@ -29,12 +29,14 @@ class MessageLookup extends MessageLookupByLibrary {
         "contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
         "contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
         "deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
         "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."),
             "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"),
         "editLocation": MessageLookupByLibrary.simpleMessage("Edit location"),
         "editsToLocationWillOnlyBeSeenWithinEnte":
         "editsToLocationWillOnlyBeSeenWithinEnte":
             MessageLookupByLibrary.simpleMessage(
             MessageLookupByLibrary.simpleMessage(
                 "Edits to location will only be seen within Ente"),
                 "Edits to location will only be seen within Ente"),
         "fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
         "fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "modifyYourQueryOrTrySearchingFor":
         "modifyYourQueryOrTrySearchingFor":
             MessageLookupByLibrary.simpleMessage(
             MessageLookupByLibrary.simpleMessage(
                 "Modify your query, or try searching for"),
                 "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?"),
             MessageLookupByLibrary.simpleMessage("Geteiltes Album löschen?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
         "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"),
             "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"),
         "deselectAll": MessageLookupByLibrary.simpleMessage("Alle abwählen"),
         "designedToOutlive":
         "designedToOutlive":
             MessageLookupByLibrary.simpleMessage("Entwickelt um zu bewahren"),
             MessageLookupByLibrary.simpleMessage("Entwickelt um zu bewahren"),
@@ -872,6 +873,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "locationName": MessageLookupByLibrary.simpleMessage("Standortname"),
         "locationName": MessageLookupByLibrary.simpleMessage("Standortname"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "Ein Standort-Tag gruppiert alle Fotos, die in einem Radius eines Fotos aufgenommen wurden"),
             "Ein Standort-Tag gruppiert alle Fotos, die in einem Radius eines Fotos aufgenommen wurden"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Sperren"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Sperren"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
             "Um den Sperrbildschirm zu aktivieren, legen Sie bitte den Geräte-Passcode oder die Bildschirmsperre in den Systemeinstellungen fest."),
             "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?"),
             MessageLookupByLibrary.simpleMessage("Delete shared album?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
         "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"),
             "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"),
         "deselectAll": MessageLookupByLibrary.simpleMessage("Deselect all"),
         "designedToOutlive":
         "designedToOutlive":
             MessageLookupByLibrary.simpleMessage("Designed to outlive"),
             MessageLookupByLibrary.simpleMessage("Designed to outlive"),
@@ -797,8 +798,6 @@ class MessageLookup extends MessageLookupByLibrary {
             "Kindly help us with this information"),
             "Kindly help us with this information"),
         "language": MessageLookupByLibrary.simpleMessage("Language"),
         "language": MessageLookupByLibrary.simpleMessage("Language"),
         "lastUpdated": MessageLookupByLibrary.simpleMessage("Last updated"),
         "lastUpdated": MessageLookupByLibrary.simpleMessage("Last updated"),
-        "launchPasskeyUrlAgain":
-            MessageLookupByLibrary.simpleMessage("Launch passkey URL again"),
         "leave": MessageLookupByLibrary.simpleMessage("Leave"),
         "leave": MessageLookupByLibrary.simpleMessage("Leave"),
         "leaveAlbum": MessageLookupByLibrary.simpleMessage("Leave album"),
         "leaveAlbum": MessageLookupByLibrary.simpleMessage("Leave album"),
         "leaveFamily": MessageLookupByLibrary.simpleMessage("Leave family"),
         "leaveFamily": MessageLookupByLibrary.simpleMessage("Leave family"),
@@ -848,6 +847,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "locationName": MessageLookupByLibrary.simpleMessage("Location name"),
         "locationName": MessageLookupByLibrary.simpleMessage("Location name"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "A location tag groups all photos that were taken within some radius of a photo"),
             "A location tag groups all photos that were taken within some radius of a photo"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Lock"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Lock"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
             "To enable lockscreen, please setup device passcode or screen lock in your system settings."),
             "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?"),
             MessageLookupByLibrary.simpleMessage("¿Borrar álbum compartido?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
         "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"),
             "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":
         "deselectAll":
             MessageLookupByLibrary.simpleMessage("Deseleccionar todo"),
             MessageLookupByLibrary.simpleMessage("Deseleccionar todo"),
         "designedToOutlive":
         "designedToOutlive":
@@ -761,6 +762,7 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("Nombre de la ubicación"),
             MessageLookupByLibrary.simpleMessage("Nombre de la ubicación"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "Una etiqueta de ubicación agrupa todas las fotos que fueron tomadas dentro de un radio de una foto"),
             "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"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Bloquear"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
         "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."),
             "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é ?"),
             "Supprimer l\'album partagé ?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
         "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"),
             "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":
         "deselectAll":
             MessageLookupByLibrary.simpleMessage("Tout déselectionner"),
             MessageLookupByLibrary.simpleMessage("Tout déselectionner"),
         "designedToOutlive":
         "designedToOutlive":
@@ -870,6 +871,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "locationName": MessageLookupByLibrary.simpleMessage("Nom du lieu"),
         "locationName": MessageLookupByLibrary.simpleMessage("Nom du lieu"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "Un tag d\'emplacement regroupe toutes les photos qui ont été prises dans un certain rayon d\'une photo"),
             "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"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Verrouiller"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
         "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."),
             "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?"),
             "Eliminare l\'album condiviso?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
         "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"),
             "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":
         "deselectAll":
             MessageLookupByLibrary.simpleMessage("Deseleziona tutti"),
             MessageLookupByLibrary.simpleMessage("Deseleziona tutti"),
         "designedToOutlive":
         "designedToOutlive":
@@ -838,6 +839,7 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("Nome della località"),
             MessageLookupByLibrary.simpleMessage("Nome della località"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "Un tag di localizzazione raggruppa tutte le foto scattate entro il raggio di una foto"),
             "Un tag di localizzazione raggruppa tutte le foto scattate entro il raggio di una foto"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Blocca"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Blocca"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
             "Per abilitare la schermata di blocco, configura il codice di accesso del dispositivo o il blocco schermo nelle impostazioni di sistema."),
             "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"),
         "contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
         "deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
         "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."),
             "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"),
         "editLocation": MessageLookupByLibrary.simpleMessage("Edit location"),
         "editsToLocationWillOnlyBeSeenWithinEnte":
         "editsToLocationWillOnlyBeSeenWithinEnte":
             MessageLookupByLibrary.simpleMessage(
             MessageLookupByLibrary.simpleMessage(
                 "Edits to location will only be seen within Ente"),
                 "Edits to location will only be seen within Ente"),
         "fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
         "fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "modifyYourQueryOrTrySearchingFor":
         "modifyYourQueryOrTrySearchingFor":
             MessageLookupByLibrary.simpleMessage(
             MessageLookupByLibrary.simpleMessage(
                 "Modify your query, or try searching for"),
                 "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?"),
             MessageLookupByLibrary.simpleMessage("Gedeeld album verwijderen?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
         "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"),
             "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":
         "deselectAll":
             MessageLookupByLibrary.simpleMessage("Alles deselecteren"),
             MessageLookupByLibrary.simpleMessage("Alles deselecteren"),
         "designedToOutlive": MessageLookupByLibrary.simpleMessage(
         "designedToOutlive": MessageLookupByLibrary.simpleMessage(
@@ -880,6 +881,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "locationName": MessageLookupByLibrary.simpleMessage("Locatie naam"),
         "locationName": MessageLookupByLibrary.simpleMessage("Locatie naam"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "Een locatie tag groept alle foto\'s die binnen een bepaalde straal van een foto zijn genomen"),
             "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"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Vergrendel"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
             "Om vergrendelscherm in te schakelen, moet u een toegangscode of schermvergrendeling instellen in uw systeeminstellingen."),
             "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."),
             "Vi er lei oss for at du forlater oss. Gi oss gjerne en tilbakemelding så vi kan forbedre oss."),
         "deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
         "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."),
             "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"),
         "editLocation": MessageLookupByLibrary.simpleMessage("Edit location"),
         "editsToLocationWillOnlyBeSeenWithinEnte":
         "editsToLocationWillOnlyBeSeenWithinEnte":
             MessageLookupByLibrary.simpleMessage(
             MessageLookupByLibrary.simpleMessage(
@@ -57,6 +58,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
         "kindlyHelpUsWithThisInformation": MessageLookupByLibrary.simpleMessage(
         "kindlyHelpUsWithThisInformation": MessageLookupByLibrary.simpleMessage(
             "Vær vennlig og hjelp oss med denne informasjonen"),
             "Vær vennlig og hjelp oss med denne informasjonen"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "modifyYourQueryOrTrySearchingFor":
         "modifyYourQueryOrTrySearchingFor":
             MessageLookupByLibrary.simpleMessage(
             MessageLookupByLibrary.simpleMessage(
                 "Modify your query, or try searching for"),
                 "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"),
             "Inna, niewymieniona wyżej przyczyna"),
         "deleteRequestSLAText": MessageLookupByLibrary.simpleMessage(
         "deleteRequestSLAText": MessageLookupByLibrary.simpleMessage(
             "Twoje żądanie zostanie przetworzone w ciągu 72 godzin."),
             "Twoje żądanie zostanie przetworzone w ciągu 72 godzin."),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "doThisLater": MessageLookupByLibrary.simpleMessage("Spróbuj później"),
         "doThisLater": MessageLookupByLibrary.simpleMessage("Spróbuj później"),
         "editLocation": MessageLookupByLibrary.simpleMessage("Edit location"),
         "editLocation": MessageLookupByLibrary.simpleMessage("Edit location"),
         "editsToLocationWillOnlyBeSeenWithinEnte":
         "editsToLocationWillOnlyBeSeenWithinEnte":
@@ -116,6 +117,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
         "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
         "kindlyHelpUsWithThisInformation":
         "kindlyHelpUsWithThisInformation":
             MessageLookupByLibrary.simpleMessage("Pomóż nam z tą informacją"),
             MessageLookupByLibrary.simpleMessage("Pomóż nam z tą informacją"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "logInLabel": MessageLookupByLibrary.simpleMessage("Zaloguj się"),
         "logInLabel": MessageLookupByLibrary.simpleMessage("Zaloguj się"),
         "moderateStrength": MessageLookupByLibrary.simpleMessage("Umiarkowana"),
         "moderateStrength": MessageLookupByLibrary.simpleMessage("Umiarkowana"),
         "modifyYourQueryOrTrySearchingFor":
         "modifyYourQueryOrTrySearchingFor":

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

@@ -558,6 +558,7 @@ class MessageLookup extends MessageLookupByLibrary {
             "Excluir álbum compartilhado?"),
             "Excluir álbum compartilhado?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
             "O álbum será apagado para todos\n\nVocê perderá o acesso a fotos compartilhadas neste álbum que pertencem aos outros"),
             "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"),
         "deselectAll": MessageLookupByLibrary.simpleMessage("Desmarcar todos"),
         "designedToOutlive":
         "designedToOutlive":
             MessageLookupByLibrary.simpleMessage("Feito para ter logenvidade"),
             MessageLookupByLibrary.simpleMessage("Feito para ter logenvidade"),
@@ -820,8 +821,6 @@ class MessageLookup extends MessageLookupByLibrary {
         "language": MessageLookupByLibrary.simpleMessage("Idioma"),
         "language": MessageLookupByLibrary.simpleMessage("Idioma"),
         "lastUpdated":
         "lastUpdated":
             MessageLookupByLibrary.simpleMessage("Última atualização"),
             MessageLookupByLibrary.simpleMessage("Última atualização"),
-        "launchPasskeyUrlAgain": MessageLookupByLibrary.simpleMessage(
-            "Iniciar a URL de chave de acesso novamente"),
         "leave": MessageLookupByLibrary.simpleMessage("Sair"),
         "leave": MessageLookupByLibrary.simpleMessage("Sair"),
         "leaveAlbum": MessageLookupByLibrary.simpleMessage("Sair do álbum"),
         "leaveAlbum": MessageLookupByLibrary.simpleMessage("Sair do álbum"),
         "leaveFamily": MessageLookupByLibrary.simpleMessage("Sair da família"),
         "leaveFamily": MessageLookupByLibrary.simpleMessage("Sair da família"),
@@ -873,6 +872,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "locationName": MessageLookupByLibrary.simpleMessage("Nome do Local"),
         "locationName": MessageLookupByLibrary.simpleMessage("Nome do Local"),
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
         "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage(
             "Uma tag em grupo de todas as fotos que foram tiradas dentro de algum raio de uma foto"),
             "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"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Bloquear"),
         "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage(
         "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."),
             "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("要删除共享相册吗?"),
         "deleteSharedAlbum": MessageLookupByLibrary.simpleMessage("要删除共享相册吗?"),
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
         "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage(
             "将为所有人删除相册\n\n您将无法访问此相册中他人拥有的共享照片"),
             "将为所有人删除相册\n\n您将无法访问此相册中他人拥有的共享照片"),
+        "descriptions": MessageLookupByLibrary.simpleMessage("Descriptions"),
         "deselectAll": MessageLookupByLibrary.simpleMessage("取消全选"),
         "deselectAll": MessageLookupByLibrary.simpleMessage("取消全选"),
         "designedToOutlive": MessageLookupByLibrary.simpleMessage("经久耐用"),
         "designedToOutlive": MessageLookupByLibrary.simpleMessage("经久耐用"),
         "details": MessageLookupByLibrary.simpleMessage("详情"),
         "details": MessageLookupByLibrary.simpleMessage("详情"),
@@ -673,8 +674,6 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("请帮助我们了解这个信息"),
             MessageLookupByLibrary.simpleMessage("请帮助我们了解这个信息"),
         "language": MessageLookupByLibrary.simpleMessage("语言"),
         "language": MessageLookupByLibrary.simpleMessage("语言"),
         "lastUpdated": MessageLookupByLibrary.simpleMessage("最后更新"),
         "lastUpdated": MessageLookupByLibrary.simpleMessage("最后更新"),
-        "launchPasskeyUrlAgain":
-            MessageLookupByLibrary.simpleMessage("再次启动 通行密钥 URL"),
         "leave": MessageLookupByLibrary.simpleMessage("离开"),
         "leave": MessageLookupByLibrary.simpleMessage("离开"),
         "leaveAlbum": MessageLookupByLibrary.simpleMessage("离开相册"),
         "leaveAlbum": MessageLookupByLibrary.simpleMessage("离开相册"),
         "leaveFamily": MessageLookupByLibrary.simpleMessage("离开家庭计划"),
         "leaveFamily": MessageLookupByLibrary.simpleMessage("离开家庭计划"),
@@ -717,6 +716,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "locationName": MessageLookupByLibrary.simpleMessage("地点名称"),
         "locationName": MessageLookupByLibrary.simpleMessage("地点名称"),
         "locationTagFeatureDescription":
         "locationTagFeatureDescription":
             MessageLookupByLibrary.simpleMessage("位置标签将在照片的某个半径范围内拍摄的所有照片进行分组"),
             MessageLookupByLibrary.simpleMessage("位置标签将在照片的某个半径范围内拍摄的所有照片进行分组"),
+        "locations": MessageLookupByLibrary.simpleMessage("Locations"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("锁定"),
         "lockButtonLabel": MessageLookupByLibrary.simpleMessage("锁定"),
         "lockScreenEnablePreSteps":
         "lockScreenEnablePreSteps":
             MessageLookupByLibrary.simpleMessage("要启用锁屏,请在系统设置中设置设备密码或屏幕锁定。"),
             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`
   /// `File types and names`
   String get fileTypesAndNames {
   String get fileTypesAndNames {
     return Intl.message(
     return Intl.message(
@@ -6949,16 +6939,6 @@ class S {
     );
     );
   }
   }
 
 
-  /// `Locations`
-  String get locations {
-    return Intl.message(
-      'Locations',
-      name: 'locations',
-      desc: '',
-      args: [],
-    );
-  }
-
   /// `Moments`
   /// `Moments`
   String get moments {
   String get moments {
     return Intl.message(
     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`
   /// `Passkey`
   String get passkey {
   String get passkey {
     return Intl.message(
     return Intl.message(
@@ -8427,6 +8397,26 @@ class S {
       args: [],
       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> {
 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",
   "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente",
   "cleanUncategorized": "Clean Uncategorized",
   "cleanUncategorized": "Clean Uncategorized",
   "waitingForVerification": "Waiting for verification...",
   "waitingForVerification": "Waiting for verification...",
-  "launchPasskeyUrlAgain": "Launch passkey URL again",
   "passkey": "Passkey",
   "passkey": "Passkey",
   "passkeyAuthTitle": "Passkey verification",
   "passkeyAuthTitle": "Passkey verification",
   "verifyPasskey": "Verify passkey",
   "verifyPasskey": "Verify passkey",

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

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