import Foundation protocol IDPServicing { func bootstrap() async throws -> BootstrapContext func signIn(with request: PairingAuthenticationRequest) async throws -> SignInResult func identify(with request: PairingAuthenticationRequest) async throws -> DashboardSnapshot func refreshDashboard() async throws -> DashboardSnapshot func approveRequest(id: UUID) async throws -> DashboardSnapshot func rejectRequest(id: UUID) async throws -> DashboardSnapshot func simulateIncomingRequest() async throws -> DashboardSnapshot func markNotificationRead(id: UUID) async throws -> DashboardSnapshot } actor MockIDPService: IDPServicing { private let profile = MemberProfile( name: "Phil Kunz", handle: "phil@idp.global", organization: "idp.global", deviceCount: 4, recoverySummary: "Recovery kit healthy with 2 of 3 backup paths verified." ) private var requests: [ApprovalRequest] = [] private var notifications: [AppNotification] = [] init() { requests = Self.seedRequests() notifications = Self.seedNotifications() } func bootstrap() async throws -> BootstrapContext { try await Task.sleep(for: .milliseconds(120)) return BootstrapContext( suggestedPairingPayload: "idp.global://pair?token=swiftapp-demo-berlin&origin=code.foss.global&device=Safari%20on%20Berlin%20MBP" ) } func signIn(with request: PairingAuthenticationRequest) async throws -> SignInResult { try await Task.sleep(for: .milliseconds(260)) try validateSignedGPSPosition(in: request) let session = try parseSession(from: request) notifications.insert( AppNotification( title: "Passport activated", message: pairingMessage(for: session), sentAt: .now, kind: .security, isUnread: true ), at: 0 ) return SignInResult( session: session, snapshot: snapshot() ) } func identify(with request: PairingAuthenticationRequest) async throws -> DashboardSnapshot { try await Task.sleep(for: .milliseconds(180)) try validateSignedGPSPosition(in: request) let context = try parsePayloadContext(from: request.pairingPayload) notifications.insert( AppNotification( title: "Identity proof completed", message: identificationMessage(for: context, signedGPSPosition: request.signedGPSPosition), sentAt: .now, kind: .security, isUnread: true ), at: 0 ) return snapshot() } func refreshDashboard() async throws -> DashboardSnapshot { try await Task.sleep(for: .milliseconds(180)) return snapshot() } func approveRequest(id: UUID) async throws -> DashboardSnapshot { try await Task.sleep(for: .milliseconds(150)) guard let index = requests.firstIndex(where: { $0.id == id }) else { throw AppError.requestNotFound } requests[index].status = .approved notifications.insert( AppNotification( title: "Identity verified", message: "\(requests[index].title) was completed for \(requests[index].source).", sentAt: .now, kind: .approval, isUnread: true ), at: 0 ) return snapshot() } func rejectRequest(id: UUID) async throws -> DashboardSnapshot { try await Task.sleep(for: .milliseconds(150)) guard let index = requests.firstIndex(where: { $0.id == id }) else { throw AppError.requestNotFound } requests[index].status = .rejected notifications.insert( AppNotification( title: "Identity proof declined", message: "\(requests[index].title) was declined before the session could continue.", sentAt: .now, kind: .security, isUnread: true ), at: 0 ) return snapshot() } func simulateIncomingRequest() async throws -> DashboardSnapshot { try await Task.sleep(for: .milliseconds(120)) let syntheticRequest = ApprovalRequest( title: "Prove identity for web sign-in", subtitle: "A browser session is asking this passport to prove that it is really you.", source: "auth.idp.global", createdAt: .now, kind: .signIn, risk: .routine, scopes: ["proof:basic", "client:web", "method:qr"], status: .pending ) requests.insert(syntheticRequest, at: 0) notifications.insert( AppNotification( title: "Fresh identity proof request", message: "A new relying party is waiting for your identity proof.", sentAt: .now, kind: .approval, isUnread: true ), at: 0 ) return snapshot() } func markNotificationRead(id: UUID) async throws -> DashboardSnapshot { try await Task.sleep(for: .milliseconds(80)) guard let index = notifications.firstIndex(where: { $0.id == id }) else { return snapshot() } notifications[index].isUnread = false return snapshot() } private func snapshot() -> DashboardSnapshot { DashboardSnapshot( profile: profile, requests: requests, notifications: notifications ) } private func validateSignedGPSPosition(in request: PairingAuthenticationRequest) throws { if request.transport == .nfc, request.signedGPSPosition == nil { throw AppError.missingSignedGPSPosition } if let signedGPSPosition = request.signedGPSPosition, !signedGPSPosition.verified(for: request.pairingPayload) { throw AppError.invalidSignedGPSPosition } } private func parseSession(from request: PairingAuthenticationRequest) throws -> AuthSession { let context = try parsePayloadContext(from: request.pairingPayload) return AuthSession( deviceName: context.deviceName, originHost: context.originHost, pairedAt: .now, tokenPreview: context.tokenPreview, pairingCode: request.pairingPayload, pairingTransport: request.transport, signedGPSPosition: request.signedGPSPosition ) } private func parsePayloadContext(from payload: String) throws -> PayloadContext { if let components = URLComponents(string: payload), components.scheme == "idp.global", components.host == "pair" { let queryItems = components.queryItems ?? [] let token = queryItems.first(where: { $0.name == "token" })?.value ?? "demo-token" let origin = queryItems.first(where: { $0.name == "origin" })?.value ?? "code.foss.global" let device = queryItems.first(where: { $0.name == "device" })?.value ?? "Web Session" return PayloadContext( deviceName: device, originHost: origin, tokenPreview: String(token.suffix(6)) ) } if payload.contains("token") || payload.contains("pair") { return PayloadContext( deviceName: "Manual Session", originHost: "code.foss.global", tokenPreview: String(payload.suffix(6)) ) } throw AppError.invalidPairingPayload } private func pairingMessage(for session: AuthSession) -> String { let transportSummary: String switch session.pairingTransport { case .qr: transportSummary = "activated via QR" case .nfc: transportSummary = "activated via NFC with a signed GPS position" case .manual: transportSummary = "activated via manual payload" case .preview: transportSummary = "activated via preview payload" } if let signedGPSPosition = session.signedGPSPosition { return "\(session.deviceName) is now acting as a passport, \(transportSummary) against \(session.originHost) from \(signedGPSPosition.coordinateSummary) \(signedGPSPosition.accuracySummary)." } return "\(session.deviceName) is now acting as a passport, \(transportSummary) against \(session.originHost)." } private func identificationMessage(for context: PayloadContext, signedGPSPosition: SignedGPSPosition?) -> String { if let signedGPSPosition { return "A signed GPS proof was sent for \(context.deviceName) on \(context.originHost) from \(signedGPSPosition.coordinateSummary) \(signedGPSPosition.accuracySummary)." } return "An identity proof was completed for \(context.deviceName) on \(context.originHost)." } private struct PayloadContext { let deviceName: String let originHost: String let tokenPreview: String } private static func seedRequests() -> [ApprovalRequest] { [ ApprovalRequest( title: "Prove identity for Safari sign-in", subtitle: "The portal wants this passport to prove that the browser session is really you.", source: "code.foss.global", createdAt: .now.addingTimeInterval(-60 * 12), kind: .signIn, risk: .routine, scopes: ["proof:basic", "client:web", "origin:trusted"], status: .pending ), ApprovalRequest( title: "Prove identity for workstation unlock", subtitle: "Your secure workspace is asking for a stronger proof before it unlocks.", source: "berlin-mbp.idp.global", createdAt: .now.addingTimeInterval(-60 * 42), kind: .elevatedAction, risk: .elevated, scopes: ["proof:high", "client:desktop", "presence:required"], status: .pending ), ApprovalRequest( title: "Prove identity for CLI session", subtitle: "The CLI session asked for proof earlier and was completed from this passport.", source: "cli.idp.global", createdAt: .now.addingTimeInterval(-60 * 180), kind: .signIn, risk: .routine, scopes: ["proof:basic", "client:cli"], status: .approved ) ] } private static func seedNotifications() -> [AppNotification] { [ AppNotification( title: "Two identity checks are waiting", message: "One routine web proof and one stronger workstation proof are waiting for this passport.", sentAt: .now.addingTimeInterval(-60 * 8), kind: .approval, isUnread: true ), AppNotification( title: "Recovery health check passed", message: "Backup recovery channels were verified in the last 24 hours.", sentAt: .now.addingTimeInterval(-60 * 95), kind: .system, isUnread: false ), AppNotification( title: "Passport quiet hours active", message: "Routine identity checks will be delivered silently until the morning.", sentAt: .now.addingTimeInterval(-60 * 220), kind: .security, isUnread: false ) ] } }