Merge conflict: Resolved

This commit is contained in:
Muhammed Ayimen 2023-11-15 08:08:38 +09:00
commit 4cfbe91ad4
14 changed files with 338 additions and 34 deletions

View file

@ -95,6 +95,12 @@
"title": "La Poste",
"slug": "laposte"
},
{
"title": "Mastodon",
"altNames": ["mstdn", "fediscience", "mathstodon", "fosstodon"],
"slug": "mastodon",
"hex": "6364FF"
},
{
"title": "Microsoft"
},

View file

@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Mastodon</title><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -1 +1 @@
Subproject commit 7e1ad4517598f36ba625741a4dfbc33610d105d8
Subproject commit 8e7701d6a40462733043f54b3849faf35af70a83

View file

@ -1,5 +1,6 @@
{
"account": "Account",
"unlock": "Unlock",
"recoveryKey": "Recovery key",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
@ -85,6 +86,7 @@
"importSelectJsonFile": "Select JSON file",
"importEnteEncGuide": "Select the encrypted JSON file exported from ente",
"importRaivoGuide": "Use the \"Export OTPs to Zip archive\" option in Raivo's Settings.\n\nExtract the zip file and import the JSON file.",
"importBitwardenGuide": "Use the \"Export vault\" option within Bitwarden Tools and import the unencrypted JSON file.",
"importAegisGuide": "Use the \"Export the vault\" option in Aegis's Settings.\n\nIf your vault is encrypted, you will need to enter vault password to decrypt the vault.",
"exportCodes": "Export codes",
"importLabel": "Import",
@ -97,6 +99,7 @@
"authToViewYourRecoveryKey": "Please authenticate to view your recovery key",
"authToChangeYourEmail": "Please authenticate to change your email",
"authToChangeYourPassword": "Please authenticate to change your password",
"authToViewSecrets": "Please authenticate to view your secrets",
"ok": "Ok",
"cancel": "Cancel",
"yes": "Yes",
@ -336,5 +339,57 @@
"deleteCodeAuthMessage": "Authenticate to delete code",
"showQRAuthMessage": "Authenticate to show QR code",
"confirmAccountDeleteTitle": "Confirm account deletion",
"confirmAccountDeleteMessage": "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."
"confirmAccountDeleteMessage": "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.",
"androidBiometricHint": "Verify identity",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
},
"androidBiometricNotRecognized": "Not recognized. Try again.",
"@androidBiometricNotRecognized": {
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
},
"androidBiometricSuccess": "Success",
"@androidBiometricSuccess": {
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
},
"androidCancelButton": "Cancel",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"androidSignInTitle": "Authentication required",
"@androidSignInTitle": {
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
},
"androidBiometricRequiredTitle": "Biometric required",
"@androidBiometricRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsRequiredTitle": "Device credentials required",
"@androidDeviceCredentialsRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsSetupDescription": "Device credentials required",
"@androidDeviceCredentialsSetupDescription": {
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
},
"goToSettings": "Go to settings",
"@goToSettings": {
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
},
"androidGoToSettingsDescription": "Biometric authentication is not set up on your device. Go to 'Settings > Security' to add biometric authentication.",
"@androidGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
},
"iOSLockOut": "Biometric authentication is disabled. Please lock and unlock your screen to enable it.",
"@iOSLockOut": {
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
},
"iOSGoToSettingsDescription": "Biometric authentication is not set up on your device. Please either enable Touch ID or Face ID on your phone.",
"@iOSGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
},
"iOSOkButton": "OK",
"@iOSOkButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
}
}

View file

