import CryptoKit import Foundation enum AppSection: String, CaseIterable, Identifiable, Hashable, Codable { case inbox case notifications case devices case identity case settings var id: String { rawValue } var title: String { switch self { case .inbox: "Inbox" case .notifications: "Notifications" case .devices: "Devices" case .identity: "Identity" case .settings: "Settings" } } var systemImage: String { switch self { case .inbox: "tray.full.fill" case .notifications: "bell.badge.fill" case .devices: "desktopcomputer" case .identity: "person.crop.rectangle.stack.fill" case .settings: "gearshape.fill" } } } enum NotificationPermissionState: String, CaseIterable, Identifiable, Codable { case unknown case allowed case provisional case denied var id: String { rawValue } var title: String { switch self { case .unknown: "Not Asked Yet" case .allowed: "Enabled" case .provisional: "Delivered Quietly" case .denied: "Disabled" } } var systemImage: String { switch self { case .unknown: "bell" case .allowed: "bell.badge.fill" case .provisional: "bell.badge" case .denied: "bell.slash.fill" } } var summary: String { switch self { case .unknown: "The app has not asked for notification delivery yet." case .allowed: "Identity proof alerts can break through immediately when a check arrives." case .provisional: "Identity proof alerts can be delivered quietly until the user promotes them." case .denied: "Identity proof events stay in-app until the user re-enables notifications." } } } struct BootstrapContext { let suggestedPairingPayload: String } enum PairingTransport: String, Hashable, Codable { case qr case nfc case manual case preview var title: String { switch self { case .qr: "QR" case .nfc: "NFC" case .manual: "Manual" case .preview: "Preview" } } } struct PairingAuthenticationRequest: Hashable { let pairingPayload: String let transport: PairingTransport let signedGPSPosition: SignedGPSPosition? } struct SignedGPSPosition: Hashable, Codable { let latitude: Double let longitude: Double let horizontalAccuracyMeters: Double let capturedAt: Date let signatureBase64: String let publicKeyBase64: String init( latitude: Double, longitude: Double, horizontalAccuracyMeters: Double, capturedAt: Date, signatureBase64: String = "", publicKeyBase64: String = "" ) { self.latitude = latitude self.longitude = longitude self.horizontalAccuracyMeters = horizontalAccuracyMeters self.capturedAt = capturedAt self.signatureBase64 = signatureBase64 self.publicKeyBase64 = publicKeyBase64 } var coordinateSummary: String { "\(Self.normalized(latitude, precision: 5)), \(Self.normalized(longitude, precision: 5))" } var accuracySummary: String { "±\(Int(horizontalAccuracyMeters.rounded())) m" } func signingPayload(for pairingPayload: String) -> Data { let lines = [ "payload=\(pairingPayload)", "latitude=\(Self.normalized(latitude, precision: 6))", "longitude=\(Self.normalized(longitude, precision: 6))", "accuracy=\(Self.normalized(horizontalAccuracyMeters, precision: 2))", "captured_at=\(Self.timestampFormatter.string(from: capturedAt))" ] return Data(lines.joined(separator: "\n").utf8) } func verified(for pairingPayload: String) -> Bool { guard let signatureData = Data(base64Encoded: signatureBase64), let publicKeyData = Data(base64Encoded: publicKeyBase64), let publicKey = try? P256.Signing.PublicKey(x963Representation: publicKeyData), let signature = try? P256.Signing.ECDSASignature(derRepresentation: signatureData) else { return false } return publicKey.isValidSignature(signature, for: signingPayload(for: pairingPayload)) } func signed(signatureData: Data, publicKeyData: Data) -> SignedGPSPosition { SignedGPSPosition( latitude: latitude, longitude: longitude, horizontalAccuracyMeters: horizontalAccuracyMeters, capturedAt: capturedAt, signatureBase64: signatureData.base64EncodedString(), publicKeyBase64: publicKeyData.base64EncodedString() ) } private static let timestampFormatter: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] return formatter }() private static func normalized(_ value: Double, precision: Int) -> String { String(format: "%.\(precision)f", locale: Locale(identifier: "en_US_POSIX"), value) } } struct DashboardSnapshot { let profile: MemberProfile let requests: [ApprovalRequest] let notifications: [AppNotification] let devices: [PassportDeviceRecord] init( profile: MemberProfile, requests: [ApprovalRequest], notifications: [AppNotification], devices: [PassportDeviceRecord] = [] ) { self.profile = profile self.requests = requests self.notifications = notifications self.devices = devices } } struct SignInResult { let session: AuthSession let snapshot: DashboardSnapshot } struct MemberProfile: Identifiable, Hashable, Codable { let id: UUID let name: String let handle: String let organization: String let deviceCount: Int let recoverySummary: String init( id: UUID = UUID(), name: String, handle: String, organization: String, deviceCount: Int, recoverySummary: String ) { self.id = id self.name = name self.handle = handle self.organization = organization self.deviceCount = deviceCount self.recoverySummary = recoverySummary } } struct AuthSession: Identifiable, Hashable, Codable { let id: UUID let deviceName: String let originHost: String let serverURL: String let passportDeviceID: String let pairedAt: Date let tokenPreview: String let pairingCode: String let pairingTransport: PairingTransport let signedGPSPosition: SignedGPSPosition? init( id: UUID = UUID(), deviceName: String, originHost: String, serverURL: String = "", passportDeviceID: String = "", pairedAt: Date, tokenPreview: String, pairingCode: String, pairingTransport: PairingTransport = .manual, signedGPSPosition: SignedGPSPosition? = nil ) { self.id = id self.deviceName = deviceName self.originHost = originHost self.serverURL = serverURL self.passportDeviceID = passportDeviceID self.pairedAt = pairedAt self.tokenPreview = tokenPreview self.pairingCode = pairingCode self.pairingTransport = pairingTransport self.signedGPSPosition = signedGPSPosition } private enum CodingKeys: String, CodingKey { case id case deviceName case originHost case serverURL case passportDeviceID case pairedAt case tokenPreview case pairingCode case pairingTransport case signedGPSPosition } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID() deviceName = try container.decode(String.self, forKey: .deviceName) originHost = try container.decode(String.self, forKey: .originHost) serverURL = try container.decodeIfPresent(String.self, forKey: .serverURL) ?? "https://\(originHost)" passportDeviceID = try container.decodeIfPresent(String.self, forKey: .passportDeviceID) ?? "" pairedAt = try container.decode(Date.self, forKey: .pairedAt) tokenPreview = try container.decode(String.self, forKey: .tokenPreview) pairingCode = try container.decode(String.self, forKey: .pairingCode) pairingTransport = try container.decodeIfPresent(PairingTransport.self, forKey: .pairingTransport) ?? .manual signedGPSPosition = try container.decodeIfPresent(SignedGPSPosition.self, forKey: .signedGPSPosition) } } enum ApprovalRequestKind: String, CaseIterable, Hashable, Codable { case signIn case accessGrant case elevatedAction var title: String { switch self { case .signIn: "Identity Check" case .accessGrant: "Strong Proof" case .elevatedAction: "Sensitive Proof" } } var systemImage: String { switch self { case .signIn: "qrcode.viewfinder" case .accessGrant: "person.badge.shield.checkmark.fill" case .elevatedAction: "shield.checkered" } } } enum ApprovalRisk: String, Hashable, Codable { case routine case elevated var title: String { switch self { case .routine: "Routine" case .elevated: "Elevated" } } var summary: String { switch self { case .routine: "A familiar identity proof for a normal sign-in or check." case .elevated: "A higher-assurance identity proof for a sensitive check." } } var guidance: String { switch self { case .routine: "Review the origin and continue only if it matches the proof you started." case .elevated: "Only continue if you initiated this proof and trust the origin asking for it." } } } enum ApprovalStatus: String, Hashable, Codable { case pending case approved case rejected var title: String { switch self { case .pending: "Pending" case .approved: "Approved" case .rejected: "Denied" } } var systemImage: String { switch self { case .pending: "clock.badge" case .approved: "checkmark.circle.fill" case .rejected: "xmark.circle.fill" } } } struct ApprovalRequest: Identifiable, Hashable, Codable { let id: UUID let serverID: String let hintID: String let title: String let subtitle: String let source: String let createdAt: Date let kind: ApprovalRequestKind let risk: ApprovalRisk let scopes: [String] let requiresLocation: Bool let challenge: String? let signingPayload: String? let deviceSummaryText: String? let locationSummaryText: String? let networkSummaryText: String? let ipSummaryText: String? let expiresAtDate: Date? var status: ApprovalStatus init( id: UUID = UUID(), serverID: String = UUID().uuidString, hintID: String = UUID().uuidString, title: String, subtitle: String, source: String, createdAt: Date, kind: ApprovalRequestKind, risk: ApprovalRisk, scopes: [String], requiresLocation: Bool = false, challenge: String? = nil, signingPayload: String? = nil, deviceSummaryText: String? = nil, locationSummaryText: String? = nil, networkSummaryText: String? = nil, ipSummaryText: String? = nil, expiresAtDate: Date? = nil, status: ApprovalStatus ) { self.id = id self.serverID = serverID self.hintID = hintID self.title = title self.subtitle = subtitle self.source = source self.createdAt = createdAt self.kind = kind self.risk = risk self.scopes = scopes self.requiresLocation = requiresLocation self.challenge = challenge self.signingPayload = signingPayload self.deviceSummaryText = deviceSummaryText self.locationSummaryText = locationSummaryText self.networkSummaryText = networkSummaryText self.ipSummaryText = ipSummaryText self.expiresAtDate = expiresAtDate self.status = status } private enum CodingKeys: String, CodingKey { case id case serverID case hintID case title case subtitle case source case createdAt case kind case risk case scopes case requiresLocation case challenge case signingPayload case deviceSummaryText case locationSummaryText case networkSummaryText case ipSummaryText case expiresAtDate case status } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID() serverID = try container.decodeIfPresent(String.self, forKey: .serverID) ?? id.uuidString hintID = try container.decodeIfPresent(String.self, forKey: .hintID) ?? serverID title = try container.decode(String.self, forKey: .title) subtitle = try container.decode(String.self, forKey: .subtitle) source = try container.decode(String.self, forKey: .source) createdAt = try container.decode(Date.self, forKey: .createdAt) kind = try container.decode(ApprovalRequestKind.self, forKey: .kind) risk = try container.decode(ApprovalRisk.self, forKey: .risk) scopes = try container.decode([String].self, forKey: .scopes) requiresLocation = try container.decodeIfPresent(Bool.self, forKey: .requiresLocation) ?? false challenge = try container.decodeIfPresent(String.self, forKey: .challenge) signingPayload = try container.decodeIfPresent(String.self, forKey: .signingPayload) deviceSummaryText = try container.decodeIfPresent(String.self, forKey: .deviceSummaryText) locationSummaryText = try container.decodeIfPresent(String.self, forKey: .locationSummaryText) networkSummaryText = try container.decodeIfPresent(String.self, forKey: .networkSummaryText) ipSummaryText = try container.decodeIfPresent(String.self, forKey: .ipSummaryText) expiresAtDate = try container.decodeIfPresent(Date.self, forKey: .expiresAtDate) status = try container.decode(ApprovalStatus.self, forKey: .status) } var scopeSummary: String { if scopes.isEmpty { return "No proof details listed" } let suffix = scopes.count == 1 ? "" : "s" return "\(scopes.count) proof detail\(suffix)" } var trustHeadline: String { switch (kind, risk) { case (.signIn, .routine): "Standard identity proof" case (.signIn, .elevated): "High-assurance sign-in proof" case (.accessGrant, _): "Cross-device identity proof" case (.elevatedAction, _): "Sensitive identity proof" } } var trustDetail: String { switch kind { case .signIn: "This request proves that the person at the browser, CLI, or device is really you." case .accessGrant: "This request asks for a stronger proof so the relying party can trust the session with higher confidence." case .elevatedAction: "This request asks for the highest confidence proof before continuing with a sensitive flow." } } } enum AppNotificationKind: String, Hashable, Codable { case approval case security case system var title: String { switch self { case .approval: "Proof" case .security: "Security" case .system: "System" } } var systemImage: String { switch self { case .approval: "checkmark.seal.fill" case .security: "shield.fill" case .system: "sparkles" } } var summary: String { switch self { case .approval: "Identity proof activity" case .security: "Passport and security posture updates" case .system: "Product and environment status messages" } } } struct AppNotification: Identifiable, Hashable, Codable { let id: UUID let serverID: String let hintID: String let title: String let message: String let sentAt: Date let kind: AppNotificationKind var isUnread: Bool init( id: UUID = UUID(), serverID: String = UUID().uuidString, hintID: String = UUID().uuidString, title: String, message: String, sentAt: Date, kind: AppNotificationKind, isUnread: Bool ) { self.id = id self.serverID = serverID self.hintID = hintID self.title = title self.message = message self.sentAt = sentAt self.kind = kind self.isUnread = isUnread } private enum CodingKeys: String, CodingKey { case id case serverID case hintID case title case message case sentAt case kind case isUnread } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID() serverID = try container.decodeIfPresent(String.self, forKey: .serverID) ?? id.uuidString hintID = try container.decodeIfPresent(String.self, forKey: .hintID) ?? serverID title = try container.decode(String.self, forKey: .title) message = try container.decode(String.self, forKey: .message) sentAt = try container.decode(Date.self, forKey: .sentAt) kind = try container.decode(AppNotificationKind.self, forKey: .kind) isUnread = try container.decode(Bool.self, forKey: .isUnread) } } struct PassportDeviceRecord: Identifiable, Hashable, Codable { let id: String let label: String let platform: String let lastSeenAt: Date? let isCurrent: Bool } enum AppError: LocalizedError, Equatable { case invalidPairingPayload case missingSignedGPSPosition case invalidSignedGPSPosition case locationPermissionDenied case locationUnavailable case requestNotFound var errorDescription: String? { switch self { case .invalidPairingPayload: "That idp.global payload is not valid for this action." case .missingSignedGPSPosition: "Tap NFC requires a signed GPS position." case .invalidSignedGPSPosition: "The signed GPS position attached to this NFC proof could not be verified." case .locationPermissionDenied: "Location access is required so Tap NFC can attach a signed GPS position." case .locationUnavailable: "Unable to determine the current GPS position for Tap NFC." case .requestNotFound: "The selected identity check could not be found." } } }