Files
swiftapp/Sources/Core/Services/MockIDPService.swift

247 lines
8.4 KiB
Swift
Raw Normal View History

import Foundation
protocol IDPServicing {
func bootstrap() async throws -> BootstrapContext
func signIn(withQRCode payload: String) async throws -> SignInResult
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(
suggestedQRCodePayload: "idp.global://pair?token=swiftapp-demo-berlin&origin=code.foss.global&device=Safari%20on%20Berlin%20MBP"
)
}
func signIn(withQRCode payload: String) async throws -> SignInResult {
try await Task.sleep(for: .milliseconds(260))
let session = try parseSession(from: payload)
notifications.insert(
AppNotification(
title: "New device paired",
message: "\(session.deviceName) completed a QR pairing against \(session.originHost).",
sentAt: .now,
kind: .security,
isUnread: true
),
at: 0
)
return SignInResult(
session: session,
snapshot: 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: "Request approved",
message: "\(requests[index].title) was approved 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: "Request rejected",
message: "\(requests[index].title) was rejected before token issuance.",
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: "Approve SSH certificate issue",
subtitle: "CI runner wants a short-lived signing certificate for a deployment pipeline.",
source: "deploy.idp.global",
createdAt: .now,
kind: .elevatedAction,
risk: .elevated,
scopes: ["sign:ssh", "ttl:10m", "environment:staging"],
status: .pending
)
requests.insert(syntheticRequest, at: 0)
notifications.insert(
AppNotification(
title: "Fresh approval request",
message: "A staging deployment is waiting for your approval.",
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 parseSession(from payload: String) throws -> AuthSession {
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 AuthSession(
deviceName: device,
originHost: origin,
pairedAt: .now,
tokenPreview: String(token.suffix(6)),
pairingCode: payload
)
}
if payload.contains("token") || payload.contains("pair") {
return AuthSession(
deviceName: "Manual Pairing",
originHost: "code.foss.global",
pairedAt: .now,
tokenPreview: String(payload.suffix(6)),
pairingCode: payload
)
}
throw AppError.invalidQRCode
}
private static func seedRequests() -> [ApprovalRequest] {
[
ApprovalRequest(
title: "Approve Safari sign-in",
subtitle: "A browser session from Berlin wants an SSO token for the portal.",
source: "code.foss.global",
createdAt: .now.addingTimeInterval(-60 * 12),
kind: .signIn,
risk: .routine,
scopes: ["openid", "profile", "groups:read"],
status: .pending
),
ApprovalRequest(
title: "Grant package publish access",
subtitle: "The release bot is asking for a scoped publish token.",
source: "registry.foss.global",
createdAt: .now.addingTimeInterval(-60 * 42),
kind: .accessGrant,
risk: .elevated,
scopes: ["packages:write", "ttl:30m"],
status: .pending
),
ApprovalRequest(
title: "Approve CLI login",
subtitle: "A terminal session completed QR pairing earlier today.",
source: "cli.idp.global",
createdAt: .now.addingTimeInterval(-60 * 180),
kind: .signIn,
risk: .routine,
scopes: ["openid", "profile"],
status: .approved
)
]
}
private static func seedNotifications() -> [AppNotification] {
[
AppNotification(
title: "Two requests are waiting",
message: "The queue includes one routine sign-in and one elevated access grant.",
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: "Quiet hours active on mobile",
message: "Routine notifications will be delivered silently until the morning.",
sentAt: .now.addingTimeInterval(-60 * 220),
kind: .security,
isUnread: false
)
]
}
}