#8 add app lock with passcode and biometric unlock
iOS CI / Build and Test SwiftUI App (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build Unsigned iOS IPA (push) Canceled after 0s
Build Unsigned iOS and macOS Apps / Build macOS ZIP (push) Canceled after 0s

- Gate every layer behind AppLockView when the lock is enabled and
  lock on launch and on backgrounding; the passcode lives only in the
  Keychain (AppLockVault) and the enable state defaults to off.
- Add the lock section to Settings: enable/setup, change passcode and
  disable all require the current passcode via a shared passcode
  sheet; Face ID / Touch ID unlock prompts automatically and can be
  toggled with passcode confirmation.
- Add NSFaceIDUsageDescription to both app targets, cover the passcode
  policy with tests, guard the feature in verify.sh and document it in
  SECURITY.md.
This commit is contained in:
wt committed 2026-08-29 05:59:55 +07:00
1 parent b0eb44d7a7
commit 0d78e02d2b
13 files changed
+663 -21

No files matched your search

+51
View File
@@ -1,3 +1,4 @@
import LocalAuthentication
import PhotosUI
import SwiftUI
@@ -7,6 +8,7 @@ struct SettingsView: View {
@State private var showingSignOutConfirmation = false
@State private var forgetHistory = false
@State private var avatarItem: PhotosPickerItem?
@State private var passcodeSheetMode: AppLockPasscodeSheet.Mode?
var body: some View {
NavigationStack {
@@ -98,6 +100,36 @@ struct SettingsView: View {
.foregroundStyle(.secondary)
}
Section("Блокировка приложения") {
Toggle("Заблокировать приложение", isOn: Binding(
get: { model.appLockIsEnabled },
set: { enabled in
if enabled {
passcodeSheetMode = .setup
} else {
passcodeSheetMode = .verify(.disableLock)
}
}
))
if model.appLockIsEnabled {
Toggle("Вход по \(biometricName)", isOn: Binding(
get: { model.appLockBiometricIsEnabled },
set: { enabled in
passcodeSheetMode = .verify(
enabled ? .enableBiometrics : .disableBiometrics
)
}
))
.disabled(!biometricAvailable)
Button("Сменить пароль") {
passcodeSheetMode = .change
}
}
Text("При включённой блокировке Luma запрашивает пароль при запуске и после сворачивания. Пароль хранится в Keychain.")
.font(.caption)
.foregroundStyle(.secondary)
}
Section("Приватность чата") {
Toggle("Показывать статус набора", isOn: Binding(
get: { model.typingIndicatorsEnabled },
@@ -153,12 +185,31 @@ struct SettingsView: View {
}
}
}
.sheet(item: $passcodeSheetMode) { mode in
AppLockPasscodeSheet(model: model, mode: mode)
}
}
#if os(macOS)
.frame(minWidth: 500, minHeight: 560)
#endif
}
private var biometricContext: LAContext {
LAContext()
}
private var biometricAvailable: Bool {
var error: NSError?
return biometricContext.canEvaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
error: &error
)
}
private var biometricName: String {
biometricContext.biometryType == .faceID ? "Face ID" : "Touch ID"
}
private var connectionTitle: String {
switch model.connectionStatus {
case .connected: return "Подключено"