Close all 25 codex audit findings across KMP, iOS, and Android

Remediate all P0-S priority findings from cross-platform architecture audit:
- Harden token storage with EncryptedSharedPreferences (Android) and Keychain (iOS)
- Add SSL pinning and certificate validation to API clients
- Fix subscription cache race conditions and add thread-safe access
- Add input validation for document uploads and file type restrictions
- Refactor DocumentApi to use proper multipart upload flow
- Add rate limiting awareness and retry logic to API layer
- Harden subscription tier enforcement in SubscriptionHelper
- Add biometric prompt for sensitive actions (Login, Onboarding)
- Fix notification permission handling and device registration
- Add UI test infrastructure (page objects, fixtures, smoke tests)
- Add CI workflow for mobile builds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-18 13:15:34 -06:00
parent ffe5716167
commit 7444f73b46
56 changed files with 1539 additions and 569 deletions

View File

@@ -1,37 +1,66 @@
package com.example.casera.cache
import androidx.compose.runtime.mutableStateOf
import com.example.casera.data.DataManager
import com.example.casera.models.FeatureBenefit
import com.example.casera.models.Promotion
import com.example.casera.models.SubscriptionStatus
import com.example.casera.models.UpgradeTriggerData
/**
* Thin facade over DataManager for subscription data.
*
* All state is delegated to DataManager (single source of truth).
* This object exists for backwards compatibility with callers that
* read subscription state (e.g. iOS SubscriptionCacheWrapper polling via Kotlin interop).
*
* For Compose UI code, prefer using DataManager StateFlows directly with collectAsState().
*/
object SubscriptionCache {
val currentSubscription = mutableStateOf<SubscriptionStatus?>(null)
val upgradeTriggers = mutableStateOf<Map<String, UpgradeTriggerData>>(emptyMap())
val featureBenefits = mutableStateOf<List<FeatureBenefit>>(emptyList())
val promotions = mutableStateOf<List<Promotion>>(emptyList())
/**
* Current subscription status, delegated to DataManager.
* For Compose callers, prefer: `val subscription by DataManager.subscription.collectAsState()`
*/
val currentSubscription: SubscriptionCacheAccessor<SubscriptionStatus?>
get() = SubscriptionCacheAccessor { DataManager.subscription.value }
val upgradeTriggers: SubscriptionCacheAccessor<Map<String, UpgradeTriggerData>>
get() = SubscriptionCacheAccessor { DataManager.upgradeTriggers.value }
val featureBenefits: SubscriptionCacheAccessor<List<FeatureBenefit>>
get() = SubscriptionCacheAccessor { DataManager.featureBenefits.value }
val promotions: SubscriptionCacheAccessor<List<Promotion>>
get() = SubscriptionCacheAccessor { DataManager.promotions.value }
fun updateSubscriptionStatus(subscription: SubscriptionStatus) {
currentSubscription.value = subscription
DataManager.setSubscription(subscription)
}
fun updateUpgradeTriggers(triggers: Map<String, UpgradeTriggerData>) {
upgradeTriggers.value = triggers
DataManager.setUpgradeTriggers(triggers)
}
fun updateFeatureBenefits(benefits: List<FeatureBenefit>) {
featureBenefits.value = benefits
DataManager.setFeatureBenefits(benefits)
}
fun updatePromotions(promos: List<Promotion>) {
promotions.value = promos
DataManager.setPromotions(promos)
}
fun clear() {
currentSubscription.value = null
upgradeTriggers.value = emptyMap()
featureBenefits.value = emptyList()
promotions.value = emptyList()
DataManager.setSubscription(null)
DataManager.setUpgradeTriggers(emptyMap())
DataManager.setFeatureBenefits(emptyList())
DataManager.setPromotions(emptyList())
}
}
/**
* Simple accessor that provides .value to read from DataManager.
* This preserves the `SubscriptionCache.currentSubscription.value` call pattern
* used by existing callers (Kotlin code and iOS interop polling).
*/
class SubscriptionCacheAccessor<T>(private val getter: () -> T) {
val value: T get() = getter()
}