fix: security hardening — Keychain, no hardcoded creds, safe URLs

- Add KeychainService for encrypted token storage (auth, refresh, health JWT, API key)
- Remove hardcoded email/password from HealthAPIService, store in Keychain
- Move all tokens from UserDefaults to Keychain
- API key sent via X-API-Key header instead of URL query parameter
- Replace force unwrap URL(string:)! with guard let + throws
- Fix force unwrap Calendar.date() in HealthKitService
- Mark HealthKitService @MainActor for thread-safe @Published
- Use withTaskGroup for parallel habit log fetching in TrackerView
- Check notification permission before scheduling reminders
- Add input validation (title max 200 chars)
- Add privacy policy and terms links in Settings
- Update CLAUDE.md with security section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 14:11:10 +03:00
parent 28fca1de89
commit 44c759c190
10 changed files with 167 additions and 58 deletions

View File

@@ -157,13 +157,16 @@ class NotificationService {
cancelReminder("morning_reminder")
cancelReminder("evening_reminder")
if morning {
let parts = morningTime.split(separator: ":").compactMap { Int($0) }
if parts.count == 2 { scheduleMorningReminder(hour: parts[0], minute: parts[1]) }
}
if evening {
let parts = eveningTime.split(separator: ":").compactMap { Int($0) }
if parts.count == 2 { scheduleEveningReminder(hour: parts[0], minute: parts[1]) }
Task {
guard await isAuthorized() else { return }
if morning {
let parts = morningTime.split(separator: ":").compactMap { Int($0) }
if parts.count == 2 { scheduleMorningReminder(hour: parts[0], minute: parts[1]) }
}
if evening {
let parts = eveningTime.split(separator: ":").compactMap { Int($0) }
if parts.count == 2 { scheduleEveningReminder(hour: parts[0], minute: parts[1]) }
}
}
}
}