Spec docs:
- lockdown-ui.md: TTL fields now shown in unlock mode, not just provision
- data-model.md: note Lazy<MeshConnectionManager> in relationships
- plan.md: correct module :core:datastore -> :core:service
Tests (2 new):
- NEEDS_PROVISION after lockNow does not trigger LockNowAcknowledged
- UNLOCKED with no deviceAddress skips save but still authorizes
Compose Multiplatform stringResource requires positional specifiers
(%1$s, %1$d) — plain %s/%d renders literal format tokens.
Boot TTL and Hour TTL fields are now shown for both provision and
unlock, matching the original implementation. Confirm passphrase
field remains provisioning-only.
MeshConnectionManagerImpl and LockdownCoordinatorImpl constructor-inject
each other, causing a StackOverflowError at Koin resolution time.
The coordinator only needs MeshConnectionManager in two rare paths
(lock-now-ack and post-unlock config reload), so defer its resolution
with Lazy<T> — matching the existing Lazy<MeshRouter> pattern in
FromRadioPacketHandlerImpl.
Replace no-op stub with PKCS12 KeyStore + AES-256-GCM file-backed
store at ~/.meshtastic/lockdown/. Passphrases now persist across
Desktop sessions with same error resilience as Android impl.
- Add LockdownCoordinator state machine with auto-replay, lock-now,
and error-resilient passphrase store calls
- Add EncryptedSharedPreferences-backed Android passphrase store
with nullable fallback on crypto init failure
- Add LockdownDialog (provision/unlock/backoff) with byte-length
passphrase validation and string resources
- Add LockdownSessionStatus composable for token info display
- Gate region-unset banner on sessionAuthorized in ConnectionsScreen
- Wire Lock Now button in SecurityConfigScreen
- Add LockdownCoordinatorImplTest covering all state transitions,
auto-replay, lock-now, error paths, and uint32 overflow
- Add FakeLockdownCoordinator and update test fakes
- Delete unused LockdownUnlockDialog.kt
- T027/T028: Auto-disconnect on LockNowAcknowledged state in app shell
- T020/T021: Confirm passphrase field in provision mode with mismatch validation
- T035/T036: LockdownSessionStatus composable showing boots remaining and expiry
- Wire session status and Lock Now button enabled state based on sessionAuthorized
- Expose lockdownTokenInfo and sessionAuthorized from RadioConfigViewModel
- Replace java.text.DateFormat/java.util.Date usage in SecurityConfigScreen
(constitution violation: no java.* in commonMain) with simplified Lock Now button
- Replace material.icons imports with MeshtasticIcons in LockdownUnlockDialog
- Proper token info display to be re-implemented in Phase 5 (T025-T026)
- LockdownUnlockDialog: passphrase entry with boots / hours TTL inputs.
Shows lock_reason on LOCKED, a backoff countdown on UNLOCK_FAILED with
backoff_seconds > 0 (Submit disabled while in backoff), and switches
the title to "Set Passphrase" on NEEDS_PROVISION.
- Main: collect lockdownState/lockdownTokenInfo, show the dialog,
auto-clear on LockNowAcknowledged so the connection drops without
a dialog flash.
- ConnectionsScreen: gate the "must set region" banner on
isLockdownAuthorized so an unauthorized client isn't told to fix a
region it can't see.
- SecurityConfigItemList: "Lock Now" button under Administration,
labelled with the active session token's boots remaining and (if set)
the wall-clock expiry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- IMeshService: sendLockdownUnlock(passphrase, bootTtl, hourTtl) and
sendLockNow() AIDL methods.
- MeshService: AIDL stubs forwarding to MeshActionHandler.
- AndroidRadioControllerImpl: forward to meshService over AIDL.
- DirectRadioControllerImpl: forward directly to actionHandler (in-process
non-Android targets).
- FakeIMeshService: test stubs.
- UIViewModel: lockdownState/lockdownTokenInfo flows, sendLockdownUnlock,
sendLockNow, clearLockdownState. Routed through radioController so the
commonMain code does not depend on the AIDL service directly.
- ConnectionsViewModel: expose lockdownState.
- RadioConfigViewModel: lockdownTokenInfo + sendLockNow for the Lock Now
button in security settings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- CommandSenderImpl: build AdminMessage.lockdown_auth = LockdownAuth(...)
for provision/unlock and lock_now=true for the lock command.
- FromRadioPacketHandlerImpl: route the new FromRadio.lockdown_status
variant to the coordinator; also notify the coordinator on
config_complete_id.
- MeshActionHandlerImpl: forward handleSendLockdownUnlock/handleSendLockNow
to the coordinator.
- MeshConnectionManagerImpl: call coordinator.onConnect/onDisconnect; add
clearRadioConfig to purge cached config after a lock-now ACK.
- ServiceRepositoryImpl: back the lockdownState/lockdownTokenInfo/
sessionAuthorized flows.
- LockdownHandlerImpl: orchestration. Switches on LockdownStatus.State
(NEEDS_PROVISION / LOCKED / UNLOCKED / UNLOCK_FAILED), auto-replays
stored passphrase on LOCKED, clears stored passphrase on a fresh
UNLOCK_FAILED, surfaces backoff_seconds on rate-limit. Tracks a
wasLockNow flag locally so the next LOCKED status after a lock-now
command is translated to LockdownState.LockNowAcknowledged for an
immediate UI disconnect (the new schema has no explicit ACK type).
- LockdownPassphraseStore: per-device EncryptedSharedPreferences store
for auto-unlock. Not biometric-gated by design.
- Add androidx.security:security-crypto dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce the TAK passphrase lockdown abstractions:
- LockdownState sealed class + LockdownTokenInfo for UI to observe.
- LockdownCoordinator interface for the authentication lifecycle
(onConnect/onDisconnect/onConfigComplete/handleLockdownStatus,
plus submitPassphrase/lockNow).
- Add sendLockdownPassphrase/sendLockNow to CommandSender, RadioController.
- Add handleSendLockdownUnlock/handleSendLockNow to MeshActionHandler.
- Add clearRadioConfig to MeshConnectionManager (used during lock-now).
- Add lockdownState/lockdownTokenInfo/sessionAuthorized flows to
ServiceRepository.
handleLockdownStatus consumes the typed firmware LockdownStatus message
from FromRadio (protobufs#911) instead of parsing string-prefixed
ClientNotification messages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>