mirror of
https://github.com/leonboe1/QuickDrop.git
synced 2026-11-11 02:51:11 +00:00
Add Trust Revoke View
This commit is contained in:
1 parent
448cc15a7e
commit
d3dc89f412
10 files changed
+2438
-1467
No files matched your search
@@ -547,9 +547,8 @@ class InboundNearbyConnection: NearbyConnection {
|
||||
|
||||
|
||||
func submitUserConsent(accepted: Bool, trustDevice: Bool, storeInTemp: Bool = false) {
|
||||
|
||||
if trustDevice, let peerCertificate = peerCertificate {
|
||||
TrustStore.shared.addTrusted(certificate: peerCertificate)
|
||||
if trustDevice, let peerCertificate = peerCertificate, let remoteDeviceInfo = remoteDeviceInfo {
|
||||
TrustStore.shared.addTrusted(certificate: peerCertificate, device: remoteDeviceInfo)
|
||||
}
|
||||
|
||||
DispatchQueue.global(qos: .utility).async {
|
||||
|
||||
@@ -46,10 +46,10 @@ public protocol OutboundAppDelegate: AnyObject {
|
||||
}
|
||||
|
||||
|
||||
public struct RemoteDeviceInfo: Identifiable, Equatable {
|
||||
public struct RemoteDeviceInfo: Codable, Identifiable, Equatable {
|
||||
public let name: String?
|
||||
public let type: DeviceType
|
||||
public let qrCodeData:Data?
|
||||
public let qrCodeData: Data?
|
||||
public var id: String?
|
||||
|
||||
init(name: String, type: DeviceType, id: String? = nil) {
|
||||
@@ -67,7 +67,7 @@ public struct RemoteDeviceInfo: Identifiable, Equatable {
|
||||
}
|
||||
|
||||
|
||||
public enum DeviceType: Int32 {
|
||||
public enum DeviceType: Int32, Codable {
|
||||
case unknown = 0
|
||||
case phone
|
||||
case tablet
|
||||
|
||||
@@ -8,26 +8,80 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
class TrustStore {
|
||||
|
||||
class TrustStore: ObservableObject {
|
||||
|
||||
static let shared = TrustStore()
|
||||
|
||||
@Published private(set) var trustedCertificates: [String: TrustedCertificate] = [:]
|
||||
|
||||
private let trustedKeysKey = "com.leonboettger.quickdrop.identity.trustedKeys"
|
||||
|
||||
|
||||
private func getTrustedKeys() -> [String: Data] {
|
||||
return AppGroup.appGroupUD.dictionary(forKey: trustedKeysKey) as? [String: Data] ?? [:]
|
||||
|
||||
private init() {
|
||||
loadTrustedCertificates()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Persistence
|
||||
|
||||
private func loadTrustedCertificates() {
|
||||
guard let dict = AppGroup.appGroupUD.dictionary(forKey: trustedKeysKey) as? [String: Data] else {
|
||||
trustedCertificates = [:]
|
||||
return
|
||||
}
|
||||
|
||||
var decoded: [String: TrustedCertificate] = [:]
|
||||
for (key, data) in dict {
|
||||
if let cert = try? JSONDecoder().decode(TrustedCertificate.self, from: data) {
|
||||
decoded[key] = cert
|
||||
}
|
||||
}
|
||||
trustedCertificates = decoded
|
||||
}
|
||||
|
||||
|
||||
private func saveTrustedCertificates() {
|
||||
var dict: [String: Data] = [:]
|
||||
for (key, cert) in trustedCertificates {
|
||||
if let data = try? JSONEncoder().encode(cert) {
|
||||
dict[key] = data
|
||||
}
|
||||
}
|
||||
AppGroup.appGroupUD.set(dict, forKey: trustedKeysKey)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Public API
|
||||
|
||||
func findTrustedKey(for secretIdHash: Data) -> Data? {
|
||||
return getTrustedKeys()[secretIdHash.hex]
|
||||
trustedCertificates[secretIdHash.hex]?.certificateData
|
||||
}
|
||||
|
||||
|
||||
func addTrusted(certificate: Sharing_Nearby_PublicCertificate) {
|
||||
|
||||
func addTrusted(certificate: Sharing_Nearby_PublicCertificate, device: RemoteDeviceInfo) {
|
||||
guard let publicKeyData = try? certificate.serializedData() else { return }
|
||||
var keys = getTrustedKeys()
|
||||
keys[certificate.secretID.hex] = publicKeyData
|
||||
AppGroup.appGroupUD.set(keys, forKey: trustedKeysKey)
|
||||
|
||||
trustedCertificates[certificate.secretID.hex] = TrustedCertificate(
|
||||
device: device,
|
||||
creationDate: Date(),
|
||||
certificateData: publicKeyData
|
||||
)
|
||||
|
||||
saveTrustedCertificates()
|
||||
}
|
||||
|
||||
|
||||
func removeTrusted(secretIdHex: String) {
|
||||
trustedCertificates.removeValue(forKey: secretIdHex)
|
||||
saveTrustedCertificates()
|
||||
}
|
||||
|
||||
|
||||
struct TrustedCertificate: Codable, Equatable {
|
||||
|
||||
// warning: do not rename for persistence
|
||||
let device: RemoteDeviceInfo
|
||||
let creationDate: Date
|
||||
let certificateData: Data
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,9 @@
|
||||
699DEBAE2AB0573200115D22 /* QuickDropShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 699DEBA12AB0573200115D22 /* QuickDropShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1129E0BF5100A442DA /* AppDelegate.swift */; };
|
||||
69DA9A1429E0BF5200A442DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; };
|
||||
EB05DFBE2E59FC8900EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
|
||||
EB05DFBF2E59FD0A00EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
|
||||
EB05DFC02E5A2EAA00EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
|
||||
EB16B6122E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; };
|
||||
EB16B6132E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; };
|
||||
EB16B6142E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; };
|
||||
@@ -299,6 +302,7 @@
|
||||
69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = offline_wire_formats.pb.swift; sourceTree = "<group>"; };
|
||||
69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securegcm.pb.swift; sourceTree = "<group>"; };
|
||||
69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securemessage.pb.swift; sourceTree = "<group>"; };
|
||||
EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedDevicesView.swift; sourceTree = "<group>"; };
|
||||
EB16B6112E58B4B9007D9792 /* IdentityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityManager.swift; sourceTree = "<group>"; };
|
||||
EB16B6162E58B4C8007D9792 /* TrustStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustStore.swift; sourceTree = "<group>"; };
|
||||
EB16B7762E59BA9F007D9792 /* AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppGroup.swift; sourceTree = "<group>"; };
|
||||
@@ -590,6 +594,7 @@
|
||||
EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */,
|
||||
EB7F54112D78E95C00989564 /* TutorialView.swift */,
|
||||
EB7F54132D78E9E400989564 /* LicenseWindow.swift */,
|
||||
EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */,
|
||||
EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */,
|
||||
EBC05C812E33B58300EECC18 /* IssueViews */,
|
||||
);
|
||||
@@ -928,6 +933,7 @@
|
||||
EB5C37B82E377A6F0052EFD7 /* SleepManager.swift in Sources */,
|
||||
EB5C37B92E377A6F0052EFD7 /* SaveFilesManager.swift in Sources */,
|
||||
EB5C37BA2E377A6F0052EFD7 /* Helpers.swift in Sources */,
|
||||
EB05DFBE2E59FC8900EF567A /* TrustedDevicesView.swift in Sources */,
|
||||
EB16B6182E58B4C8007D9792 /* TrustStore.swift in Sources */,
|
||||
EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */,
|
||||
EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */,
|
||||
@@ -974,6 +980,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EB05DFBF2E59FD0A00EF567A /* TrustedDevicesView.swift in Sources */,
|
||||
EB5C37F12E377CE70052EFD7 /* ukey.pb.swift in Sources */,
|
||||
EB5C37F22E377CE70052EFD7 /* NearbyConnection.swift in Sources */,
|
||||
EB5C37F32E377CE70052EFD7 /* SaveFilesManager.swift in Sources */,
|
||||
@@ -1034,6 +1041,7 @@
|
||||
EB950B512E520436002D68FA /* securemessage.pb.swift in Sources */,
|
||||
EB950B522E520436002D68FA /* NearbyConnectionManager.swift in Sources */,
|
||||
EB950B532E520436002D68FA /* ukey.pb.swift in Sources */,
|
||||
EB05DFC02E5A2EAA00EF567A /* TrustedDevicesView.swift in Sources */,
|
||||
EB950B542E520436002D68FA /* HKDF.swift in Sources */,
|
||||
EB950B552E520436002D68FA /* SaveFilesManager.swift in Sources */,
|
||||
EB950B562E520436002D68FA /* DataModel.swift in Sources */,
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = "en"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
+2186
-1438
File diff suppressed because it is too large.
Load diff
@@ -45,21 +45,27 @@ struct SettingsView: View {
|
||||
Toggle("OpenFinderAfterReceiving", isOn: $openFinderAfterReceiving)
|
||||
.padding(.top, 5)
|
||||
|
||||
Text("OpenFinderAfterReceivingFooter")
|
||||
.font(.footnote)
|
||||
.padding(.top, 5)
|
||||
|
||||
|
||||
if #available(macOS 13.0, *) {
|
||||
|
||||
Divider()
|
||||
.padding(.vertical, 10)
|
||||
|
||||
LaunchAtLogin.Toggle {
|
||||
Text("LaunchAtLogin")
|
||||
}
|
||||
.padding(.top, 5)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.padding(.vertical, 10)
|
||||
|
||||
Button {
|
||||
openTrustedDevicesWindow()
|
||||
} label: {
|
||||
Text("ManageTrustedDevices")
|
||||
.underline()
|
||||
.font(.subheadline)
|
||||
.opacity(0.5)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 15)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,8 +104,27 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func openTrustedDevicesWindow() {
|
||||
let window = NSWindow(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 600, height: 400),
|
||||
styleMask: [.titled, .closable, .resizable],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
window.center()
|
||||
window.isReleasedWhenClosed = false
|
||||
window.title = "TrustedDevices".localized()
|
||||
window.contentView = NSHostingView(rootView: TrustedDevicesView())
|
||||
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
window.level = .normal
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsView()
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// TrustedDevicesView.swift
|
||||
// QuickDrop
|
||||
//
|
||||
// Created by Leon Böttger on 23.08.25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
#if !os(macOS)
|
||||
import LUI
|
||||
#endif
|
||||
|
||||
struct TrustedDevicesView: View {
|
||||
@StateObject private var store = TrustStore.shared
|
||||
|
||||
@State private var deviceToRemove: String? = nil
|
||||
@State private var showRemoveAlert = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
if store.trustedCertificates.isEmpty {
|
||||
VStack(spacing: 12) {
|
||||
Text("NoTrustedDevices")
|
||||
.font(.headline)
|
||||
.foregroundColor(.secondary)
|
||||
Text("TrustedDevicesDescription")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.multilineTextAlignment(.center)
|
||||
.padding()
|
||||
.frame(maxHeight: .infinity)
|
||||
} else {
|
||||
|
||||
#if !os(macOS)
|
||||
CustomSection(footer: "TrustedDevicesDescription") {
|
||||
devicesView
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
#else
|
||||
List {
|
||||
devicesView
|
||||
|
||||
Text("TrustedDevicesDescription")
|
||||
.font(.footnote)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.animation(.default, value: store.trustedCertificates)
|
||||
.alert(isPresented: $showRemoveAlert) {
|
||||
Alert(
|
||||
title: Text("RemoveTrustedDeviceAlertTitle"),
|
||||
message: Text("RemoveTrustedDeviceAlertDescription"),
|
||||
primaryButton: .destructive(Text("Remove")) {
|
||||
if let key = deviceToRemove {
|
||||
store.removeTrusted(secretIdHex: key)
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var devicesView: some View {
|
||||
ForEach(Array(store.trustedCertificates.keys), id: \.self) { key in
|
||||
if let cert = store.trustedCertificates[key] {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(cert.device.name ?? "Unknown".localized())
|
||||
.font(.headline)
|
||||
|
||||
// macOS 11: use DateFormatter instead of .formatted
|
||||
Text(Self.dateFormatter.string(from: cert.creationDate))
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
Button(action: {
|
||||
deviceToRemove = key
|
||||
showRemoveAlert = true
|
||||
}) {
|
||||
Image(systemName: "minus.circle.fill")
|
||||
.font(.system(size: 20))
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.buttonStyle(BorderlessButtonStyle())
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DateFormatter for macOS 11
|
||||
private static let dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
formatter.timeStyle = .short
|
||||
return formatter
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
#Preview {
|
||||
TrustedDevicesView()
|
||||
.onAppear {
|
||||
|
||||
var cert1 = Sharing_Nearby_PublicCertificate()
|
||||
cert1.secretID = Data([0x01, 0x02, 0x03, 0x04])
|
||||
|
||||
TrustStore.shared.addTrusted(certificate: cert1, device: RemoteDeviceInfo(name: "MacBook Pro", type: .computer))
|
||||
|
||||
|
||||
var cert2 = Sharing_Nearby_PublicCertificate()
|
||||
cert2.secretID = Data([0x0A, 0x0B, 0x0C, 0x0D])
|
||||
|
||||
TrustStore.shared.addTrusted(certificate: cert2, device: RemoteDeviceInfo(name: "iPhone", type: .phone))
|
||||
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,12 @@ struct ContentView: View {
|
||||
} label: {
|
||||
NavigationLinkLabel(imageName: "folder.fill", text: "BrowseDownloadedFiles")
|
||||
}
|
||||
|
||||
LUILink(destination: NavigationSubView(header: "TrustedDevices") {
|
||||
TrustedDevicesView()
|
||||
}) {
|
||||
NavigationLinkLabel(imageName: "checkmark.shield.fill", text: "ManageTrustedDevices", backgroundColor: .green)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,8 +61,15 @@ class ReceiveModel: ObservableObject, InboundAppDelegate {
|
||||
|
||||
let pinCodeMessage = String(format: "PinCode".localized(), arguments: [transfer.pinCode ?? "?"])
|
||||
let transferID = transfer.id
|
||||
|
||||
#if os(macOS)
|
||||
let isMac = true
|
||||
#else
|
||||
let isMac = false
|
||||
#endif
|
||||
|
||||
|
||||
if acceptAutomatically && isMac() {
|
||||
if acceptAutomatically && isMac {
|
||||
pressAcceptButton(transferID: transfer.id, trustDevice: false)
|
||||
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, _ in
|
||||
|
||||
Reference in new issue
Block a user