mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-23 12:08:53 +00:00
feat(@desktop/onboarding): support (optionally) OS keychain to login password
This feature works for MacOs only, for now. On login, whether new or already created user may select between options: "Store" - store password to the Keychain "Not now" - don't store it now, but ask next time again "Never" - don't store them ever and don't ask again Selected preference may be changed later in: `ProfileSettings > Privacy and security > Store pass to Keychain` On the next app run, if `Store` was selected, a user will be asked to confirm his identity using Touch Id in order to log in the app. If any error happens he will be able to login using password. Fixes: #2675
This commit is contained in:
parent
e0c53b7012
commit
8af104a16e
2
Makefile
2
Makefile
@ -129,7 +129,7 @@ ifneq ($(detected_OS),Windows)
|
|||||||
QT5_LIBDIR := $(QTDIR)/lib
|
QT5_LIBDIR := $(QTDIR)/lib
|
||||||
# some manually installed Qt5 instances have wrong paths in their *.pc files, so we pass the right one to the linker here
|
# some manually installed Qt5 instances have wrong paths in their *.pc files, so we pass the right one to the linker here
|
||||||
ifeq ($(detected_OS),Darwin)
|
ifeq ($(detected_OS),Darwin)
|
||||||
NIM_PARAMS += -L:"-framework Foundation -framework Security -framework IOKit -framework CoreServices"
|
NIM_PARAMS += -L:"-framework Foundation -framework Security -framework IOKit -framework CoreServices -framework LocalAuthentication"
|
||||||
# Fix for failures due to 'can't allocate code signature data for'
|
# Fix for failures due to 'can't allocate code signature data for'
|
||||||
NIM_PARAMS += --passL:"-headerpad_max_install_names"
|
NIM_PARAMS += --passL:"-headerpad_max_install_names"
|
||||||
NIM_PARAMS += --passL:"-F$(QT5_LIBDIR)"
|
NIM_PARAMS += --passL:"-F$(QT5_LIBDIR)"
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
import NimQml, chronicles, options, std/wrapnils
|
import NimQml, chronicles, options, std/wrapnils
|
||||||
import status/[signals, status]
|
import status/[signals, status]
|
||||||
import status/types/[account, rpc_response]
|
import status/types/[account, rpc_response]
|
||||||
|
import ../../app_service/[main]
|
||||||
import view
|
import view
|
||||||
import eventemitter
|
import eventemitter
|
||||||
import ../../constants
|
import ../../constants
|
||||||
|
|
||||||
type LoginController* = ref object
|
type LoginController* = ref object
|
||||||
status*: Status
|
status*: Status
|
||||||
|
appService: AppService
|
||||||
view*: LoginView
|
view*: LoginView
|
||||||
variant*: QVariant
|
variant*: QVariant
|
||||||
|
|
||||||
proc newController*(status: Status): LoginController =
|
proc newController*(status: Status, appService: AppService): LoginController =
|
||||||
result = LoginController()
|
result = LoginController()
|
||||||
result.status = status
|
result.status = status
|
||||||
result.view = newLoginView(status)
|
result.appService = appService
|
||||||
|
result.view = newLoginView(status, appService)
|
||||||
result.variant = newQVariant(result.view)
|
result.variant = newQVariant(result.view)
|
||||||
|
|
||||||
proc delete*(self: LoginController) =
|
proc delete*(self: LoginController) =
|
||||||
|
@ -2,8 +2,15 @@ import NimQml, Tables, json, nimcrypto, strformat, json_serialization, chronicle
|
|||||||
import status/accounts as AccountModel
|
import status/accounts as AccountModel
|
||||||
import status/types/[account, rpc_response]
|
import status/types/[account, rpc_response]
|
||||||
import status/[status]
|
import status/[status]
|
||||||
|
import ../../app_service/[main]
|
||||||
import ../onboarding/views/account_info
|
import ../onboarding/views/account_info
|
||||||
|
|
||||||
|
const ERROR_TYPE_AUTHENTICATION = "authentication"
|
||||||
|
const ERROR_TYPE_KEYCHAIN = "keychain"
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
topics = "login-model"
|
||||||
|
|
||||||
type
|
type
|
||||||
AccountRoles {.pure.} = enum
|
AccountRoles {.pure.} = enum
|
||||||
Username = UserRole + 1
|
Username = UserRole + 1
|
||||||
@ -15,23 +22,32 @@ type
|
|||||||
QtObject:
|
QtObject:
|
||||||
type LoginView* = ref object of QAbstractListModel
|
type LoginView* = ref object of QAbstractListModel
|
||||||
status: Status
|
status: Status
|
||||||
|
appService: AppService
|
||||||
accounts: seq[NodeAccount]
|
accounts: seq[NodeAccount]
|
||||||
currentAccount*: AccountInfoView
|
currentAccount*: AccountInfoView
|
||||||
isCurrentFlow*: bool
|
isCurrentFlow*: bool
|
||||||
|
keychainManager*: StatusKeychainManager
|
||||||
|
|
||||||
proc setup(self: LoginView) =
|
proc setup(self: LoginView) =
|
||||||
self.QAbstractListModel.setup
|
self.QAbstractListModel.setup
|
||||||
|
self.keychainManager = newStatusKeychainManager("StatusDesktop", "authenticate you")
|
||||||
|
signalConnect(self.keychainManager, "success(QString)", self,
|
||||||
|
"onKeychainManagerSuccess(QString)", 2)
|
||||||
|
signalConnect(self.keychainManager, "error(QString, int, QString)", self,
|
||||||
|
"onKeychainManagerError(QString, int, QString)", 2)
|
||||||
|
|
||||||
proc delete*(self: LoginView) =
|
proc delete*(self: LoginView) =
|
||||||
self.currentAccount.delete
|
self.currentAccount.delete
|
||||||
self.accounts = @[]
|
self.accounts = @[]
|
||||||
|
self.keychainManager.delete
|
||||||
self.QAbstractListModel.delete
|
self.QAbstractListModel.delete
|
||||||
|
|
||||||
proc newLoginView*(status: Status): LoginView =
|
proc newLoginView*(status: Status, appService: AppService): LoginView =
|
||||||
new(result, delete)
|
new(result, delete)
|
||||||
result.accounts = @[]
|
result.accounts = @[]
|
||||||
result.currentAccount = newAccountInfoView()
|
result.currentAccount = newAccountInfoView()
|
||||||
result.status = status
|
result.status = status
|
||||||
|
result.appService = appService
|
||||||
result.isCurrentFlow = false
|
result.isCurrentFlow = false
|
||||||
result.setup
|
result.setup
|
||||||
|
|
||||||
@ -139,3 +155,46 @@ QtObject:
|
|||||||
read = isCurrentFlow
|
read = isCurrentFlow
|
||||||
write = setCurrentFlow
|
write = setCurrentFlow
|
||||||
notify = currentFlowChanged
|
notify = currentFlowChanged
|
||||||
|
|
||||||
|
proc storePassword*(self: LoginView, username: string, password: string) {.slot.} =
|
||||||
|
# The following check is commented out only because we maintaing a single file
|
||||||
|
# using two QSettings instances, one created in qml and one here in nim part.
|
||||||
|
# Once we move to maintain settings file only via nim part this condition need
|
||||||
|
# to be applied. The reason why it's commented out is, if you change something
|
||||||
|
# from the qml part and try in a next step to read that property from the nim
|
||||||
|
# part, that property won't be read correctly cause even 'sync' method is called
|
||||||
|
# we need to wait untill the event loop ends, cause data are flushed at regular
|
||||||
|
# intervals to the file.
|
||||||
|
# let value = self.appService.localSettingsService.getValue(
|
||||||
|
# LS_KEY_STORE_TO_KEYCHAIN).stringVal
|
||||||
|
# if (value == LS_VALUE_STORE):
|
||||||
|
|
||||||
|
if (username.len > 0):
|
||||||
|
self.keychainManager.storeDataAsync(username, password)
|
||||||
|
|
||||||
|
proc tryToObtainPassword*(self: LoginView) {.slot.} =
|
||||||
|
let value = self.appService.localSettingsService.getValue(
|
||||||
|
LS_KEY_STORE_TO_KEYCHAIN).stringVal
|
||||||
|
if (value == LS_VALUE_STORE):
|
||||||
|
self.keychainManager.readDataAsync(self.currentAccount.username)
|
||||||
|
|
||||||
|
proc obtainingPasswordError*(self:LoginView, errorDescription: string) {.signal.}
|
||||||
|
proc obtainingPasswordSuccess*(self:LoginView, password: string) {.signal.}
|
||||||
|
|
||||||
|
proc onKeychainManagerError*(self: LoginView, errType: string, code: int,
|
||||||
|
errorDescription: string) {.slot.} =
|
||||||
|
## This slot is called in case an error occured while we're dealing with
|
||||||
|
## KeychainManager. So far we're just logging the error.
|
||||||
|
info "KeychainManager stopped: ", msg = code, errorDescription
|
||||||
|
if (errType == ERROR_TYPE_AUTHENTICATION):
|
||||||
|
return
|
||||||
|
|
||||||
|
# We are notifying user only about keychain errors.
|
||||||
|
self.appService.localSettingsService.removeValue(LS_KEY_STORE_TO_KEYCHAIN)
|
||||||
|
self.obtainingPasswordError(errorDescription)
|
||||||
|
|
||||||
|
proc onKeychainManagerSuccess*(self: LoginView, data: string) {.slot.} =
|
||||||
|
## This slot is called in case a password is successfully retrieved from the
|
||||||
|
## Keychain. In this case @data contains required password.
|
||||||
|
self.obtainingPasswordSuccess(data)
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import NimQml, Tables, json, nimcrypto, strformat, json_serialization, strutils
|
import NimQml, Tables, json, nimcrypto, strformat, json_serialization, strutils
|
||||||
import status/accounts as AccountModel
|
import status/accounts as AccountModel
|
||||||
import status/[status, wallet]
|
import status/[status, wallet]
|
||||||
import status/types/[account, rpc_response]
|
import status/types/[rpc_response]
|
||||||
|
import status/types/account as status_account_type
|
||||||
import views/account_info
|
import views/account_info
|
||||||
|
|
||||||
type
|
type
|
||||||
@ -99,8 +100,13 @@ QtObject:
|
|||||||
result = self.status.wallet.validateMnemonic(mnemonic.strip())
|
result = self.status.wallet.validateMnemonic(mnemonic.strip())
|
||||||
|
|
||||||
proc storeDerivedAndLogin(self: OnboardingView, password: string): string {.slot.} =
|
proc storeDerivedAndLogin(self: OnboardingView, password: string): string {.slot.} =
|
||||||
|
# In this moment we're sure that new account will be logged in, and emit signal.
|
||||||
|
let genAcc = self.currentAccount.account
|
||||||
|
let acc = Account(name: genAcc.name, keyUid: genAcc.keyUid, identicon: genAcc.identicon, identityImage: genAcc.identityImage)
|
||||||
|
self.status.events.emit("currentAccountUpdated", status_account_type.AccountArgs(account: acc))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = self.status.accounts.storeDerivedAndLogin(self.status.fleet.config, self.currentAccount.account, password).toJson
|
result = self.status.accounts.storeDerivedAndLogin(self.status.fleet.config, genAcc, password).toJson
|
||||||
except StatusGoException as e:
|
except StatusGoException as e:
|
||||||
var msg = e.msg
|
var msg = e.msg
|
||||||
if e.msg.contains("account already exists"):
|
if e.msg.contains("account already exists"):
|
||||||
|
@ -5,6 +5,7 @@ import QtGraphicalEffects 1.13
|
|||||||
import "../../../../imports"
|
import "../../../../imports"
|
||||||
import "../../../../shared"
|
import "../../../../shared"
|
||||||
import "../../../../shared/status"
|
import "../../../../shared/status"
|
||||||
|
import "../../../../onboarding/" as OnboardingComponents
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: privacyContainer
|
id: privacyContainer
|
||||||
@ -45,6 +46,35 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatusSettingsLineButton {
|
||||||
|
text: qsTr("Store pass to Keychain")
|
||||||
|
visible: Qt.platform.os == "osx" // For now, this is available only on MacOS
|
||||||
|
currentValue: {
|
||||||
|
let value = appSettings.storeToKeychain
|
||||||
|
if(value == Constants.storeToKeychainValueStore)
|
||||||
|
return qsTr("Store")
|
||||||
|
|
||||||
|
if(value == Constants.storeToKeychainValueNever)
|
||||||
|
return qsTr("Never")
|
||||||
|
|
||||||
|
return qsTr("Not now")
|
||||||
|
}
|
||||||
|
onClicked: openPopup(storeToKeychainSelectionModal)
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: storePasswordModal
|
||||||
|
OnboardingComponents.CreatePasswordModal {
|
||||||
|
storingPasswordModal: true
|
||||||
|
height: 350
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: storeToKeychainSelectionModal
|
||||||
|
StoreToKeychainSelectionModal {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BackupSeedModal {
|
BackupSeedModal {
|
||||||
id: backupSeedModal
|
id: backupSeedModal
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
import QtQuick 2.13
|
||||||
|
import QtQuick.Controls 2.13
|
||||||
|
import "../../../../imports"
|
||||||
|
import "../../../../shared"
|
||||||
|
import "../../../../shared/status"
|
||||||
|
|
||||||
|
ModalPopup {
|
||||||
|
id: popup
|
||||||
|
|
||||||
|
title: qsTr("Store pass to Keychain")
|
||||||
|
|
||||||
|
onClosed: {
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Style.current.padding
|
||||||
|
anchors.leftMargin: Style.current.padding
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
ButtonGroup {
|
||||||
|
id: openLinksWithGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusRadioButtonRow {
|
||||||
|
text: qsTr("Store")
|
||||||
|
buttonGroup: openLinksWithGroup
|
||||||
|
checked: appSettings.storeToKeychain === Constants.storeToKeychainValueStore
|
||||||
|
onRadioCheckedChanged: {
|
||||||
|
if (checked && appSettings.storeToKeychain !== Constants.storeToKeychainValueStore) {
|
||||||
|
var storePassPopup = openPopup(storePasswordModal)
|
||||||
|
if(storePassPopup)
|
||||||
|
{
|
||||||
|
storePassPopup.closed.connect(function(){
|
||||||
|
if (appSettings.storeToKeychain === Constants.storeToKeychainValueStore)
|
||||||
|
popup.close()
|
||||||
|
else if (appSettings.storeToKeychain === Constants.storeToKeychainValueNotNow)
|
||||||
|
notNowBtn.checked = true
|
||||||
|
else if (appSettings.storeToKeychain === Constants.storeToKeychainValueNever)
|
||||||
|
neverBtn.checked = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusRadioButtonRow {
|
||||||
|
id: notNowBtn
|
||||||
|
text: qsTr("Not now")
|
||||||
|
buttonGroup: openLinksWithGroup
|
||||||
|
checked: appSettings.storeToKeychain === Constants.storeToKeychainValueNotNow ||
|
||||||
|
appSettings.storeToKeychain === ""
|
||||||
|
onRadioCheckedChanged: {
|
||||||
|
if (checked) {
|
||||||
|
appSettings.storeToKeychain = Constants.storeToKeychainValueNotNow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusRadioButtonRow {
|
||||||
|
id: neverBtn
|
||||||
|
text: qsTr("Never")
|
||||||
|
buttonGroup: openLinksWithGroup
|
||||||
|
checked: appSettings.storeToKeychain === Constants.storeToKeychainValueNever
|
||||||
|
onRadioCheckedChanged: {
|
||||||
|
if (checked) {
|
||||||
|
appSettings.storeToKeychain = Constants.storeToKeychainValueNever
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -163,6 +163,10 @@ QtObject {
|
|||||||
readonly property string ens_connected: "connected"
|
readonly property string ens_connected: "connected"
|
||||||
readonly property string ens_connected_dkey: "connected-different-key"
|
readonly property string ens_connected_dkey: "connected-different-key"
|
||||||
|
|
||||||
|
readonly property string storeToKeychainValueStore: "store"
|
||||||
|
readonly property string storeToKeychainValueNotNow: "notNow"
|
||||||
|
readonly property string storeToKeychainValueNever: "never"
|
||||||
|
|
||||||
//% "(edited)"
|
//% "(edited)"
|
||||||
readonly property string editLabel: ` <span class="isEdited">` + qsTrId("-edited-") + `</span>`
|
readonly property string editLabel: ` <span class="isEdited">` + qsTrId("-edited-") + `</span>`
|
||||||
|
|
||||||
|
55
ui/main.qml
55
ui/main.qml
@ -41,6 +41,7 @@ StatusWindow {
|
|||||||
Settings {
|
Settings {
|
||||||
id: appSettings
|
id: appSettings
|
||||||
fileName: profileModel.settingsFile
|
fileName: profileModel.settingsFile
|
||||||
|
property string storeToKeychain: ""
|
||||||
|
|
||||||
property var chatSplitView
|
property var chatSplitView
|
||||||
property var walletSplitView
|
property var walletSplitView
|
||||||
@ -277,6 +278,60 @@ StatusWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkForStoringPassToKeychain(username, password, clearStoredValue) {
|
||||||
|
if(Qt.platform.os == "osx")
|
||||||
|
{
|
||||||
|
if(clearStoredValue)
|
||||||
|
{
|
||||||
|
appSettings.storeToKeychain = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if(appSettings.storeToKeychain === "" ||
|
||||||
|
appSettings.storeToKeychain === Constants.storeToKeychainValueNotNow)
|
||||||
|
{
|
||||||
|
storeToKeychainConfirmationPopup.password = password
|
||||||
|
storeToKeychainConfirmationPopup.username = username
|
||||||
|
storeToKeychainConfirmationPopup.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmationDialog {
|
||||||
|
id: storeToKeychainConfirmationPopup
|
||||||
|
property string password: ""
|
||||||
|
property string username: ""
|
||||||
|
height: 200
|
||||||
|
confirmationText: qsTr("Would you like to store password to the Keychain?")
|
||||||
|
showRejectButton: true
|
||||||
|
showCancelButton: true
|
||||||
|
confirmButtonLabel: qsTr("Store")
|
||||||
|
rejectButtonLabel: qsTr("Not now")
|
||||||
|
cancelButtonLabel: qsTr("Never")
|
||||||
|
|
||||||
|
function finish()
|
||||||
|
{
|
||||||
|
password = ""
|
||||||
|
username = ""
|
||||||
|
storeToKeychainConfirmationPopup.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfirmButtonClicked: {
|
||||||
|
appSettings.storeToKeychain = Constants.storeToKeychainValueStore
|
||||||
|
loginModel.storePassword(username, password)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
onRejectButtonClicked: {
|
||||||
|
appSettings.storeToKeychain = Constants.storeToKeychainValueNotNow
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancelButtonClicked: {
|
||||||
|
appSettings.storeToKeychain = Constants.storeToKeychainValueNever
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DSM.StateMachine {
|
DSM.StateMachine {
|
||||||
id: stateMachine
|
id: stateMachine
|
||||||
initialState: onboardingState
|
initialState: onboardingState
|
||||||
|
@ -11,10 +11,13 @@ ModalPopup {
|
|||||||
property bool repeatPasswordFieldValid: false
|
property bool repeatPasswordFieldValid: false
|
||||||
property string passwordValidationError: ""
|
property string passwordValidationError: ""
|
||||||
property string repeatPasswordValidationError: ""
|
property string repeatPasswordValidationError: ""
|
||||||
|
property bool storingPasswordModal: false
|
||||||
|
|
||||||
id: popup
|
id: popup
|
||||||
|
title: storingPasswordModal?
|
||||||
|
qsTr("Store password") :
|
||||||
//% "Create a password"
|
//% "Create a password"
|
||||||
title: qsTrId("intro-wizard-title-alt4")
|
qsTrId("intro-wizard-title-alt4")
|
||||||
height: 500
|
height: 500
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
@ -27,9 +30,11 @@ ModalPopup {
|
|||||||
anchors.rightMargin: 56
|
anchors.rightMargin: 56
|
||||||
anchors.leftMargin: 56
|
anchors.leftMargin: 56
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: 88
|
anchors.topMargin: storingPasswordModal? Style.current.xlPadding : 88
|
||||||
|
placeholderText: storingPasswordModal?
|
||||||
|
qsTr("Current password...") :
|
||||||
//% "New password..."
|
//% "New password..."
|
||||||
placeholderText: qsTrId("new-password...")
|
qsTrId("new-password...")
|
||||||
textField.echoMode: TextInput.Password
|
textField.echoMode: TextInput.Password
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
[firstPasswordFieldValid, passwordValidationError] =
|
[firstPasswordFieldValid, passwordValidationError] =
|
||||||
@ -79,6 +84,7 @@ ModalPopup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
|
visible: !storingPasswordModal
|
||||||
//% "At least 6 characters. You will use this password to unlock status on this device & sign transactions."
|
//% "At least 6 characters. You will use this password to unlock status on this device & sign transactions."
|
||||||
text: qsTrId("at-least-6-characters-you-will-use-this-password-to-unlock-status-on-this-device-sign-transactions.")
|
text: qsTrId("at-least-6-characters-you-will-use-this-password-to-unlock-status-on-this-device-sign-transactions.")
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
@ -103,8 +109,11 @@ ModalPopup {
|
|||||||
anchors.topMargin: Style.current.padding
|
anchors.topMargin: Style.current.padding
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
state: loading ? "pending" : "default"
|
state: loading ? "pending" : "default"
|
||||||
|
|
||||||
|
text: storingPasswordModal?
|
||||||
|
qsTr("Store password") :
|
||||||
//% "Create password"
|
//% "Create password"
|
||||||
text: qsTrId("create-password")
|
qsTrId("create-password")
|
||||||
|
|
||||||
enabled: firstPasswordFieldValid && repeatPasswordFieldValid && !loading
|
enabled: firstPasswordFieldValid && repeatPasswordFieldValid && !loading
|
||||||
|
|
||||||
@ -146,6 +155,14 @@ ModalPopup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
if (storingPasswordModal)
|
||||||
|
{
|
||||||
|
appSettings.storeToKeychain = Constants.storeToKeychainValueStore
|
||||||
|
loginModel.storePassword(profileModel.profile.username, repeatPasswordField.text)
|
||||||
|
popup.close()
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
loading = true
|
loading = true
|
||||||
loginModel.isCurrentFlow = false;
|
loginModel.isCurrentFlow = false;
|
||||||
onboardingModel.isCurrentFlow = true;
|
onboardingModel.isCurrentFlow = true;
|
||||||
@ -156,13 +173,11 @@ ModalPopup {
|
|||||||
return importError.open()
|
return importError.open()
|
||||||
}
|
}
|
||||||
onboardingModel.firstTimeLogin = true
|
onboardingModel.firstTimeLogin = true
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*##^##
|
applicationWindow.checkForStoringPassToKeychain(onboardingModel.currentAccount.username,
|
||||||
Designer {
|
repeatPasswordField.text, true)
|
||||||
D{i:0;formeditorColor:"#ffffff";height:500;width:400}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
##^##*/
|
|
||||||
|
@ -21,9 +21,64 @@ Item {
|
|||||||
onboardingModel.isCurrentFlow = !isLogin;
|
onboardingModel.isCurrentFlow = !isLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
function doLogin(password) {
|
||||||
|
if (loading || password.length === 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
setCurrentFlow(true);
|
||||||
|
loading = true
|
||||||
|
loginModel.login(password)
|
||||||
|
applicationWindow.checkForStoringPassToKeychain(loginModel.currentAccount.username, password, false)
|
||||||
|
txtPassword.textField.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetLogin() {
|
||||||
|
if(appSettings.storeToKeychain === Constants.storeToKeychainValueStore)
|
||||||
|
{
|
||||||
|
connection.enabled = true
|
||||||
|
txtPassword.visible = false
|
||||||
|
loginModel.tryToObtainPassword()
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
txtPassword.visible = true
|
||||||
txtPassword.forceActiveFocus(Qt.MouseFocusReason)
|
txtPassword.forceActiveFocus(Qt.MouseFocusReason)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
resetLogin()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections{
|
||||||
|
id: connection
|
||||||
|
target: loginModel
|
||||||
|
|
||||||
|
onObtainingPasswordError: {
|
||||||
|
enabled = false
|
||||||
|
obtainingPasswordErrorNotification.confirmationText = errorDescription
|
||||||
|
obtainingPasswordErrorNotification.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
onObtainingPasswordSuccess: {
|
||||||
|
enabled = false
|
||||||
|
doLogin(password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmationDialog {
|
||||||
|
id: obtainingPasswordErrorNotification
|
||||||
|
height: 270
|
||||||
|
confirmButtonLabel: qsTr("Ok")
|
||||||
|
|
||||||
|
onConfirmButtonClicked: {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: {
|
||||||
|
txtPassword.visible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: element
|
id: element
|
||||||
@ -60,6 +115,7 @@ Item {
|
|||||||
id: selectAnotherAccountModal
|
id: selectAnotherAccountModal
|
||||||
onAccountSelect: function (index) {
|
onAccountSelect: function (index) {
|
||||||
loginModel.setCurrentAccount(index)
|
loginModel.setCurrentAccount(index)
|
||||||
|
resetLogin()
|
||||||
}
|
}
|
||||||
onOpenModalClick: function () {
|
onOpenModalClick: function () {
|
||||||
setCurrentFlow(true);
|
setCurrentFlow(true);
|
||||||
@ -127,7 +183,7 @@ Item {
|
|||||||
textField.echoMode: TextInput.Password
|
textField.echoMode: TextInput.Password
|
||||||
textField.focus: true
|
textField.focus: true
|
||||||
Keys.onReturnPressed: {
|
Keys.onReturnPressed: {
|
||||||
submitBtn.clicked()
|
doLogin(textField.text)
|
||||||
}
|
}
|
||||||
onTextEdited: {
|
onTextEdited: {
|
||||||
errMsg.visible = false
|
errMsg.visible = false
|
||||||
@ -143,18 +199,12 @@ Item {
|
|||||||
icon.width: 18
|
icon.width: 18
|
||||||
icon.height: 14
|
icon.height: 14
|
||||||
opacity: (loading || txtPassword.text.length > 0) ? 1 : 0
|
opacity: (loading || txtPassword.text.length > 0) ? 1 : 0
|
||||||
anchors.left: txtPassword.right
|
anchors.left: txtPassword.visible? txtPassword.right : changeAccountBtn.right
|
||||||
anchors.leftMargin: (loading || txtPassword.text.length > 0) ? Style.current.padding : Style.current.smallPadding
|
anchors.leftMargin: (loading || txtPassword.text.length > 0) ? Style.current.padding : Style.current.smallPadding
|
||||||
anchors.verticalCenter: txtPassword.verticalCenter
|
anchors.verticalCenter: txtPassword.visible? txtPassword.verticalCenter : changeAccountBtn.verticalCenter
|
||||||
state: loading ? "pending" : "default"
|
state: loading ? "pending" : "default"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (loading) {
|
doLogin(txtPassword.textField.text)
|
||||||
return;
|
|
||||||
}
|
|
||||||
setCurrentFlow(true);
|
|
||||||
loading = true
|
|
||||||
loginModel.login(txtPassword.textField.text)
|
|
||||||
txtPassword.textField.clear()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.figma.com/file/BTS422M9AkvWjfRrXED3WC/%F0%9F%91%8B-Onboarding%E2%8E%9CDesktop?node-id=6%3A0
|
// https://www.figma.com/file/BTS422M9AkvWjfRrXED3WC/%F0%9F%91%8B-Onboarding%E2%8E%9CDesktop?node-id=6%3A0
|
||||||
@ -194,7 +244,7 @@ Item {
|
|||||||
id: generateKeysLinkText
|
id: generateKeysLinkText
|
||||||
//% "Generate new keys"
|
//% "Generate new keys"
|
||||||
text: qsTrId("generate-new-keys")
|
text: qsTrId("generate-new-keys")
|
||||||
anchors.top: txtPassword.bottom
|
anchors.top: txtPassword.visible? txtPassword.bottom : changeAccountBtn.bottom
|
||||||
anchors.topMargin: 16
|
anchors.topMargin: 16
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
font.pixelSize: 13
|
font.pixelSize: 13
|
||||||
@ -219,9 +269,3 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*##^##
|
|
||||||
Designer {
|
|
||||||
D{i:0;autoSize:true;formeditorColor:"#ffffff";formeditorZoom:0.75;height:480;width:640}
|
|
||||||
}
|
|
||||||
##^##*/
|
|
||||||
|
@ -14,11 +14,14 @@ StatusModal {
|
|||||||
property Popup parentPopup
|
property Popup parentPopup
|
||||||
property var value
|
property var value
|
||||||
property var executeConfirm
|
property var executeConfirm
|
||||||
|
property var executeReject
|
||||||
property var executeCancel
|
property var executeCancel
|
||||||
property string btnType: "warn"
|
property string btnType: "warn"
|
||||||
property string confirmButtonLabel: qsTr("Confirm")
|
property string confirmButtonLabel: qsTr("Confirm")
|
||||||
|
property string rejectButtonLabel: qsTr("Reject")
|
||||||
property string cancelButtonLabel: qsTr("Cancel")
|
property string cancelButtonLabel: qsTr("Cancel")
|
||||||
property string confirmationText: qsTr("Are you sure you want to do this?")
|
property string confirmationText: qsTr("Are you sure you want to do this?")
|
||||||
|
property bool showRejectButton: false
|
||||||
property bool showCancelButton: false
|
property bool showCancelButton: false
|
||||||
property alias checkbox: checkbox
|
property alias checkbox: checkbox
|
||||||
|
|
||||||
@ -27,6 +30,7 @@ StatusModal {
|
|||||||
focus: visible
|
focus: visible
|
||||||
|
|
||||||
signal confirmButtonClicked()
|
signal confirmButtonClicked()
|
||||||
|
signal rejectButtonClicked()
|
||||||
signal cancelButtonClicked()
|
signal cancelButtonClicked()
|
||||||
|
|
||||||
|
|
||||||
@ -83,6 +87,16 @@ StatusModal {
|
|||||||
confirmationDialog.cancelButtonClicked()
|
confirmationDialog.cancelButtonClicked()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
StatusFlatButton {
|
||||||
|
visible: showRejectButton
|
||||||
|
text: confirmationDialog.rejectButtonLabel
|
||||||
|
onClicked: {
|
||||||
|
if (executeReject && typeof executeReject === "function") {
|
||||||
|
executeReject()
|
||||||
|
}
|
||||||
|
confirmationDialog.rejectButtonClicked()
|
||||||
|
}
|
||||||
|
},
|
||||||
StatusButton {
|
StatusButton {
|
||||||
id: confirmButton
|
id: confirmButton
|
||||||
type: {
|
type: {
|
||||||
|
@ -7,7 +7,7 @@ import "."
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
property alias text: textElement.text
|
property alias text: textElement.text
|
||||||
property var buttonGroup
|
property var buttonGroup
|
||||||
property bool checked: false
|
property alias checked: radioButton.checked
|
||||||
property bool isHovered: false
|
property bool isHovered: false
|
||||||
signal radioCheckedChanged(checked: bool)
|
signal radioCheckedChanged(checked: bool)
|
||||||
|
|
||||||
|
2
vendor/DOtherSide
vendored
2
vendor/DOtherSide
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 0f8ed95fc7a47e4d3efb218ef961d36e60610cb3
|
Subproject commit b1e4d3b68629a101e21cfdfd448ef6f54364f235
|
2
vendor/nimqml
vendored
2
vendor/nimqml
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 00ee27ca52bcf5216c0de0e19e594ddfc1790452
|
Subproject commit 4351b9a61f7ff2b6798cadd4151b34b3c0670a56
|
Loading…
x
Reference in New Issue
Block a user