[2026-07-resubmission] Add guest browsing flow with App Attest session for App Review resubmission

Adds a pre-login public store locator (guest session via DeviceCheck/App
Attest, keychain-backed token storage) so the app no longer forces sign-in
before showing any content, plus updated support URL metadata.
This commit is contained in:
Daniel Arantes Loverde
2026-07-30 11:35:25 -03:00
parent 3e93196b92
commit 017bd7168f
21 changed files with 1140 additions and 136 deletions

View File

@@ -0,0 +1,77 @@
import Foundation
/// Local (device-only) state for the pre-login public store locator
/// see docs/plans/app-migrate-atomenta-calls-to-pedifoods-bff.md.
final class GuestLocationStore: @unchecked Sendable {
static let shared = GuestLocationStore()
private let serviceName = "com.br.pedifoods.app.guest"
private let deviceIdKey = "device_id"
private let attestationKey = "attestation"
private let attestKeyIdKey = "attest_key_id"
private let stateKey = "selected_state"
private let cityKey = "selected_city"
/// Stable per-install identifier sent as `deviceId` in the guest handshake.
var deviceId: String {
if let existing = KeychainStore.load(service: serviceName, key: deviceIdKey) {
return existing
}
let generated = UUID().uuidString
KeychainStore.save(generated, service: serviceName, key: deviceIdKey)
return generated
}
/// Fallback-only placeholder (backend just checks non-empty) for
/// environments that can't run real App Attest Simulator, or non-iOS.
/// Real devices use DCAppAttestService via GuestSessionService instead.
var attestationPlaceholder: String {
if let existing = KeychainStore.load(service: serviceName, key: attestationKey) {
return existing
}
let generated = UUID().uuidString
KeychainStore.save(generated, service: serviceName, key: attestationKey)
return generated
}
/// App Attest key ID already registered with the backend for this
/// device, if any. Present -> use it to sign assertions; absent -> this
/// device needs to attest a freshly generated key first.
var appAttestKeyId: String? {
get { KeychainStore.load(service: serviceName, key: attestKeyIdKey) }
set {
if let newValue {
KeychainStore.save(newValue, service: serviceName, key: attestKeyIdKey)
} else {
KeychainStore.delete(service: serviceName, key: attestKeyIdKey)
}
}
}
var selectedState: String? {
get { KeychainStore.load(service: serviceName, key: stateKey) }
set {
if let newValue {
KeychainStore.save(newValue, service: serviceName, key: stateKey)
} else {
KeychainStore.delete(service: serviceName, key: stateKey)
}
}
}
var selectedCity: String? {
get { KeychainStore.load(service: serviceName, key: cityKey) }
set {
if let newValue {
KeychainStore.save(newValue, service: serviceName, key: cityKey)
} else {
KeychainStore.delete(service: serviceName, key: cityKey)
}
}
}
func clearSelectedLocation() {
KeychainStore.delete(service: serviceName, key: stateKey)
KeychainStore.delete(service: serviceName, key: cityKey)
}
}