[app-attest-env] Merge production-environment fix into opt-in-prompts, port to flat layout

Resolves the App Attest env/entitlements changes onto the flattened
PediFoods.xcodeproj structure (Darwin/PediFoods.xcodeproj no longer
exists on this branch). Also restores Release signing to Manual /
Apple Distribution / AppStore provisioning profile - the merged-in
fix had switched it to Automatic / Apple Development, which breaks
headless CI archiving.
This commit is contained in:
Daniel Arantes Loverde
2026-08-24 11:39:23 -03:00
4 changed files with 40 additions and 4 deletions

View File

@@ -981,6 +981,7 @@
388EFAF707AD295240B9E951 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
APP_ATTEST_ENVIRONMENT = production;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
@@ -1025,6 +1026,7 @@
4424276379B7A6A98892CFBC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APP_ATTEST_ENVIRONMENT = development;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;

View File

@@ -4,7 +4,11 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.devicecheck.app-attest-opt-in</key>
<array>
<string>CDhash</string>
</array>
<key>com.apple.developer.devicecheck.appattest-environment</key>
<string>development</string>
<string>$(APP_ATTEST_ENVIRONMENT)</string>
</dict>
</plist>

View File

@@ -93,6 +93,7 @@ actor GuestSessionService {
#if os(iOS)
private func refreshTokenWithAppAttest() async throws -> String {
print("[GuestSessionService] DCAppAttestService.isSupported = \(DCAppAttestService.shared.isSupported), existingKeyId = \(store.appAttestKeyId ?? "nil")")
guard DCAppAttestService.shared.isSupported else {
// Simulator can never support App Attest (hardware limitation,
// not environment-specific) server has its own documented
@@ -112,6 +113,15 @@ actor GuestSessionService {
// other error (network blip, timeout, decode issue) must
// NOT wipe a perfectly valid registered key.
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 {
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 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(
platform: "ios",
@@ -175,7 +198,13 @@ actor GuestSessionService {
private func handshakeWithAssertion(keyId: String, challenge: String) async throws -> String {
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(
platform: "ios",

View File

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