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.

Upstream source Exact commit MPL 2.0

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.