Section 10/161 menit

10. Error Handling dan Edge Cases

10. Error Handling dan Edge Cases

Semua Error Code ASAuthorization

swift
extension AuthenticationCoordinator: ASAuthorizationControllerDelegate {
    func authorizationController(
        controller: ASAuthorizationController,
        didCompleteWithError error: Error
    ) {
        let authError = error as? ASAuthorizationError

        switch authError?.code {
        case .canceled:
            // User tap Cancel atau tekan Home — NORMAL, jangan tampilkan error ke user
            handleUserCanceled()

        case .failed:
            // Biometrik gagal (fingerprint tidak cocok) atau passkey tidak valid
            handleAuthenticationFailed(error)

        case .invalidResponse:
            // Response dari authenticator tidak valid — mungkin bug atau security issue
            handleInvalidResponse(error)

        case .notHandled:
            // Request tidak bisa ditangani — biasanya karena tidak ada passkey yang match
            handleNoPasskeyFound()

        case .unknown:
            // Catch-all untuk error tak terduga
            handleUnknownError(error)

        case .notInteractive:
            // Request memerlukan interaksi user tapi kondisi tidak memungkinkan
            // (misal: AutoFill request tapi tidak ada field aktif)
            handleNonInteractiveError()

        case .matchedExcludedCredential:
            // Credential yang dicoba register sudah ada di excluded credentials
            // (Duplikasi registration dicegah)
            handleDuplicateCredential()

        default:
            handleUnknownError(error)
        }
    }

    private func handleNoPasskeyFound() {
        // Fallback ke password login
        showPasswordLoginForm()
    }

    private func handleUserCanceled() {
        // Resume conditional AutoFill jika ada
        Task { await beginConditionalSignIn() }
    }
}

Edge Cases yang Sering Diabaikan

swift
// 1. Device baru setelah factory reset — iCloud Keychain belum sync
func handleFirstLaunchAfterReset() async {
    // Passkey mungkin belum tersedia — iCloud sync butuh waktu
    // Tampilkan loading state atau opsi login alternatif
    if !isPasskeyAvailable() {
        showAlternativeLoginOptions()
        // Monitor iCloud sync state dengan CKContainer
    }
}

// 2. Account upgrade: user register passkey sementara masih login via password
func upgradeToPasskey() async throws {
    // 1. Verifikasi sesi password masih valid
    guard sessionManager.isAuthenticated else { throw UpgradeError.notAuthenticated }

    // 2. Register passkey baru
    let registration = try await registerNewPasskey()

    // 3. Kirim ke server — server simpan passkey DAN tetap simpan password hash
    try await server.addPasskeyToAccount(registration, keepPassword: true)

    // 4. Tandai di UserDefaults bahwa upgrade sudah dilakukan
    UserDefaults.standard.set(true, forKey: "passkeyUpgraded")
}

// 3. Multiple devices — passkey di device lain
// iCloud Keychain sync otomatis, tapi ada delay
// Berikan UI yang jelas bahwa passkey bisa digunakan dari semua device iCloud

// 4. Excluded credentials — mencegah duplikasi
func registerNewPasskey(excludingExisting: [Data]) async throws {
    let request = provider.createCredentialRegistrationRequest(...)

    // Cegah register ulang credential yang sudah ada
    request.excludedCredentials = excludingExisting.map {
        ASAuthorizationPlatformPublicKeyCredentialDescriptor(credentialID: $0)
    }
    // Jika user coba register credential yang sudah ada → .matchedExcludedCredential error
}