fix(account): harden session refresh recovery

Persist refresh operation identity across failures and validate revoked Apple credentials so clients recover without unsafe token rotation.
This commit is contained in:
Rocky
2026-08-25 16:43:47 +08:00
parent 57844ce615
commit 309d743fd3
16 changed files with 438 additions and 33 deletions
@@ -40,10 +40,13 @@ public struct HostPrivateAccountKeychainDescriptor: Equatable, Sendable {
public actor HostPrivateAccountKeychain:
AccountSessionVault,
AppleUserIdentifierStoring,
AppAttestKeyStateStoring,
OOBEInstallationIDStoring {
private enum Account {
static let session = "account.session"
static let refreshTransaction = "account.refresh-transaction"
static let appleUserIdentifier = "account.apple-user-identifier"
static let appAttestKeyState = "integrity.app-attest-key-state"
static let oobeInstallationID = "oobe.installation-id"
}
@@ -70,6 +73,39 @@ public actor HostPrivateAccountKeychain:
try delete(account: Account.session)
}
public func beginRefreshTransaction(
refreshTokenDigest: String
) async throws -> AccountRefreshTransaction {
if let existing = try read(
AccountRefreshTransaction.self,
account: Account.refreshTransaction
), existing.refreshTokenDigest == refreshTokenDigest {
return existing
}
let transaction = AccountRefreshTransaction(
refreshTokenDigest: refreshTokenDigest,
operationId: UUID()
)
try write(transaction, account: Account.refreshTransaction)
return transaction
}
public func clearRefreshTransaction() async throws {
try delete(account: Account.refreshTransaction)
}
public func loadAppleUserIdentifier() async throws -> String? {
try read(String.self, account: Account.appleUserIdentifier)
}
public func saveAppleUserIdentifier(_ userIdentifier: String) async throws {
try write(userIdentifier, account: Account.appleUserIdentifier)
}
public func clearAppleUserIdentifier() async throws {
try delete(account: Account.appleUserIdentifier)
}
public func loadAppAttestKeyState() async throws -> AppAttestKeyState? {
try read(AppAttestKeyState.self, account: Account.appAttestKeyState)
}