import AppKit import VoiceCatCore final class ServerIdentitySheet: NSViewController { var onComplete: ((Bool) -> Void)? private let tofuStatus: VoiceCatTofuStatus private let displayFingerprint: String init(tofuStatus: VoiceCatTofuStatus, displayFingerprint: String) { self.tofuStatus = tofuStatus self.displayFingerprint = displayFingerprint super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError() } override func loadView() { view = NSView(frame: NSRect(x: 0, y: 0, width: 440, height: 220)) } override func viewDidLoad() { super.viewDidLoad() buildUI() } private func buildUI() { let isMismatch = tofuStatus == .mismatch let titleText = isMismatch ? "Server Identity Mismatch — Possible MITM!" : "New Server Identity" let titleLabel = NSTextField(labelWithString: titleText) titleLabel.font = .boldSystemFont(ofSize: 14) if isMismatch { titleLabel.textColor = .systemRed } titleLabel.setAccessibilityLabel(titleText) let bodyText: String if isMismatch { bodyText = "The server's identity has changed since you last connected. This may indicate a man-in-the-middle attack or that the server was reinstalled. Do NOT accept unless you know why the identity changed." } else { bodyText = "This is the first time you are connecting to this server. Verify the fingerprint below with the server administrator before accepting." } let bodyLabel = NSTextField(wrappingLabelWithString: bodyText) bodyLabel.textColor = .labelColor let fpLabel = NSTextField(labelWithString: "Server fingerprint:") let fpField = NSTextField(labelWithString: displayFingerprint.isEmpty ? "(not available)" : displayFingerprint) fpField.font = NSFont.monospacedSystemFont(ofSize: 11, weight: .regular) fpField.isSelectable = true fpField.setAccessibilityLabel("Server identity fingerprint: \(displayFingerprint)") let rejectButton = NSButton(title: "Reject (Disconnect)", target: self, action: #selector(rejectClicked)) rejectButton.bezelStyle = .rounded rejectButton.setAccessibilityLabel("Reject server identity and disconnect") if isMismatch { rejectButton.keyEquivalent = "\r" } let acceptButton = NSButton(title: "Accept", target: self, action: #selector(acceptClicked)) acceptButton.bezelStyle = .rounded if !isMismatch { acceptButton.keyEquivalent = "\r" } acceptButton.setAccessibilityLabel("Accept server identity and continue connecting") let buttonRow = NSStackView(views: [NSView(), rejectButton, acceptButton]) buttonRow.orientation = .horizontal; buttonRow.spacing = 8 let fpRow = NSStackView(views: [fpLabel, fpField]) fpRow.orientation = .horizontal; fpRow.spacing = 8 let stack = NSStackView(views: [titleLabel, bodyLabel, fpRow, buttonRow]) stack.orientation = .vertical stack.spacing = 12 stack.edgeInsets = NSEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) stack.translatesAutoresizingMaskIntoConstraints = false view.addSubview(stack) NSLayoutConstraint.activate([ stack.topAnchor.constraint(equalTo: view.topAnchor), stack.leadingAnchor.constraint(equalTo: view.leadingAnchor), stack.trailingAnchor.constraint(equalTo: view.trailingAnchor), stack.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) } @objc private func acceptClicked() { dismiss(nil) onComplete?(true) } @objc private func rejectClicked() { dismiss(nil) onComplete?(false) } }