Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Biometrics lock (Lock app with FaceID/TouchID/iOS Password) #2398

Closed
wants to merge 30 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1afab5a
Biometrics lock concept
bgoncal Aug 17, 2023
a7564fb
Improvements on authentication handler and protection overlay
bgoncal Aug 21, 2023
1d757ca
Add localized placeholders
bgoncal Aug 21, 2023
c56f638
Lint autocorrect
bgoncal Nov 1, 2023
1335665
PR improvements and macOS taking in consideration
bgoncal Nov 2, 2023
f85e6ae
Linter autocorrect
bgoncal Nov 2, 2023
9fd70fa
Add missing self reference
bgoncal Nov 7, 2023
5850f35
Fix typo and move biometrics logic
bgoncal Nov 16, 2023
ce1f140
Added new UI for the "lock" screen, moved some code around to a prope…
bgoncal Nov 16, 2023
96cdfac
Biometrics lock concept
bgoncal Aug 17, 2023
fc15eac
Improvements on authentication handler and protection overlay
bgoncal Aug 21, 2023
b728292
Add localized placeholders
bgoncal Aug 21, 2023
fe07834
Lint autocorrect
bgoncal Nov 1, 2023
6ffe64c
PR improvements and macOS taking in consideration
bgoncal Nov 2, 2023
2a9ddda
Linter autocorrect
bgoncal Nov 2, 2023
1f0b692
Add missing self reference
bgoncal Nov 7, 2023
e051dd4
Fix typo and move biometrics logic
bgoncal Nov 16, 2023
63c9c46
Added new UI for the "lock" screen, moved some code around to a prope…
bgoncal Nov 16, 2023
8c656d3
Merge branch 'Biometric-lock-concept' of github.com:bgoncal/HA-iOS in…
bgoncal Dec 4, 2023
9f1a418
Merge branch 'master' into Biometric-lock-concept
bgoncal Dec 5, 2023
75982af
Removed mac implementation and moved to SwiftUI for iOS
bgoncal Dec 5, 2023
27cf8ac
Present on top most controller
bgoncal Dec 5, 2023
97839c3
Linter
bgoncal Dec 5, 2023
77e5077
Present biometric lock on window level
bgoncal Dec 5, 2023
27e2a63
Remove unneeded check
bgoncal Dec 6, 2023
2bc8f60
Improve logic to hide security toggle for mac
bgoncal Dec 6, 2023
072af4e
Use camel case for spacing values
bgoncal Dec 6, 2023
6674456
Auto disable biometric lock when delete all servers
bgoncal Dec 6, 2023
eec44b0
Re-add hebrew and estonian languages
bgoncal Dec 6, 2023
5dbb72e
Merge branch 'master' into Biometric-lock-concept
bgoncal Dec 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix typo and move biometrics logic
bgoncal committed Nov 28, 2023
commit e051dd4acfc109b01737640065b9339201650fb9
2 changes: 1 addition & 1 deletion Sources/App/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -413,7 +413,7 @@ Home Assistant is free and open source home automation software with a focus on
"settings_details.general.visibility.title" = "Show App In…";
"settings_details.general.security.title" = "Security";
"settings_details.general.security.action" = "Enable biometric lock";
"settings_details.general.security.footer" = "You will be required to authenticate with biometrics everytime you open the app.";
"settings_details.general.security.footer" = "You will be required to authenticate with biometrics every time you open the app.";
"settings_details.location.background_refresh.disabled" = "Disabled";
"settings_details.location.background_refresh.enabled" = "Enabled";
"settings_details.location.background_refresh.title" = "Background Refresh";
81 changes: 0 additions & 81 deletions Sources/App/WebView/WebViewController.swift
Original file line number Diff line number Diff line change
@@ -24,10 +24,6 @@ class WebViewController: UIViewController, WKNavigationDelegate, WKUIDelegate, U

var keepAliveTimer: Timer?
private var initialURL: URL?
private let biometricOverlay: UIView = {
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
return visualEffectView
}()

static func viewController(
withRestorationIdentifierPath identifierComponents: [String],
@@ -251,34 +247,6 @@ class WebViewController: UIViewController, WKNavigationDelegate, WKUIDelegate, U

styleUI()
updateWebViewForServerValues()

if #available(iOS 13.0, *) {
NotificationCenter.default.addObserver(
self,
selector: #selector(checkForBiometrics),
name: UIScene.willEnterForegroundNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(protectAppIfNeeded),
name: UIScene.didEnterBackgroundNotification,
object: nil
)
} else {
NotificationCenter.default.addObserver(
self,
selector: #selector(checkForBiometrics),
name: UIApplication.willEnterForegroundNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(protectAppIfNeeded),
name: UIApplication.didEnterBackgroundNotification,
object: nil
)
}
}

public func showSettingsViewController() {
@@ -654,43 +622,6 @@ class WebViewController: UIViewController, WKNavigationDelegate, WKUIDelegate, U
decisionHandler(.grant)
}

@objc private func checkForBiometrics() {
if Current.settingsStore.biometricsRequired {
addBiometricOverlayProtection()
Current.authenticationService.delegate = self
Current.authenticationService.authenticate()
} else {
DispatchQueue.main.async { [weak self] in
self?.biometricOverlay.removeFromSuperview()
}
}
}

