fix(@desktop/general): invalid pin saved to keychain when enabling biometrics on account using keycard
Fixes: #7729
This commit is contained in:
parent
ca3f82848b
commit
224fd3f42d
|
@ -95,7 +95,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface,
|
|||
result.profileModule = profile_module.newModule(result, profileService, settingsService)
|
||||
result.contactsModule = contacts_module.newModule(result, events, contactsService, chatService)
|
||||
result.languageModule = language_module.newModule(result, events, languageService)
|
||||
result.privacyModule = privacy_module.newModule(result, events, settingsService, privacyService, generalService)
|
||||
result.privacyModule = privacy_module.newModule(result, events, settingsService, keychainService, privacyService, generalService)
|
||||
result.aboutModule = about_module.newModule(result, events, aboutService)
|
||||
result.advancedModule = advanced_module.newModule(result, events, settingsService, stickersService, nodeConfigurationService)
|
||||
result.devicesModule = devices_module.newModule(result, events, settingsService, devicesService)
|
||||
|
|
|
@ -1,35 +1,68 @@
|
|||
import io_interface
|
||||
import uuids
|
||||
|
||||
import ../../../../../constants as main_constants
|
||||
import ../../../../global/global_singleton
|
||||
import ../../../../core/eventemitter
|
||||
import ../../../../../app_service/service/settings/service as settings_service
|
||||
import ../../../../../app_service/service/keychain/service as keychain_service
|
||||
import ../../../../../app_service/service/privacy/service as privacy_service
|
||||
import ../../../../../app_service/service/general/service as general_service
|
||||
import ../../../shared_modules/keycard_popup/io_interface as keycard_shared_module
|
||||
|
||||
const UNIQUE_PRIVACY_SECTION_MODULE_AUTH_IDENTIFIER* = "PrivacySectionModule-Authentication"
|
||||
|
||||
type
|
||||
Controller* = ref object of RootObj
|
||||
delegate: io_interface.AccessInterface
|
||||
events: EventEmitter
|
||||
settingsService: settings_service.Service
|
||||
keychainService: keychain_service.Service
|
||||
privacyService: privacy_service.Service
|
||||
generalService: general_service.Service
|
||||
keychainConnectionIds: seq[UUID]
|
||||
|
||||
proc newController*(delegate: io_interface.AccessInterface, events: EventEmitter,
|
||||
|
||||
settingsService: settings_service.Service,
|
||||
keychainService: keychain_service.Service,
|
||||
privacyService: privacy_service.Service,
|
||||
generalService: general_service.Service): Controller =
|
||||
result = Controller()
|
||||
result.delegate = delegate
|
||||
result.events = events
|
||||
result.settingsService = settingsService
|
||||
result.keychainService = keychainService
|
||||
result.privacyService = privacyService
|
||||
result.generalService = generalService
|
||||
|
||||
proc delete*(self: Controller) =
|
||||
discard
|
||||
|
||||
proc disconnectKeychain*(self: Controller) =
|
||||
for id in self.keychainConnectionIds:
|
||||
self.events.disconnect(id)
|
||||
self.keychainConnectionIds = @[]
|
||||
|
||||
proc connectKeychain*(self: Controller) =
|
||||
var handlerId = self.events.onWithUUID(SIGNAL_KEYCHAIN_SERVICE_SUCCESS) do(e:Args):
|
||||
let args = KeyChainServiceArg(e)
|
||||
self.disconnectKeychain()
|
||||
self.delegate.onStoreToKeychainSuccess(args.data)
|
||||
self.keychainConnectionIds.add(handlerId)
|
||||
|
||||
handlerId = self.events.onWithUUID(SIGNAL_KEYCHAIN_SERVICE_ERROR) do(e:Args):
|
||||
let args = KeyChainServiceArg(e)
|
||||
self.disconnectKeychain()
|
||||
self.delegate.onStoreToKeychainError(args.errDescription, args.errType)
|
||||
self.keychainConnectionIds.add(handlerId)
|
||||
|
||||
proc init*(self: Controller) =
|
||||
self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args):
|
||||
let args = SharedKeycarModuleArgs(e)
|
||||
if args.uniqueIdentifier != UNIQUE_PRIVACY_SECTION_MODULE_AUTH_IDENTIFIER:
|
||||
return
|
||||
self.delegate.onUserAuthenticated(args.pin, args.password, args.keyUid)
|
||||
|
||||
self.events.on(SIGNAL_MNEMONIC_REMOVAL) do(e: Args):
|
||||
self.delegate.onMnemonicUpdated()
|
||||
|
||||
|
@ -67,3 +100,30 @@ proc validatePassword*(self: Controller, password: string): bool =
|
|||
method getPasswordStrengthScore*(self: Controller, password, userName: string): int =
|
||||
return self.generalService.getPasswordStrengthScore(password, userName)
|
||||
|
||||
proc storeToKeychain*(self: Controller, data: string) =
|
||||
let myName = singletonInstance.userProfile.getName()
|
||||
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
|
||||
if not main_constants.IS_MACOS or # Dealing with Keychain is the MacOS only feature
|
||||
data.len == 0 or
|
||||
value == LS_VALUE_STORE or
|
||||
myName.len == 0:
|
||||
self.delegate.onStoreToKeychainError("", "")
|
||||
return
|
||||
self.connectKeychain()
|
||||
self.keychainService.storeData(myName, data)
|
||||
|
||||
proc removeFromKeychain*(self: Controller, key: string) =
|
||||
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
|
||||
if not main_constants.IS_MACOS or # Dealing with Keychain is the MacOS only feature
|
||||
key.len == 0 or
|
||||
value != LS_VALUE_STORE:
|
||||
self.delegate.onStoreToKeychainError("", "")
|
||||
return
|
||||
self.connectKeychain()
|
||||
self.keychainService.tryToDeleteData(key)
|
||||
|
||||
proc authenticateLoggedInUser*(self: Controller) =
|
||||
var data = SharedKeycarModuleAuthenticationArgs(uniqueIdentifier: UNIQUE_PRIVACY_SECTION_MODULE_AUTH_IDENTIFIER)
|
||||
if singletonInstance.userProfile.getIsKeycardUser():
|
||||
data.keyUid = singletonInstance.userProfile.getKeyUid()
|
||||
self.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_AUTHENTICATE_USER, data)
|
|
@ -58,3 +58,18 @@ method validatePassword*(self: AccessInterface, password: string): bool {.base.}
|
|||
|
||||
method getPasswordStrengthScore*(self: AccessInterface, password: string): int {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onStoreToKeychainError*(self: AccessInterface, errorDescription: string, errorType: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onStoreToKeychainSuccess*(self: AccessInterface, data: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method tryStoreToKeyChain*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method tryRemoveFromKeyChain*(self: AccessInterface) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
||||
|
||||
method onUserAuthenticated*(self: AccessInterface, pin: string, password: string, keyUid: string) {.base.} =
|
||||
raise newException(ValueError, "No implementation available")
|
|
@ -7,11 +7,17 @@ import ../io_interface as delegate_interface
|
|||
|
||||
import ../../../../core/eventemitter
|
||||
import ../../../../../app_service/service/settings/service as settings_service
|
||||
import ../../../../../app_service/service/keychain/service as keychain_service
|
||||
import ../../../../../app_service/service/privacy/service as privacy_service
|
||||
import ../../../../../app_service/service/general/service as general_service
|
||||
|
||||
export io_interface
|
||||
|
||||
type
|
||||
KeychainActivityReason {.pure.} = enum
|
||||
StoreTo = 0
|
||||
RemoveFrom
|
||||
|
||||
type
|
||||
Module* = ref object of io_interface.AccessInterface
|
||||
delegate: delegate_interface.AccessInterface
|
||||
|
@ -19,9 +25,11 @@ type
|
|||
view: View
|
||||
viewVariant: QVariant
|
||||
moduleLoaded: bool
|
||||
keychainActivityReason: KeychainActivityReason
|
||||
|
||||
proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitter,
|
||||
settingsService: settings_service.Service,
|
||||
keychainService: keychain_service.Service,
|
||||
privacyService: privacy_service.Service,
|
||||
generalService: general_service.Service):
|
||||
Module =
|
||||
|
@ -29,7 +37,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitt
|
|||
result.delegate = delegate
|
||||
result.view = newView(result)
|
||||
result.viewVariant = newQVariant(result.view)
|
||||
result.controller = controller.newController(result, events, settingsService, privacyService, generalService)
|
||||
result.controller = controller.newController(result, events, settingsService, keychainService, privacyService, generalService)
|
||||
result.moduleLoaded = false
|
||||
|
||||
method delete*(self: Module) =
|
||||
|
@ -85,3 +93,28 @@ method validatePassword*(self: Module, password: string): bool =
|
|||
|
||||
method getPasswordStrengthScore*(self: Module, password: string): int =
|
||||
return self.controller.getPasswordStrengthScore(password, singletonInstance.userProfile.getUsername())
|
||||
|
||||
method onStoreToKeychainError*(self: Module, errorDescription: string, errorType: string) =
|
||||
self.view.emitStoreToKeychainError(errorDescription)
|
||||
|
||||
method onStoreToKeychainSuccess*(self: Module, data: string) =
|
||||
if self.keychainActivityReason == KeychainActivityReason.StoreTo:
|
||||
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_STORE)
|
||||
elif self.keychainActivityReason == KeychainActivityReason.RemoveFrom:
|
||||
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_NEVER)
|
||||
self.view.emitStoreToKeychainSuccess()
|
||||
|
||||
method tryStoreToKeyChain*(self: Module) =
|
||||
self.controller.authenticateLoggedInUser()
|
||||
|
||||
method tryRemoveFromKeyChain*(self: Module) =
|
||||
self.keychainActivityReason = KeychainActivityReason.RemoveFrom
|
||||
let myName = singletonInstance.userProfile.getName()
|
||||
self.controller.removeFromKeychain(myName)
|
||||
|
||||
method onUserAuthenticated*(self: Module, pin: string, password: string, keyUid: string) =
|
||||
self.keychainActivityReason = KeychainActivityReason.StoreTo
|
||||
if pin.len > 0:
|
||||
self.controller.storeToKeychain(pin)
|
||||
else:
|
||||
self.controller.storeToKeychain(password)
|
|
@ -63,3 +63,18 @@ QtObject:
|
|||
|
||||
proc getPasswordStrengthScore*(self: View, password: string): int {.slot.} =
|
||||
return self.delegate.getPasswordStrengthScore(password)
|
||||
|
||||
proc storeToKeychainError*(self:View, errorDescription: string) {.signal.}
|
||||
proc emitStoreToKeychainError*(self: View, errorDescription: string) =
|
||||
self.storeToKeychainError(errorDescription)
|
||||
|
||||
proc storeToKeychainSuccess*(self:View) {.signal.}
|
||||
proc emitStoreToKeychainSuccess*(self: View) =
|
||||
self.storeToKeychainSuccess()
|
||||
|
||||
proc tryStoreToKeyChain*(self: View) {.slot.} =
|
||||
self.delegate.tryStoreToKeyChain()
|
||||
|
||||
proc tryRemoveFromKeyChain*(self: View) {.slot.} =
|
||||
self.delegate.tryRemoveFromKeyChain()
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Dialogs 1.3
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Controls.Validators 0.1
|
||||
|
||||
import utils 1.0
|
||||
import shared 1.0
|
||||
import shared.panels 1.0
|
||||
import shared.popups 1.0
|
||||
import shared.controls 1.0
|
||||
|
||||
import "../stores"
|
||||
|
||||
// TODO: replace with StatusModal
|
||||
ModalPopup {
|
||||
property var privacyStore
|
||||
property bool loading: false
|
||||
property bool firstPasswordFieldValid: false
|
||||
property bool repeatPasswordFieldValid: false
|
||||
property string passwordValidationError: ""
|
||||
property string repeatPasswordValidationError: ""
|
||||
|
||||
signal offerToStorePassword(string password, bool runStoreToKeychainPopup)
|
||||
|
||||
id: popup
|
||||
title: qsTr("Store password")
|
||||
height: 500
|
||||
|
||||
onOpened: {
|
||||
if (userProfile.isKeycardUser) {
|
||||
firstPinInputField.statesInitialization()
|
||||
firstPinInputField.forceFocus()
|
||||
}
|
||||
else {
|
||||
firstPasswordField.text = "";
|
||||
firstPasswordField.forceActiveFocus(Qt.MouseFocusReason)
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
leftPadding: d.padding
|
||||
rightPadding: d.padding
|
||||
topPadding: Style.current.xlPadding
|
||||
bottomPadding: Style.current.xlPadding
|
||||
spacing: Style.current.xlPadding
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property int padding: 56
|
||||
readonly property int fontSize: 15
|
||||
}
|
||||
|
||||
Input {
|
||||
id: firstPasswordField
|
||||
width: parent.width - 2 * d.padding
|
||||
visible: !userProfile.isKeycardUser
|
||||
placeholderText: qsTr("Current password...")
|
||||
textField.echoMode: TextInput.Password
|
||||
onTextChanged: {
|
||||
[firstPasswordFieldValid, passwordValidationError] =
|
||||
Utils.validatePasswords("first", firstPasswordField, repeatPasswordField);
|
||||
[repeatPasswordFieldValid, repeatPasswordValidationError] =
|
||||
Utils.validatePasswords("repeat", firstPasswordField, repeatPasswordField);
|
||||
}
|
||||
}
|
||||
|
||||
Input {
|
||||
id: repeatPasswordField
|
||||
visible: !userProfile.isKeycardUser
|
||||
width: parent.width - 2 * d.padding
|
||||
enabled: firstPasswordFieldValid
|
||||
placeholderText: qsTr("Confirm password…")
|
||||
textField.echoMode: TextInput.Password
|
||||
Keys.onReturnPressed: function(event) {
|
||||
if (submitBtn.enabled) {
|
||||
submitBtn.clicked(event)
|
||||
}
|
||||
}
|
||||
onTextChanged: {
|
||||
[repeatPasswordFieldValid, repeatPasswordValidationError] =
|
||||
Utils.validatePasswords("repeat", firstPasswordField, repeatPasswordField);
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: userProfile.isKeycardUser
|
||||
text: qsTr("Enter new PIN")
|
||||
font.pixelSize: d.fontSize
|
||||
}
|
||||
|
||||
StatusPinInput {
|
||||
id: firstPinInputField
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: userProfile.isKeycardUser
|
||||
validator: StatusIntValidator{bottom: 0; top: 999999;}
|
||||
pinLen: Constants.keycard.general.keycardPinLength
|
||||
|
||||
onPinInputChanged: {
|
||||
if (pinInput.length == Constants.keycard.general.keycardPinLength) {
|
||||
repeatPinInputField.statesInitialization()
|
||||
repeatPinInputField.forceFocus()
|
||||
}
|
||||
|
||||
[firstPasswordFieldValid, passwordValidationError] =
|
||||
Utils.validatePINs("first", firstPinInputField, repeatPinInputField);
|
||||
[repeatPasswordFieldValid, repeatPasswordValidationError] =
|
||||
Utils.validatePINs("repeat", firstPinInputField, repeatPinInputField);
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: userProfile.isKeycardUser
|
||||
text: qsTr("Repeat PIN")
|
||||
font.pixelSize: d.fontSize
|
||||
}
|
||||
|
||||
StatusPinInput {
|
||||
id: repeatPinInputField
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: userProfile.isKeycardUser
|
||||
validator: StatusIntValidator{bottom: 0; top: 999999;}
|
||||
pinLen: Constants.keycard.general.keycardPinLength
|
||||
|
||||
onPinInputChanged: {
|
||||
[repeatPasswordFieldValid, repeatPasswordValidationError] =
|
||||
Utils.validatePINs("repeat", firstPinInputField, repeatPinInputField);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StyledText {
|
||||
id: validationError
|
||||
text: {
|
||||
if (passwordValidationError !== "") return passwordValidationError;
|
||||
if (repeatPasswordValidationError !== "") return repeatPasswordValidationError;
|
||||
return "";
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Style.current.danger
|
||||
font.pixelSize: 11
|
||||
}
|
||||
}
|
||||
|
||||
footer: Item {
|
||||
width: parent.width
|
||||
height: submitBtn.height
|
||||
|
||||
StatusButton {
|
||||
id: submitBtn
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.topMargin: Style.current.padding
|
||||
anchors.right: parent.right
|
||||
state: loading ? "pending" : "default"
|
||||
|
||||
text: userProfile.isKeycardUser? qsTr("Store PIN") : qsTr("Store password")
|
||||
|
||||
enabled: firstPasswordFieldValid && repeatPasswordFieldValid && !loading
|
||||
|
||||
MessageDialog {
|
||||
id: importError
|
||||
title: qsTr("Error importing account")
|
||||
text: qsTr("An error occurred while importing your account: ")
|
||||
icon: StandardIcon.Critical
|
||||
standardButtons: StandardButton.Ok
|
||||
onVisibilityChanged: {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: importLoginError
|
||||
title: qsTr("Login failed")
|
||||
text: qsTr("Login failed. Please re-enter your password and try again.")
|
||||
icon: StandardIcon.Critical
|
||||
standardButtons: StandardButton.Ok
|
||||
onVisibilityChanged: {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (!userProfile.isKeycardUser) {
|
||||
// validate the entered password
|
||||
var validatePassword = privacyStore.validatePassword(repeatPasswordField.text)
|
||||
if(!validatePassword) {
|
||||
firstPasswordFieldValid = false
|
||||
passwordValidationError = qsTr("Incorrect password")
|
||||
}
|
||||
else {
|
||||
popup.offerToStorePassword(repeatPasswordField.text, true)
|
||||
popup.close()
|
||||
}
|
||||
}
|
||||
else {
|
||||
popup.offerToStorePassword(repeatPinInputField.pinInput, true)
|
||||
popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,7 +29,11 @@ QtObject {
|
|||
return root.privacyModule.validatePassword(password)
|
||||
}
|
||||
|
||||
function storeToKeyChain(pass) {
|
||||
mainModule.storePassword(pass);
|
||||
function tryStoreToKeyChain() {
|
||||
root.privacyModule.tryStoreToKeyChain()
|
||||
}
|
||||
|
||||
function tryRemoveFromKeyChain() {
|
||||
root.privacyModule.tryRemoveFromKeyChain()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ import "../../popups"
|
|||
import "../../stores"
|
||||
import "../../controls"
|
||||
import "../../panels"
|
||||
import "../../../Onboarding/shared" as OnboardingComponents
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
@ -69,20 +68,23 @@ ColumnLayout {
|
|||
}
|
||||
|
||||
if (biometricsSwitch.checked && !biometricsSwitch.currentStoredValue)
|
||||
Global.openPopup(storePasswordModal)
|
||||
root.privacyStore.tryStoreToKeyChain()
|
||||
else if (!biometricsSwitch.checked)
|
||||
localAccountSettings.storeToKeychainValue = Constants.keychain.storedValue.never;
|
||||
root.privacyStore.tryRemoveFromKeyChain()
|
||||
|
||||
reset()
|
||||
}
|
||||
|
||||
function offerToStorePassword(password, runStoreToKeyChainPopup)
|
||||
{
|
||||
if (Qt.platform.os !== "osx")
|
||||
return;
|
||||
Connections {
|
||||
target: Qt.platform.os === Constants.mac? root.privacyStore.privacyModule : null
|
||||
|
||||
localAccountSettings.storeToKeychainValue = Constants.keychain.storedValue.store;
|
||||
root.privacyStore.storeToKeyChain(password);
|
||||
function onStoreToKeychainError(errorDescription: string) {
|
||||
root.reset()
|
||||
}
|
||||
|
||||
function onStoreToKeychainSuccess() {
|
||||
root.reset()
|
||||
}
|
||||
}
|
||||
|
||||
ProfileHeader {
|
||||
|
@ -247,15 +249,4 @@ ColumnLayout {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: storePasswordModal
|
||||
|
||||
OnboardingComponents.CreatePasswordModal {
|
||||
privacyStore: root.privacyStore
|
||||
onOfferToStorePassword: {
|
||||
root.offerToStorePassword(password, runStoreToKeychainPopup)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -659,7 +659,7 @@ QtObject {
|
|||
|
||||
readonly property string windows: "windows"
|
||||
readonly property string linux: "linux"
|
||||
readonly property string mac: "mac"
|
||||
readonly property string mac: "osx"
|
||||
|
||||
// Transaction states
|
||||
readonly property int addressRequested: 1
|
||||
|
|
Loading…
Reference in New Issue