Merge pull request #10345 from f321x/enforce_unified_password_qml_button
qml: limit creation of new wallets to existing password
This commit is contained in:
@@ -564,6 +564,8 @@ class Daemon(Logger):
|
||||
if os.path.exists(path):
|
||||
os.unlink(path)
|
||||
self.update_recently_opened_wallets(path, remove=True)
|
||||
if self.config.CURRENT_WALLET == path:
|
||||
self.config.CURRENT_WALLET = None
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -665,11 +667,12 @@ class Daemon(Logger):
|
||||
asyncio.run_coroutine_threadsafe(self.stop(), self.asyncio_loop).result()
|
||||
|
||||
@with_wallet_lock
|
||||
def _check_password_for_directory(self, *, old_password, new_password=None, wallet_dir: str) -> Tuple[bool, bool]:
|
||||
def check_password_for_directory(self, *, old_password, new_password=None, wallet_dir: str) -> Tuple[bool, bool, list[str]]:
|
||||
"""Checks password against all wallets (in dir), returns whether they can be unified and whether they are already.
|
||||
If new_password is not None, update all wallet passwords to new_password.
|
||||
"""
|
||||
assert os.path.exists(wallet_dir), f"path {wallet_dir!r} does not exist"
|
||||
succeeded = []
|
||||
failed = []
|
||||
is_unified = True
|
||||
for filename in os.listdir(wallet_dir):
|
||||
@@ -708,9 +711,11 @@ class Daemon(Logger):
|
||||
if new_password:
|
||||
self.logger.info(f'updating password for wallet: {path!r}')
|
||||
wallet.update_password(old_password_real, new_password, encrypt_storage=True)
|
||||
succeeded.append(path)
|
||||
|
||||
can_be_unified = failed == []
|
||||
is_unified = can_be_unified and is_unified
|
||||
return can_be_unified, is_unified
|
||||
return can_be_unified, is_unified, succeeded
|
||||
|
||||
@with_wallet_lock
|
||||
def update_password_for_directory(
|
||||
@@ -726,13 +731,13 @@ class Daemon(Logger):
|
||||
return False
|
||||
if wallet_dir is None:
|
||||
wallet_dir = os.path.dirname(self.config.get_wallet_path())
|
||||
can_be_unified, is_unified = self._check_password_for_directory(
|
||||
can_be_unified, is_unified, _ = self.check_password_for_directory(
|
||||
old_password=old_password, new_password=None, wallet_dir=wallet_dir)
|
||||
if not can_be_unified:
|
||||
return False
|
||||
if is_unified and old_password == new_password:
|
||||
return True
|
||||
self._check_password_for_directory(
|
||||
self.check_password_for_directory(
|
||||
old_password=old_password, new_password=new_password, wallet_dir=wallet_dir)
|
||||
return True
|
||||
|
||||
|
||||
@@ -44,7 +44,13 @@ ElDialog {
|
||||
console.log('daemon loading ' + Daemon.loading)
|
||||
if (!Daemon.loading) {
|
||||
showTimer.stop()
|
||||
dialog.close()
|
||||
if (dialog.visible) {
|
||||
dialog.close()
|
||||
} else {
|
||||
// if the dialog wasn't visible its onClosed callbacks don't get called, so it
|
||||
// needs to be destroyed manually
|
||||
Qt.callLater(function() { dialog.destroy() })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ ElDialog {
|
||||
|
||||
property string name
|
||||
property string path
|
||||
property bool isStartup
|
||||
|
||||
property bool _invalidPassword: false
|
||||
property bool _unlockClicked: false
|
||||
@@ -40,7 +41,7 @@ ElDialog {
|
||||
|
||||
InfoTextArea {
|
||||
id: notice
|
||||
text: Daemon.singlePasswordEnabled || !Daemon.currentWallet
|
||||
text: Daemon.singlePasswordEnabled || isStartup
|
||||
? qsTr('Please enter password')
|
||||
: qsTr('Wallet <b>%1</b> requires password to unlock').arg(name)
|
||||
iconStyle: InfoTextArea.IconStyle.Warn
|
||||
@@ -94,9 +95,39 @@ ElDialog {
|
||||
Daemon.loadWallet(openwalletdialog.path, password.text)
|
||||
}
|
||||
|
||||
function maybeUnlockAnyOtherWallet() {
|
||||
// try to open any other wallet with the password the user entered, hack to improve ux for
|
||||
// users with non-unified wallet password.
|
||||
// we should only fall back to opening a random wallet if:
|
||||
// - the user did not select a specific wallet, otherwise this is confusing
|
||||
// - there can be more than one password, otherwise this scan would be pointless
|
||||
if (Daemon.availableWallets.rowCount() <= 1 || password.text === '') {
|
||||
return false
|
||||
}
|
||||
if (Config.walletDidUseSinglePassword) {
|
||||
// the last time the wallet was unlocked all wallets used the same password.
|
||||
// trying to decrypt all of them now is most probably useless.
|
||||
return false
|
||||
}
|
||||
if (!openwalletdialog.isStartup) {
|
||||
return false // this dialog got opened because the user clicked on a specific wallet
|
||||
}
|
||||
let wallet_paths = Daemon.getWalletsUnlockableWithPassword(password.text)
|
||||
if (wallet_paths && wallet_paths.length > 0) {
|
||||
console.log('could not unlock recent wallet, falling back to: ' + wallet_paths[0])
|
||||
Daemon.loadWallet(wallet_paths[0], password.text)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Daemon
|
||||
function onWalletRequiresPassword() {
|
||||
if (maybeUnlockAnyOtherWallet()) {
|
||||
password.text = '' // reset pw so we cannot end up in a loop
|
||||
return
|
||||
}
|
||||
console.log('invalid password')
|
||||
_invalidPassword = true
|
||||
password.tf.forceActiveFocus()
|
||||
|
||||
@@ -525,15 +525,33 @@ Pane {
|
||||
confirmPassword: true,
|
||||
title: qsTr('Enter new password'),
|
||||
infotext: qsTr('If you forget your password, you\'ll need to restore from seed. Please make sure you have your seed stored safely')
|
||||
+ (Daemon.availableWallets.rowCount() > 1 && Config.walletShouldUseSinglePassword
|
||||
? "\n\n" + qsTr('The new password needs to match the password of any other existing wallet.')
|
||||
: "")
|
||||
})
|
||||
dialog.accepted.connect(function() {
|
||||
var success = Daemon.currentWallet.setPassword(dialog.password)
|
||||
if (Config.walletShouldUseSinglePassword // android
|
||||
&& Daemon.availableWallets.rowCount() > 1 // has more than one wallet
|
||||
&& Daemon.numWalletsWithPassword(dialog.password) < 1 // no other wallet uses this new password
|
||||
) {
|
||||
var success = false
|
||||
var error_msg = [
|
||||
qsTr('You need to use the password of any other existing wallet.'),
|
||||
qsTr('Using different wallet passwords is not supported.'),
|
||||
].join("\n")
|
||||
} else {
|
||||
var success = Daemon.currentWallet.setPassword(dialog.password)
|
||||
if (success && Config.walletShouldUseSinglePassword) {
|
||||
Daemon.singlePassword = dialog.password
|
||||
}
|
||||
var error_msg = qsTr('Password change failed')
|
||||
}
|
||||
var done_dialog = app.messageDialog.createObject(app, {
|
||||
title: success ? qsTr('Success') : qsTr('Error'),
|
||||
iconSource: success
|
||||
? Qt.resolvedUrl('../../icons/info.png')
|
||||
: Qt.resolvedUrl('../../icons/warning.png'),
|
||||
text: success ? qsTr('Password changed') : qsTr('Password change failed')
|
||||
text: success ? qsTr('Password changed') : error_msg
|
||||
})
|
||||
done_dialog.open()
|
||||
})
|
||||
|
||||
@@ -11,10 +11,9 @@ import "controls"
|
||||
Item {
|
||||
id: mainView
|
||||
|
||||
property string title: Daemon.currentWallet ? Daemon.currentWallet.name : qsTr('no wallet loaded')
|
||||
property string title: Daemon.currentWallet.name
|
||||
|
||||
property var _sendDialog
|
||||
property string _intentUri
|
||||
|
||||
property string _request_amount
|
||||
property string _request_description
|
||||
@@ -188,7 +187,7 @@ Item {
|
||||
icon.source: '../../icons/wallet.png'
|
||||
action: Action {
|
||||
text: qsTr('Wallet details')
|
||||
enabled: Daemon.currentWallet && app.stack.currentItem.objectName != 'WalletDetails'
|
||||
enabled: app.stack.currentItem.objectName != 'WalletDetails'
|
||||
onTriggered: menu.openPage(Qt.resolvedUrl('WalletDetails.qml'))
|
||||
}
|
||||
}
|
||||
@@ -198,7 +197,7 @@ Item {
|
||||
action: Action {
|
||||
text: qsTr('Addresses/Coins');
|
||||
onTriggered: menu.openPage(Qt.resolvedUrl('Addresses.qml'));
|
||||
enabled: Daemon.currentWallet && app.stack.currentItem.objectName != 'Addresses'
|
||||
enabled: app.stack.currentItem.objectName != 'Addresses'
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
@@ -206,7 +205,7 @@ Item {
|
||||
icon.source: '../../icons/lightning.png'
|
||||
action: Action {
|
||||
text: qsTr('Channels');
|
||||
enabled: Daemon.currentWallet && Daemon.currentWallet.isLightning && app.stack.currentItem.objectName != 'Channels'
|
||||
enabled: Daemon.currentWallet.isLightning && app.stack.currentItem.objectName != 'Channels'
|
||||
onTriggered: menu.openPage(Qt.resolvedUrl('Channels.qml'))
|
||||
}
|
||||
}
|
||||
@@ -285,62 +284,16 @@ Item {
|
||||
|
||||
History {
|
||||
id: history
|
||||
visible: Daemon.currentWallet
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillHeight: true
|
||||
spacing: 2*constants.paddingXLarge
|
||||
visible: !Daemon.currentWallet
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr('No wallet loaded')
|
||||
font.pixelSize: constants.fontSizeXXLarge
|
||||
}
|
||||
|
||||
Pane {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
padding: 0
|
||||
background: Rectangle {
|
||||
color: Material.dialogColor
|
||||
}
|
||||
FlatButton {
|
||||
text: qsTr('Open/Create Wallet')
|
||||
icon.source: '../../icons/wallet.png'
|
||||
onClicked: {
|
||||
if (Daemon.availableWallets.rowCount() > 0) {
|
||||
stack.push(Qt.resolvedUrl('Wallets.qml'))
|
||||
} else {
|
||||
var newww = app.newWalletWizard.createObject(app)
|
||||
newww.walletCreated.connect(function() {
|
||||
Daemon.availableWallets.reload()
|
||||
// and load the new wallet
|
||||
Daemon.loadWallet(newww.path, newww.wizard_data['password'])
|
||||
})
|
||||
newww.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
|
||||
ButtonContainer {
|
||||
id: buttonContainer
|
||||
Layout.fillWidth: true
|
||||
|
||||
FlatButton {
|
||||
id: receiveButton
|
||||
visible: Daemon.currentWallet
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 1
|
||||
icon.source: '../../icons/tab_receive.png'
|
||||
@@ -357,7 +310,6 @@ Item {
|
||||
}
|
||||
}
|
||||
FlatButton {
|
||||
visible: Daemon.currentWallet
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 1
|
||||
icon.source: '../../icons/tab_send.png'
|
||||
@@ -489,25 +441,22 @@ Item {
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AppController
|
||||
function onUriReceived(uri) {
|
||||
console.log('uri received: ' + uri)
|
||||
if (!Daemon.currentWallet) {
|
||||
console.log('No wallet open, deferring')
|
||||
_intentUri = uri
|
||||
target: Daemon
|
||||
function onWalletLoaded() {
|
||||
if (!Daemon.currentWallet) { // wallet got deleted
|
||||
app.stack.replaceRoot('Wallets.qml')
|
||||
return
|
||||
}
|
||||
piResolver.recipient = uri
|
||||
infobanner.hide() // start hidden when switching wallets
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Daemon
|
||||
function onWalletLoaded() {
|
||||
infobanner.hide() // start hidden when switching wallets
|
||||
if (_intentUri) {
|
||||
piResolver.recipient = _intentUri
|
||||
_intentUri = ''
|
||||
target: app
|
||||
function onPendingIntentChanged() {
|
||||
if (app.pendingIntent) {
|
||||
piResolver.recipient = app.pendingIntent
|
||||
app.pendingIntent = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -819,5 +768,12 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("WalletMainView completed: ", Daemon.currentWallet.name)
|
||||
if (app.pendingIntent) {
|
||||
piResolver.recipient = app.pendingIntent
|
||||
app.pendingIntent = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,21 @@ Pane {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr('Create Wallet')
|
||||
icon.source: '../../icons/add.png'
|
||||
onClicked: rootItem.createWallet()
|
||||
onClicked: {
|
||||
if (Daemon.availableWallets.rowCount() > 0 && Config.walletShouldUseSinglePassword
|
||||
&& (!Daemon.singlePassword || Daemon.numWalletsWithPassword(Daemon.singlePassword) < 1)) {
|
||||
// if the user has wallets but hasn't unlocked any wallet yet force them to do so.
|
||||
// this ensures they know at least one wallets password and can complete the wizard
|
||||
// where they will need to enter the password of an existing wallet.
|
||||
var dialog = app.messageDialog.createObject(app, {
|
||||
title: qsTr('Wallet unlock required'),
|
||||
text: qsTr("You have to unlock any existing wallet first before creating a new wallet."),
|
||||
})
|
||||
dialog.open()
|
||||
} else {
|
||||
rootItem.createWallet()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +143,11 @@ Pane {
|
||||
target: Daemon
|
||||
function onWalletLoaded() {
|
||||
if (app.stack.currentItem.objectName == 'Wallets')
|
||||
app.stack.pop()
|
||||
if (app.stack.getRoot().objectName == 'Wallets') {
|
||||
app.stack.replaceRoot('WalletMainView.qml')
|
||||
} else {
|
||||
app.stack.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ ApplicationWindow
|
||||
property alias keyboardFreeZone: _keyboardFreeZone
|
||||
property alias infobanner: _infobanner
|
||||
|
||||
property string pendingIntent: ""
|
||||
|
||||
property variant activeDialogs: []
|
||||
|
||||
property var _exceptionDialog
|
||||
@@ -120,7 +122,7 @@ ApplicationWindow
|
||||
|
||||
header: ToolBar {
|
||||
id: toolbar
|
||||
|
||||
|
||||
// Add top margin for status bar on Android when using edge-to-edge
|
||||
topPadding: app.statusBarHeight
|
||||
|
||||
@@ -269,7 +271,7 @@ ApplicationWindow
|
||||
Layout.fillWidth: true
|
||||
|
||||
initialItem: Component {
|
||||
WalletMainView {}
|
||||
Wallets {}
|
||||
}
|
||||
|
||||
function getRoot() {
|
||||
@@ -282,6 +284,10 @@ ApplicationWindow
|
||||
mainStackView.push(item)
|
||||
}
|
||||
}
|
||||
function replaceRoot(item_url) {
|
||||
mainStackView.clear()
|
||||
mainStackView.push(Qt.resolvedUrl(item_url))
|
||||
}
|
||||
}
|
||||
|
||||
// Add bottom padding for navigation bar on Android when UI is edge-to-edge
|
||||
@@ -628,12 +634,18 @@ ApplicationWindow
|
||||
}
|
||||
|
||||
property var _opendialog: undefined
|
||||
property var _opendialog_startup: true
|
||||
|
||||
function showOpenWalletDialog(name, path) {
|
||||
if (_opendialog == undefined) {
|
||||
_opendialog = openWalletDialog.createObject(app, { name: name, path: path })
|
||||
_opendialog = openWalletDialog.createObject(app, {
|
||||
name: name,
|
||||
path: path,
|
||||
isStartup: _opendialog_startup,
|
||||
})
|
||||
_opendialog.closed.connect(function() {
|
||||
_opendialog = undefined
|
||||
_opendialog_startup = false
|
||||
})
|
||||
_opendialog.open()
|
||||
}
|
||||
@@ -698,6 +710,10 @@ ApplicationWindow
|
||||
if (obj != null)
|
||||
app.pluginobjects[name] = obj
|
||||
}
|
||||
function onUriReceived(uri) {
|
||||
console.log('uri received (main): ' + uri)
|
||||
app.pendingIntent = uri
|
||||
}
|
||||
}
|
||||
|
||||
function pluginsComponentsByName(comp_name) {
|
||||
|
||||
@@ -5,37 +5,70 @@ import QtQuick.Controls.Material
|
||||
|
||||
import "../controls"
|
||||
|
||||
// We will only end up here if Daemon.singlePasswordEnabled == False.
|
||||
// If there are existing wallets, the user must reuse the password of one of them.
|
||||
// This way they are guided towards password unification.
|
||||
// NOTE: This also needs to be enforced when changing a wallets password.
|
||||
|
||||
WizardComponent {
|
||||
valid: password1.text === password2.text && password1.text.length >= 6
|
||||
id: root
|
||||
valid: isInputValid()
|
||||
property bool enforceExistingPassword: Config.walletShouldUseSinglePassword && Daemon.availableWallets.rowCount() > 0
|
||||
property bool passwordMatchesAnyExisting: false
|
||||
|
||||
function apply() {
|
||||
wizard_data['password'] = password1.text
|
||||
wizard_data['encrypt'] = password1.text != ''
|
||||
}
|
||||
|
||||
function isInputValid() {
|
||||
if (password1.text == "") {
|
||||
return false
|
||||
}
|
||||
if (enforceExistingPassword) {
|
||||
return passwordMatchesAnyExisting
|
||||
}
|
||||
return password1.text === password2.text && password1.text.length >= 6
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: passwordComparisonTimer
|
||||
interval: 500
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.passwordMatchesAnyExisting = Daemon.numWalletsWithPassword(password1.text) > 0
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Daemon.singlePasswordEnabled
|
||||
? qsTr('Enter password')
|
||||
: qsTr('Enter password for %1').arg(wizard_data['wallet_name'])
|
||||
text: !enforceExistingPassword ? qsTr('Enter password') : qsTr('Enter existing password')
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
PasswordField {
|
||||
id: password1
|
||||
onTextChanged: {
|
||||
if (enforceExistingPassword) {
|
||||
root.passwordMatchesAnyExisting = false
|
||||
passwordComparisonTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr('Enter password (again)')
|
||||
visible: !enforceExistingPassword
|
||||
}
|
||||
|
||||
PasswordField {
|
||||
id: password2
|
||||
showReveal: false
|
||||
echoMode: password1.echoMode
|
||||
visible: !enforceExistingPassword
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -44,7 +77,7 @@ WizardComponent {
|
||||
Layout.rightMargin: constants.paddingXLarge
|
||||
Layout.topMargin: constants.paddingXLarge
|
||||
|
||||
visible: password1.text != ''
|
||||
visible: password1.text != '' && !enforceExistingPassword
|
||||
|
||||
Label {
|
||||
Layout.rightMargin: constants.paddingLarge
|
||||
@@ -65,13 +98,30 @@ WizardComponent {
|
||||
InfoTextArea {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
text: qsTr('Passwords don\'t match')
|
||||
visible: password1.text != password2.text
|
||||
visible: (password1.text != password2.text) && !enforceExistingPassword
|
||||
iconStyle: InfoTextArea.IconStyle.Warn
|
||||
}
|
||||
InfoTextArea {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
text: qsTr('Password too short')
|
||||
visible: (password1.text == password2.text) && !valid
|
||||
visible: (password1.text == password2.text) && !valid && !enforceExistingPassword
|
||||
iconStyle: InfoTextArea.IconStyle.Warn
|
||||
}
|
||||
InfoTextArea {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.fillWidth: true
|
||||
visible: password1.text == "" && enforceExistingPassword
|
||||
text: [
|
||||
qsTr("Use the password of any existing wallet."),
|
||||
qsTr("Creating new wallets with different passwords is not supported.")
|
||||
].join("\n")
|
||||
iconStyle: InfoTextArea.IconStyle.Info
|
||||
}
|
||||
InfoTextArea {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.fillWidth: true
|
||||
visible: password1.text != "" && !valid && enforceExistingPassword
|
||||
text: qsTr('Password does not match any existing wallets password.')
|
||||
iconStyle: InfoTextArea.IconStyle.Warn
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +331,25 @@ class QEConfig(AuthMixin, QObject):
|
||||
self._lnutxoreserve = QEAmount(amount_sat=self.config.LN_UTXO_RESERVE)
|
||||
return self._lnutxoreserve
|
||||
|
||||
walletShouldUseSinglePasswordChanged = pyqtSignal()
|
||||
@pyqtProperty(bool, notify=walletShouldUseSinglePasswordChanged)
|
||||
def walletShouldUseSinglePassword(self):
|
||||
"""
|
||||
NOTE: this only indicates if we even want to use a single password, to check if we
|
||||
actually use a single password the daemon needs to be checked.
|
||||
"""
|
||||
return self.config.WALLET_SHOULD_USE_SINGLE_PASSWORD
|
||||
|
||||
walletDidUseSinglePasswordChanged = pyqtSignal()
|
||||
@pyqtProperty(bool, notify=walletDidUseSinglePasswordChanged)
|
||||
def walletDidUseSinglePassword(self):
|
||||
"""
|
||||
Allows to guess if this is a unified password instance without having
|
||||
unlocked any wallet yet. Might be out of sync e.g. if wallet files get copied manually.
|
||||
"""
|
||||
# TODO: consider removing once encrypted wallet file headers are available
|
||||
return self.config.WALLET_DID_USE_SINGLE_PASSWORD
|
||||
|
||||
@pyqtSlot('qint64', result=str)
|
||||
@pyqtSlot(QEAmount, result=str)
|
||||
def formatSatsForEditing(self, satoshis):
|
||||
|
||||
@@ -98,7 +98,7 @@ class QEWalletListModel(QAbstractListModel):
|
||||
i += 1
|
||||
|
||||
if remove >= 0:
|
||||
self.beginRemoveRows(QModelIndex(), i, i)
|
||||
self.beginRemoveRows(QModelIndex(), remove, remove)
|
||||
self._wallets = wallets
|
||||
self.endRemoveRows()
|
||||
|
||||
@@ -219,7 +219,7 @@ class QEDaemon(AuthMixin, QObject):
|
||||
except InvalidPassword:
|
||||
self.walletRequiresPassword.emit(self._name, self._path)
|
||||
except FileNotFoundError:
|
||||
self.walletOpenError.emit(_('File not found'))
|
||||
self.walletOpenError.emit(_('File not found') + f":\n{self._path}")
|
||||
except StorageReadWriteError:
|
||||
self.walletOpenError.emit(_('Could not read/write file'))
|
||||
except WalletFileException as e:
|
||||
@@ -230,13 +230,14 @@ class QEDaemon(AuthMixin, QObject):
|
||||
if wallet is None:
|
||||
return
|
||||
|
||||
if self.daemon.config.WALLET_USE_SINGLE_PASSWORD:
|
||||
if self.daemon.config.WALLET_SHOULD_USE_SINGLE_PASSWORD:
|
||||
self._use_single_password = self.daemon.update_password_for_directory(old_password=local_password, new_password=local_password)
|
||||
self._password = local_password
|
||||
self.singlePasswordChanged.emit()
|
||||
self._logger.info(f'use single password: {self._use_single_password}')
|
||||
else:
|
||||
self._logger.info('use single password disabled by config')
|
||||
self.daemon.config.WALLET_DID_USE_SINGLE_PASSWORD = self._use_single_password
|
||||
|
||||
run_hook('load_wallet', wallet)
|
||||
|
||||
@@ -318,15 +319,59 @@ class QEDaemon(AuthMixin, QObject):
|
||||
def fx(self):
|
||||
return self.qefx
|
||||
|
||||
@pyqtSlot(str, result=list)
|
||||
def getWalletsUnlockableWithPassword(self, password: str) -> list[str]:
|
||||
"""
|
||||
Returns any wallet that can be unlocked with the given password.
|
||||
Can be used as fallback to unlock another wallet the user entered a
|
||||
password that doesn't work for the current wallet but might work for another one.
|
||||
"""
|
||||
wallet_dir = os.path.dirname(self.daemon.config.get_wallet_path())
|
||||
_, _, wallet_paths_can_unlock = self.daemon.check_password_for_directory(
|
||||
old_password=password,
|
||||
new_password=None,
|
||||
wallet_dir=wallet_dir,
|
||||
)
|
||||
if not wallet_paths_can_unlock:
|
||||
return []
|
||||
self._logger.debug(f"getWalletsUnlockableWithPassword: can unlock {len(wallet_paths_can_unlock)} wallets")
|
||||
return [str(path) for path in wallet_paths_can_unlock]
|
||||
|
||||
@pyqtSlot(str, result=int)
|
||||
def numWalletsWithPassword(self, password: str) -> int:
|
||||
"""Returns the number of wallets that can be unlocked with the given password"""
|
||||
wallet_paths_can_unlock = self.getWalletsUnlockableWithPassword(password)
|
||||
return len(wallet_paths_can_unlock)
|
||||
|
||||
singlePasswordChanged = pyqtSignal()
|
||||
@pyqtProperty(bool, notify=singlePasswordChanged)
|
||||
def singlePasswordEnabled(self):
|
||||
"""
|
||||
singlePasswordEnabled is False if:
|
||||
a.) the user has no wallet (and password) yet
|
||||
b.) the user has wallets with different passwords (legacy)
|
||||
c.) all wallets are locked, we couldn't check yet if they all use the same password
|
||||
d.) we are on desktop where different passwords are allowed
|
||||
"""
|
||||
return self._use_single_password
|
||||
|
||||
@pyqtProperty(str, notify=singlePasswordChanged)
|
||||
def singlePassword(self):
|
||||
"""
|
||||
self._password is also set to the last loaded wallet password if we WANT a single password,
|
||||
but don't actually have a single password yet. So singlePassword being set doesn't strictly
|
||||
mean all wallets use the same password.
|
||||
"""
|
||||
return self._password
|
||||
|
||||
@singlePassword.setter
|
||||
def singlePassword(self, password: str):
|
||||
assert password
|
||||
assert self.daemon.config.WALLET_SHOULD_USE_SINGLE_PASSWORD
|
||||
if self._password != password:
|
||||
self._password = password
|
||||
self.singlePasswordChanged.emit()
|
||||
|
||||
@pyqtSlot(result=str)
|
||||
def suggestWalletName(self):
|
||||
# FIXME why not use util.get_new_wallet_name ?
|
||||
|
||||
@@ -675,7 +675,9 @@ class SimpleConfig(Logger):
|
||||
)
|
||||
WALLET_UNCONF_UTXO_FREEZE_THRESHOLD_SAT = ConfigVar('unconf_utxo_freeze_threshold', default=5_000, type_=int)
|
||||
WALLET_PAYREQ_EXPIRY_SECONDS = ConfigVar('request_expiry', default=invoices.PR_DEFAULT_EXPIRATION_WHEN_CREATING, type_=int)
|
||||
WALLET_USE_SINGLE_PASSWORD = ConfigVar('single_password', default=False, type_=bool)
|
||||
WALLET_SHOULD_USE_SINGLE_PASSWORD = ConfigVar('should_use_single_password', default=False, type_=bool)
|
||||
# TODO: consider removing WALLET_DID_USE_SINGLE_PASSWORD once encrypted wallet file headers are available
|
||||
WALLET_DID_USE_SINGLE_PASSWORD = ConfigVar('did_use_single_password', default=False, type_=bool)
|
||||
# note: 'use_change' and 'multiple_change' are per-wallet settings
|
||||
WALLET_SEND_CHANGE_TO_LIGHTNING = ConfigVar(
|
||||
'send_change_to_lightning', default=False, type_=bool,
|
||||
|
||||
Reference in New Issue
Block a user