@@ -18,11 +18,9 @@ import Foundation
1818 import FirebaseAuthInternal
1919#endif
2020
21- #if os(iOS)
22- import RecaptchaInterop
23- #endif
21+ import RecaptchaInterop
2422
25- @available ( iOS 13 , tvOS 13 , macOS 10 . 15 , macCatalyst 13 , watchOS 7 , * )
23+ @available ( iOS 13 , * )
2624class AuthRecaptchaConfig {
2725 var siteKey : String ?
2826 let enablementStatus : [ AuthRecaptchaProvider : AuthRecaptchaEnablementStatus ]
@@ -34,7 +32,7 @@ class AuthRecaptchaConfig {
3432 }
3533}
3634
37- @available ( iOS 13 , tvOS 13 , macOS 10 . 15 , macCatalyst 13 , watchOS 7 , * )
35+ @available ( iOS 13 , * )
3836enum AuthRecaptchaEnablementStatus : String , CaseIterable {
3937 case enforce = " ENFORCE "
4038 case audit = " AUDIT "
@@ -44,7 +42,7 @@ enum AuthRecaptchaEnablementStatus: String, CaseIterable {
4442 var stringValue : String { rawValue }
4543}
4644
47- @available ( iOS 13 , tvOS 13 , macOS 10 . 15 , macCatalyst 13 , watchOS 7 , * )
45+ @available ( iOS 13 , * )
4846enum AuthRecaptchaProvider : String , CaseIterable {
4947 case password = " EMAIL_PASSWORD_PROVIDER "
5048 case phone = " PHONE_PROVIDER "
@@ -53,7 +51,7 @@ enum AuthRecaptchaProvider: String, CaseIterable {
5351 var stringValue : String { rawValue }
5452}
5553
56- @available ( iOS 13 , tvOS 13 , macOS 10 . 15 , macCatalyst 13 , watchOS 7 , * )
54+ @available ( iOS 13 , * )
5755enum AuthRecaptchaAction : String {
5856 case defaultAction
5957 case signInWithPassword
@@ -67,16 +65,14 @@ enum AuthRecaptchaAction: String {
6765 var stringValue : String { rawValue }
6866}
6967
70- @available ( iOS 13 , tvOS 13 , macOS 10 . 15 , macCatalyst 13 , watchOS 7 , * )
68+ @available ( iOS 13 , * )
7169class AuthRecaptchaVerifier {
7270 weak var auth : Auth ?
7371 private var agentConfig : AuthRecaptchaConfig ?
7472 private var tenantConfigs : [ String : AuthRecaptchaConfig ] = [ : ]
7573 private var recaptchaClient : RCARecaptchaClientProtocol ?
7674 private let kRecaptchaVersion = " RECAPTCHA_ENTERPRISE "
7775
78- init ( ) { }
79-
8076 func verify( forceRefresh: Bool , action: AuthRecaptchaAction ) async throws -> String {
8177 try await retrieveRecaptchaConfig ( forceRefresh: forceRefresh)
8278 guard let siteKey = siteKey ( ) else {
@@ -114,154 +110,149 @@ class AuthRecaptchaVerifier {
114110 return token
115111 #endif // !(COCOAPODS || SWIFT_PACKAGE)
116112 }
117- }
118113
119- #if os(iOS)
120- @available ( iOS 13 , tvOS 13 , macOS 10 . 15 , macCatalyst 13 , watchOS 7 , * )
121- extension AuthRecaptchaVerifier {
122- func siteKey( ) -> String ? {
114+ func enablementStatus( forProvider provider: AuthRecaptchaProvider )
115+ -> AuthRecaptchaEnablementStatus {
116+ if let tenantID = auth? . tenantID,
117+ let tenantConfig = tenantConfigs [ tenantID] ,
118+ let status = tenantConfig. enablementStatus [ provider] {
119+ return status
120+ } else if let agentConfig = agentConfig,
121+ let status = agentConfig. enablementStatus [ provider] {
122+ return status
123+ } else {
124+ return AuthRecaptchaEnablementStatus . off
125+ }
126+ }
127+
128+ func retrieveRecaptchaConfig( forceRefresh: Bool ) async throws {
129+ if !forceRefresh {
123130 if let tenantID = auth? . tenantID {
124- if let config = tenantConfigs [ tenantID] {
125- return config . siteKey
131+ if tenantConfigs [ tenantID] != nil {
132+ return
126133 }
127- return nil
134+ } else if agentConfig != nil {
135+ return
128136 }
129- return agentConfig? . siteKey
130137 }
131138
132- func enablementStatus( forProvider provider: AuthRecaptchaProvider )
133- -> AuthRecaptchaEnablementStatus {
134- if let tenantID = auth? . tenantID,
135- let tenantConfig = tenantConfigs [ tenantID] ,
136- let status = tenantConfig. enablementStatus [ provider] {
137- return status
138- } else if let agentConfig = agentConfig,
139- let status = agentConfig. enablementStatus [ provider] {
140- return status
141- } else {
142- return AuthRecaptchaEnablementStatus . off
139+ guard let auth = auth else {
140+ throw AuthErrorUtils . error ( code: . recaptchaNotEnabled,
141+ message: " No requestConfiguration for Auth instance " )
142+ }
143+ let request = GetRecaptchaConfigRequest ( requestConfiguration: auth. requestConfiguration)
144+ let response = try await auth. backend. call ( with: request)
145+ AuthLog . logInfo ( code: " I-AUT000029 " , message: " reCAPTCHA config retrieval succeeded. " )
146+ try await parseRecaptchaConfigFromResponse ( response: response)
147+ }
148+
149+ func injectRecaptchaFields( request: any AuthRPCRequest ,
150+ provider: AuthRecaptchaProvider ,
151+ action: AuthRecaptchaAction ) async throws {
152+ try await retrieveRecaptchaConfig ( forceRefresh: false )
153+ if enablementStatus ( forProvider: provider) != . off {
154+ let token = try await verify ( forceRefresh: false , action: action)
155+ request. injectRecaptchaFields ( recaptchaResponse: token, recaptchaVersion: kRecaptchaVersion)
156+ } else {
157+ request. injectRecaptchaFields ( recaptchaResponse: nil , recaptchaVersion: kRecaptchaVersion)
158+ }
159+ }
160+
161+ private func siteKey( ) -> String ? {
162+ if let tenantID = auth? . tenantID {
163+ if let config = tenantConfigs [ tenantID] {
164+ return config. siteKey
143165 }
166+ return nil
167+ }
168+ return agentConfig? . siteKey
169+ }
170+
171+ private func recaptchaToken( siteKey: String ,
172+ actionString: String ,
173+ fakeToken: String ) async -> ( token: String , error: Error ? ,
174+ linked: Bool , actionCreated: Bool ) {
175+ if let recaptchaClient {
176+ return await retrieveToken (
177+ actionString: actionString,
178+ fakeToken: fakeToken,
179+ recaptchaClient: recaptchaClient
180+ )
144181 }
145182
146- private func recaptchaToken( siteKey: String ,
147- actionString: String ,
148- fakeToken: String ) async -> ( token: String , error: Error ? ,
149- linked: Bool , actionCreated: Bool ) {
150- if let recaptchaClient {
183+ if let recaptcha =
184+ NSClassFromString ( " RecaptchaEnterprise.RCARecaptcha " ) as? RCARecaptchaProtocol . Type {
185+ do {
186+ // Note, reCAPTCHA does not support multi-tenancy, so only one site key can be used per
187+ // runtime.
188+ // let client = try await recaptcha.fetchClient(withSiteKey: siteKey)
189+ let client = try await recaptcha. getClient ( withSiteKey: siteKey)
190+ recaptchaClient = client
151191 return await retrieveToken (
152192 actionString: actionString,
153193 fakeToken: fakeToken,
154- recaptchaClient: recaptchaClient
194+ recaptchaClient: client
155195 )
196+ } catch {
197+ return ( " " , error, true , true )
156198 }
157-
158- if let recaptcha =
159- NSClassFromString ( " RecaptchaEnterprise.RCARecaptcha " ) as? RCARecaptchaProtocol . Type {
160- do {
161- // Note, reCAPTCHA does not support multi-tenancy, so only one site key can be used per
162- // runtime.
163- // let client = try await recaptcha.fetchClient(withSiteKey: siteKey)
164- let client = try await recaptcha. getClient ( withSiteKey: siteKey)
165- recaptchaClient = client
166- return await retrieveToken (
167- actionString: actionString,
168- fakeToken: fakeToken,
169- recaptchaClient: client
170- )
171- } catch {
172- return ( " " , error, true , true )
173- }
174- } else {
175- // RecaptchaEnterprise not linked.
176- return ( " " , nil , false , false )
177- }
199+ } else {
200+ // RecaptchaEnterprise not linked.
201+ return ( " " , nil , false , false )
178202 }
203+ }
179204
180- private func retrieveToken( actionString: String ,
181- fakeToken: String ,
182- recaptchaClient: RCARecaptchaClientProtocol ) async -> ( token: String ,
183- error: Error ? ,
184- linked: Bool ,
185- actionCreated: Bool ) {
186- if let recaptchaAction =
187- NSClassFromString ( " RecaptchaEnterprise.RCAAction " ) as? RCAActionProtocol . Type {
188- let action = recaptchaAction. init ( customAction: actionString)
189- let token = try ? await recaptchaClient. execute ( withAction: action)
190- return ( token ?? " NO_RECAPTCHA " , nil , true , true )
191- } else {
192- // RecaptchaEnterprise not linked.
193- return ( " " , nil , false , false )
194- }
205+ private func retrieveToken( actionString: String ,
206+ fakeToken: String ,
207+ recaptchaClient: RCARecaptchaClientProtocol ) async -> ( token: String ,
208+ error: Error ? ,
209+ linked: Bool ,
210+ actionCreated: Bool ) {
211+ if let recaptchaAction =
212+ NSClassFromString ( " RecaptchaEnterprise.RCAAction " ) as? RCAActionProtocol . Type {
213+ let action = recaptchaAction. init ( customAction: actionString)
214+ let token = try ? await recaptchaClient. execute ( withAction: action)
215+ return ( token ?? " NO_RECAPTCHA " , nil , true , true )
216+ } else {
217+ // RecaptchaEnterprise not linked.
218+ return ( " " , nil , false , false )
195219 }
220+ }
196221
197- func retrieveRecaptchaConfig( forceRefresh: Bool ) async throws {
198- if !forceRefresh {
199- if let tenantID = auth? . tenantID {
200- if tenantConfigs [ tenantID] != nil {
201- return
202- }
203- } else if agentConfig != nil {
204- return
222+ private func parseRecaptchaConfigFromResponse( response: GetRecaptchaConfigResponse ) async throws {
223+ var enablementStatus : [ AuthRecaptchaProvider : AuthRecaptchaEnablementStatus ] = [ : ]
224+ var isRecaptchaEnabled = false
225+ if let enforcementState = response. enforcementState {
226+ for state in enforcementState {
227+ guard let providerString = state [ " provider " ] ,
228+ let enforcementString = state [ " enforcementState " ] ,
229+ let provider = AuthRecaptchaProvider ( rawValue: providerString) ,
230+ let enforcement = AuthRecaptchaEnablementStatus ( rawValue: enforcementString) else {
231+ continue // Skip to the next state in the loop
205232 }
206- }
207-
208- guard let auth = auth else {
209- throw AuthErrorUtils . error ( code: . recaptchaNotEnabled,
210- message: " No requestConfiguration for Auth instance " )
211- }
212- let request = GetRecaptchaConfigRequest ( requestConfiguration: auth. requestConfiguration)
213- let response = try await auth. backend. call ( with: request)
214- AuthLog . logInfo ( code: " I-AUT000029 " , message: " reCAPTCHA config retrieval succeeded. " )
215- try await parseRecaptchaConfigFromResponse ( response: response)
216- }
217-
218- func parseRecaptchaConfigFromResponse( response: GetRecaptchaConfigResponse ) async throws {
219- var enablementStatus : [ AuthRecaptchaProvider : AuthRecaptchaEnablementStatus ] = [ : ]
220- var isRecaptchaEnabled = false
221- if let enforcementState = response. enforcementState {
222- for state in enforcementState {
223- guard let providerString = state [ " provider " ] ,
224- let enforcementString = state [ " enforcementState " ] ,
225- let provider = AuthRecaptchaProvider ( rawValue: providerString) ,
226- let enforcement = AuthRecaptchaEnablementStatus ( rawValue: enforcementString) else {
227- continue // Skip to the next state in the loop
228- }
229- enablementStatus [ provider] = enforcement
230- if enforcement != . off {
231- isRecaptchaEnabled = true
232- }
233+ enablementStatus [ provider] = enforcement
234+ if enforcement != . off {
235+ isRecaptchaEnabled = true
233236 }
234237 }
235- var siteKey = " "
236- // Response's site key is of the format projects/<project-id>/keys/<site-key>'
237- if isRecaptchaEnabled {
238- if let recaptchaKey = response. recaptchaKey {
239- let keys = recaptchaKey. components ( separatedBy: " / " )
240- if keys. count != 4 {
241- throw AuthErrorUtils . error ( code: . recaptchaNotEnabled, message: " Invalid siteKey " )
242- }
243- siteKey = keys [ 3 ]
238+ }
239+ var siteKey = " "
240+ // Response's site key is of the format projects/<project-id>/keys/<site-key>'
241+ if isRecaptchaEnabled {
242+ if let recaptchaKey = response. recaptchaKey {
243+ let keys = recaptchaKey. components ( separatedBy: " / " )
244+ if keys. count != 4 {
245+ throw AuthErrorUtils . error ( code: . recaptchaNotEnabled, message: " Invalid siteKey " )
244246 }
245- }
246- let config = AuthRecaptchaConfig ( siteKey: siteKey, enablementStatus: enablementStatus)
247-
248- if let tenantID = auth? . tenantID {
249- tenantConfigs [ tenantID] = config
250- } else {
251- agentConfig = config
247+ siteKey = keys [ 3 ]
252248 }
253249 }
250+ let config = AuthRecaptchaConfig ( siteKey: siteKey, enablementStatus: enablementStatus)
254251
255- func injectRecaptchaFields( request: any AuthRPCRequest ,
256- provider: AuthRecaptchaProvider ,
257- action: AuthRecaptchaAction ) async throws {
258- try await retrieveRecaptchaConfig ( forceRefresh: false )
259- if enablementStatus ( forProvider: provider) != . off {
260- let token = try await verify ( forceRefresh: false , action: action)
261- request. injectRecaptchaFields ( recaptchaResponse: token, recaptchaVersion: kRecaptchaVersion)
262- } else {
263- request. injectRecaptchaFields ( recaptchaResponse: nil , recaptchaVersion: kRecaptchaVersion)
264- }
252+ if let tenantID = auth? . tenantID {
253+ tenantConfigs [ tenantID] = config
254+ } else {
255+ agentConfig = config
265256 }
266257 }
267- #endif
258+ }
0 commit comments