@objc private func protectAppIfNeeded() {
if Current.settingsStore.biometricsRequired {
addBiometricOverlayProtection()
} else {
DispatchQueue.main.async { [weak self] in
self?.biometricOverlay.removeFromSuperview()
}
}
}

private func addBiometricOverlayProtection() {
DispatchQueue.main.async { [weak self] in
guard let self else { return }
self.biometricOverlay.removeFromSuperview()
self.biometricOverlay.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.biometricOverlay)
NSLayoutConstraint.activate([
self.biometricOverlay.topAnchor.constraint(equalTo: self.view.topAnchor),
self.biometricOverlay.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
self.biometricOverlay.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
self.biometricOverlay.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
])
}
}

private func updateWebViewForServerValues() {
sidebarGestureRecognizer.isEnabled = server.info.version >= .externalBusCommandSidebar
}
@@ -1228,15 +1159,3 @@ extension ConnectionInfo {
}
}
}

extension WebViewController: AuthenticationServiceDelegate {
func didFinishAuthentication(authorized: Bool) {
if authorized {
DispatchQueue.main.async { [weak self] in
self?.biometricOverlay.removeFromSuperview()
}
} else {
addBiometricOverlayProtection()
}
}
}
90 changes: 90 additions & 0 deletions Sources/App/WebView/WebViewWindowController.swift
Original file line number Diff line number Diff line change
@@ -20,6 +20,10 @@ class WebViewWindowController {

private var webViewControllerSeal: (WebViewController) -> Void
private var onboardingPreloadWebViewController: WebViewController?
private let biometricOverlay: UIView = {
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
return visualEffectView
}()

init(window: UIWindow, restorationActivity: NSUserActivity?) {
self.window = window
@@ -88,6 +92,8 @@ class WebViewWindowController {
restorationActivity = nil
}
}

listenForBiometricsLockRelatedEvents()
}

func present(_ viewController: UIViewController, animated: Bool = true, completion: (() -> Void)? = nil) {
@@ -407,6 +413,36 @@ class WebViewWindowController {
hud.hide(animated: true, afterDelay: 1.0)
}
}

private func listenForBiometricsLockRelatedEvents() {
if #available(iOS 13.0, *) {
NotificationCenter.default.addObserver(
self,
selector: #selector(checkForBiometrics),
name: UIScene.willEnterForegroundNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(protectAppIfNeeded),
name: UIScene.didEnterBackgroundNotification,
object: nil
)
} else {
NotificationCenter.default.addObserver(
self,
selector: #selector(checkForBiometrics),
name: UIApplication.willEnterForegroundNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(protectAppIfNeeded),
name: UIApplication.didEnterBackgroundNotification,
object: nil
)
}
}
}

extension WebViewWindowController: OnboardingStateObserver {
@@ -469,3 +505,57 @@ extension WebViewWindowController: OnboardingStateObserver {
}
}
}

// MARK: - Biometrics lock
extension WebViewWindowController {
@objc private func checkForBiometrics() {
if Current.settingsStore.biometricsRequired {
addBiometricOverlayProtection()
Current.authenticationService.delegate = self
Current.authenticationService.authenticate()
} else {
DispatchQueue.main.async { [weak self] in
self?.biometricOverlay.removeFromSuperview()
}
}
}

@objc private func protectAppIfNeeded() {
if Current.settingsStore.biometricsRequired {
addBiometricOverlayProtection()
} else {
DispatchQueue.main.async { [weak self] in
self?.biometricOverlay.removeFromSuperview()
}
}
}

private func addBiometricOverlayProtection() {
guard let view = window.rootViewController?.view else { return }
DispatchQueue.main.async { [weak self] in
guard let self else { return }
self.biometricOverlay.removeFromSuperview()
self.biometricOverlay.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(self.biometricOverlay)
NSLayoutConstraint.activate([
self.biometricOverlay.topAnchor.constraint(equalTo: view.topAnchor),
self.biometricOverlay.bottomAnchor.constraint(equalTo: view.bottomAnchor),
self.biometricOverlay.leadingAnchor.constraint(equalTo: view.leadingAnchor),
self.biometricOverlay.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
}
}
}

// MARK: - AuthenticationServiceDelegate
extension WebViewWindowController: AuthenticationServiceDelegate {
func didFinishAuthentication(authorized: Bool) {
if authorized {
DispatchQueue.main.async { [weak self] in
self?.biometricOverlay.removeFromSuperview()
}
} else {
addBiometricOverlayProtection()
}
}
}
2 changes: 1 addition & 1 deletion Sources/Shared/Resources/Swiftgen/Strings.swift
Original file line number Diff line number Diff line change
@@ -1349,7 +1349,7 @@ public enum L10n {
public enum Security {
/// Enable biometric lock
public static var action: String { return L10n.tr("Localizable", "settings_details.general.security.action") }
/// You will be required to authenticate with biometrics everytime you open the app.
/// You will be required to authenticate with biometrics every time you open the app.
public static var footer: String { return L10n.tr("Localizable", "settings_details.general.security.footer") }
/// Security
public static var title: String { return L10n.tr("Localizable", "settings_details.general.security.title") }