core: /_wallet delete takes no confirmation word

The wallet is an API, and no other API command takes a typed confirmation:
/_delete user takes the profile password. The word was also no barrier to a
caller, which can read it from /_wallet export. Confirming belongs in the
command that will wrap this one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rvc3HbiWBTqbAvRT45G5oX
This commit is contained in:
Alain Brenzikofer
2026-09-10 08:43:43 +00:00
co-authored by Claude Opus 5
parent e5d18bd59e
commit fe9c20370f
3 changed files with 8 additions and 14 deletions
+2 -2
View File
@@ -421,7 +421,7 @@ data ChatCommand
| APIWalletCreate
| APIWalletImport {recoveryPhrase :: Text}
| APIWalletExport
| APIWalletDelete {confirmWord :: Text}
| APIWalletDelete
| APISendCallInvitation ContactId CallType
| SendCallInvitation ContactName CallType
| APIRejectCall ContactId
@@ -751,7 +751,7 @@ allowRemoteCommand = \case
APIWalletCreate -> False
APIWalletImport _ -> False
APIWalletExport -> False
APIWalletDelete _ -> False
APIWalletDelete -> False
_ -> True
data RelayConnectionResult = RelayConnectionResult
+4 -8
View File
@@ -1519,14 +1519,10 @@ processChatCommand cxt nm = \case
seed <- withFastStore' getDeviceSeed >>= maybe (throwCmdError noKeyError) pure
phrase <- either (throwCmdError . ("wallet: " <>)) pure $ recoveryKeyPhrase seed
pure $ CRWalletPhrase user (safeDecodeUtf8 phrase)
APIWalletDelete confirmWord -> withUser $ \user -> do
APIWalletDelete -> withUser $ \_ -> do
seed <- withFastStore' getDeviceSeed >>= maybe (throwCmdError noKeyError) pure
phrase <- either (throwCmdError . ("wallet: " <>)) pure $ recoveryKeyPhrase seed
case reverse . T.words $ safeDecodeUtf8 phrase of
w : _ | w == T.toLower confirmWord -> do
withFastStore' $ \db -> deleteSeed db (wsId seed)
processChatCommand cxt nm APIWallet
_ -> throwCmdError "this deletes the wallet key for all profiles on this device, to confirm pass the last word of the recovery phrase"
withFastStore' $ \db -> deleteSeed db (wsId seed)
processChatCommand cxt nm APIWallet
APISendCallInvitation contactId callType -> withUser $ \user -> do
-- party initiating call
ct <- withFastStore $ \db -> getContact db cxt user contactId
@@ -5591,7 +5587,7 @@ chatCommandP =
"/_wallet create" $> APIWalletCreate,
"/_wallet import " *> (APIWalletImport <$> textP),
"/_wallet export" $> APIWalletExport,
"/_wallet delete " *> (APIWalletDelete <$> textP),
"/_wallet delete" $> APIWalletDelete,
"/_wallet" $> APIWallet,
"/_call invite @" *> (APISendCallInvitation <$> A.decimal <* A.space <*> jsonP),
"/call " *> char_ '@' *> (SendCallInvitation <$> displayNameP <*> pure defaultCallType),
+2 -4
View File
@@ -46,7 +46,7 @@ walletTests = do
it "the key and the addresses come back after a restart" testWalletPersists
it "a second profile gets its own account, on the same key" testWalletSecondProfile
it "imports a phrase, exports it, and refuses a second import" testWalletImport
it "deletes the key only with the last word of the phrase" testWalletDelete
it "deletes the key, and a key can be imported again" testWalletDelete
accountRows :: HasCallStack => TestCC -> String -> Int -> IO [(String, String)]
accountRows cc profile acct = do
@@ -119,9 +119,7 @@ testWalletDelete :: HasCallStack => TestParams -> IO ()
testWalletDelete ps = withNewTestChat ps "alice" aliceProfile $ \alice -> do
alice ##> ("/_wallet import " <> B.unpack testPhrase)
_ <- accountRows alice "alice, active" 0
alice ##> "/_wallet delete abandon"
alice <## "bad chat command: this deletes the wallet key for all profiles on this device, to confirm pass the last word of the recovery phrase"
alice ##> "/_wallet delete About"
alice ##> "/_wallet delete"
alice <## "no wallet key"
-- deleting unbinds the profile, so a key can be imported again
alice ##> ("/_wallet import " <> B.unpack testPhrase)