Open Source Notices
Pinstripe Fleet includes open-source software. This page identifies a modified component, its license, and the corresponding source needed to reproduce Pinstripe's distributed version.
@capgo/capacitor-social-login 8.5.10
This component is licensed under the Mozilla Public License 2.0. Pinstripe modifiesios/Sources/SocialLoginPlugin/AppleProvider.swiftto remove plaintext Apple-token persistence, clear local Apple token/name/subject state on initialization and sign-out, and remove sensitive logging while preserving the authorization-code handoff.
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. The corresponding source is the exact upstream source at commit1467163a87f38efa7ec623189733cbe5f8cd00d5together with the Pinstripe patch reproduced below.
Pinstripe modification source
Apply this patch at the root of the upstream 8.5.10 source tree.
diff --git a/ios/Sources/SocialLoginPlugin/AppleProvider.swift b/ios/Sources/SocialLoginPlugin/AppleProvider.swift
index 051ea4b5..23d7a90d 100644
--- a/ios/Sources/SocialLoginPlugin/AppleProvider.swift
+++ b/ios/Sources/SocialLoginPlugin/AppleProvider.swift
@@ -127,75 +127,35 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
}
self.useProperTokenExchange = useProperTokenExchange
- do {
- try retrieveState()
- } catch {
- print("retrieveState error: \(error)")
- }
+ // Versions before Pinstripe's vendor hardening stored Apple identity,
+ // refresh and access tokens as plaintext JSON in UserDefaults. Never
+ // restore that state; remove it on every provider initialization so an
+ // upgrade also clears any value left by an earlier app build.
+ clearSessionState()
}
func persistState(idToken: String, refreshToken: String, accessToken: String) throws {
- // Create a dictionary to represent the JSON object
- let object: [String: String] = [
- "idToken": idToken,
- "refreshToken": refreshToken,
- "accessToken": accessToken
- ]
-
- // Assign to instance variables
+ // Keep session state in memory only. Pinstripe's proper-token-exchange
+ // flow sends the one-time authorization code to its backend, which
+ // stores the refresh token encrypted. Tokens must not be written to
+ // UserDefaults or emitted to device logs.
self.idToken = idToken
self.refreshToken = refreshToken
self.accessToken = accessToken
-
- // Convert the object to JSON data
- let jsonData = try JSONSerialization.data(withJSONObject: object, options: [])
-
- // Convert JSON data to a string for logging
- if let jsonString = String(data: jsonData, encoding: .utf8) {
- // Log the object
- print("Apple persistState: \(jsonString)")
-
- // Save the JSON string to UserDefaults or use your helper method
- UserDefaults.standard.set(jsonString, forKey: SHARED_PREFERENCE_NAME)
- } else {
- print("Error converting JSON data to String")
- }
+ clearPersistentState()
}
- func retrieveState() throws {
- // Retrieve the JSON string from persistent storage
- guard let jsonString = UserDefaults.standard.string(forKey: SHARED_PREFERENCE_NAME) else {
- print("No saved state found")
- return
- }
-
- // Convert JSON string to Data
- guard let jsonData = jsonString.data(using: .utf8) else {
- print("Error converting JSON string to Data")
- return
- }
-
- // Parse the JSON data
- guard let object = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] else {
- print("Error parsing JSON data")
- return
- }
-
- // Extract tokens
- guard let idToken = object["idToken"],
- let refreshToken = object["refreshToken"],
- let accessToken = object["accessToken"] else {
- print("Error: Missing tokens in retrieved data")
- return
- }
-
- // Assign to instance variables
- self.idToken = idToken
- self.refreshToken = refreshToken
- self.accessToken = accessToken
+ private func clearPersistentState() {
+ UserDefaults.standard.removeObject(forKey: SHARED_PREFERENCE_NAME)
+ UserDefaults.standard.removeObject(forKey: USER_INFO_KEY)
+ }
- // Log the retrieved object
- print("Apple retrieveState: \(object)")
+ private func clearSessionState() {
+ self.idToken = nil
+ self.refreshToken = nil
+ self.accessToken = nil
+ self.completion = nil
+ clearPersistentState()
}
func login(payload: [String: Any], completion: @escaping (Result<AppleProviderResponse, Error>) -> Void) {
@@ -221,20 +181,11 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
}
func logout(completion: @escaping (Result<Void, Error>) -> Void) {
- // we check only idtoken, because with apple, refresh token MIGHT not be set
- if self.idToken == nil || ((self.idToken?.isEmpty) == true) {
-
- completion(.failure(NSError(domain: "AppleProvider", code: 1, userInfo: [NSLocalizedDescriptionKey: "Not logged in; Cannot logout"])))
- return
- }
-
- self.idToken = nil
- self.refreshToken = nil
- self.accessToken = nil
-
- UserDefaults.standard.removeObject(forKey: SHARED_PREFERENCE_NAME)
+ // Sign-out is idempotent. Always erase transient tokens, the retained
+ // completion closure, and any identity/name state left by an earlier
+ // plugin build, even when this provider instance has no current token.
+ clearSessionState()
completion(.success(()))
- return
}
func refresh(completion: @escaping (Result<Void, Error>) -> Void) {
@@ -250,15 +201,11 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
let fullName = appleIDCredential.fullName
let email = appleIDCredential.email
- // If we get a name, save it
- if fullName?.givenName != nil || fullName?.familyName != nil {
- persistName(userId: userIdentifier, givenName: fullName?.givenName, familyName: fullName?.familyName)
- }
-
- // Use saved name as fallback
- let savedName = retrieveName(userId: userIdentifier)
- let finalGivenName = fullName?.givenName ?? savedName?.givenName
- let finalFamilyName = fullName?.familyName ?? savedName?.familyName
+ // Apple sends a name only on first authorization. Pinstripe adopts
+ // it into the server profile immediately, so the provider must not
+ // retain a local user-id/name map after the sign-in attempt.
+ let finalGivenName = fullName?.givenName
+ let finalFamilyName = fullName?.familyName
// Create proper access token and decode JWT
let authorizationCode = String(data: appleIDCredential.authorizationCode ?? Data(), encoding: .utf8) ?? ""
@@ -290,7 +237,6 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
if let decodedData = Data(base64Encoded: base64String, options: []),
let payload = try? JSONSerialization.jsonObject(with: decodedData, options: []) as? [String: Any] {
- print("payload", payload)
decodedEmail = payload["email"] as? String ?? email
}
}
@@ -362,7 +308,7 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
// Convert the user dictionary to a JSON string
guard let userData = try? JSONSerialization.data(withJSONObject: user, options: []),
let userJSONString = String(data: userData, encoding: .utf8) else {
- print("Error converting user data to JSON string")
+ completion(.failure(.userDataSerializationError))
return
}
@@ -381,20 +327,13 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
.response { response in
// Access the HTTPURLResponse
if let httpResponse = response.response {
- print("Status Code: \(httpResponse.statusCode)")
-
// Check if the response is a redirect
if (300...399).contains(httpResponse.statusCode) {
if let location = httpResponse.headers.value(for: "Location") {
- print("Redirect Location: \(location)")
-
// Parse the redirect URL
if let redirectURL = URL(string: location),
let urlComponents = URLComponents(url: redirectURL, resolvingAgainstBaseURL: false),
let pathComponents = urlComponents.queryItems {
-
- print("Query items: \(String(describing: urlComponents.queryItems))")
-
// there are 4 main ways this can go:
// 1. it provides the "code" and we fetch apple servers in order to get the JWT (yuck)
// 2. It doesn't provide the code but it provides access_token, refresh_token, id_token
@@ -476,28 +415,17 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
} else {
completion(.failure(.pathComponentsNotFound))
- print("Path components not found")
return
}
} else {
completion(.failure(.noLocationHeader))
- print("No Location header found in the redirect response")
return
}
} else {
- // Handle non-redirect responses
- if let data = response.data,
- let responseString = String(data: data, encoding: .utf8) {
- print("Response: \(responseString)")
- } else {
- print("No response data received")
- }
-
completion(.failure(.invalidResponseCode(statusCode: httpResponse.statusCode)))
}
} else if let error = response.error {
completion(.failure(.responseError(error)))
- print("Error: \(error)")
}
}
}
@@ -568,13 +496,6 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
authorizationCode: nil
)
- // Log the tokens (replace with your logging mechanism)
- print("Apple Access Token is: \(accessToken)")
- print("Expires in: \(expiresIn)")
- print("Refresh token: \(refreshToken)")
- print("ID Token: \(idToken)")
- print("Apple User ID: \(userId)")
-
do {
try self.persistState(idToken: idToken, refreshToken: refreshToken, accessToken: accessToken)
} catch {
@@ -591,7 +512,6 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
}
case .failure(let error):
if let statusCode = response.response?.statusCode {
- print("error", response.debugDescription)
completion(.failure(.invalidResponseCode(statusCode: statusCode)))
} else {
completion(.failure(.responseError(error)))
@@ -611,22 +531,4 @@ class AppleProvider: NSObject, ASAuthorizationControllerDelegate, ASAuthorizatio
return UIApplication.shared.windows.first!
}
- private func persistName(userId: String, givenName: String?, familyName: String?) {
- if givenName == nil && familyName == nil { return }
-
- var names = UserDefaults.standard.dictionary(forKey: USER_INFO_KEY) as? [String: [String: String]] ?? [:]
- names[userId] = [
- "givenName": givenName ?? "",
- "familyName": familyName ?? ""
- ]
- UserDefaults.standard.set(names, forKey: USER_INFO_KEY)
- }
-
- private func retrieveName(userId: String) -> (givenName: String?, familyName: String?)? {
- guard let names = UserDefaults.standard.dictionary(forKey: USER_INFO_KEY) as? [String: [String: String]],
- let userNames = names[userId] else {
- return nil
- }
- return (userNames["givenName"], userNames["familyName"])
- }
}
Open-source notices describe third-party licensing and are not a replacement for the Terms of Service or Privacy Policy.