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

Improve Hub error handling and updates #404

Merged
merged 4 commits into from
Feb 6, 2025
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
13 changes: 8 additions & 5 deletions Cryptomator/VaultDetail/UnlockSectionFooterViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// Copyright © 2021 Skymatic GmbH. All rights reserved.
//

import CryptomatorCloudAccessCore
import CryptomatorCommonCore
import Foundation

Expand All @@ -31,21 +32,23 @@ class UnlockSectionFooterViewModel: HeaderFooterViewModel {
}

var biometryTypeName: String?
var vaultInfo: VaultInfo

init(vaultUnlocked: Bool, biometricalUnlockEnabled: Bool, biometryTypeName: String?, keepUnlockedDuration: KeepUnlockedDuration) {
init(vaultUnlocked: Bool, biometricalUnlockEnabled: Bool, biometryTypeName: String?, keepUnlockedDuration: KeepUnlockedDuration, vaultInfo: VaultInfo) {
self.vaultUnlocked = vaultUnlocked
self.biometricalUnlockEnabled = biometricalUnlockEnabled
self.biometryTypeName = biometryTypeName
let titleText = UnlockSectionFooterViewModel.getTitleText(vaultUnlocked: vaultUnlocked, biometricalUnlockEnabled: biometricalUnlockEnabled, biometryTypeName: biometryTypeName, keepUnlockedDuration: keepUnlockedDuration)
let titleText = UnlockSectionFooterViewModel.getTitleText(vaultUnlocked: vaultUnlocked, biometricalUnlockEnabled: biometricalUnlockEnabled, biometryTypeName: biometryTypeName, keepUnlockedDuration: keepUnlockedDuration, vaultInfo: vaultInfo)
self.title = Bindable(titleText)
self.keepUnlockedDuration = keepUnlockedDuration
self.vaultInfo = vaultInfo
}

private func updateTitle() {
title.value = UnlockSectionFooterViewModel.getTitleText(vaultUnlocked: vaultUnlocked, biometricalUnlockEnabled: biometricalUnlockEnabled, biometryTypeName: biometryTypeName, keepUnlockedDuration: keepUnlockedDuration)
title.value = UnlockSectionFooterViewModel.getTitleText(vaultUnlocked: vaultUnlocked, biometricalUnlockEnabled: biometricalUnlockEnabled, biometryTypeName: biometryTypeName, keepUnlockedDuration: keepUnlockedDuration, vaultInfo: vaultInfo)
}

private static func getTitleText(vaultUnlocked: Bool, biometricalUnlockEnabled: Bool, biometryTypeName: String?, keepUnlockedDuration: KeepUnlockedDuration) -> String {
private static func getTitleText(vaultUnlocked: Bool, biometricalUnlockEnabled: Bool, biometryTypeName: String?, keepUnlockedDuration: KeepUnlockedDuration, vaultInfo: VaultInfo) -> String {
let unlockedText: String
if vaultUnlocked {
unlockedText = LocalizedString.getValue("vaultDetail.unlocked.footer")
Expand All @@ -62,7 +65,7 @@ class UnlockSectionFooterViewModel: HeaderFooterViewModel {
keepUnlockedText = String(format: LocalizedString.getValue("vaultDetail.keepUnlocked.footer.limitedDuration"), keepUnlockedDuration.description ?? "")
}
var footerText = "\(unlockedText)\n\n\(keepUnlockedText)"
if let biometryTypeName = biometryTypeName {
if vaultInfo.vaultConfigType != .hub, let biometryTypeName = biometryTypeName {
let biometricalUnlockText: String
if biometricalUnlockEnabled {
biometricalUnlockText = String(format: LocalizedString.getValue("vaultDetail.enabledBiometricalUnlock.footer"), biometryTypeName)
Expand Down
4 changes: 2 additions & 2 deletions Cryptomator/VaultDetail/VaultDetailViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class VaultDetailViewModel: VaultDetailViewModelProtocol {

private var lockSectionCells: [BindableTableViewCellViewModel] {
var cells: [BindableTableViewCellViewModel] = [lockButton, keepUnlockedCellViewModel]
if let biometryTypeName = context.enrolledBiometricsAuthenticationName() {
if vaultInfo.vaultConfigType != .hub, let biometryTypeName = context.enrolledBiometricsAuthenticationName() {
let switchCellViewModel = getSwitchCellViewModel(biometryTypeName: biometryTypeName)
cells.append(switchCellViewModel)
}
Expand All @@ -146,7 +146,7 @@ class VaultDetailViewModel: VaultDetailViewModelProtocol {
.lockingSection: unlockSectionFooterViewModel,
.removeVaultSection: BaseHeaderFooterViewModel(title: LocalizedString.getValue("vaultDetail.removeVault.footer"))]

private lazy var unlockSectionFooterViewModel = UnlockSectionFooterViewModel(vaultUnlocked: vaultInfo.vaultIsUnlocked.value, biometricalUnlockEnabled: biometricalUnlockEnabled, biometryTypeName: context.enrolledBiometricsAuthenticationName(), keepUnlockedDuration: currentKeepUnlockedDuration.value)
private lazy var unlockSectionFooterViewModel = UnlockSectionFooterViewModel(vaultUnlocked: vaultInfo.vaultIsUnlocked.value, biometricalUnlockEnabled: biometricalUnlockEnabled, biometryTypeName: context.enrolledBiometricsAuthenticationName(), keepUnlockedDuration: currentKeepUnlockedDuration.value, vaultInfo: vaultInfo)

private lazy var vaultInfoCellViewModel = BindableTableViewCellViewModel(title: vaultInfo.vaultName, detailTitle: vaultInfo.vaultPath.path, detailTitleTextColor: .secondaryLabel, image: UIImage(vaultIconFor: vaultInfo.cloudProviderType, state: .normal), selectionStyle: .none)
private lazy var renameVaultCellViewModel = ButtonCellViewModel.createDisclosureButton(action: VaultDetailButtonAction.showRenameVault, title: LocalizedString.getValue("vaultDetail.button.renameVault"), detailTitle: vaultName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum HubAuthenticationFlow {
case needsDeviceRegistration
case licenseExceeded
case requiresAccountInitialization(at: URL)
case vaultArchived
}

public struct HubAuthenticationFlowSuccess {
Expand Down Expand Up @@ -48,6 +49,7 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving

public init() {}

// swiftlint:disable:next cyclomatic_complexity
public func receiveKey(authState: OIDAuthState, vaultConfig: UnverifiedVaultConfig) async throws -> HubAuthenticationFlow {
guard let hubConfig = vaultConfig.allegedHubConfig, let vaultBaseURL = getVaultBaseURL(from: vaultConfig) else {
throw CryptomatorHubAuthenticatorError.invalidVaultConfig
Expand Down Expand Up @@ -79,6 +81,8 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving
return .requiresAccountInitialization(at: profileURL)
case .legacyHubVersion:
throw CryptomatorHubAuthenticatorError.incompatibleHubVersion
case .vaultArchived:
return .vaultArchived
}

let retrieveUserPrivateKeyResponse = try await getUserKey(apiBaseURL: apiBaseURL, authState: authState)
Expand Down Expand Up @@ -240,8 +244,10 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving
return .success(encryptedVaultKey: body, header: httpResponse?.allHeaderFields ?? [:])
case 402:
return .licenseExceeded
case 403, 410:
case 403:
return .accessNotGranted
case 410:
return .vaultArchived
case 404:
return .legacyHubVersion
case 449:
Expand Down Expand Up @@ -297,14 +303,16 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving
private enum RetrieveVaultMasterkeyEncryptedForUserResponse {
// 200
case success(encryptedVaultKey: String, header: [AnyHashable: Any])
// 403, 410
// 403
case accessNotGranted
// 402
case licenseExceeded
// 449
case requiresAccountInitialization(at: URL)
// 404
case legacyHubVersion
// 410
case vaultArchived
}

private struct DeviceDto: Codable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ public struct HubAuthenticationView: View {
onRegisterTap: { Task { await viewModel.register() }}
)
case .accessNotGranted:
HubAccessNotGrantedView(onRefresh: { Task { await viewModel.refresh() }})
CryptomatorErrorWithRefreshView(headerTitle: LocalizedString.getValue("hubAuthentication.accessNotGranted"), onRefresh: { Task { await viewModel.refresh() }})
case .vaultArchived:
CryptomatorErrorWithRefreshView(headerTitle: LocalizedString.getValue("hubAuthentication.vaultArchived"), onRefresh: { Task { await viewModel.refresh() }})
case .licenseExceeded:
CryptomatorErrorView(text: LocalizedString.getValue("hubAuthentication.licenseExceeded"))
case let .error(description):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public final class HubAuthenticationViewModel: ObservableObject {
case licenseExceeded
case deviceRegistration(DeviceRegistration)
case error(description: String)
case vaultArchived
}

public enum DeviceRegistration: Equatable {
Expand Down Expand Up @@ -108,6 +109,8 @@ public final class HubAuthenticationViewModel: ObservableObject {
await setState(to: .licenseExceeded)
case let .requiresAccountInitialization(profileURL):
await delegate?.hubAuthenticationViewModelWantsToShowNeedsAccountInitAlert(profileURL: profileURL)
case .vaultArchived:
await setState(to: .vaultArchived)
}
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import SwiftUI

struct CryptomatorErrorWithRefreshView: View {
var headerTitle: String
var onRefresh: () -> Void

var body: some View {
CryptomatorSimpleButtonView(
buttonTitle: LocalizedString.getValue("common.button.refresh"),
onButtonTap: onRefresh,
headerTitle: headerTitle
)
}
}

struct CryptomatorErrorWithRefreshView_Previews: PreviewProvider {
static var previews: some View {
CryptomatorErrorWithRefreshView(headerTitle: "Example Header Title", onRefresh: {})
}
}
5 changes: 3 additions & 2 deletions SharedResources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,17 @@
"getFolderIntent.error.noVaultSelected" = "No vault has been selected.";

"hubAuthentication.title" = "Hub Vault";
"hubAuthentication.accessNotGranted" = "Your device has not yet been authorized to access this vault. Ask the vault owner to authorize it.";
"hubAuthentication.accessNotGranted" = "You do not have permission to access this vault. Ask the vault owner to authorize you.";
"hubAuthentication.licenseExceeded" = "Your Cryptomator Hub instance has an invalid license. Please inform a Hub administrator to upgrade or renew the license.";
"hubAuthentication.deviceRegistration.deviceName.cells.name" = "Device Name";
"hubAuthentication.deviceRegistration.deviceName.footer.title" = "This seems to be the first Hub access from this device. In order to identify it for access authorization, you need to name this device.";
"hubAuthentication.deviceRegistration.accountKey.footer.title" = "Your Account Key is required to login from new apps or browsers. It can be found in your profile.";
"hubAuthentication.deviceRegistration.needsAuthorization.alert.title" = "Register Device Successful";
"hubAuthentication.deviceRegistration.needsAuthorization.alert.message" = "To access the vault, your device needs to be authorized by the vault owner.";
"hubAuthentication.deviceRegistration.needsAuthorization.alert.message" = "To access the vault, the vault owner needs to grant you permission.";
"hubAuthentication.requireAccountInit.alert.title" = "Action Required";
"hubAuthentication.requireAccountInit.alert.message" = "To proceed, please complete the steps required in your Hub user profile.";
"hubAuthentication.requireAccountInit.alert.actionButton" = "Go to Profile";
"hubAuthentication.vaultArchived" = "This vault has been archived. Please ask the vault owner to unarchive it.";

"intents.saveFile.missingFile" = "The provided file is not valid.";
"intents.saveFile.invalidFolder" = "The provided folder is not valid.";
Expand Down
Loading