Skip to content

Commit

Permalink
Add attestation event logging to FC API clients
Browse files Browse the repository at this point in the history
  • Loading branch information
mats-stripe committed Jan 20, 2025
1 parent ac88f3b commit d8d06bc
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
495539EE2C484DC200543D18 /* FinancialConnectionsTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 495539ED2C484DC200543D18 /* FinancialConnectionsTheme.swift */; };
496A6AE72C29E0BB00D34F8E /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 496A6AE62C29E0BB00D34F8E /* [email protected] */; };
497142BC2C514B08000DFA64 /* FlowRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497142BB2C514B08000DFA64 /* FlowRouterTests.swift */; };
499EEAFD2D3E948B00E1BE85 /* FinancialConnectionsAPIClientLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499EEAFC2D3E948B00E1BE85 /* FinancialConnectionsAPIClientLogger.swift */; };
49A0B5862C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49A0B5852C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift */; };
49AC518C2C52DE2C00B712CC /* FinancialConnectionsLinkLoginPane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49AC518B2C52DE2C00B712CC /* FinancialConnectionsLinkLoginPane.swift */; };
49C911372C597EAF00589E0D /* LinkLoginDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C911332C597EAF00589E0D /* LinkLoginDataSource.swift */; };
Expand Down Expand Up @@ -330,6 +331,7 @@
495539ED2C484DC200543D18 /* FinancialConnectionsTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsTheme.swift; sourceTree = "<group>"; };
496A6AE62C29E0BB00D34F8E /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
497142BB2C514B08000DFA64 /* FlowRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowRouterTests.swift; sourceTree = "<group>"; };
499EEAFC2D3E948B00E1BE85 /* FinancialConnectionsAPIClientLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAPIClientLogger.swift; sourceTree = "<group>"; };
49A0B5852C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAPIClientTests.swift; sourceTree = "<group>"; };
49AC518B2C52DE2C00B712CC /* FinancialConnectionsLinkLoginPane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsLinkLoginPane.swift; sourceTree = "<group>"; };
49C911332C597EAF00589E0D /* LinkLoginDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkLoginDataSource.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -657,6 +659,7 @@
4E2EAD7059FF8358E674774A /* FinancialConnectionsAPIClient.swift */,
49F1B8392D2DAE7100136303 /* FinancialConnectionsAsyncAPIClient.swift */,
49F1B83D2D2EC82300136303 /* FinancialConnectionsAsyncAPIClient+Legacy.swift */,
499EEAFC2D3E948B00E1BE85 /* FinancialConnectionsAPIClientLogger.swift */,
);
path = "API Bindings";
sourceTree = "<group>";
Expand Down Expand Up @@ -1319,6 +1322,7 @@
CBF7BE2271D309F2B1E794CC /* FinancialConnectionsDataAccessNotice.swift in Sources */,
F67624595BD2CD7B6793BFDA /* FinancialConnectionsImage.swift in Sources */,
07712610C7D2F484AAB96982 /* FinancialConnectionsInstitution.swift in Sources */,
499EEAFD2D3E948B00E1BE85 /* FinancialConnectionsAPIClientLogger.swift in Sources */,
7386E1F9256B23CE29BF996D /* FinancialConnectionsInstitutionSearchResultResource.swift in Sources */,
C7D2763ACCE2CC71E788E18F /* FinancialConnectionsLegalDetailsNotice.swift in Sources */,
B271AAF41C9FE6AE392B88D3 /* FinancialConnectionsMixedOAuthParams.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ final class FinancialConnectionsAPIClient {
var consumerPublishableKey: String?
var consumerSession: ConsumerSessionData?

private lazy var logger = FinancialConnectionsAPIClientLogger()

var requestSurface: String {
isLinkWithStripe ? "ios_instant_debits" : "ios_connections"
}
Expand All @@ -46,28 +48,32 @@ final class FinancialConnectionsAPIClient {
/// Applies attestation-related parameters to the given base parameters
/// In case of an assertion error, returns the unmodified base parameters
func applyAttestationParameters(
to baseParameters: [String: Any]
to baseParameters: [String: Any],
pane: FinancialConnectionsSessionManifest.NextPane
) -> Future<[String: Any]> {
let promise = Promise<[String: Any]>()
Task {
do {
let attest = backingAPIClient.stripeAttest
let handle = try await attest.assert()
logger.log(.attestationRequestTokenSucceeded, pane: pane)
let newParameters = baseParameters.merging(handle.assertion.requestFields) { (_, new) in new }
promise.resolve(with: newParameters)
} catch {
// Fail silently if we can't get an assertion, we'll try the request anyway. It may fail.
logger.log(.attestationRequestTokenFailed, pane: pane)
promise.resolve(with: baseParameters)
}
}
return promise
}

/// Marks the assertion as completed and forwards attestation errors to the `StripeAttest` client for logging.
func completeAssertion(possibleError: Error?) {
func completeAssertion(possibleError: Error?, pane: FinancialConnectionsSessionManifest.NextPane) {
let attest = backingAPIClient.stripeAttest
Task {
if let error = possibleError, StripeAttest.isLinkAssertionError(error: error) {
logger.log(.attestationVerdictFailed, pane: pane)
await attest.receivedAssertionError(error)
}
await attest.assertionCompleted()
Expand Down Expand Up @@ -139,11 +145,15 @@ protocol FinancialConnectionsAPI {
var consumerPublishableKey: String? { get set }
var consumerSession: ConsumerSessionData? { get set }

func completeAssertion(possibleError: Error?)
func completeAssertion(
possibleError: Error?,
pane: FinancialConnectionsSessionManifest.NextPane
)

func synchronize(
clientSecret: String,
returnURL: String?
returnURL: String?,
initialSynchronize: Bool
) -> Future<FinancialConnectionsSynchronize>

func fetchFinancialConnectionsAccounts(
Expand Down Expand Up @@ -256,7 +266,8 @@ protocol FinancialConnectionsAPI {
clientSecret: String,
sessionId: String,
emailSource: FinancialConnectionsAPIClient.EmailSource,
useMobileEndpoints: Bool
useMobileEndpoints: Bool,
pane: FinancialConnectionsSessionManifest.NextPane
) -> Future<LookupConsumerSessionResponse>

// MARK: - Link API's
Expand Down Expand Up @@ -285,7 +296,8 @@ protocol FinancialConnectionsAPI {
amount: Int?,
currency: String?,
incentiveEligibilitySession: ElementsSessionContext.IntentID?,
useMobileEndpoints: Bool
useMobileEndpoints: Bool,
pane: FinancialConnectionsSessionManifest.NextPane
) -> Future<LinkSignUpResponse>

func attachLinkConsumerToLinkAccountSession(
Expand Down Expand Up @@ -348,7 +360,8 @@ extension FinancialConnectionsAPIClient: FinancialConnectionsAPI {

func synchronize(
clientSecret: String,
returnURL: String?
returnURL: String?,
initialSynchronize: Bool = false
) -> Future<FinancialConnectionsSynchronize> {
var parameters: [String: Any] = [
"expand": ["manifest.active_auth_session"],
Expand All @@ -363,10 +376,15 @@ extension FinancialConnectionsAPIClient: FinancialConnectionsAPI {
]
mobileParameters["app_return_url"] = returnURL

let attest = backingAPIClient.stripeAttest
if attest.isSupported {
mobileParameters["supports_app_verification"] = true
mobileParameters["verified_app_id"] = Bundle.main.bundleIdentifier
if initialSynchronize {
let attest = backingAPIClient.stripeAttest
if attest.isSupported {
mobileParameters["supports_app_verification"] = true
mobileParameters["verified_app_id"] = Bundle.main.bundleIdentifier
logger.log(.attestationInitSucceeded, pane: .consent)
} else {
logger.log(.attestationInitFailed, pane: .consent)
}
}
parameters["mobile"] = mobileParameters

Expand Down Expand Up @@ -917,7 +935,8 @@ extension FinancialConnectionsAPIClient: FinancialConnectionsAPI {
clientSecret: String,
sessionId: String,
emailSource: FinancialConnectionsAPIClient.EmailSource,
useMobileEndpoints: Bool
useMobileEndpoints: Bool,
pane: FinancialConnectionsSessionManifest.NextPane
) -> Future<LookupConsumerSessionResponse> {
var parameters: [String: Any] = [
"email_address":
Expand All @@ -930,7 +949,7 @@ extension FinancialConnectionsAPIClient: FinancialConnectionsAPI {
parameters["request_surface"] = requestSurface
parameters["session_id"] = sessionId
parameters["email_source"] = emailSource.rawValue
return applyAttestationParameters(to: parameters)
return applyAttestationParameters(to: parameters, pane: pane)
.chained { [weak self] updatedParameters in
guard let self else {
return Promise(error: FinancialConnectionsSheetError.unknown(debugDescription: "FinancialConnectionsAPIClient was deallocated."))
Expand Down Expand Up @@ -1003,7 +1022,8 @@ extension FinancialConnectionsAPIClient: FinancialConnectionsAPI {
amount: Int?,
currency: String?,
incentiveEligibilitySession: ElementsSessionContext.IntentID?,
useMobileEndpoints: Bool
useMobileEndpoints: Bool,
pane: FinancialConnectionsSessionManifest.NextPane
) -> Future<LinkSignUpResponse> {
var parameters: [String: Any] = [
"request_surface": requestSurface,
Expand Down Expand Up @@ -1040,7 +1060,7 @@ extension FinancialConnectionsAPIClient: FinancialConnectionsAPI {
}

if useMobileEndpoints {
return applyAttestationParameters(to: parameters)
return applyAttestationParameters(to: parameters, pane: pane)
.chained { [weak self] updatedParameters in
guard let self else {
return Promise(error: FinancialConnectionsSheetError.unknown(debugDescription: "FinancialConnectionsAPIClient was deallocated."))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// FinancialConnectionsAPIClientLogger.swift
// StripeFinancialConnections
//
// Created by Mat Schmid on 2025-01-20.
//

import Foundation

struct FinancialConnectionsAPIClientLogger {
private var analyticsClient = FinancialConnectionsAnalyticsClient()

enum Event {
/// When checking if generating attestation is supported succeeds.
case attestationInitSucceeded
/// When checking if generating attestation is supported does not succeed.
case attestationInitFailed
/// When an attestation token gets generated successfully.
case attestationRequestTokenSucceeded
/// When a token generation attempt fails client-side.
case attestationRequestTokenFailed
/// When an attestation verdict fails backend side and we get an attestation related error.
case attestationVerdictFailed

var name: String {
switch self {
case .attestationInitSucceeded:
return "attestation.init_succeeded"
case .attestationInitFailed:
return "attestation.init_failed"
case .attestationRequestTokenSucceeded:
return "attestation.request_token_succeeded"
case .attestationRequestTokenFailed:
return "attestation.request_token_failed"
case .attestationVerdictFailed:
return "attestation.verdict_failed"
}
}

var parameters: [String: Any] {
switch self {
case .attestationInitFailed:
var reason: String
if #available(iOS 14.0, *) {
// If the iOS version is supported, we assume the device is unsupported (i.e. simulator).
reason = "ios_device_unsupported"
} else {
// Otherwise, attestation is unavailable due to the OS version being unsupported.
reason = "ios_os_version_unsupported"
}
return ["reason": reason]
default:
return [:]
}
}
}

func log(_ event: Event, pane: FinancialConnectionsSessionManifest.NextPane) {
analyticsClient.log(
eventName: event.name,
parameters: event.parameters,
pane: pane
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,15 @@ extension FinancialConnectionsAsyncAPIClient {
extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAPI {
func synchronize(
clientSecret: String,
returnURL: String?
returnURL: String?,
initialSynchronize: Bool
) -> Future<FinancialConnectionsSynchronize> {
wrapAsyncToFuture {
try await self.synchronize(clientSecret: clientSecret, returnURL: returnURL)
try await self.synchronize(
clientSecret: clientSecret,
returnURL: returnURL,
initialSynchronize: initialSynchronize
)
}
}

Expand Down Expand Up @@ -311,15 +316,17 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAPI {
clientSecret: String,
sessionId: String,
emailSource: FinancialConnectionsAPIClient.EmailSource,
useMobileEndpoints: Bool
useMobileEndpoints: Bool,
pane: FinancialConnectionsSessionManifest.NextPane
) -> Future<LookupConsumerSessionResponse> {
wrapAsyncToFuture {
try await self.consumerSessionLookup(
emailAddress: emailAddress,
clientSecret: clientSecret,
sessionId: sessionId,
emailSource: emailSource,
useMobileEndpoints: useMobileEndpoints
useMobileEndpoints: useMobileEndpoints,
pane: pane
)
}
}
Expand Down Expand Up @@ -369,7 +376,8 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAPI {
amount: Int?,
currency: String?,
incentiveEligibilitySession: ElementsSessionContext.IntentID?,
useMobileEndpoints: Bool
useMobileEndpoints: Bool,
pane: FinancialConnectionsSessionManifest.NextPane
) -> Future<LinkSignUpResponse> {
wrapAsyncToFuture {
try await self.linkAccountSignUp(
Expand All @@ -379,7 +387,8 @@ extension FinancialConnectionsAsyncAPIClient: FinancialConnectionsAPI {
amount: amount,
currency: currency,
incentiveEligibilitySession: incentiveEligibilitySession,
useMobileEndpoints: useMobileEndpoints
useMobileEndpoints: useMobileEndpoints,
pane: pane
)
}
}
Expand Down
Loading

0 comments on commit d8d06bc

Please sign in to comment.