Merge pull request 'fix/app-attest/production-environment' (#41) from fix/app-attest/production-environment into main

Reviewed-on: Loverde-Company-LTDA/Pedi-Foods-Skip#41
This commit is contained in:
2026-08-24 11:04:18 -03:00
4 changed files with 43 additions and 7 deletions

View File

@@ -2,7 +2,11 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.developer.devicecheck.app-attest-opt-in</key>
<array>
<string>CDhash</string>
</array>
<key>com.apple.developer.devicecheck.appattest-environment</key> <key>com.apple.developer.devicecheck.appattest-environment</key>
<string>development</string> <string>$(APP_ATTEST_ENVIRONMENT)</string>
</dict> </dict>
</plist> </plist>

View File

@@ -178,6 +178,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */; baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */;
buildSettings = { buildSettings = {
APP_ATTEST_ENVIRONMENT = development;
DEVELOPMENT_TEAM = K4E5BZMM4V; DEVELOPMENT_TEAM = K4E5BZMM4V;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods"; INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods";
@@ -196,8 +197,9 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */; baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */;
buildSettings = { buildSettings = {
CODE_SIGN_IDENTITY = "Apple Distribution"; APP_ATTEST_ENVIRONMENT = production;
CODE_SIGN_STYLE = Manual; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = K4E5BZMM4V; DEVELOPMENT_TEAM = K4E5BZMM4V;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods"; INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods";
@@ -206,7 +208,7 @@
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
PROVISIONING_PROFILE_SPECIFIER = "com.br.pedifoods.app AppStore"; PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
TARGETED_DEVICE_FAMILY = 1; TARGETED_DEVICE_FAMILY = 1;

View File

@@ -93,6 +93,7 @@ actor GuestSessionService {
#if os(iOS) #if os(iOS)
private func refreshTokenWithAppAttest() async throws -> String { private func refreshTokenWithAppAttest() async throws -> String {
print("[GuestSessionService] DCAppAttestService.isSupported = \(DCAppAttestService.shared.isSupported), existingKeyId = \(store.appAttestKeyId ?? "nil")")
guard DCAppAttestService.shared.isSupported else { guard DCAppAttestService.shared.isSupported else {
// Simulator can never support App Attest (hardware limitation, // Simulator can never support App Attest (hardware limitation,
// not environment-specific) server has its own documented // not environment-specific) server has its own documented
@@ -112,6 +113,15 @@ actor GuestSessionService {
// other error (network blip, timeout, decode issue) must // other error (network blip, timeout, decode issue) must
// NOT wipe a perfectly valid registered key. // NOT wipe a perfectly valid registered key.
store.appAttestKeyId = nil store.appAttestKeyId = nil
} catch let error as DCError {
// DeviceCheck itself rejects the key locally (e.g. the app
// was reinstalled and the Secure Enclave key backing this
// keyId no longer exists) - distinct from the server
// rejecting it, but equally unrecoverable without a fresh
// key. Without this, generateAssertion fails the same way
// forever since appAttestKeyId is never cleared.
print("[GuestSessionService] existing key rejected locally, re-attesting with a fresh key: \(error)")
store.appAttestKeyId = nil
} }
} }
@@ -141,9 +151,22 @@ actor GuestSessionService {
} }
private func handshakeWithFreshAttestation(challenge: String) async throws -> String { private func handshakeWithFreshAttestation(challenge: String) async throws -> String {
let keyId = try await DCAppAttestService.shared.generateKey() let keyId: String
do {
keyId = try await DCAppAttestService.shared.generateKey()
} catch {
print("[GuestSessionService] generateKey failed: \(error)")
throw error
}
let clientDataHash = Data(SHA256.hash(data: Data(challenge.utf8))) let clientDataHash = Data(SHA256.hash(data: Data(challenge.utf8)))
let attestationObject = try await DCAppAttestService.shared.attestKey(keyId, clientDataHash: clientDataHash) let attestationObject: Data
do {
attestationObject = try await DCAppAttestService.shared.attestKey(keyId, clientDataHash: clientDataHash)
} catch {
print("[GuestSessionService] attestKey failed: \(error)")
throw error
}
let payload = GuestSessionAttestPayload( let payload = GuestSessionAttestPayload(
platform: "ios", platform: "ios",
@@ -175,7 +198,13 @@ actor GuestSessionService {
private func handshakeWithAssertion(keyId: String, challenge: String) async throws -> String { private func handshakeWithAssertion(keyId: String, challenge: String) async throws -> String {
let clientDataHash = Data(SHA256.hash(data: Data(challenge.utf8))) let clientDataHash = Data(SHA256.hash(data: Data(challenge.utf8)))
let assertionObject = try await DCAppAttestService.shared.generateAssertion(keyId, clientDataHash: clientDataHash) let assertionObject: Data
do {
assertionObject = try await DCAppAttestService.shared.generateAssertion(keyId, clientDataHash: clientDataHash)
} catch {
print("[GuestSessionService] generateAssertion failed: \(error)")
throw error
}
let payload = GuestSessionAttestPayload( let payload = GuestSessionAttestPayload(
platform: "ios", platform: "ios",

View File

@@ -161,6 +161,7 @@ struct PublicLocationPickerView: View {
errorMessage = "Nenhum estado disponível no momento." errorMessage = "Nenhum estado disponível no momento."
} }
} catch { } catch {
print("[PublicLocationPickerView] loadLocations failed: \(error)")
errorMessage = "Não foi possível carregar. Tente novamente." errorMessage = "Não foi possível carregar. Tente novamente."
} }
isLoading = false isLoading = false