Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct PassiveCaptchaData: Equatable, Hashable {

let siteKey: String
let rqdata: String?
let tokenTimeoutSeconds: TimeInterval?

/// Helper method to decode the `v1/elements/sessions` response's `passive_captcha` hash.
/// - Parameter response: The value of the `passive_captcha` key in the `v1/elements/sessions` response.
Expand All @@ -32,9 +33,11 @@ struct PassiveCaptchaData: Equatable, Hashable {

// Optional
let rqdata = response["rqdata"] as? String
let tokenTimeoutSeconds = response["token_timeout_seconds"] as? TimeInterval
return PassiveCaptchaData(
siteKey: siteKey,
rqdata: rqdata
rqdata: rqdata,
tokenTimeoutSeconds: tokenTimeoutSeconds
)
}

Expand All @@ -43,27 +46,24 @@ struct PassiveCaptchaData: Equatable, Hashable {
actor PassiveCaptchaChallenge {
let passiveCaptchaData: PassiveCaptchaData
private let hcaptchaFactory: HCaptchaFactory
private let sessionExpiration: TimeInterval
private var tokenTask: Task<String, Error>?
var isTokenReady: Bool { // used for the attach analytic to indicate whether it's blocking checkout
return hasFetchedToken && !hasSessionExpired
}
private var hasFetchedToken: Bool = false
private var sessionExpirationDate: Date?
private var hasSessionExpired: Bool {
guard let sessionExpirationDate else { return false } // if we don't have a session expiration date, then we don't have a token yet
guard let sessionExpirationDate else { return false }
return Date() >= sessionExpirationDate
}

public init(passiveCaptchaData: PassiveCaptchaData) {
self.init(passiveCaptchaData: passiveCaptchaData, hcaptchaFactory: PassiveHCaptchaFactory())
}

init(passiveCaptchaData: PassiveCaptchaData, hcaptchaFactory: HCaptchaFactory, sessionExpiration: TimeInterval = 29 * 60) {
init(passiveCaptchaData: PassiveCaptchaData, hcaptchaFactory: HCaptchaFactory) {
self.passiveCaptchaData = passiveCaptchaData
self.hcaptchaFactory = hcaptchaFactory
// The max_age of the token set on the backend is 1800 seconds, or 30 minutes. As a preventative measure, we expire the token a minute early so a user won't send an expired token
self.sessionExpiration = sessionExpiration
Task { try await fetchToken() } // Intentionally not blocking loading/initialization!
}

Expand Down Expand Up @@ -139,7 +139,10 @@ actor PassiveCaptchaChallenge {
}

private func setSessionExpirationDate() {
self.sessionExpirationDate = Date().addingTimeInterval(sessionExpiration)
guard let tokenTimeoutSeconds = passiveCaptchaData.tokenTimeoutSeconds else {
return
}
self.sessionExpirationDate = Date().addingTimeInterval(tokenTimeoutSeconds)
}

private func setValidationComplete() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class PassiveCaptchaChallengeTests: XCTestCase {
let siteKey = "143aadb6-fb60-4ab6-b128-f7fe53426d4a"

func testPassiveCaptcha() async throws {
let passiveCaptchaData = PassiveCaptchaData(siteKey: siteKey, rqdata: nil)
let passiveCaptchaData = PassiveCaptchaData(siteKey: siteKey, rqdata: nil, tokenTimeoutSeconds: nil)
let passiveCaptchaChallenge = PassiveCaptchaChallenge(passiveCaptchaData: passiveCaptchaData)
// wait to make sure that the token will be ready by the time we call fetchToken
try await Task.sleep(nanoseconds: 6_000_000_000)
Expand All @@ -62,7 +62,7 @@ class PassiveCaptchaChallengeTests: XCTestCase {
}

func testPassiveCaptchaTimeout() async throws {
let passiveCaptchaData = PassiveCaptchaData(siteKey: siteKey, rqdata: nil)
let passiveCaptchaData = PassiveCaptchaData(siteKey: siteKey, rqdata: nil, tokenTimeoutSeconds: nil)
let passiveCaptchaChallenge = PassiveCaptchaChallenge(passiveCaptchaData: passiveCaptchaData, hcaptchaFactory: TestDelayHCaptchaFactory())
let startTime = Date()
let hcaptchaTokenResult = await withTimeout(1) {
Expand All @@ -77,7 +77,7 @@ class PassiveCaptchaChallengeTests: XCTestCase {
}

func testPassiveCaptchaLongTimeout() async throws {
let passiveCaptchaData = PassiveCaptchaData(siteKey: siteKey, rqdata: nil)
let passiveCaptchaData = PassiveCaptchaData(siteKey: siteKey, rqdata: nil, tokenTimeoutSeconds: nil)
let passiveCaptchaChallenge = PassiveCaptchaChallenge(passiveCaptchaData: passiveCaptchaData)
let startTime = Date()
let hcaptchaToken = try await withTimeout(30) {
Expand All @@ -89,9 +89,9 @@ class PassiveCaptchaChallengeTests: XCTestCase {
}

func testTokenResetAndRefetchAfterExpiration() async throws {
let passiveCaptchaData = PassiveCaptchaData(siteKey: siteKey, rqdata: nil)
// Use a very short expiration time (5 seconds) for testing
let passiveCaptchaChallenge = PassiveCaptchaChallenge(passiveCaptchaData: passiveCaptchaData, hcaptchaFactory: PassiveHCaptchaFactory(), sessionExpiration: 5.0)
let passiveCaptchaData = PassiveCaptchaData(siteKey: siteKey, rqdata: nil, tokenTimeoutSeconds: 5)
let passiveCaptchaChallenge = PassiveCaptchaChallenge(passiveCaptchaData: passiveCaptchaData, hcaptchaFactory: PassiveHCaptchaFactory())

// Fetch first token
let token = try await passiveCaptchaChallenge.fetchToken()
Expand Down
Loading