From 5ab9ed3190465ee23e4c49418ca1ac1e3a1c8c92 Mon Sep 17 00:00:00 2001 From: Wolfgang Mathurin Date: Mon, 24 Nov 2025 15:16:23 -0800 Subject: [PATCH 1/2] Showing all the user credentials information Not masking empty values Grouping user credentials in sections --- .../AuthFlowTester.xcodeproj/project.pbxproj | 4 + .../AuthFlowTester/Views/InfoRowView.swift | 5 +- .../Views/InfoSectionView.swift | 71 +++++ .../Views/UserCredentialsView.swift | 252 ++++++++++++++++-- 4 files changed, 303 insertions(+), 29 deletions(-) create mode 100644 native/SampleApps/AuthFlowTester/AuthFlowTester/Views/InfoSectionView.swift diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTester.xcodeproj/project.pbxproj b/native/SampleApps/AuthFlowTester/AuthFlowTester.xcodeproj/project.pbxproj index e4b7791884..4baf5fd771 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTester.xcodeproj/project.pbxproj +++ b/native/SampleApps/AuthFlowTester/AuthFlowTester.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 4F5945B82ED51C00003C5BDE /* InfoSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F5945B72ED51C00003C5BDE /* InfoSectionView.swift */; }; 4F95A89C2EA806E700C98D18 /* SalesforceAnalytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F95A8962EA801DC00C98D18 /* SalesforceAnalytics.framework */; }; 4F95A89D2EA806E700C98D18 /* SalesforceAnalytics.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4F95A8962EA801DC00C98D18 /* SalesforceAnalytics.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4F95A89F2EA806E900C98D18 /* SalesforceSDKCommon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F95A8982EA801DC00C98D18 /* SalesforceSDKCommon.framework */; }; @@ -44,6 +45,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 4F5945B72ED51C00003C5BDE /* InfoSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoSectionView.swift; sourceTree = ""; }; 4F95A8962EA801DC00C98D18 /* SalesforceAnalytics.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SalesforceAnalytics.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4F95A8982EA801DC00C98D18 /* SalesforceSDKCommon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SalesforceSDKCommon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4F95A89A2EA801DC00C98D18 /* SalesforceSDKCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SalesforceSDKCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -149,6 +151,7 @@ AUTH039 /* Views */ = { isa = PBXGroup; children = ( + 4F5945B72ED51C00003C5BDE /* InfoSectionView.swift */, 4FEBAF282EA9B91500D4880A /* RevokeView.swift */, AUTH038 /* UserCredentialsView.swift */, AUTH041 /* RestApiTestView.swift */, @@ -246,6 +249,7 @@ 4FEBAF292EA9B91500D4880A /* RevokeView.swift in Sources */, AUTH049 /* JwtAccessView.swift in Sources */, AUTH051 /* InfoRowView.swift in Sources */, + 4F5945B82ED51C00003C5BDE /* InfoSectionView.swift in Sources */, ); }; /* End PBXSourcesBuildPhase section */ diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/InfoRowView.swift b/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/InfoRowView.swift index 6c3057ce62..2b3e36e610 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/InfoRowView.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/InfoRowView.swift @@ -40,7 +40,7 @@ struct InfoRowView: View { .font(.caption) .foregroundColor(.secondary) - if isSensitive && !isRevealed { + if isSensitive && !isRevealed && !value.isEmpty { HStack { Text(maskedValue) .font(.system(.caption, design: .monospaced)) @@ -54,9 +54,8 @@ struct InfoRowView: View { HStack { Text(value.isEmpty ? "(empty)" : value) .font(.system(.caption, design: .monospaced)) - .foregroundColor(value.isEmpty ? .secondary : .primary) Spacer() - if isSensitive { + if isSensitive && !value.isEmpty { Button(action: { isRevealed.toggle() }) { Image(systemName: "eye.slash") .foregroundColor(.blue) diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/InfoSectionView.swift b/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/InfoSectionView.swift new file mode 100644 index 0000000000..c1cceb6138 --- /dev/null +++ b/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/InfoSectionView.swift @@ -0,0 +1,71 @@ +/* + InfoSectionView.swift + AuthFlowTester + + Copyright (c) 2025-present, salesforce.com, inc. All rights reserved. + + Redistribution and use of this software in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to + endorse or promote products derived from this software without specific prior written + permission of salesforce.com, inc. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import SwiftUI + +struct InfoSectionView: View { + let title: String + @Binding var isExpanded: Bool + let content: Content + + init(title: String, isExpanded: Binding, @ViewBuilder content: () -> Content) { + self.title = title + self._isExpanded = isExpanded + self.content = content() + } + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + Button(action: { + withAnimation { + isExpanded.toggle() + } + }) { + HStack { + Text(title) + .font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(.primary) + Spacer() + Image(systemName: isExpanded ? "chevron.up" : "chevron.down") + .font(.caption2) + .foregroundColor(.secondary) + } + } + .padding(.horizontal, 8) + .padding(.vertical, 6) + .background(Color(.tertiarySystemBackground)) + .cornerRadius(6) + + if isExpanded { + content + .padding(.leading, 8) + } + } + } +} + diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/UserCredentialsView.swift b/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/UserCredentialsView.swift index 2f5937c4b3..53dbeb3f53 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/UserCredentialsView.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/UserCredentialsView.swift @@ -31,6 +31,17 @@ import SalesforceSDKCore struct UserCredentialsView: View { @Binding var isExpanded: Bool + // Section expansion states - all start expanded + @State private var userIdentityExpanded = false + @State private var oauthConfigExpanded = false + @State private var tokensExpanded = false + @State private var urlsExpanded = false + @State private var communityExpanded = false + @State private var domainsAndSidsExpanded = false + @State private var cookiesAndSecurityExpanded = false + @State private var beaconExpanded = false + @State private var otherExpanded = false + var body: some View { VStack(alignment: .leading, spacing: 8) { Button(action: { @@ -51,15 +62,69 @@ struct UserCredentialsView: View { } if isExpanded { - InfoRowView(label: "Username:", value: username) - InfoRowView(label: "Access Token:", value: accessToken, isSensitive: true) - InfoRowView(label: "Token Format:", value: tokenFormat) - InfoRowView(label: "Refresh Token:", value: refreshToken, isSensitive: true) - InfoRowView(label: "Client ID:", value: clientId, isSensitive: true) - InfoRowView(label: "Redirect URI:", value: redirectUri) - InfoRowView(label: "Instance URL:", value: instanceUrl) - InfoRowView(label: "Scopes:", value: credentialsScopes) - InfoRowView(label: "Beacon Child Consumer Key:", value: beaconChildConsumerKey) + InfoSectionView(title: "User Identity", isExpanded: $userIdentityExpanded) { + InfoRowView(label: "Username:", value: username) + InfoRowView(label: "User ID:", value: userId) + InfoRowView(label: "Organization ID:", value: organizationId) + } + + InfoSectionView(title: "OAuth Client Configuration", isExpanded: $oauthConfigExpanded) { + InfoRowView(label: "Client ID:", value: clientId, isSensitive: true) + InfoRowView(label: "Redirect URI:", value: redirectUri) + InfoRowView(label: "Protocol:", value: authProtocol) + InfoRowView(label: "Domain:", value: domain) + InfoRowView(label: "Identifier:", value: identifier) + } + + InfoSectionView(title: "Tokens", isExpanded: $tokensExpanded) { + InfoRowView(label: "Access Token:", value: accessToken, isSensitive: true) + InfoRowView(label: "Refresh Token:", value: refreshToken, isSensitive: true) + InfoRowView(label: "Token Format:", value: tokenFormat) + InfoRowView(label: "JWT:", value: jwt, isSensitive: true) + InfoRowView(label: "Auth Code:", value: authCode, isSensitive: true) + InfoRowView(label: "Challenge String:", value: challengeString, isSensitive: true) + InfoRowView(label: "Issued At:", value: issuedAt) + } + + InfoSectionView(title: "URLs", isExpanded: $urlsExpanded) { + InfoRowView(label: "Instance URL:", value: instanceUrl) + InfoRowView(label: "API Instance URL:", value: apiInstanceUrl) + InfoRowView(label: "API URL:", value: apiUrl) + InfoRowView(label: "Identity URL:", value: identityUrl) + } + + InfoSectionView(title: "Community", isExpanded: $communityExpanded) { + InfoRowView(label: "Community ID:", value: communityId) + InfoRowView(label: "Community URL:", value: communityUrl) + } + + InfoSectionView(title: "Domains and SIDs", isExpanded: $domainsAndSidsExpanded) { + InfoRowView(label: "Lightning Domain:", value: lightningDomain) + InfoRowView(label: "Lightning SID:", value: lightningSid, isSensitive: true) + InfoRowView(label: "VF Domain:", value: vfDomain) + InfoRowView(label: "VF SID:", value: vfSid, isSensitive: true) + InfoRowView(label: "Content Domain:", value: contentDomain) + InfoRowView(label: "Content SID:", value: contentSid, isSensitive: true) + InfoRowView(label: "Parent SID:", value: parentSid, isSensitive: true) + InfoRowView(label: "SID Cookie Name:", value: sidCookieName) + } + + InfoSectionView(title: "Cookies and Security", isExpanded: $cookiesAndSecurityExpanded) { + InfoRowView(label: "CSRF Token:", value: csrfToken, isSensitive: true) + InfoRowView(label: "Cookie Client Src:", value: cookieClientSrc) + InfoRowView(label: "Cookie SID Client:", value: cookieSidClient, isSensitive: true) + } + + InfoSectionView(title: "Beacon", isExpanded: $beaconExpanded) { + InfoRowView(label: "Beacon Child Consumer Key:", value: beaconChildConsumerKey) + InfoRowView(label: "Beacon Child Consumer Secret:", value: beaconChildConsumerSecret, isSensitive: true) + } + + InfoSectionView(title: "Other", isExpanded: $otherExpanded) { + InfoRowView(label: "Scopes:", value: credentialsScopes) + InfoRowView(label: "Encrypted:", value: encrypted) + InfoRowView(label: "Additional OAuth Fields:", value: additionalOAuthFields) + } } } .padding() @@ -69,43 +134,178 @@ struct UserCredentialsView: View { // MARK: - Computed Properties + private var credentials: OAuthCredentials? { + return UserAccountManager.shared.currentUserAccount?.credentials + } + + // User Identity + private var username: String { + return UserAccountManager.shared.currentUserAccount?.idData.username ?? "" + } + + private var userId: String { + return credentials?.userId ?? "" + } + + private var organizationId: String { + return credentials?.organizationId ?? "" + } + + // OAuth Client Configuration private var clientId: String { - return UserAccountManager.shared.currentUserAccount?.credentials.clientId ?? "" + return credentials?.clientId ?? "" } private var redirectUri: String { - return UserAccountManager.shared.currentUserAccount?.credentials.redirectUri ?? "" + return credentials?.redirectUri ?? "" } - private var instanceUrl: String { - return UserAccountManager.shared.currentUserAccount?.credentials.instanceUrl?.absoluteString ?? "" + private var authProtocol: String { + return credentials?.protocol ?? "" } - private var username: String { - return UserAccountManager.shared.currentUserAccount?.idData.username ?? "" + private var domain: String { + return credentials?.domain ?? "" } - private var credentialsScopes: String { - guard let scopes = UserAccountManager.shared.currentUserAccount?.credentials.scopes else { - return "" - } - return scopes.joined(separator: " ") + private var identifier: String { + return credentials?.identifier ?? "" + } + + // Tokens + private var accessToken: String { + return credentials?.accessToken ?? "" + } + + private var refreshToken: String { + return credentials?.refreshToken ?? "" } private var tokenFormat: String { - return UserAccountManager.shared.currentUserAccount?.credentials.tokenFormat ?? "" + return credentials?.tokenFormat ?? "" + } + + private var jwt: String { + return credentials?.jwt ?? "" + } + + private var authCode: String { + return credentials?.authCode ?? "" + } + + private var challengeString: String { + return credentials?.challengeString ?? "" + } + + private var issuedAt: String { + guard let date = credentials?.issuedAt else { return "" } + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .medium + return formatter.string(from: date) + } + + // URLs + private var instanceUrl: String { + return credentials?.instanceUrl?.absoluteString ?? "" } + private var apiInstanceUrl: String { + return credentials?.apiInstanceUrl?.absoluteString ?? "" + } + + private var apiUrl: String { + return credentials?.apiUrl?.absoluteString ?? "" + } + + private var identityUrl: String { + return credentials?.identityUrl?.absoluteString ?? "" + } + + // Community + private var communityId: String { + return credentials?.communityId ?? "" + } + + private var communityUrl: String { + return credentials?.communityUrl?.absoluteString ?? "" + } + + // Domains and SIDs + private var lightningDomain: String { + return credentials?.lightningDomain ?? "" + } + + private var lightningSid: String { + return credentials?.lightningSid ?? "" + } + + private var vfDomain: String { + return credentials?.vfDomain ?? "" + } + + private var vfSid: String { + return credentials?.vfSid ?? "" + } + + private var contentDomain: String { + return credentials?.contentDomain ?? "" + } + + private var contentSid: String { + return credentials?.contentSid ?? "" + } + + private var parentSid: String { + return credentials?.parentSid ?? "" + } + + private var sidCookieName: String { + return credentials?.sidCookieName ?? "" + } + + // Cookies and Security + private var csrfToken: String { + return credentials?.csrfToken ?? "" + } + + private var cookieClientSrc: String { + return credentials?.cookieClientSrc ?? "" + } + + private var cookieSidClient: String { + return credentials?.cookieSidClient ?? "" + } + + // Beacon private var beaconChildConsumerKey: String { - return UserAccountManager.shared.currentUserAccount?.credentials.beaconChildConsumerKey ?? "" + return credentials?.beaconChildConsumerKey ?? "" } - private var accessToken: String { - return UserAccountManager.shared.currentUserAccount?.credentials.accessToken ?? "" + private var beaconChildConsumerSecret: String { + return credentials?.beaconChildConsumerSecret ?? "" } - private var refreshToken: String { - return UserAccountManager.shared.currentUserAccount?.credentials.refreshToken ?? "" + // Other + private var credentialsScopes: String { + guard let scopes = credentials?.scopes else { + return "" + } + return scopes.joined(separator: " ") + } + + private var encrypted: String { + guard let creds = credentials else { return "" } + return creds.isEncrypted ? "Yes" : "No" + } + + private var additionalOAuthFields: String { + guard let fields = credentials?.additionalOAuthFields, + let jsonData = try? JSONSerialization.data(withJSONObject: fields, options: .prettyPrinted), + let jsonString = String(data: jsonData, encoding: .utf8) else { + return "" + } + return jsonString } } From a16828695a82f2d16d88e5e6173aa7f2dbd86ee6 Mon Sep 17 00:00:00 2001 From: Wolfgang Mathurin Date: Mon, 24 Nov 2025 16:04:41 -0800 Subject: [PATCH 2/2] User credentials not collapsing when a request is run Request success shown in alert / response collapsed by default --- .../SessionDetailViewController.swift | 3 +- .../Views/RestApiTestView.swift | 101 +++++++----- .../Views/UserCredentialsView.swift | 148 +++++++++--------- 3 files changed, 137 insertions(+), 115 deletions(-) diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTester/ViewControllers/SessionDetailViewController.swift b/native/SampleApps/AuthFlowTester/AuthFlowTester/ViewControllers/SessionDetailViewController.swift index aa7f8b0002..6067c9959f 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTester/ViewControllers/SessionDetailViewController.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTester/ViewControllers/SessionDetailViewController.swift @@ -60,8 +60,7 @@ struct SessionDetailView: View { }) // User Credentials Section - UserCredentialsView(isExpanded: $isUserCredentialsExpanded) - .id(refreshTrigger) + UserCredentialsView(isExpanded: $isUserCredentialsExpanded, refreshTrigger: refreshTrigger) // JWT Access Token Details Section (if applicable) if let credentials = UserAccountManager.shared.currentUserAccount?.credentials, diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/RestApiTestView.swift b/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/RestApiTestView.swift index d550c669df..342e9069ce 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/RestApiTestView.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/RestApiTestView.swift @@ -29,9 +29,15 @@ import SwiftUI import SalesforceSDKCore struct RestApiTestView: View { + enum AlertType { + case success + case error(String) + } + @State private var isLoading = false @State private var lastRequestResult: String = "" @State private var isResultExpanded = false + @State private var alertType: AlertType? let onRequestCompleted: () -> Void @@ -59,57 +65,73 @@ struct RestApiTestView: View { } .disabled(isLoading) - // Result section - always visible - VStack(alignment: .leading, spacing: 8) { - Button(action: { - if !lastRequestResult.isEmpty { + // Response details section - collapsible + if !lastRequestResult.isEmpty { + VStack(alignment: .leading, spacing: 8) { + Button(action: { withAnimation { isResultExpanded.toggle() } - } - }) { - HStack { - Text("Last Request Result:") - .font(.subheadline) - .fontWeight(.semibold) - .foregroundColor(.primary) - Spacer() - if !lastRequestResult.isEmpty { + }) { + HStack { + Text("Response Details") + .font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(.primary) + Spacer() Image(systemName: isResultExpanded ? "chevron.up" : "chevron.down") .font(.caption) .foregroundColor(.secondary) } - } - } - .disabled(lastRequestResult.isEmpty) - - if lastRequestResult.isEmpty { - Text("No request made yet") - .font(.system(.caption, design: .monospaced)) - .foregroundColor(.secondary) .padding(8) - .frame(maxWidth: .infinity, alignment: .leading) + .background(Color(.tertiarySystemBackground)) + .cornerRadius(6) + } + + if isResultExpanded { + ScrollView([.vertical, .horizontal], showsIndicators: true) { + Text(lastRequestResult) + .font(.system(.caption, design: .monospaced)) + .foregroundColor(.primary) + .padding(8) + .frame(maxWidth: .infinity, alignment: .leading) + .textSelection(.enabled) + } + .frame(minHeight: 200, maxHeight: 400) .background(Color(.systemGray6)) .cornerRadius(4) - } else if isResultExpanded { - ScrollView([.vertical, .horizontal], showsIndicators: true) { - Text(lastRequestResult) - .font(.system(.caption, design: .monospaced)) - .foregroundColor(lastRequestResult.hasPrefix("✓") ? .green : .red) - .padding(8) - .frame(maxWidth: .infinity, alignment: .leading) - .textSelection(.enabled) } - .frame(minHeight: 200, maxHeight: 400) - .background(Color(.systemGray6)) - .cornerRadius(4) } + .padding(.vertical, 4) } - .padding(.vertical, 4) } .padding() .background(Color(.secondarySystemBackground)) .cornerRadius(8) + .alert(item: Binding( + get: { alertType.map { AlertItem(type: $0) } }, + set: { alertType = $0?.type } + )) { alertItem in + switch alertItem.type { + case .success: + return Alert( + title: Text("Request Successful"), + message: Text("The REST API request completed successfully. Expand 'Response Details' below to see the full response."), + dismissButton: .default(Text("OK")) + ) + case .error(let message): + return Alert( + title: Text("Request Failed"), + message: Text(message), + dismissButton: .default(Text("OK")) + ) + } + } + } + + struct AlertItem: Identifiable { + let id = UUID() + let type: AlertType } // MARK: - REST API Request @@ -118,6 +140,7 @@ struct RestApiTestView: View { private func makeRestRequest() async { isLoading = true lastRequestResult = "" + isResultExpanded = false // Start collapsed do { let request = RestClient.shared.cheapRequest("v63.0") @@ -125,15 +148,17 @@ struct RestApiTestView: View { // Request succeeded - pretty print the JSON let prettyJSON = prettyPrintJSON(response.asString()) - lastRequestResult = "✓ Success:\n\n\(prettyJSON)" - isResultExpanded = true // Auto-expand on new result + lastRequestResult = prettyJSON + alertType = .success + // Response starts collapsed - user can expand to see details // Notify parent to refresh fields onRequestCompleted() } catch { // Request failed - lastRequestResult = "✗ Error: \(error.localizedDescription)" - isResultExpanded = true // Auto-expand on error + lastRequestResult = error.localizedDescription + alertType = .error(error.localizedDescription) + // Error details start collapsed - user can expand to see details } isLoading = false diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/UserCredentialsView.swift b/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/UserCredentialsView.swift index 53dbeb3f53..42cf1abdbd 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/UserCredentialsView.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTester/Views/UserCredentialsView.swift @@ -30,8 +30,9 @@ import SalesforceSDKCore struct UserCredentialsView: View { @Binding var isExpanded: Bool + let refreshTrigger: UUID - // Section expansion states - all start expanded + // Section expansion states - all start collapsed @State private var userIdentityExpanded = false @State private var oauthConfigExpanded = false @State private var tokensExpanded = false @@ -62,69 +63,71 @@ struct UserCredentialsView: View { } if isExpanded { - InfoSectionView(title: "User Identity", isExpanded: $userIdentityExpanded) { - InfoRowView(label: "Username:", value: username) - InfoRowView(label: "User ID:", value: userId) - InfoRowView(label: "Organization ID:", value: organizationId) - } - - InfoSectionView(title: "OAuth Client Configuration", isExpanded: $oauthConfigExpanded) { - InfoRowView(label: "Client ID:", value: clientId, isSensitive: true) - InfoRowView(label: "Redirect URI:", value: redirectUri) - InfoRowView(label: "Protocol:", value: authProtocol) - InfoRowView(label: "Domain:", value: domain) - InfoRowView(label: "Identifier:", value: identifier) - } - - InfoSectionView(title: "Tokens", isExpanded: $tokensExpanded) { - InfoRowView(label: "Access Token:", value: accessToken, isSensitive: true) - InfoRowView(label: "Refresh Token:", value: refreshToken, isSensitive: true) - InfoRowView(label: "Token Format:", value: tokenFormat) - InfoRowView(label: "JWT:", value: jwt, isSensitive: true) - InfoRowView(label: "Auth Code:", value: authCode, isSensitive: true) - InfoRowView(label: "Challenge String:", value: challengeString, isSensitive: true) - InfoRowView(label: "Issued At:", value: issuedAt) - } - - InfoSectionView(title: "URLs", isExpanded: $urlsExpanded) { - InfoRowView(label: "Instance URL:", value: instanceUrl) - InfoRowView(label: "API Instance URL:", value: apiInstanceUrl) - InfoRowView(label: "API URL:", value: apiUrl) - InfoRowView(label: "Identity URL:", value: identityUrl) - } - - InfoSectionView(title: "Community", isExpanded: $communityExpanded) { - InfoRowView(label: "Community ID:", value: communityId) - InfoRowView(label: "Community URL:", value: communityUrl) - } - - InfoSectionView(title: "Domains and SIDs", isExpanded: $domainsAndSidsExpanded) { - InfoRowView(label: "Lightning Domain:", value: lightningDomain) - InfoRowView(label: "Lightning SID:", value: lightningSid, isSensitive: true) - InfoRowView(label: "VF Domain:", value: vfDomain) - InfoRowView(label: "VF SID:", value: vfSid, isSensitive: true) - InfoRowView(label: "Content Domain:", value: contentDomain) - InfoRowView(label: "Content SID:", value: contentSid, isSensitive: true) - InfoRowView(label: "Parent SID:", value: parentSid, isSensitive: true) - InfoRowView(label: "SID Cookie Name:", value: sidCookieName) - } - - InfoSectionView(title: "Cookies and Security", isExpanded: $cookiesAndSecurityExpanded) { - InfoRowView(label: "CSRF Token:", value: csrfToken, isSensitive: true) - InfoRowView(label: "Cookie Client Src:", value: cookieClientSrc) - InfoRowView(label: "Cookie SID Client:", value: cookieSidClient, isSensitive: true) - } - - InfoSectionView(title: "Beacon", isExpanded: $beaconExpanded) { - InfoRowView(label: "Beacon Child Consumer Key:", value: beaconChildConsumerKey) - InfoRowView(label: "Beacon Child Consumer Secret:", value: beaconChildConsumerSecret, isSensitive: true) - } - - InfoSectionView(title: "Other", isExpanded: $otherExpanded) { - InfoRowView(label: "Scopes:", value: credentialsScopes) - InfoRowView(label: "Encrypted:", value: encrypted) - InfoRowView(label: "Additional OAuth Fields:", value: additionalOAuthFields) + VStack(spacing: 8) { + InfoSectionView(title: "User Identity", isExpanded: $userIdentityExpanded) { + InfoRowView(label: "Username:", value: username) + InfoRowView(label: "User ID:", value: userId) + InfoRowView(label: "Organization ID:", value: organizationId) + } + + InfoSectionView(title: "OAuth Client Configuration", isExpanded: $oauthConfigExpanded) { + InfoRowView(label: "Client ID:", value: clientId, isSensitive: true) + InfoRowView(label: "Redirect URI:", value: redirectUri) + InfoRowView(label: "Protocol:", value: authProtocol) + InfoRowView(label: "Domain:", value: domain) + InfoRowView(label: "Identifier:", value: identifier) + } + + InfoSectionView(title: "Tokens", isExpanded: $tokensExpanded) { + InfoRowView(label: "Access Token:", value: accessToken, isSensitive: true) + InfoRowView(label: "Refresh Token:", value: refreshToken, isSensitive: true) + InfoRowView(label: "Token Format:", value: tokenFormat) + InfoRowView(label: "JWT:", value: jwt, isSensitive: true) + InfoRowView(label: "Auth Code:", value: authCode, isSensitive: true) + InfoRowView(label: "Challenge String:", value: challengeString, isSensitive: true) + InfoRowView(label: "Issued At:", value: issuedAt) + InfoRowView(label: "Scopes:", value: credentialsScopes) + } + + InfoSectionView(title: "URLs", isExpanded: $urlsExpanded) { + InfoRowView(label: "Instance URL:", value: instanceUrl) + InfoRowView(label: "API Instance URL:", value: apiInstanceUrl) + InfoRowView(label: "API URL:", value: apiUrl) + InfoRowView(label: "Identity URL:", value: identityUrl) + } + + InfoSectionView(title: "Community", isExpanded: $communityExpanded) { + InfoRowView(label: "Community ID:", value: communityId) + InfoRowView(label: "Community URL:", value: communityUrl) + } + + InfoSectionView(title: "Domains and SIDs", isExpanded: $domainsAndSidsExpanded) { + InfoRowView(label: "Lightning Domain:", value: lightningDomain) + InfoRowView(label: "Lightning SID:", value: lightningSid, isSensitive: true) + InfoRowView(label: "VF Domain:", value: vfDomain) + InfoRowView(label: "VF SID:", value: vfSid, isSensitive: true) + InfoRowView(label: "Content Domain:", value: contentDomain) + InfoRowView(label: "Content SID:", value: contentSid, isSensitive: true) + InfoRowView(label: "Parent SID:", value: parentSid, isSensitive: true) + InfoRowView(label: "SID Cookie Name:", value: sidCookieName) + } + + InfoSectionView(title: "Cookies and Security", isExpanded: $cookiesAndSecurityExpanded) { + InfoRowView(label: "CSRF Token:", value: csrfToken, isSensitive: true) + InfoRowView(label: "Cookie Client Src:", value: cookieClientSrc) + InfoRowView(label: "Cookie SID Client:", value: cookieSidClient, isSensitive: true) + } + + InfoSectionView(title: "Beacon", isExpanded: $beaconExpanded) { + InfoRowView(label: "Beacon Child Consumer Key:", value: beaconChildConsumerKey) + InfoRowView(label: "Beacon Child Consumer Secret:", value: beaconChildConsumerSecret, isSensitive: true) + } + + InfoSectionView(title: "Other", isExpanded: $otherExpanded) { + InfoRowView(label: "Additional OAuth Fields:", value: additionalOAuthFields) + } } + .id(refreshTrigger) } } .padding() @@ -204,6 +207,13 @@ struct UserCredentialsView: View { formatter.timeStyle = .medium return formatter.string(from: date) } + + private var credentialsScopes: String { + guard let scopes = credentials?.scopes else { + return "" + } + return scopes.joined(separator: " ") + } // URLs private var instanceUrl: String { @@ -287,18 +297,6 @@ struct UserCredentialsView: View { } // Other - private var credentialsScopes: String { - guard let scopes = credentials?.scopes else { - return "" - } - return scopes.joined(separator: " ") - } - - private var encrypted: String { - guard let creds = credentials else { return "" } - return creds.isEncrypted ? "Yes" : "No" - } - private var additionalOAuthFields: String { guard let fields = credentials?.additionalOAuthFields, let jsonData = try? JSONSerialization.data(withJSONObject: fields, options: .prettyPrinted),