@ -1,5 +1,6 @@
{
"account": "账户",
"unlock": "解锁",
"recoveryKey": "恢复密钥",
"counterAppBarTitle": "计数器",
"@counterAppBarTitle": {
@ -97,6 +98,7 @@
"authToViewYourRecoveryKey": "请验证以查看您的恢复密钥",
"authToChangeYourEmail": "请验证以更改您的电子邮件",
"authToChangeYourPassword": "请验证以更改密码",
"authToViewSecrets": "请进行身份验证以查看您的秘密",
"ok": "好的",
"cancel": "取消",
"yes": "是",
@ -336,5 +338,57 @@
"deleteCodeAuthMessage": "删除代码需要身份验证",
"showQRAuthMessage": "显示QR码需要身份验证",
"confirmAccountDeleteTitle": "确认删除账户",
"confirmAccountDeleteMessage": "该账户已链接到其他ente旗下的应用程序如果您使用任何相关的应用程序。\n\n您在所有ente旗下应用程序中上传的数据都将被安排删除并且您的账户将被永久删除。"
"confirmAccountDeleteMessage": "该账户已链接到其他ente旗下的应用程序如果您使用任何相关的应用程序。\n\n您在所有ente旗下应用程序中上传的数据都将被安排删除并且您的账户将被永久删除。",
"androidBiometricHint": "验证身份",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
},
"androidBiometricNotRecognized": "未能识别。再试一次。",
"@androidBiometricNotRecognized": {
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
},
"androidBiometricSuccess": "成功",
"@androidBiometricSuccess": {
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
},
"androidCancelButton": "取消",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"androidSignInTitle": "需要进行身份验证",
"@androidSignInTitle": {
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
},
"androidBiometricRequiredTitle": "需要进行生物识别认证",
"@androidBiometricRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsRequiredTitle": "需要设备凭据",
"@androidDeviceCredentialsRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsSetupDescription": "需要设备凭据",
"@androidDeviceCredentialsSetupDescription": {
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
},
"goToSettings": "前往设置",
"@goToSettings": {
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
},
"androidGoToSettingsDescription": "您的设备上未设置生物识别身份验证。转到“设置 > 安全”以添加生物识别身份验证。",
"@androidGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
},
"iOSLockOut": "生物识别身份验证已禁用。请锁定再解锁您的屏幕以启用它。",
"@iOSLockOut": {
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
},
"iOSGoToSettingsDescription": "您的设备上未设置生物识别身份验证。请在您的手机上启用 触控 ID 或 面容 ID。",
"@iOSGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
},
"iOSOkButton": "好的",
"@iOSOkButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
}
}

View file

@ -1,5 +1,3 @@
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/ui/tools/app_lock.dart';
import 'package:ente_auth/utils/auth_util.dart';
@ -19,7 +17,7 @@ class LocalAuthenticationService {
) async {
if (await _isLocalAuthSupportedOnDevice()) {
AppLock.of(context)!.setEnabled(false);
final result = await requestAuthentication(infoMessage);
final result = await requestAuthentication(context, infoMessage);
AppLock.of(context)!.setEnabled(
Configuration.instance.shouldShowLockScreen(),
);
@ -43,6 +41,7 @@ class LocalAuthenticationService {
if (await LocalAuthentication().isDeviceSupported()) {
AppLock.of(context)!.disable();
final result = await requestAuthentication(
context,
infoMessage,
);
if (result) {

View file

@ -0,0 +1,103 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/code.dart';
import 'package:ente_auth/services/authenticator_service.dart';
import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/settings/data/import/import_success.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
Future<void> showBitwardenImportInstruction(BuildContext context) async {
final l10n = context.l10n;
final result = await showDialogWidget(
context: context,
title: l10n.importFromApp("Bitwarden"),
body: l10n.importBitwardenGuide,
buttons: [
ButtonWidget(
buttonType: ButtonType.primary,
labelText: l10n.importSelectJsonFile,
isInAlert: true,
buttonSize: ButtonSize.large,
buttonAction: ButtonAction.first,
),
ButtonWidget(
buttonType: ButtonType.secondary,
labelText: context.l10n.cancel,
buttonSize: ButtonSize.large,
isInAlert: true,
buttonAction: ButtonAction.second,
),
],
);
if (result?.action != null && result!.action != ButtonAction.cancel) {
if (result.action == ButtonAction.first) {
await _pickBitwardenJsonFile(context);
}
}
}
Future<void> _pickBitwardenJsonFile(BuildContext context) async {
final l10n = context.l10n;
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result == null) {
return;
}
final progressDialog = createProgressDialog(context, l10n.pleaseWait);
await progressDialog.show();
try {
String path = result.files.single.path!;
int? count = await _processBitwardenExportFile(context, path);
await progressDialog.hide();
if (count != null) {
await importSuccessDialog(context, count);
}
} catch (e) {
await progressDialog.hide();
await showErrorDialog(
context,
context.l10n.sorry,
context.l10n.importFailureDesc,
);
}
}
Future<int?> _processBitwardenExportFile(
BuildContext context,
String path,
) async {
File file = File(path);
final jsonString = await file.readAsString();
final data = jsonDecode(jsonString);
List<dynamic> jsonArray = data['items'];
final parsedCodes = [];
for (var item in jsonArray) {
if (item['login']['totp'] != null) {
var issuer = item['name'];
var account = item['login']['username'];
var secret = item['login']['totp'];
parsedCodes.add(
Code.fromAccountAndSecret(
account,
issuer,
secret,
),
);
}
}
for (final code in parsedCodes) {
await CodeStore.instance.addCode(code, shouldSync: false);
}
unawaited(AuthenticatorService.instance.onlineSync());
return parsedCodes.length;
}

View file

@ -1,5 +1,6 @@
import 'package:ente_auth/ui/settings/data/import/aegis_import.dart';
import 'package:ente_auth/ui/settings/data/import/analyze_qr_code.dart';
import 'package:ente_auth/ui/settings/data/import/bitwarden_import.dart';
import 'package:ente_auth/ui/settings/data/import/encrypted_ente_import.dart';
import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart';
import 'package:ente_auth/ui/settings/data/import/plain_text_import.dart';
@ -40,6 +41,8 @@ class ImportService {
},
),
);
case ImportType.bitwarden:
showBitwardenImportInstruction(context);
break;
}
}

