diff --git a/lib/core/errors.dart b/lib/core/errors.dart index 634988e0d..a9e175b88 100644 --- a/lib/core/errors.dart +++ b/lib/core/errors.dart @@ -7,8 +7,8 @@ enum InvalidReason { livePhotoVideoMissing, thumbnailMissing, unknown, - } + extension InvalidReasonExn on InvalidReason { bool get isLivePhotoErr => this == InvalidReason.livePhotoToImageTypeChanged || @@ -73,6 +73,8 @@ class InvalidStateError extends AssertionError { class KeyDerivationError extends Error {} +class LoginKeyDerivationError extends Error {} + class SrpSetupNotCompleteError extends Error {} class SharingNotPermittedForFreeAccountsError extends Error {} diff --git a/lib/generated/intl/messages_cs.dart b/lib/generated/intl/messages_cs.dart index 815bedb4a..a0b3b9f7d 100644 --- a/lib/generated/intl/messages_cs.dart +++ b/lib/generated/intl/messages_cs.dart @@ -24,6 +24,7 @@ class MessageLookup extends MessageLookupByLibrary { static Map _notInlinedMessages(_) => { "addToHiddenAlbum": MessageLookupByLibrary.simpleMessage("Add to hidden album"), + "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."), "fileTypes": MessageLookupByLibrary.simpleMessage("File types"), diff --git a/lib/generated/intl/messages_de.dart b/lib/generated/intl/messages_de.dart index 1c31ebad2..1b935485f 100644 --- a/lib/generated/intl/messages_de.dart +++ b/lib/generated/intl/messages_de.dart @@ -442,6 +442,7 @@ class MessageLookup extends MessageLookupByLibrary { "contactSupport": MessageLookupByLibrary.simpleMessage("Support kontaktieren"), "contactToManageSubscription": m10, + "contacts": MessageLookupByLibrary.simpleMessage("Contacts"), "continueLabel": MessageLookupByLibrary.simpleMessage("Weiter"), "continueOnFreeTrial": MessageLookupByLibrary.simpleMessage( "Mit kostenloser Testversion fortfahren"), diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 5cbdf629e..ad75c50ab 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -441,6 +441,7 @@ class MessageLookup extends MessageLookupByLibrary { "contactSupport": MessageLookupByLibrary.simpleMessage("Contact support"), "contactToManageSubscription": m10, + "contacts": MessageLookupByLibrary.simpleMessage("Contacts"), "contents": MessageLookupByLibrary.simpleMessage("Contents"), "continueLabel": MessageLookupByLibrary.simpleMessage("Continue"), "continueOnFreeTrial": @@ -879,6 +880,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("No hidden photos or videos"), "noImagesWithLocation": MessageLookupByLibrary.simpleMessage("No images with location"), + "noInternetConnection": + MessageLookupByLibrary.simpleMessage("No internet connection"), "noPhotosAreBeingBackedUpRightNow": MessageLookupByLibrary.simpleMessage( "No photos are being backed up right now"), @@ -926,7 +929,6 @@ class MessageLookup extends MessageLookupByLibrary { "paymentFailedTalkToProvider": m33, "paymentFailedWithReason": m34, "pendingSync": MessageLookupByLibrary.simpleMessage("Pending sync"), - "people": MessageLookupByLibrary.simpleMessage("People"), "peopleUsingYourCode": MessageLookupByLibrary.simpleMessage("People using your code"), "permDeleteWarning": MessageLookupByLibrary.simpleMessage( @@ -950,6 +952,9 @@ class MessageLookup extends MessageLookupByLibrary { "playStoreFreeTrialValidTill": m35, "playstoreSubscription": MessageLookupByLibrary.simpleMessage("PlayStore subscription"), + "pleaseCheckYourInternetConnectionAndTryAgain": + MessageLookupByLibrary.simpleMessage( + "Please check your internet connection and try again."), "pleaseContactSupportAndWeWillBeHappyToHelp": MessageLookupByLibrary.simpleMessage( "Please contact support@ente.io and we will be happy to help!"), @@ -1120,7 +1125,7 @@ class MessageLookup extends MessageLookupByLibrary { "Albums, file names, and types"), "searchHint4": MessageLookupByLibrary.simpleMessage("Location"), "searchHint5": MessageLookupByLibrary.simpleMessage( - "Coming soon: Photo contents, faces"), + "Coming soon: Faces & magic search ✨"), "searchHintText": MessageLookupByLibrary.simpleMessage( "Albums, months, days, years, ..."), "searchLocationEmptySection": MessageLookupByLibrary.simpleMessage( diff --git a/lib/generated/intl/messages_es.dart b/lib/generated/intl/messages_es.dart index 9d6829d71..499277175 100644 --- a/lib/generated/intl/messages_es.dart +++ b/lib/generated/intl/messages_es.dart @@ -398,6 +398,7 @@ class MessageLookup extends MessageLookupByLibrary { "contactSupport": MessageLookupByLibrary.simpleMessage("Contactar con soporte"), "contactToManageSubscription": m10, + "contacts": MessageLookupByLibrary.simpleMessage("Contacts"), "continueLabel": MessageLookupByLibrary.simpleMessage("Continuar"), "continueOnFreeTrial": MessageLookupByLibrary.simpleMessage( "Continuar con el plan gratuito"), diff --git a/lib/generated/intl/messages_fr.dart b/lib/generated/intl/messages_fr.dart index 264b5dd96..79391df74 100644 --- a/lib/generated/intl/messages_fr.dart +++ b/lib/generated/intl/messages_fr.dart @@ -140,6 +140,9 @@ class MessageLookup extends MessageLookupByLibrary { static String m41(endDate) => "Renouvellement le ${endDate}"; + static String m64(count) => + "${Intl.plural(count, one: '${count} résultat trouvé', other: '${count} résultats trouvés')}"; + static String m42(count) => "${count} sélectionné(s)"; static String m43(count, yourCount) => @@ -189,6 +192,8 @@ class MessageLookup extends MessageLookupByLibrary { static String m59(count) => "${Intl.plural(count, zero: '0 jour', one: '1 jour', other: '${count} jours')}"; + static String m65(endDate) => "Valable jusqu\'au ${endDate}"; + static String m60(email) => "Vérifier ${email}"; static String m61(email) => @@ -223,6 +228,11 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Ajouter la localisation"), "addLocationButton": MessageLookupByLibrary.simpleMessage("Ajouter"), "addMore": MessageLookupByLibrary.simpleMessage("Ajouter Plus"), + "addNew": MessageLookupByLibrary.simpleMessage("Ajouter un nouveau"), + "addOnPageSubtitle": MessageLookupByLibrary.simpleMessage( + "Détails des modules complémentaires"), + "addOns": + MessageLookupByLibrary.simpleMessage("Modules complémentaires"), "addPhotos": MessageLookupByLibrary.simpleMessage("Ajouter des photos"), "addSelected": MessageLookupByLibrary.simpleMessage("Ajouter la sélection"), @@ -233,6 +243,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Ajouter à un album masqué"), "addViewer": MessageLookupByLibrary.simpleMessage("Ajouter un observateur"), + "addYourPhotosNow": MessageLookupByLibrary.simpleMessage( + "Ajoutez vos photos maintenant"), "addedAs": MessageLookupByLibrary.simpleMessage("Ajouté comme"), "addedBy": m1, "addedSuccessfullyTo": m2, @@ -356,6 +368,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Paramètres de la sauvegarde"), "backupVideos": MessageLookupByLibrary.simpleMessage("Sauvegarde des vidéos"), + "blackFridaySale": + MessageLookupByLibrary.simpleMessage("Offre Black Friday"), "blog": MessageLookupByLibrary.simpleMessage("Blog"), "cachedData": MessageLookupByLibrary.simpleMessage("Données mises en cache"), @@ -446,6 +460,8 @@ class MessageLookup extends MessageLookupByLibrary { "contactSupport": MessageLookupByLibrary.simpleMessage("Contacter l\'assistance"), "contactToManageSubscription": m10, + "contacts": MessageLookupByLibrary.simpleMessage("Contacts"), + "contents": MessageLookupByLibrary.simpleMessage("Contenus"), "continueLabel": MessageLookupByLibrary.simpleMessage("Continuer"), "continueOnFreeTrial": MessageLookupByLibrary.simpleMessage( "Poursuivre avec la version d\'essai gratuite"), @@ -510,7 +526,7 @@ class MessageLookup extends MessageLookupByLibrary { "Ceci supprimera tous les albums vides. Ceci est utile lorsque vous voulez réduire l\'encombrement dans votre liste d\'albums."), "deleteAll": MessageLookupByLibrary.simpleMessage("Tout Supprimer"), "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."), + "Ce compte est lié à d\'autres applications ente, si vous en utilisez une.\\n\\nVos données téléchargées, dans toutes les applications ente, seront planifiées pour suppression, et votre compte sera définitivement supprimé."), "deleteEmailRequest": MessageLookupByLibrary.simpleMessage( "Veuillez envoyer un e-mail à account-deletion@ente.io à partir de votre adresse e-mail enregistrée."), "deleteEmptyAlbums": @@ -658,6 +674,7 @@ class MessageLookup extends MessageLookupByLibrary { "exportLogs": MessageLookupByLibrary.simpleMessage("Exporter les logs"), "exportYourData": MessageLookupByLibrary.simpleMessage("Exportez vos données"), + "faces": MessageLookupByLibrary.simpleMessage("Visages"), "failedToApplyCode": MessageLookupByLibrary.simpleMessage( "Impossible d\'appliquer le code"), "failedToCancel": @@ -689,7 +706,9 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Ajouter une description..."), "fileSavedToGallery": MessageLookupByLibrary.simpleMessage( "Fichier enregistré dans la galerie"), - "fileTypes": MessageLookupByLibrary.simpleMessage("File types"), + "fileTypes": MessageLookupByLibrary.simpleMessage("Types de fichiers"), + "fileTypesAndNames": + MessageLookupByLibrary.simpleMessage("Types et noms de fichiers"), "filesBackedUpFromDevice": m19, "filesBackedUpInAlbum": m20, "filesDeleted": @@ -729,6 +748,10 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Accorder la permission"), "groupNearbyPhotos": MessageLookupByLibrary.simpleMessage( "Grouper les photos à proximité"), + "hearUsExplanation": MessageLookupByLibrary.simpleMessage( + "Nous ne suivons pas les installations d\'applications. Il serait utile que vous nous disiez comment vous nous avez trouvés !"), + "hearUsWhereTitle": MessageLookupByLibrary.simpleMessage( + "Comment avez-vous entendu parler de Ente? (facultatif)"), "hidden": MessageLookupByLibrary.simpleMessage("Masqué"), "hide": MessageLookupByLibrary.simpleMessage("Masquer"), "hiding": MessageLookupByLibrary.simpleMessage("Masquage en cours..."), @@ -810,6 +833,7 @@ class MessageLookup extends MessageLookupByLibrary { "linkHasExpired": MessageLookupByLibrary.simpleMessage("Le lien a expiré"), "linkNeverExpires": MessageLookupByLibrary.simpleMessage("Jamais"), + "livePhotos": MessageLookupByLibrary.simpleMessage("Photos en direct"), "loadMessage1": MessageLookupByLibrary.simpleMessage( "Vous pouvez partager votre abonnement avec votre famille"), "loadMessage2": MessageLookupByLibrary.simpleMessage( @@ -876,7 +900,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Sécurité moyenne"), "modifyYourQueryOrTrySearchingFor": MessageLookupByLibrary.simpleMessage( - "Modify your query, or try searching for"), + "Modifiez votre requête, ou essayez de rechercher"), + "moments": MessageLookupByLibrary.simpleMessage("Souvenirs"), "monthly": MessageLookupByLibrary.simpleMessage("Mensuel"), "moveItem": m30, "moveToAlbum": @@ -966,9 +991,12 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Supprimer définitivement"), "permanentlyDeleteFromDevice": MessageLookupByLibrary.simpleMessage( "Supprimer définitivement de l\'appareil ?"), + "photoDescriptions": + MessageLookupByLibrary.simpleMessage("Descriptions de la photo"), "photoGridSize": MessageLookupByLibrary.simpleMessage("Taille de la grille photo"), "photoSmallCase": MessageLookupByLibrary.simpleMessage("photo"), + "photos": MessageLookupByLibrary.simpleMessage("Photos"), "photosAddedByYouWillBeRemovedFromTheAlbum": MessageLookupByLibrary.simpleMessage( "Les photos ajoutées par vous seront retirées de l\'album"), @@ -1136,12 +1164,36 @@ class MessageLookup extends MessageLookupByLibrary { "scanThisBarcodeWithnyourAuthenticatorApp": MessageLookupByLibrary.simpleMessage( "Scannez ce code-barres avec\nvotre application d\'authentification"), + "searchAlbumsEmptySection": + MessageLookupByLibrary.simpleMessage("Albums"), "searchByAlbumNameHint": MessageLookupByLibrary.simpleMessage("Nom de l\'album"), "searchByExamples": MessageLookupByLibrary.simpleMessage( "• Noms d\'albums (par exemple \"Caméra\")\n• Types de fichiers (par exemple \"Vidéos\", \".gif\")\n• Années et mois (par exemple \"2022\", \"Janvier\")\n• Vacances (par exemple \"Noël\")\n• Descriptions de photos (par exemple \"#fun\")"), + "searchCaptionEmptySection": MessageLookupByLibrary.simpleMessage( + "Ajoutez des descriptions comme \"#trip\" dans les infos photo pour les retrouver ici plus rapidement"), + "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( + "Recherche par date, mois ou année"), + "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( + "Trouver toutes les photos d\'une personne"), + "searchFileTypesAndNamesEmptySection": + MessageLookupByLibrary.simpleMessage("Types et noms de fichiers"), + "searchHint1": MessageLookupByLibrary.simpleMessage( + "Recherche rapide, sur l\'appareil"), + "searchHint2": MessageLookupByLibrary.simpleMessage( + "Dates des photos, descriptions"), + "searchHint3": MessageLookupByLibrary.simpleMessage( + "Albums, noms de fichiers et types"), + "searchHint4": MessageLookupByLibrary.simpleMessage("Emplacement"), + "searchHint5": MessageLookupByLibrary.simpleMessage( + "Bientôt: Visages & recherche magique ✨"), "searchHintText": MessageLookupByLibrary.simpleMessage( "Albums, mois, jours, années, ..."), + "searchLocationEmptySection": MessageLookupByLibrary.simpleMessage( + "Grouper les photos qui sont prises dans un certain angle d\'une photo"), + "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( + "Invitez des gens, et vous verrez ici toutes les photos qu\'ils partagent"), + "searchResultCount": m64, "security": MessageLookupByLibrary.simpleMessage("Sécurité"), "selectAlbum": MessageLookupByLibrary.simpleMessage("Sélectionner album"), @@ -1400,6 +1452,8 @@ class MessageLookup extends MessageLookupByLibrary { "upgrade": MessageLookupByLibrary.simpleMessage("Améliorer"), "uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage( "Envoi des fichiers vers l\'album..."), + "upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage( + "Jusqu\'à 50% de réduction, jusqu\'au 4ème déc."), "usableReferralStorageInfo": MessageLookupByLibrary.simpleMessage( "Le stockage utilisable est limité par votre offre actuelle. Le stockage excédentaire deviendra automatiquement utilisable lorsque vous mettez à niveau votre offre."), "usePublicLinksForPeopleNotOnEnte": MessageLookupByLibrary.simpleMessage( @@ -1409,6 +1463,7 @@ class MessageLookup extends MessageLookupByLibrary { "useSelectedPhoto": MessageLookupByLibrary.simpleMessage( "Utiliser la photo sélectionnée"), "usedSpace": MessageLookupByLibrary.simpleMessage("Mémoire utilisée"), + "validTill": m65, "verificationFailedPleaseTryAgain": MessageLookupByLibrary.simpleMessage( "La vérification a échouée, veuillez réessayer"), @@ -1426,8 +1481,11 @@ class MessageLookup extends MessageLookupByLibrary { "verifyingRecoveryKey": MessageLookupByLibrary.simpleMessage( "Vérification de la clé de récupération..."), "videoSmallCase": MessageLookupByLibrary.simpleMessage("vidéo"), + "videos": MessageLookupByLibrary.simpleMessage("Vidéos"), "viewActiveSessions": MessageLookupByLibrary.simpleMessage( "Afficher les sessions actives"), + "viewAddOnButton": MessageLookupByLibrary.simpleMessage( + "Afficher les modules complémentaires"), "viewAll": MessageLookupByLibrary.simpleMessage("Tout afficher"), "viewAllExifData": MessageLookupByLibrary.simpleMessage( "Visualiser toutes les données EXIF"), @@ -1481,7 +1539,7 @@ class MessageLookup extends MessageLookupByLibrary { "youHaveSuccessfullyFreedUp": m63, "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage("Votre compte a été supprimé"), - "yourMap": MessageLookupByLibrary.simpleMessage("Your map"), + "yourMap": MessageLookupByLibrary.simpleMessage("Votre carte"), "yourPlanWasSuccessfullyDowngraded": MessageLookupByLibrary.simpleMessage( "Votre plan a été rétrogradé avec succès"), diff --git a/lib/generated/intl/messages_it.dart b/lib/generated/intl/messages_it.dart index 1659be2cb..359f6d569 100644 --- a/lib/generated/intl/messages_it.dart +++ b/lib/generated/intl/messages_it.dart @@ -446,6 +446,7 @@ class MessageLookup extends MessageLookupByLibrary { "contactSupport": MessageLookupByLibrary.simpleMessage("Contatta il supporto"), "contactToManageSubscription": m10, + "contacts": MessageLookupByLibrary.simpleMessage("Contacts"), "continueLabel": MessageLookupByLibrary.simpleMessage("Continua"), "continueOnFreeTrial": MessageLookupByLibrary.simpleMessage("Continua la prova gratuita"), diff --git a/lib/generated/intl/messages_ko.dart b/lib/generated/intl/messages_ko.dart index 653419bae..7c5259342 100644 --- a/lib/generated/intl/messages_ko.dart +++ b/lib/generated/intl/messages_ko.dart @@ -24,6 +24,7 @@ class MessageLookup extends MessageLookupByLibrary { static Map _notInlinedMessages(_) => { "addToHiddenAlbum": MessageLookupByLibrary.simpleMessage("Add to hidden album"), + "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."), "fileTypes": MessageLookupByLibrary.simpleMessage("File types"), diff --git a/lib/generated/intl/messages_nl.dart b/lib/generated/intl/messages_nl.dart index f2f316213..90ac6a215 100644 --- a/lib/generated/intl/messages_nl.dart +++ b/lib/generated/intl/messages_nl.dart @@ -441,6 +441,7 @@ class MessageLookup extends MessageLookupByLibrary { "contactSupport": MessageLookupByLibrary.simpleMessage("Contacteer klantenservice"), "contactToManageSubscription": m10, + "contacts": MessageLookupByLibrary.simpleMessage("Contacts"), "continueLabel": MessageLookupByLibrary.simpleMessage("Doorgaan"), "continueOnFreeTrial": MessageLookupByLibrary.simpleMessage( "Doorgaan met gratis proefversie"), diff --git a/lib/generated/intl/messages_no.dart b/lib/generated/intl/messages_no.dart index 4de30a91b..d162c4550 100644 --- a/lib/generated/intl/messages_no.dart +++ b/lib/generated/intl/messages_no.dart @@ -33,6 +33,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Bekreft sletting av konto"), "confirmDeletePrompt": MessageLookupByLibrary.simpleMessage( "Ja, jeg ønsker å slette denne kontoen og all dataen dens permanent."), + "contacts": MessageLookupByLibrary.simpleMessage("Contacts"), "deleteAccount": MessageLookupByLibrary.simpleMessage("Slett konto"), "deleteAccountFeedbackPrompt": MessageLookupByLibrary.simpleMessage( "Vi er lei oss for at du forlater oss. Gi oss gjerne en tilbakemelding så vi kan forbedre oss."), diff --git a/lib/generated/intl/messages_pl.dart b/lib/generated/intl/messages_pl.dart index 6fb8aa6f0..7a567715b 100644 --- a/lib/generated/intl/messages_pl.dart +++ b/lib/generated/intl/messages_pl.dart @@ -50,6 +50,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Powtórz hasło"), "contactSupport": MessageLookupByLibrary.simpleMessage( "Skontaktuj się z pomocą techniczną"), + "contacts": MessageLookupByLibrary.simpleMessage("Contacts"), "continueLabel": MessageLookupByLibrary.simpleMessage("Kontynuuj"), "createAccount": MessageLookupByLibrary.simpleMessage("Stwórz konto"), "createNewAccount": diff --git a/lib/generated/intl/messages_pt.dart b/lib/generated/intl/messages_pt.dart index a873f19d4..9ddbbd8d9 100644 --- a/lib/generated/intl/messages_pt.dart +++ b/lib/generated/intl/messages_pt.dart @@ -136,6 +136,7 @@ class MessageLookup extends MessageLookupByLibrary { "Confirme sua chave de recuperação"), "contactSupport": MessageLookupByLibrary.simpleMessage("Falar com o suporte"), + "contacts": MessageLookupByLibrary.simpleMessage("Contacts"), "continueLabel": MessageLookupByLibrary.simpleMessage("Continuar"), "copypasteThisCodentoYourAuthenticatorApp": MessageLookupByLibrary.simpleMessage( diff --git a/lib/generated/intl/messages_zh.dart b/lib/generated/intl/messages_zh.dart index 1e62fea04..9370da75e 100644 --- a/lib/generated/intl/messages_zh.dart +++ b/lib/generated/intl/messages_zh.dart @@ -127,6 +127,9 @@ class MessageLookup extends MessageLookupByLibrary { static String m41(endDate) => "在 ${endDate} 前续费"; + static String m64(count) => + "${Intl.plural(count, other: '已找到 ${count} 个结果')}"; + static String m42(count) => "已选择 ${count} 个"; static String m43(count, yourCount) => "选择了 ${count} 个 (您的 ${yourCount} 个)"; @@ -198,6 +201,7 @@ class MessageLookup extends MessageLookupByLibrary { "addLocation": MessageLookupByLibrary.simpleMessage("添加地点"), "addLocationButton": MessageLookupByLibrary.simpleMessage("添加"), "addMore": MessageLookupByLibrary.simpleMessage("添加更多"), + "addNew": MessageLookupByLibrary.simpleMessage("新建"), "addOnPageSubtitle": MessageLookupByLibrary.simpleMessage("附加组件详情"), "addOns": MessageLookupByLibrary.simpleMessage("附加组件"), "addPhotos": MessageLookupByLibrary.simpleMessage("添加照片"), @@ -206,6 +210,7 @@ class MessageLookup extends MessageLookupByLibrary { "addToEnte": MessageLookupByLibrary.simpleMessage("添加到 ente"), "addToHiddenAlbum": MessageLookupByLibrary.simpleMessage("添加到隐藏相册"), "addViewer": MessageLookupByLibrary.simpleMessage("添加查看者"), + "addYourPhotosNow": MessageLookupByLibrary.simpleMessage("立即添加您的照片"), "addedAs": MessageLookupByLibrary.simpleMessage("已添加为"), "addedBy": m1, "addedSuccessfullyTo": m2, @@ -308,6 +313,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("通过移动数据备份"), "backupSettings": MessageLookupByLibrary.simpleMessage("备份设置"), "backupVideos": MessageLookupByLibrary.simpleMessage("备份视频"), + "blackFridaySale": MessageLookupByLibrary.simpleMessage("黑色星期五特惠"), "blog": MessageLookupByLibrary.simpleMessage("博客"), "cachedData": MessageLookupByLibrary.simpleMessage("缓存数据"), "calculating": MessageLookupByLibrary.simpleMessage("正在计算..."), @@ -374,6 +380,8 @@ class MessageLookup extends MessageLookupByLibrary { "contactFamilyAdmin": m9, "contactSupport": MessageLookupByLibrary.simpleMessage("联系支持"), "contactToManageSubscription": m10, + "contacts": MessageLookupByLibrary.simpleMessage("联系人"), + "contents": MessageLookupByLibrary.simpleMessage("内容"), "continueLabel": MessageLookupByLibrary.simpleMessage("继续"), "continueOnFreeTrial": MessageLookupByLibrary.simpleMessage("继续免费试用"), "convertToAlbum": MessageLookupByLibrary.simpleMessage("转换为相册"), @@ -535,6 +543,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("此链接已过期。请选择新的过期时间或禁用链接过期。"), "exportLogs": MessageLookupByLibrary.simpleMessage("导出日志"), "exportYourData": MessageLookupByLibrary.simpleMessage("导出您的数据"), + "faces": MessageLookupByLibrary.simpleMessage("人脸"), "failedToApplyCode": MessageLookupByLibrary.simpleMessage("无法应用代码"), "failedToCancel": MessageLookupByLibrary.simpleMessage("取消失败"), "failedToDownloadVideo": MessageLookupByLibrary.simpleMessage("视频下载失败"), @@ -558,6 +567,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("无法将文件保存到相册"), "fileInfoAddDescHint": MessageLookupByLibrary.simpleMessage("添加说明..."), "fileSavedToGallery": MessageLookupByLibrary.simpleMessage("文件已保存到相册"), + "fileTypes": MessageLookupByLibrary.simpleMessage("文件类型"), + "fileTypesAndNames": MessageLookupByLibrary.simpleMessage("文件类型和名称"), "filesBackedUpFromDevice": m19, "filesBackedUpInAlbum": m20, "filesDeleted": MessageLookupByLibrary.simpleMessage("文件已删除"), @@ -655,6 +666,7 @@ class MessageLookup extends MessageLookupByLibrary { "linkExpiry": MessageLookupByLibrary.simpleMessage("链接过期"), "linkHasExpired": MessageLookupByLibrary.simpleMessage("链接已过期"), "linkNeverExpires": MessageLookupByLibrary.simpleMessage("永不"), + "livePhotos": MessageLookupByLibrary.simpleMessage("实况照片"), "loadMessage1": MessageLookupByLibrary.simpleMessage("您可以与家庭分享您的订阅"), "loadMessage2": MessageLookupByLibrary.simpleMessage("到目前为止,我们已经保存了1 000多万个回忆"), @@ -710,8 +722,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("移动端, 网页端, 桌面端"), "moderateStrength": MessageLookupByLibrary.simpleMessage("中等"), "modifyYourQueryOrTrySearchingFor": - MessageLookupByLibrary.simpleMessage( - "Modify your query, or try searching for"), + MessageLookupByLibrary.simpleMessage("修改您的查询,或尝试搜索"), + "moments": MessageLookupByLibrary.simpleMessage("瞬间"), "monthly": MessageLookupByLibrary.simpleMessage("每月"), "moveItem": m30, "moveToAlbum": MessageLookupByLibrary.simpleMessage("移动到相册"), @@ -784,8 +796,10 @@ class MessageLookup extends MessageLookupByLibrary { "permanentlyDelete": MessageLookupByLibrary.simpleMessage("永久删除"), "permanentlyDeleteFromDevice": MessageLookupByLibrary.simpleMessage("要从设备中永久删除吗?"), + "photoDescriptions": MessageLookupByLibrary.simpleMessage("照片说明"), "photoGridSize": MessageLookupByLibrary.simpleMessage("照片网格大小"), "photoSmallCase": MessageLookupByLibrary.simpleMessage("照片"), + "photos": MessageLookupByLibrary.simpleMessage("照片"), "photosAddedByYouWillBeRemovedFromTheAlbum": MessageLookupByLibrary.simpleMessage("您添加的照片将从相册中移除"), "pickCenterPoint": MessageLookupByLibrary.simpleMessage("选择中心点"), @@ -908,10 +922,29 @@ class MessageLookup extends MessageLookupByLibrary { "scanCode": MessageLookupByLibrary.simpleMessage("扫描代码"), "scanThisBarcodeWithnyourAuthenticatorApp": MessageLookupByLibrary.simpleMessage("用您的身份验证器应用\n扫描此条码"), + "searchAlbumsEmptySection": MessageLookupByLibrary.simpleMessage("相册"), "searchByAlbumNameHint": MessageLookupByLibrary.simpleMessage("相册名称"), "searchByExamples": MessageLookupByLibrary.simpleMessage( "• 相册名称(例如“相机”)\n• 文件类型(例如“视频”、“.gif”)\n• 年份和月份(例如“2022”、“一月”)\n• 假期(例如“圣诞节”)\n• 照片说明(例如“#和女儿独居,好开心啊”)"), + "searchCaptionEmptySection": MessageLookupByLibrary.simpleMessage( + "在照片信息中添加“#旅游”等描述,以便在此处快速找到它们"), + "searchDatesEmptySection": + MessageLookupByLibrary.simpleMessage("按日期搜索,月份或年份"), + "searchFaceEmptySection": + MessageLookupByLibrary.simpleMessage("查找一个人的所有照片"), + "searchFileTypesAndNamesEmptySection": + MessageLookupByLibrary.simpleMessage("文件类型和名称"), + "searchHint1": MessageLookupByLibrary.simpleMessage("在设备上快速搜索"), + "searchHint2": MessageLookupByLibrary.simpleMessage("照片日期、描述"), + "searchHint3": MessageLookupByLibrary.simpleMessage("相册、文件名和类型"), + "searchHint4": MessageLookupByLibrary.simpleMessage("位置"), + "searchHint5": MessageLookupByLibrary.simpleMessage("即将到来:面部和魔法搜索✨"), "searchHintText": MessageLookupByLibrary.simpleMessage("相册,月,日,年,..."), + "searchLocationEmptySection": + MessageLookupByLibrary.simpleMessage("在照片的一定半径内拍摄的几组照片"), + "searchPeopleEmptySection": + MessageLookupByLibrary.simpleMessage("邀请他人,您将在此看到他们分享的所有照片"), + "searchResultCount": m64, "security": MessageLookupByLibrary.simpleMessage("安全"), "selectAlbum": MessageLookupByLibrary.simpleMessage("选择相册"), "selectAll": MessageLookupByLibrary.simpleMessage("全选"), @@ -1113,6 +1146,8 @@ class MessageLookup extends MessageLookupByLibrary { "upgrade": MessageLookupByLibrary.simpleMessage("升级"), "uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage("正在将文件上传到相册..."), + "upto50OffUntil4thDec": + MessageLookupByLibrary.simpleMessage("最高五折优惠,直至12月4日。"), "usableReferralStorageInfo": MessageLookupByLibrary.simpleMessage( "可用存储空间受您当前计划的限制。 当您升级您的计划时,超出要求的存储空间将自动变为可用。"), "usePublicLinksForPeopleNotOnEnte": @@ -1133,6 +1168,7 @@ class MessageLookup extends MessageLookupByLibrary { "verifyingRecoveryKey": MessageLookupByLibrary.simpleMessage("正在验证恢复密钥..."), "videoSmallCase": MessageLookupByLibrary.simpleMessage("视频"), + "videos": MessageLookupByLibrary.simpleMessage("视频"), "viewActiveSessions": MessageLookupByLibrary.simpleMessage("查看活动会话"), "viewAddOnButton": MessageLookupByLibrary.simpleMessage("查看附加组件"), "viewAll": MessageLookupByLibrary.simpleMessage("查看全部"), @@ -1178,7 +1214,7 @@ class MessageLookup extends MessageLookupByLibrary { "youHaveSuccessfullyFreedUp": m63, "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage("您的账户已删除"), - "yourMap": MessageLookupByLibrary.simpleMessage("Your map"), + "yourMap": MessageLookupByLibrary.simpleMessage("您的地图"), "yourPlanWasSuccessfullyDowngraded": MessageLookupByLibrary.simpleMessage("您的计划已成功降级"), "yourPlanWasSuccessfullyUpgraded": diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 523110e70..1c2421f77 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -7995,10 +7995,10 @@ class S { ); } - /// `Coming soon: Photo contents, faces` + /// `Coming soon: Faces & magic search ✨` String get searchHint5 { return Intl.message( - 'Coming soon: Photo contents, faces', + 'Coming soon: Faces & magic search ✨', name: 'searchHint5', desc: '', args: [], @@ -8067,6 +8067,26 @@ class S { args: [], ); } + + /// `No internet connection` + String get noInternetConnection { + return Intl.message( + 'No internet connection', + name: 'noInternetConnection', + desc: '', + args: [], + ); + } + + /// `Please check your internet connection and try again.` + String get pleaseCheckYourInternetConnectionAndTryAgain { + return Intl.message( + 'Please check your internet connection and try again.', + name: 'pleaseCheckYourInternetConnectionAndTryAgain', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index f3fca6a0c..92b293cd9 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1149,5 +1149,7 @@ "@addNew": { "description": "Text to add a new item (location tag, album, caption etc)" }, - "contacts": "Contacts" + "contacts": "Contacts", + "noInternetConnection": "No internet connection", + "pleaseCheckYourInternetConnectionAndTryAgain": "Please check your internet connection and try again." } \ No newline at end of file diff --git a/lib/services/user_service.dart b/lib/services/user_service.dart index 13d085ca2..e52a69984 100644 --- a/lib/services/user_service.dart +++ b/lib/services/user_service.dart @@ -34,11 +34,10 @@ import "package:photos/ui/account/recovery_page.dart"; import 'package:photos/ui/account/two_factor_authentication_page.dart'; import 'package:photos/ui/account/two_factor_recovery_page.dart'; import 'package:photos/ui/account/two_factor_setup_page.dart'; -import "package:photos/ui/components/buttons/button_widget.dart"; +import "package:photos/ui/common/progress_dialog.dart"; import "package:photos/ui/tabs/home_widget.dart"; import 'package:photos/utils/crypto_util.dart'; import 'package:photos/utils/dialog_util.dart'; -import "package:photos/utils/email_util.dart"; import 'package:photos/utils/navigation_util.dart'; import 'package:photos/utils/toast_util.dart'; import "package:pointycastle/export.dart"; @@ -586,120 +585,92 @@ class UserService { BuildContext context, SrpAttributes srpAttributes, String userPassword, + ProgressDialog dialog, ) async { - final dialog = createProgressDialog( - context, - S.of(context).pleaseWait, - isDismissible: true, - ); - await dialog.show(); late Uint8List keyEncryptionKey; - try { - keyEncryptionKey = await CryptoUtil.deriveKey( - utf8.encode(userPassword) as Uint8List, - CryptoUtil.base642bin(srpAttributes.kekSalt), - srpAttributes.memLimit, - srpAttributes.opsLimit, - ); - final loginKey = await CryptoUtil.deriveLoginKey(keyEncryptionKey); - final Uint8List identity = Uint8List.fromList( - utf8.encode(srpAttributes.srpUserID), - ); - final Uint8List salt = base64Decode(srpAttributes.srpSalt); - final Uint8List password = loginKey; - final SecureRandom random = _getSecureRandom(); + _logger.finest('Start deriving key'); + keyEncryptionKey = await CryptoUtil.deriveKey( + utf8.encode(userPassword) as Uint8List, + CryptoUtil.base642bin(srpAttributes.kekSalt), + srpAttributes.memLimit, + srpAttributes.opsLimit, + ); + _logger.finest('keyDerivation done, derive LoginKey'); + final loginKey = await CryptoUtil.deriveLoginKey(keyEncryptionKey); + final Uint8List identity = Uint8List.fromList( + utf8.encode(srpAttributes.srpUserID), + ); + _logger.finest('longinKey derivation done'); + final Uint8List salt = base64Decode(srpAttributes.srpSalt); + final Uint8List password = loginKey; + final SecureRandom random = _getSecureRandom(); - final client = SRP6Client( - group: kDefaultSrpGroup, - digest: Digest('SHA-256'), - random: random, - ); + final client = SRP6Client( + group: kDefaultSrpGroup, + digest: Digest('SHA-256'), + random: random, + ); - final A = client.generateClientCredentials(salt, identity, password); - final createSessionResponse = await _dio.post( - _config.getHttpEndpoint() + "/users/srp/create-session", - data: { - "srpUserID": srpAttributes.srpUserID, - "srpA": base64Encode(SRP6Util.encodeBigInt(A!)), - }, - ); - final String sessionID = createSessionResponse.data["sessionID"]; - final String srpB = createSessionResponse.data["srpB"]; + final A = client.generateClientCredentials(salt, identity, password); + final createSessionResponse = await _dio.post( + _config.getHttpEndpoint() + "/users/srp/create-session", + data: { + "srpUserID": srpAttributes.srpUserID, + "srpA": base64Encode(SRP6Util.encodeBigInt(A!)), + }, + ); + final String sessionID = createSessionResponse.data["sessionID"]; + final String srpB = createSessionResponse.data["srpB"]; - final serverB = SRP6Util.decodeBigInt(base64Decode(srpB)); - // ignore: need to calculate secret to get M1, unused_local_variable - final clientS = client.calculateSecret(serverB); - final clientM = client.calculateClientEvidenceMessage(); - final response = await _dio.post( - _config.getHttpEndpoint() + "/users/srp/verify-session", - data: { - "sessionID": sessionID, - "srpUserID": srpAttributes.srpUserID, - "srpM1": base64Encode(SRP6Util.encodeBigInt(clientM!)), - }, - ); - if (response.statusCode == 200) { - Widget page; - final String twoFASessionID = response.data["twoFactorSessionID"]; - Configuration.instance.setVolatilePassword(userPassword); - if (twoFASessionID.isNotEmpty) { - setTwoFactor(value: true); - page = TwoFactorAuthenticationPage(twoFASessionID); - } else { - await _saveConfiguration(response); - if (Configuration.instance.getEncryptedToken() != null) { - await Configuration.instance.decryptSecretsAndGetKeyEncKey( - userPassword, - Configuration.instance.getKeyAttributes()!, - keyEncryptionKey: keyEncryptionKey, - ); - page = const HomeWidget(); - } else { - throw Exception("unexpected response during email verification"); - } - } - await dialog.hide(); - if (page is HomeWidget) { - Navigator.of(context).popUntil((route) => route.isFirst); - Bus.instance.fire(AccountConfiguredEvent()); - } else { - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (BuildContext context) { - return page; - }, - ), - (route) => route.isFirst, + final serverB = SRP6Util.decodeBigInt(base64Decode(srpB)); + // ignore: need to calculate secret to get M1, unused_local_variable + final clientS = client.calculateSecret(serverB); + final clientM = client.calculateClientEvidenceMessage(); + final response = await _dio.post( + _config.getHttpEndpoint() + "/users/srp/verify-session", + data: { + "sessionID": sessionID, + "srpUserID": srpAttributes.srpUserID, + "srpM1": base64Encode(SRP6Util.encodeBigInt(clientM!)), + }, + ); + if (response.statusCode == 200) { + Widget page; + final String twoFASessionID = response.data["twoFactorSessionID"]; + Configuration.instance.setVolatilePassword(userPassword); + if (twoFASessionID.isNotEmpty) { + setTwoFactor(value: true); + page = TwoFactorAuthenticationPage(twoFASessionID); + } else { + await _saveConfiguration(response); + if (Configuration.instance.getEncryptedToken() != null) { + await Configuration.instance.decryptSecretsAndGetKeyEncKey( + userPassword, + Configuration.instance.getKeyAttributes()!, + keyEncryptionKey: keyEncryptionKey, ); + page = const HomeWidget(); + } else { + throw Exception("unexpected response during email verification"); } - } else { - // should never reach here - throw Exception("unexpected response during email verification"); } - } on DioError catch (e, s) { await dialog.hide(); - if (e.response != null && e.response!.statusCode == 401) { - await _showContactSupportDialog( - context, - S.of(context).incorrectPasswordTitle, - S.of(context).pleaseTryAgain, - ); + if (page is HomeWidget) { + Navigator.of(context).popUntil((route) => route.isFirst); + Bus.instance.fire(AccountConfiguredEvent()); } else { - _logger.severe('failed to verify password', e, s); - await _showContactSupportDialog( - context, - S.of(context).oops, - S.of(context).verificationFailedPleaseTryAgain, + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (BuildContext context) { + return page; + }, + ), + (route) => route.isFirst, ); } - } catch (e, s) { - _logger.severe('failed to verify password', e, s); - await dialog.hide(); - await _showContactSupportDialog( - context, - S.of(context).oops, - S.of(context).verificationFailedPleaseTryAgain, - ); + } else { + // should never reach here + throw Exception("unexpected response during email verification"); } } @@ -1164,26 +1135,4 @@ class UserService { rethrow; } } - - Future _showContactSupportDialog( - BuildContext context, - String title, - String message, - ) async { - final dialogChoice = await showChoiceDialog( - context, - title: title, - body: message, - firstButtonLabel: S.of(context).contactSupport, - secondButtonLabel: S.of(context).ok, - ); - if (dialogChoice!.action == ButtonAction.first) { - await sendLogs( - context, - S.of(context).contactSupport, - "support@ente.io", - postShare: () {}, - ); - } - } } diff --git a/lib/ui/account/login_pwd_verification_page.dart b/lib/ui/account/login_pwd_verification_page.dart index f29827a31..e643b9b25 100644 --- a/lib/ui/account/login_pwd_verification_page.dart +++ b/lib/ui/account/login_pwd_verification_page.dart @@ -1,12 +1,17 @@ +import "package:dio/dio.dart"; import "package:flutter/foundation.dart"; import 'package:flutter/material.dart'; +import "package:logging/logging.dart"; import 'package:photos/core/configuration.dart'; +import "package:photos/core/errors.dart"; import "package:photos/generated/l10n.dart"; import "package:photos/models/api/user/srp.dart"; import "package:photos/services/user_service.dart"; import "package:photos/theme/ente_theme.dart"; import 'package:photos/ui/common/dynamic_fab.dart'; +import "package:photos/ui/components/buttons/button_widget.dart"; import "package:photos/utils/dialog_util.dart"; +import "package:photos/utils/email_util.dart"; // LoginPasswordVerificationPage is a page that allows the user to enter their password to verify their identity. // If the password is correct, then the user is either directed to @@ -31,6 +36,7 @@ class _LoginPasswordVerificationPageState String? email; bool _passwordInFocus = false; bool _passwordVisible = false; + final Logger _logger = Logger("LoginPasswordVerificationPage"); @override void initState() { @@ -85,11 +91,7 @@ class _LoginPasswordVerificationPageState buttonText: S.of(context).logInLabel, onPressedFunction: () async { FocusScope.of(context).unfocus(); - await UserService.instance.verifyEmailViaPassword( - context, - widget.srpAttributes, - _passwordController.text, - ); + await verifyPassword(context, _passwordController.text); }, ), floatingActionButtonLocation: fabLocation(), @@ -97,6 +99,94 @@ class _LoginPasswordVerificationPageState ); } + Future verifyPassword(BuildContext context, String password) async { + final dialog = createProgressDialog( + context, + S.of(context).pleaseWait, + isDismissible: true, + ); + await dialog.show(); + try { + await UserService.instance.verifyEmailViaPassword( + context, + widget.srpAttributes, + password, + dialog, + ); + } on DioError catch (e, s) { + await dialog.hide(); + if (e.response != null && e.response!.statusCode == 401) { + _logger.severe('server reject, failed verify SRP login', e, s); + await _showContactSupportDialog( + context, + S.of(context).incorrectPasswordTitle, + S.of(context).pleaseTryAgain, + ); + } else { + _logger.severe('API failure during SRP login', e, s); + if (e.type == DioErrorType.other) { + await _showContactSupportDialog( + context, + S.of(context).noInternetConnection, + S.of(context).pleaseCheckYourInternetConnectionAndTryAgain, + ); + } else { + await _showContactSupportDialog( + context, + S.of(context).somethingWentWrong, + S.of(context).verificationFailedPleaseTryAgain, + ); + } + } + } catch (e, s) { + _logger.severe('error while verifying password', e, s); + await dialog.hide(); + if (e is KeyDerivationError || e is LoginKeyDerivationError) { + final dialogChoice = await showChoiceDialog( + context, + title: S.of(context).recreatePasswordTitle, + body: S.of(context).recreatePasswordBody, + firstButtonLabel: S.of(context).useRecoveryKey, + ); + if (dialogChoice!.action == ButtonAction.first) { + await UserService.instance.sendOtt( + context, + email!, + isResetPasswordScreen: true, + ); + } + return; + } + await _showContactSupportDialog( + context, + S.of(context).oops, + S.of(context).verificationFailedPleaseTryAgain, + ); + } + } + + Future _showContactSupportDialog( + BuildContext context, + String title, + String message, + ) async { + final dialogChoice = await showChoiceDialog( + context, + title: title, + body: message, + firstButtonLabel: S.of(context).contactSupport, + secondButtonLabel: S.of(context).ok, + ); + if (dialogChoice!.action == ButtonAction.first) { + await sendLogs( + context, + S.of(context).contactSupport, + "support@ente.io", + postShare: () {}, + ); + } + } + Widget _getBody() { return Column( children: [ diff --git a/lib/utils/crypto_util.dart b/lib/utils/crypto_util.dart index c2f02b59b..3c6be311b 100644 --- a/lib/utils/crypto_util.dart +++ b/lib/utils/crypto_util.dart @@ -77,7 +77,7 @@ Future cryptoGenericHash(Map args) async { EncryptionResult chachaEncryptData(Map args) { final initPushResult = - Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]); + Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]); final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push( initPushResult.state, args["source"], @@ -102,7 +102,7 @@ Future chachaEncryptFile(Map args) async { final inputFile = sourceFile.openSync(mode: FileMode.read); final key = args["key"] ?? Sodium.cryptoSecretstreamXchacha20poly1305Keygen(); final initPushResult = - Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key); + Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key); var bytesRead = 0; var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage; while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) { @@ -156,7 +156,7 @@ Future chachaDecryptFile(Map args) async { final buffer = await inputFile.read(chunkSize); bytesRead += chunkSize; final pullResult = - Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null); + Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null); await destinationFile.writeAsBytes(pullResult.m, mode: FileMode.append); tag = pullResult.tag; } @@ -190,20 +190,22 @@ class CryptoUtil { Sodium.init(); } - static Uint8List base642bin(String b64, { + static Uint8List base642bin( + String b64, { String? ignore, int variant = Sodium.base64VariantOriginal, }) { return Sodium.base642bin(b64, ignore: ignore, variant: variant); } - static String bin2base64(Uint8List bin, { + static String bin2base64( + Uint8List bin, { bool urlSafe = false, }) { return Sodium.bin2base64( bin, variant: - urlSafe ? Sodium.base64VariantUrlsafe : Sodium.base64VariantOriginal, + urlSafe ? Sodium.base64VariantUrlsafe : Sodium.base64VariantOriginal, ); } @@ -237,9 +239,11 @@ class CryptoUtil { // Decrypts the given cipher, with the given key and nonce using XSalsa20 // (w Poly1305 MAC). - static Future decrypt(Uint8List cipher, - Uint8List key, - Uint8List nonce,) async { + static Future decrypt( + Uint8List cipher, + Uint8List key, + Uint8List nonce, + ) async { final args = {}; args["cipher"] = cipher; args["nonce"] = nonce; @@ -256,9 +260,11 @@ class CryptoUtil { // This function runs on the same thread as the caller, so should be used only // for small amounts of data where thread switching can result in a degraded // user experience - static Uint8List decryptSync(Uint8List cipher, - Uint8List key, - Uint8List nonce,) { + static Uint8List decryptSync( + Uint8List cipher, + Uint8List key, + Uint8List nonce, + ) { final args = {}; args["cipher"] = cipher; args["nonce"] = nonce; @@ -270,8 +276,10 @@ class CryptoUtil { // nonce, using XChaCha20 (w Poly1305 MAC). // This function runs on the isolate pool held by `_computer`. // TODO: Remove "ChaCha", an implementation detail from the function name - static Future encryptChaCha(Uint8List source, - Uint8List key,) async { + static Future encryptChaCha( + Uint8List source, + Uint8List key, + ) async { final args = {}; args["source"] = source; args["key"] = key; @@ -285,9 +293,11 @@ class CryptoUtil { // Decrypts the given source, with the given key and header using XChaCha20 // (w Poly1305 MAC). // TODO: Remove "ChaCha", an implementation detail from the function name - static Future decryptChaCha(Uint8List source, - Uint8List key, - Uint8List header,) async { + static Future decryptChaCha( + Uint8List source, + Uint8List key, + Uint8List header, + ) async { final args = {}; args["source"] = source; args["key"] = key; @@ -304,10 +314,10 @@ class CryptoUtil { // to the destinationFilePath. // If a key is not provided, one is generated and returned. static Future encryptFile( - String sourceFilePath, - String destinationFilePath, { - Uint8List? key, - }) { + String sourceFilePath, + String destinationFilePath, { + Uint8List? key, + }) { final args = {}; args["sourceFilePath"] = sourceFilePath; args["destinationFilePath"] = destinationFilePath; @@ -322,10 +332,11 @@ class CryptoUtil { // Decrypts the file at sourceFilePath, with the given key and header using // XChaCha20 (w Poly1305 MAC), and writes it to the destinationFilePath. static Future decryptFile( - String sourceFilePath, - String destinationFilePath, - Uint8List header, - Uint8List key,) { + String sourceFilePath, + String destinationFilePath, + Uint8List header, + Uint8List key, + ) { final args = {}; args["sourceFilePath"] = sourceFilePath; args["destinationFilePath"] = destinationFilePath; @@ -356,10 +367,10 @@ class CryptoUtil { // Decrypts the input using the given publicKey-secretKey pair static Uint8List openSealSync( - Uint8List input, - Uint8List publicKey, - Uint8List secretKey, - ) { + Uint8List input, + Uint8List publicKey, + Uint8List secretKey, + ) { return Sodium.cryptoBoxSealOpen(input, publicKey, secretKey); } @@ -377,9 +388,9 @@ class CryptoUtil { // At all points, we ensure that the product of these two variables (the area // under the graph that determines the amount of work required) is a constant. static Future deriveSensitiveKey( - Uint8List password, - Uint8List salt, - ) async { + Uint8List password, + Uint8List salt, + ) async { final logger = Logger("pwhash"); int memLimit = Sodium.cryptoPwhashMemlimitSensitive; int opsLimit = Sodium.cryptoPwhashOpslimitSensitive; @@ -407,7 +418,10 @@ class CryptoUtil { return DerivedKeyResult(key, memLimit, opsLimit); } catch (e, s) { logger.warning( - "failed to deriveKey mem: $memLimit, ops: $opsLimit", e, s,); + "failed to deriveKey mem: $memLimit, ops: $opsLimit", + e, + s, + ); } memLimit = (memLimit / 2).round(); opsLimit = opsLimit * 2; @@ -421,9 +435,9 @@ class CryptoUtil { // extra layer of authentication (atop the access token and collection key). // More details @ https://ente.io/blog/building-shareable-links/ static Future deriveInteractiveKey( - Uint8List password, - Uint8List salt, - ) async { + Uint8List password, + Uint8List salt, + ) async { final int memLimit = Sodium.cryptoPwhashMemlimitInteractive; final int opsLimit = Sodium.cryptoPwhashOpslimitInteractive; final key = await deriveKey(password, salt, memLimit, opsLimit); @@ -433,23 +447,23 @@ class CryptoUtil { // Derives a key for a given password, salt, memLimit and opsLimit using // Argon2id, v1.3. static Future deriveKey( - Uint8List password, - Uint8List salt, - int memLimit, - int opsLimit, - ) { + Uint8List password, + Uint8List salt, + int memLimit, + int opsLimit, + ) { try { - return _computer.compute( - cryptoPwHash, - param: { - "password": password, - "salt": salt, - "memLimit": memLimit, - "opsLimit": opsLimit, - }, - taskName: "deriveKey", - ); - } catch(e,s) { + return _computer.compute( + cryptoPwHash, + param: { + "password": password, + "salt": salt, + "memLimit": memLimit, + "opsLimit": opsLimit, + }, + taskName: "deriveKey", + ); + } catch (e, s) { final String errMessage = 'failed to deriveKey memLimit: $memLimit and ' 'opsLimit: $opsLimit'; Logger("CryptoUtilDeriveKey").warning(errMessage, e, s); @@ -461,20 +475,25 @@ class CryptoUtil { // (Key Derivation Function) with the `loginSubKeyId` and // `loginSubKeyLen` and `loginSubKeyContext` as context static Future deriveLoginKey( - Uint8List key, - ) async { - final Uint8List derivedKey = await _computer.compute( - cryptoKdfDeriveFromKey, - param: { - "key": key, - "subkeyId": loginSubKeyId, - "subkeyLen": loginSubKeyLen, - "context": utf8.encode(loginSubKeyContext), - }, - taskName: "deriveLoginKey", - ); - // return the first 16 bytes of the derived key - return derivedKey.sublist(0, 16); + Uint8List key, + ) async { + try { + final Uint8List derivedKey = await _computer.compute( + cryptoKdfDeriveFromKey, + param: { + "key": key, + "subkeyId": loginSubKeyId, + "subkeyLen": loginSubKeyLen, + "context": utf8.encode(loginSubKeyContext), + }, + taskName: "deriveLoginKey", + ); + // return the first 16 bytes of the derived key + return derivedKey.sublist(0, 16); + } catch (e, s) { + Logger("deriveLoginKey").severe("loginKeyDerivation failed", e, s); + throw LoginKeyDerivationError(); + } } // Computes and returns the hash of the source file