Lock screen fixes (#345)

This commit is contained in:
Neeraj Gupta 2023-11-14 10:39:52 +05:30 committed by GitHub
commit 55e9a7049e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 153 additions and 31 deletions

View file

@ -1,5 +1,6 @@
{
"account": "Account",
"unlock": "Unlock",
"recoveryKey": "Recovery key",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
@ -97,6 +98,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 +338,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,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

@ -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,65 @@ 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

@ -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

@ -768,7 +768,7 @@ packages:
source: hosted
version: "2.1.7"
local_auth_android:
dependency: transitive
dependency: "direct main"
description:
name: local_auth_android
sha256: "523dd636ce061ddb296cbc3db410cb8f21efb7d8798f7b9532c8038ce2f8bad5"
@ -776,7 +776,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
modal_bottom_sheet: ^3.0.0-pre
move_to_background: ^1.0.2