View file

@ -15,7 +15,8 @@ enum ImportType {
ravio,
googleAuthenticator,
aegis,
googleAuthenticatorImage
googleAuthenticatorImage,
bitwarden,
}
class ImportCodePage extends StatelessWidget {
@ -26,6 +27,7 @@ class ImportCodePage extends StatelessWidget {
ImportType.aegis,
ImportType.googleAuthenticator,
ImportType.googleAuthenticatorImage,
ImportType.bitwarden,
];
ImportCodePage({super.key});
@ -45,6 +47,8 @@ class ImportCodePage extends StatelessWidget {
return 'Aegis Authenticator';
case ImportType.googleAuthenticatorImage:
return 'Google Authenticator (saved image)';
case ImportType.bitwarden:
return 'Bitwarden';
}
}

View file

@ -1,5 +1,4 @@
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/ui/common/gradient_button.dart';
import 'package:ente_auth/ui/tools/app_lock.dart';
import 'package:ente_auth/utils/auth_util.dart';
@ -13,13 +12,19 @@ class LockScreen extends StatefulWidget {
State<LockScreen> createState() => _LockScreenState();
}
class _LockScreenState extends State<LockScreen> {
class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
final _logger = Logger("LockScreen");
bool _isShowingLockScreen = false;
bool _hasPlacedAppInBackground = false;
bool _hasAuthenticationFailed = false;
int? lastAuthenticatingTime;
@override
void initState() {
_showLockScreen();
_logger.info("initState");
super.initState();
_showLockScreen(source: "initState");
WidgetsBinding.instance.addObserver(this);
}
@override
@ -34,16 +39,16 @@ class _LockScreenState extends State<LockScreen> {
alignment: Alignment.center,
children: [
Opacity(
opacity: 0.3,
opacity: 0.2,
child: Image.asset('assets/loading_photos_background.png'),
),
SizedBox(
width: 142,
width: 180,
child: GradientButton(
text: "Unlock",
text: context.l10n.unlock,
iconData: Icons.lock_open_outlined,
onTap: () async {
_showLockScreen();
_showLockScreen(source: "tapUnlock");
},
),
),
@ -55,16 +60,67 @@ class _LockScreenState extends State<LockScreen> {
);
}
Future<void> _showLockScreen() async {
_logger.info("Showing lockscreen");
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
_logger.info(state.toString());
if (state == AppLifecycleState.resumed && !_isShowingLockScreen) {
// This is triggered either when the lock screen is dismissed or when
// the app is brought to foreground
_hasPlacedAppInBackground = false;
final bool didAuthInLast5Seconds = lastAuthenticatingTime != null &&
DateTime.now().millisecondsSinceEpoch - lastAuthenticatingTime! <
5000;
if (!_hasAuthenticationFailed && !didAuthInLast5Seconds) {
// Show the lock screen again only if the app is resuming from the
// background, and not when the lock screen was explicitly dismissed
Future.delayed(
Duration.zero,
() => _showLockScreen(source: "lifeCycle"),
);
} else {
_hasAuthenticationFailed = false; // Reset failure state
}
} else if (state == AppLifecycleState.paused ||
state == AppLifecycleState.inactive) {
// This is triggered either when the lock screen pops up or when
// the app is pushed to background
if (!_isShowingLockScreen) {
_hasPlacedAppInBackground = true;
_hasAuthenticationFailed = false; // reset failure state
}
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
Future<void> _showLockScreen({String source = ''}) async {
final int id = DateTime.now().millisecondsSinceEpoch;
_logger.info("Showing lock screen $source $id");
try {
_isShowingLockScreen = true;
final result = await requestAuthentication(
"Please authenticate to view your secrets",
context,
context.l10n.authToViewSecrets,
);
_logger.finest("LockScreen Result $result $id");
_isShowingLockScreen = false;
if (result) {
lastAuthenticatingTime = DateTime.now().millisecondsSinceEpoch;
AppLock.of(context)!.didUnlock();
} else {
if (!_hasPlacedAppInBackground) {
// Treat this as a failure only if user did not explicitly
// put the app in background
_hasAuthenticationFailed = true;
_logger.info("Authentication failed");
}
}
} catch (e, s) {
_isShowingLockScreen = false;
_logger.severe(e, s);
}
}

View file

@ -93,6 +93,14 @@ class IconUtils {
icon["slug"],
icon["hex"],
);
if (icon["altNames"] != null) {
for (final name in icon["altNames"]) {
_customIcons[name] = CustomIconData(
icon["slug"],
icon["hex"],
);
}
}
}
} catch (e) {
Logger("IconUtils").severe("Error loading icons", e);

View file

@ -1,25 +1,37 @@
import 'package:ente_auth/l10n/l10n.dart';
import 'package:flutter/cupertino.dart';
import 'package:local_auth/local_auth.dart';
import 'package:local_auth_android/local_auth_android.dart';
import 'package:local_auth_ios/types/auth_messages_ios.dart';
import 'package:logging/logging.dart';
Future<bool> requestAuthentication(String reason) async {
Future<bool> requestAuthentication(BuildContext context, String reason) async {
Logger("AuthUtil").info("Requesting authentication");
await LocalAuthentication().stopAuthentication();
final l10n = context.l10n;
return await LocalAuthentication().authenticate(
localizedReason: reason,
authMessages: [
const AndroidAuthMessages(
biometricHint: "Verify identity",
biometricNotRecognized: "Not recognized, try again",
biometricRequiredTitle: "Biometric required",
biometricSuccess: "Successfully verified",
cancelButton: "Cancel",
deviceCredentialsRequiredTitle: "Device credentials required",
deviceCredentialsSetupDescription: "Device credentials required",
goToSettingsButton: "Go to settings",
goToSettingsDescription:
"Authentication is not setup on your device, go to Settings > Security to set it up",
signInTitle: "Authentication required",
AndroidAuthMessages(
biometricHint: l10n.androidBiometricHint,
biometricNotRecognized: l10n.androidBiometricNotRecognized,
biometricRequiredTitle: l10n.androidBiometricRequiredTitle,
biometricSuccess: l10n.androidBiometricSuccess,
cancelButton: l10n.androidCancelButton,
deviceCredentialsRequiredTitle:
l10n.androidDeviceCredentialsRequiredTitle,
deviceCredentialsSetupDescription:
l10n.androidDeviceCredentialsSetupDescription,
goToSettingsButton: l10n.goToSettings,
goToSettingsDescription: l10n.androidGoToSettingsDescription,
signInTitle: l10n.androidSignInTitle,
),
IOSAuthMessages(
goToSettingsButton: l10n.goToSettings,
goToSettingsDescription: l10n.goToSettings,
lockOut: l10n.iOSLockOut,
// cancelButton default value is "Ok"
cancelButton: l10n.iOSOkButton,
),
],
);

View file

@ -792,7 +792,7 @@ packages:
source: hosted
version: "2.1.7"
local_auth_android:
dependency: transitive
dependency: "direct main"
description:
name: local_auth_android
sha256: "523dd636ce061ddb296cbc3db410cb8f21efb7d8798f7b9532c8038ce2f8bad5"
@ -800,7 +800,7 @@ packages:
source: hosted
version: "1.0.31"
local_auth_ios:
dependency: transitive
dependency: "direct main"
description:
name: local_auth_ios
sha256: edc2977c5145492f3451db9507a2f2f284ee4f408950b3e16670838726761940

View file

@ -1,6 +1,6 @@
name: ente_auth
description: ente two-factor authenticator
version: 2.0.15+215
version: 2.0.17+217
publish_to: none
environment:
@ -54,6 +54,9 @@ dependencies:
intl: ^0.18.0
json_annotation: ^4.5.0
local_auth: ^2.1.7
local_auth_android: ^1.0.31
local_auth_ios: ^1.1.3
logging: ^1.0.1
mobile_scanner: ^3.5.2
modal_bottom_sheet: ^3.0.0-pre