@@ -8,9 +8,13 @@ public actor AppleSessionService {
88 public typealias KeychainSet = @Sendable ( String, String) throws -> Void
99 public typealias KeychainRemove = @Sendable ( String) throws -> Void
1010 public typealias ReadLine = @Sendable ( String ) -> String ?
11+ public typealias ReadLongLine = @Sendable ( String ) -> String ?
1112 public typealias ReadSecureLine = @Sendable ( String ) -> String ?
1213 public typealias ValidateSession = @Sendable ( ) async throws -> Void
1314 public typealias Login = @Sendable ( String, String) async throws -> Void
15+ public typealias CheckIsFederated = @Sendable ( String) async throws -> FederationResponse
16+ public typealias ValidateFederatedCallbackURL = @Sendable ( String) async throws -> Void
17+ public typealias OpenURL = @Sendable ( URL ) -> Void
1418 public typealias Signout = @Sendable ( ) async -> Void
1519 public typealias LoadData = @Sendable ( URLRequest) async throws -> ( Data , URLResponse )
1620 public typealias Log = @Sendable ( String ) -> Void
@@ -23,9 +27,13 @@ public actor AppleSessionService {
2327 public var keychainSet : KeychainSet
2428 public var keychainRemove : KeychainRemove
2529 public var readLine : ReadLine
30+ public var readLongLine : ReadLongLine
2631 public var readSecureLine : ReadSecureLine
2732 public var validateSession : ValidateSession
2833 public var login : Login
34+ public var checkIsFederated : CheckIsFederated
35+ public var validateFederatedCallbackURL : ValidateFederatedCallbackURL
36+ public var openURL : OpenURL
2937 public var signout : Signout
3038 public var loadData : LoadData
3139 public var log : Log
@@ -38,9 +46,13 @@ public actor AppleSessionService {
3846 keychainSet: @escaping KeychainSet ,
3947 keychainRemove: @escaping KeychainRemove ,
4048 readLine: @escaping ReadLine ,
49+ readLongLine: @escaping ReadLongLine ,
4150 readSecureLine: @escaping ReadSecureLine ,
4251 validateSession: @escaping ValidateSession ,
4352 login: @escaping Login ,
53+ checkIsFederated: @escaping CheckIsFederated ,
54+ validateFederatedCallbackURL: @escaping ValidateFederatedCallbackURL ,
55+ openURL: @escaping OpenURL ,
4456 signout: @escaping Signout ,
4557 loadData: @escaping LoadData ,
4658 log: @escaping Log = { _ in }
@@ -52,9 +64,13 @@ public actor AppleSessionService {
5264 self . keychainSet = keychainSet
5365 self . keychainRemove = keychainRemove
5466 self . readLine = readLine
67+ self . readLongLine = readLongLine
5568 self . readSecureLine = readSecureLine
5669 self . validateSession = validateSession
5770 self . login = login
71+ self . checkIsFederated = checkIsFederated
72+ self . validateFederatedCallbackURL = validateFederatedCallbackURL
73+ self . openURL = openURL
5874 self . signout = signout
5975 self . loadData = loadData
6076 self . log = log
@@ -106,6 +122,12 @@ public actor AppleSessionService {
106122 }
107123 guard let username = possibleUsername else { throw Error . missingUsernameOrPassword }
108124
125+ let federationResponse = try await dependencies. checkIsFederated ( username)
126+ if federationResponse. federated {
127+ try await handleFederatedLogin ( username: username, federationResponse: federationResponse)
128+ return
129+ }
130+
109131 let passwordPrompt : String
110132 if hasPromptedForUsername {
111133 passwordPrompt = " Apple ID Password: "
@@ -131,6 +153,33 @@ public actor AppleSessionService {
131153 }
132154 }
133155
156+ private func handleFederatedLogin( username: String , federationResponse: FederationResponse ) async throws {
157+ guard let idpURL = federationResponse. idpURL else {
158+ throw AuthenticationError . federatedAuthenticationRequired
159+ }
160+
161+ let orgName = federationResponse. federatedAuthIntro? . orgName ?? " your organization "
162+ let idpName = federationResponse. federatedAuthIntro? . idpName
163+ let orgNameWithIdp = idpName. map { " \( orgName) ( \( $0) ) " } ?? orgName
164+
165+ dependencies. log ( " \n - This account uses federated authentication via \( orgNameWithIdp) " )
166+ dependencies. log ( " - Your browser will open to complete sign-in " )
167+ dependencies. log ( " - After signing in, you will be redirected to a blank page " )
168+ dependencies. log ( " - Copy the URL from your browser's address bar, then return here and paste it " )
169+ dependencies. log ( " \n Opening your browser... " )
170+ dependencies. openURL ( idpURL)
171+
172+ guard let callbackURLString = dependencies. readLongLine ( " \n Paste the URL here: " ) else {
173+ throw Error . missingUsernameOrPassword
174+ }
175+
176+ try await dependencies. validateFederatedCallbackURL ( callbackURLString)
177+
178+ if dependencies. defaultUsername ( ) != username {
179+ try ? dependencies. setDefaultUsername ( username)
180+ }
181+ }
182+
134183 public func login( _ username: String , password: String ) async throws {
135184 do {
136185 try await dependencies. login ( username, password)
0 commit comments