2026-04-17 22:08:27 +02:00
import Foundation
protocol IDPServicing {
func bootstrap ( ) async throws -> BootstrapContext
2026-04-18 01:05:22 +02:00
func signIn ( with request : PairingAuthenticationRequest ) async throws -> SignInResult
func identify ( with request : PairingAuthenticationRequest ) async throws -> DashboardSnapshot
2026-04-17 22:08:27 +02:00
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 (
2026-04-18 01:05:22 +02:00
suggestedPairingPayload : " idp.global://pair?token=swiftapp-demo-berlin&origin=code.foss.global&device=Safari%20on%20Berlin%20MBP "
2026-04-17 22:08:27 +02:00
)
}
2026-04-18 01:05:22 +02:00
func signIn ( with request : PairingAuthenticationRequest ) async throws -> SignInResult {
2026-04-17 22:08:27 +02:00
try await Task . sleep ( for : . milliseconds ( 260 ) )
2026-04-18 01:05:22 +02:00
try validateSignedGPSPosition ( in : request )
let session = try parseSession ( from : request )
2026-04-17 22:08:27 +02:00
notifications . insert (
AppNotification (
2026-04-18 01:05:22 +02:00
title : " Passport activated " ,
message : pairingMessage ( for : session ) ,
2026-04-17 22:08:27 +02:00
sentAt : . now ,
kind : . security ,
isUnread : true
) ,
at : 0
)
return SignInResult (
session : session ,
snapshot : snapshot ( )
)
}
2026-04-18 01:05:22 +02:00
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 ( )
}
2026-04-17 22:08:27 +02:00
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 (
2026-04-18 01:05:22 +02:00
title : " Identity verified " ,
message : " \( requests [ index ] . title ) was completed for \( requests [ index ] . source ) . " ,
2026-04-17 22:08:27 +02:00
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 (
2026-04-18 01:05:22 +02:00
title : " Identity proof declined " ,
message : " \( requests [ index ] . title ) was declined before the session could continue. " ,
2026-04-17 22:08:27 +02:00
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 (
2026-04-18 01:05:22 +02:00
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 " ,
2026-04-17 22:08:27 +02:00
createdAt : . now ,
2026-04-18 01:05:22 +02:00
kind : . signIn ,
risk : . routine ,
scopes : [ " proof:basic " , " client:web " , " method:qr " ] ,
2026-04-17 22:08:27 +02:00
status : . pending
)
requests . insert ( syntheticRequest , at : 0 )
notifications . insert (
AppNotification (
2026-04-18 01:05:22 +02:00
title : " Fresh identity proof request " ,
message : " A new relying party is waiting for your identity proof. " ,
2026-04-17 22:08:27 +02:00
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
)
}
2026-04-18 01:05:22 +02:00
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 {
2026-04-17 22:08:27 +02:00
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 "
2026-04-18 01:05:22 +02:00
return PayloadContext (
2026-04-17 22:08:27 +02:00
deviceName : device ,
originHost : origin ,
2026-04-18 01:05:22 +02:00
tokenPreview : String ( token . suffix ( 6 ) )
2026-04-17 22:08:27 +02:00
)
}
if payload . contains ( " token " ) || payload . contains ( " pair " ) {
2026-04-18 01:05:22 +02:00
return PayloadContext (
deviceName : " Manual Session " ,
2026-04-17 22:08:27 +02:00
originHost : " code.foss.global " ,
2026-04-18 01:05:22 +02:00
tokenPreview : String ( payload . suffix ( 6 ) )
2026-04-17 22:08:27 +02:00
)
}
2026-04-18 01:05:22 +02:00
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
2026-04-17 22:08:27 +02:00
}
private static func seedRequests ( ) -> [ ApprovalRequest ] {
[
ApprovalRequest (
2026-04-18 01:05:22 +02:00
title : " Prove identity for Safari sign-in " ,
subtitle : " The portal wants this passport to prove that the browser session is really you. " ,
2026-04-17 22:08:27 +02:00
source : " code.foss.global " ,
createdAt : . now . addingTimeInterval ( - 60 * 12 ) ,
kind : . signIn ,
risk : . routine ,
2026-04-18 01:05:22 +02:00
scopes : [ " proof:basic " , " client:web " , " origin:trusted " ] ,
2026-04-17 22:08:27 +02:00
status : . pending
) ,
ApprovalRequest (
2026-04-18 01:05:22 +02:00
title : " Prove identity for workstation unlock " ,
subtitle : " Your secure workspace is asking for a stronger proof before it unlocks. " ,
source : " berlin-mbp.idp.global " ,
2026-04-17 22:08:27 +02:00
createdAt : . now . addingTimeInterval ( - 60 * 42 ) ,
2026-04-18 01:05:22 +02:00
kind : . elevatedAction ,
2026-04-17 22:08:27 +02:00
risk : . elevated ,
2026-04-18 01:05:22 +02:00
scopes : [ " proof:high " , " client:desktop " , " presence:required " ] ,
2026-04-17 22:08:27 +02:00
status : . pending
) ,
ApprovalRequest (
2026-04-18 01:05:22 +02:00
title : " Prove identity for CLI session " ,
subtitle : " The CLI session asked for proof earlier and was completed from this passport. " ,
2026-04-17 22:08:27 +02:00
source : " cli.idp.global " ,
createdAt : . now . addingTimeInterval ( - 60 * 180 ) ,
kind : . signIn ,
risk : . routine ,
2026-04-18 01:05:22 +02:00
scopes : [ " proof:basic " , " client:cli " ] ,
2026-04-17 22:08:27 +02:00
status : . approved
)
]
}
private static func seedNotifications ( ) -> [ AppNotification ] {
[
AppNotification (
2026-04-18 01:05:22 +02:00
title : " Two identity checks are waiting " ,
message : " One routine web proof and one stronger workstation proof are waiting for this passport. " ,
2026-04-17 22:08:27 +02:00
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 (
2026-04-18 01:05:22 +02:00
title : " Passport quiet hours active " ,
message : " Routine identity checks will be delivered silently until the morning. " ,
2026-04-17 22:08:27 +02:00
sentAt : . now . addingTimeInterval ( - 60 * 220 ) ,
kind : . security ,
isUnread : false
)
]
}
}