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

Require device auth to be set in order to use Sync #2612

Merged
merged 1 commit into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions DuckDuckGo/Common/Extensions/URLExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,8 @@ extension URL {

static var fullDiskAccess = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles")!

static var touchIDAndPassword = URL(string: "x-apple.systempreferences:com.apple.preferences.password")!

// MARK: - Blob URLs

var isBlobURL: Bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Foundation
enum DeviceAuthenticationResult {
case success
case failure
case noAuthAvailable

var authenticated: Bool {
return self == .success
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ final class LocalAuthenticationService: DeviceAuthenticationService {
func authenticateDevice(reason: String, result: @escaping DeviceAuthenticationResultHandler) {
let context = LAContext()

var error: NSError?
guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
result(.noAuthAvailable)
return
}

context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { authenticated, _ in
DispatchQueue.main.async {
let authenticationResult: DeviceAuthenticationResult = authenticated ? .success : .failure
Expand Down
36 changes: 30 additions & 6 deletions DuckDuckGo/Preferences/Model/SyncPreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -559,15 +559,19 @@ extension SyncPreferences: ManagementDialogModelDelegate {
return
}

let data = RecoveryPDFGenerator()
.generate(recoveryCode)

Task { @MainActor in
let authenticationResult = await userAuthenticator.authenticateUser(reason: .syncSettings)
guard authenticationResult.authenticated else {
if authenticationResult == .noAuthAvailable {
presentDialog(for: .empty)
managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToAuthenticateOnDevice, description: "")
}
return
}

let data = RecoveryPDFGenerator()
.generate(recoveryCode)

let panel = NSSavePanel.savePanelWithFileTypeChooser(fileTypes: [.pdf], suggestedFilename: "Sync Data Recovery - DuckDuckGo.pdf")
let response = await panel.begin()

Expand Down Expand Up @@ -607,7 +611,12 @@ extension SyncPreferences: ManagementDialogModelDelegate {

@MainActor
func syncWithAnotherDevicePressed() async {
guard await userAuthenticator.authenticateUser(reason: .syncSettings).authenticated else {
let authenticationResult = await userAuthenticator.authenticateUser(reason: .syncSettings)
guard authenticationResult.authenticated else {
if authenticationResult == .noAuthAvailable {
presentDialog(for: .empty)
managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToAuthenticateOnDevice, description: "")
}
return
}
if isSyncEnabled {
Expand All @@ -619,15 +628,25 @@ extension SyncPreferences: ManagementDialogModelDelegate {

@MainActor
func syncWithServerPressed() async {
guard await userAuthenticator.authenticateUser(reason: .syncSettings).authenticated else {
let authenticationResult = await userAuthenticator.authenticateUser(reason: .syncSettings)
guard authenticationResult.authenticated else {
if authenticationResult == .noAuthAvailable {
presentDialog(for: .empty)
managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToAuthenticateOnDevice, description: "")
}
return
}
presentDialog(for: .syncWithServer)
}

@MainActor
func recoverDataPressed() async {
guard await userAuthenticator.authenticateUser(reason: .syncSettings).authenticated else {
let authenticationResult = await userAuthenticator.authenticateUser(reason: .syncSettings)
guard authenticationResult.authenticated else {
if authenticationResult == .noAuthAvailable {
presentDialog(for: .empty)
managementDialogModel.syncErrorMessage = SyncErrorMessage(type: .unableToAuthenticateOnDevice, description: "")
}
return
}
presentDialog(for: .recoverSyncedData)
Expand All @@ -652,6 +671,11 @@ extension SyncPreferences: ManagementDialogModelDelegate {
showDevicesSynced()
}

@MainActor
func openSystemPasswordSettings() {
NSWorkspace.shared.open(URL.touchIDAndPassword)
}

@MainActor
private func showDevicesSynced() {
presentDialog(for: .nowSyncing)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,30 @@
}
}
},
"alert.sync-device-auth-error" : {
"comment" : "Title for an error alert",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Sync & Backup Error"
}
}
}
},
"alert.sync-device-auth-error-button" : {
"comment" : "Button Title of an error alert",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Go to Settings"
}
}
}
},
"alert.sync-error" : {
"comment" : "Title for sync error alert",
"extractionState" : "extracted_with_value",
Expand Down Expand Up @@ -179,6 +203,18 @@
}
}
},
"alert.unable-to-authenticate-device" : {
"comment" : "Description for unable to authenticate error",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "A device password is required to use Sync & Backup."
}
}
}
},
"alert.unable-to-create-recovery-pdf-description" : {
"comment" : "Description for unable to create recovery pdf error",
"extractionState" : "extracted_with_value",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public protocol ManagementDialogModelDelegate: AnyObject {
func recoveryCodePasted(_ code: String)
func enterRecoveryCodePressed()
func copyCode()
func openSystemPasswordSettings()
}

public final class ManagementDialogModel: ObservableObject {
Expand All @@ -54,6 +55,8 @@ public final class ManagementDialogModel: ObservableObject {
}

public func endFlow() {
syncErrorMessage?.type.onButtonPressed(delegate: delegate)
syncErrorMessage = nil
currentDialog = nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,15 @@ public enum SyncErrorType {
case unableToRemoveDevice
case invalidCode
case unableCreateRecoveryPDF
case unableToAuthenticateOnDevice

var title: String {
return UserText.syncErrorAlertTitle
switch self {
case .unableToAuthenticateOnDevice:
return UserText.syncDeviceAuthenticationErrorAlertTitle
default:
return UserText.syncErrorAlertTitle
}
}

var description: String {
Expand All @@ -93,6 +99,26 @@ public enum SyncErrorType {
return UserText.invalidCodeDescription
case .unableCreateRecoveryPDF:
return UserText.unableCreateRecoveryPdfDescription
case .unableToAuthenticateOnDevice:
return UserText.unableToAuthenticateDevice
}
}

var buttonTitle: String {
switch self {
case .unableToAuthenticateOnDevice:
return UserText.syncDeviceAuthenticationErrorAlertButton
default:
return UserText.ok
}
}

func onButtonPressed(delegate: ManagementDialogModelDelegate?) {
switch self {
case .unableToAuthenticateOnDevice:
delegate?.openSystemPasswordSettings()
default:
break
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public enum ManagementDialogKind: Equatable {
case syncWithServer
case enterRecoveryCode(code: String)
case recoverSyncedData
case empty
}

public struct ManagementDialog: View {
Expand All @@ -49,6 +50,10 @@ public struct ManagementDialog: View {
return typeDescription + "\n" + errorDescription
}

var buttonTitle: String {
return model.syncErrorMessage?.type.buttonTitle ?? UserText.ok
}

public init(model: ManagementDialogModel, recoveryCodeModel: RecoveryCodeViewModel = .init()) {
self.model = model
self.recoveryCodeModel = recoveryCodeModel
Expand All @@ -60,7 +65,7 @@ public struct ManagementDialog: View {
Alert(
title: Text(errorTitle),
message: Text(errorDescription),
dismissButton: .default(Text(UserText.ok)) {
dismissButton: .default(Text(buttonTitle)) {
model.endFlow()
}
)
Expand Down
3 changes: 3 additions & 0 deletions LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ enum UserText {
}

static let syncErrorAlertTitle = NSLocalizedString("alert.sync-error", bundle: Bundle.module, value: "Sync & Backup Error", comment: "Title for sync error alert")
static let syncDeviceAuthenticationErrorAlertTitle = NSLocalizedString("alert.sync-device-auth-error", bundle: Bundle.module, value: "Sync & Backup Error", comment: "Title for an error alert")
static let syncDeviceAuthenticationErrorAlertButton = NSLocalizedString("alert.sync-device-auth-error-button", bundle: Bundle.module, value: "Go to Settings", comment: "Button Title of an error alert")
static let unableToAuthenticateDevice = NSLocalizedString("alert.unable-to-authenticate-device", bundle: Bundle.module, value: "A device password is required to use Sync & Backup.", comment: "Description for unable to authenticate error")
static let unableToSyncToServerDescription = NSLocalizedString("alert.unable-to-sync-to-server-description", bundle: Bundle.module, value: "Unable to connect to the server.", comment: "Description for unable to sync to server error")
static let unableToSyncWithAnotherDeviceDescription = NSLocalizedString("alert.unable-to-sync-with-another-device-description", bundle: Bundle.module, value: "Unable to Sync with another device.", comment: "Description for unable to sync with another device error")
static let unableToMergeTwoAccountsDescription = NSLocalizedString("alert.unable-to-merge-two-accounts-description", bundle: Bundle.module, value: "To pair these devices, turn off Sync & Backup on one device then tap \"Sync With Another Device\" on the other device.", comment: "Description for unable to merge two accounts error")
Expand Down
Loading