2022-03-01 15:59:38 +00:00
import QtQuick 2.14
import QtQuick . Controls 2.14
import QtQuick . Layouts 1.12
import shared . panels 1.0
import shared . controls 1.0
2022-03-07 22:59:38 +00:00
import shared . stores 1.0
2022-03-01 15:59:38 +00:00
import utils 1.0
import StatusQ . Controls 0.1
import StatusQ . Core 0.1
import StatusQ . Core . Theme 0.1
import StatusQ . Components 0.1
2023-04-02 18:49:43 +00:00
2022-10-11 13:58:34 +00:00
ColumnLayout {
2022-03-01 15:59:38 +00:00
id: root
2022-11-21 07:30:11 +00:00
property bool ready: newPswInput . text . length >= Constants . minPasswordLength && newPswInput . text === confirmPswInput . text && errorTxt . text === ""
2022-03-01 15:59:38 +00:00
property bool createNewPsw: true
property string title: qsTr ( "Create a password" )
property bool titleVisible: true
property string introText: qsTr ( "Create a password to unlock Status on this device & sign transactions." )
property string recoverText: qsTr ( "You will not be able to recover this password if it is lost." )
2022-11-21 07:30:11 +00:00
property string strengthenText: qsTr ( "Minimum %n character(s). To strengthen your password consider including:" , "" , Constants . minPasswordLength )
2022-10-11 13:58:34 +00:00
property bool highSizeIntro: false
2022-07-20 12:34:44 +00:00
property var passwordStrengthScoreFunction: function ( ) { }
2022-03-22 09:29:59 +00:00
2022-03-01 15:59:38 +00:00
readonly property int zBehind: 1
readonly property int zFront: 100
property alias currentPswText: currentPswInput . text
property alias newPswText: newPswInput . text
property alias confirmationPswText: confirmPswInput . text
property alias errorMsgText: errorTxt . text
2022-05-06 12:47:46 +00:00
signal returnPressed ( )
2022-03-01 15:59:38 +00:00
function forceNewPswInputFocus ( ) { newPswInput . forceActiveFocus ( Qt . MouseFocusReason ) }
function reset ( ) {
newPswInput . text = ""
currentPswInput . text = ""
confirmPswInput . text = ""
errorTxt . text = ""
strengthInditactor . strength = StatusPasswordStrengthIndicator . Strength . None
// Update focus:
if ( root . createNewPsw )
newPswInput . forceActiveFocus ( Qt . MouseFocusReason )
else
currentPswInput . forceActiveFocus ( Qt . MouseFocusReason )
}
2022-05-11 08:10:31 +00:00
function checkPasswordMatches ( onlyIfConfirmPasswordHasFocus = true ) {
2022-10-11 13:58:34 +00:00
if ( confirmPswInput . text . length === 0 ) {
2022-05-11 11:33:07 +00:00
errorTxt . text = ""
return
}
2022-10-11 13:58:34 +00:00
if ( onlyIfConfirmPasswordHasFocus && ! confirmPswInput . focus ) {
2022-05-11 08:10:31 +00:00
return
}
2022-11-21 07:30:11 +00:00
if ( newPswInput . text . length >= Constants . minPasswordLength ) {
2022-03-01 15:59:38 +00:00
errorTxt . text = ""
if ( confirmPswInput . text !== newPswInput . text ) {
errorTxt . text = qsTr ( "Passwords don't match" )
}
}
}
QtObject {
id: d
property bool containsLower: false
property bool containsUpper: false
property bool containsNumbers: false
property bool containsSymbols: false
2022-03-22 09:29:59 +00:00
readonly property var validator: RegExpValidator { regExp: /^[!-~]{0,64}$/ } // That incudes NOT extended ASCII printable characters less space and a maximum of 64 characters allowed
2022-03-01 15:59:38 +00:00
// Password strength categorization / validation
function lowerCaseValidator ( text ) { return ( /[a-z]/ . test ( text ) ) }
function upperCaseValidator ( text ) { return ( /[A-Z]/ . test ( text ) ) }
function numbersValidator ( text ) { return ( /\d/ . test ( text ) ) }
// That incudes NOT extended ASCII printable symbols less space:
function symbolsValidator ( text ) { return ( /[!-\/:-@[-`{-~]/ . test ( text ) ) }
2022-03-22 09:29:59 +00:00
// Used to convert strength from a given score to a specific category
function convertStrength ( score ) {
var strength = StatusPasswordStrengthIndicator . Strength . None
switch ( score ) {
2022-05-23 12:36:23 +00:00
case 0 : strength = StatusPasswordStrengthIndicator . Strength . VeryWeak ; break
case 1 : strength = StatusPasswordStrengthIndicator . Strength . Weak ; break
case 2 : strength = StatusPasswordStrengthIndicator . Strength . SoSo ; break
case 3 : strength = StatusPasswordStrengthIndicator . Strength . Good ; break
case 4 : strength = StatusPasswordStrengthIndicator . Strength . Great ; break
2022-03-22 09:29:59 +00:00
}
if ( strength > 4 )
strength = StatusPasswordStrengthIndicator . Strength . Great
return strength
2022-03-01 15:59:38 +00:00
}
// Password validation / error message selection:
function passwordValidation ( ) {
errorTxt . text = ""
// 3 rules to validate:
// * Password is in pwnd passwords database
if ( isInPwndDatabase ( ) )
errorTxt . text = qsTr ( "This password has been pwned and shouldn't be used" )
// * Common password
else if ( isCommonPassword ( ) )
errorTxt . text = qsTr ( "This password is a common word and shouldn't be used" )
// * Password too short
else if ( isTooShort ( ) )
2022-11-21 07:30:11 +00:00
errorTxt . text = qsTr ( "Password must be at least %n character(s) long" , "" , Constants . minPasswordLength )
2022-03-01 15:59:38 +00:00
}
function isInPwndDatabase ( ) {
// "TODO - Nice To Have: Pwnd password validation NOT implemented yet! "
return false
}
function isCommonPassword ( ) {
// "TODO - Nice To Have: Common password validation NOT implemented yet! "
return false
}
2022-11-21 07:30:11 +00:00
function isTooShort ( ) { return newPswInput . text . length < Constants . minPasswordLength }
2022-03-01 15:59:38 +00:00
}
2022-10-11 13:58:34 +00:00
spacing: Style . current . bigPadding
2022-03-01 15:59:38 +00:00
z: root . zFront
// View visual content:
StatusBaseText {
id: title
2022-10-11 13:58:34 +00:00
Layout.alignment: Qt . AlignHCenter
2022-03-01 15:59:38 +00:00
visible: root . titleVisible
text: root . title
font.pixelSize: 22
font.bold: true
color: Theme . palette . directColor1
}
2022-10-11 13:58:34 +00:00
StatusBaseText {
id: introTxtField
Layout.fillWidth: true
text: "%1 <font color=\"%2\">%3</font>" . arg ( root . introText ) . arg ( Theme . palette . dangerColor1 ) . arg ( root . recoverText )
font.pixelSize: root . highSizeIntro ? 15 : 12
color: Theme . palette . baseColor1
wrapMode: Text . WordWrap
horizontalAlignment: TextEdit . AlignHCenter
2022-03-01 15:59:38 +00:00
}
2022-10-11 13:58:34 +00:00
StatusPasswordInput {
2022-03-01 15:59:38 +00:00
id: currentPswInput
2022-10-11 13:58:34 +00:00
objectName: "passwordViewCurrentPassword"
2022-03-01 15:59:38 +00:00
property bool showPassword
z: root . zFront
visible: ! root . createNewPsw
2022-10-11 13:58:34 +00:00
Layout.fillWidth: true
2022-03-01 15:59:38 +00:00
placeholderText: qsTr ( "Current password" )
2022-10-11 13:58:34 +00:00
echoMode: showPassword ? TextInput.Normal : TextInput . Password
validator: d . validator
rightPadding: showHideCurrentIcon . width + showHideCurrentIcon . anchors . rightMargin + Style . current . padding / 2
2023-04-02 18:49:43 +00:00
onAccepted: root . returnPressed ( )
2022-03-01 15:59:38 +00:00
StatusFlatRoundButton {
id: showHideCurrentIcon
visible: currentPswInput . text !== ""
anchors.verticalCenter: parent . verticalCenter
anchors.right: parent . right
anchors.rightMargin: 16
width: 24
height: 24
icon.name: currentPswInput . showPassword ? "hide" : "show"
icon.color: Theme . palette . baseColor1
onClicked: currentPswInput . showPassword = ! currentPswInput . showPassword
}
}
2022-10-11 13:58:34 +00:00
ColumnLayout {
2022-03-01 15:59:38 +00:00
spacing: Style . current . padding / 2
z: root . zFront
2022-10-11 13:58:34 +00:00
Layout.fillWidth: true
2022-03-01 15:59:38 +00:00
2022-10-11 13:58:34 +00:00
StatusPasswordInput {
2022-03-01 15:59:38 +00:00
id: newPswInput
2022-10-11 13:58:34 +00:00
objectName: "passwordViewNewPassword"
2022-03-01 15:59:38 +00:00
property bool showPassword
2022-10-11 13:58:34 +00:00
Layout.fillWidth: true
2022-03-01 15:59:38 +00:00
placeholderText: qsTr ( "New password" )
2022-10-11 13:58:34 +00:00
echoMode: showPassword ? TextInput.Normal : TextInput . Password
validator: d . validator
rightPadding: showHideNewIcon . width + showHideNewIcon . anchors . rightMargin + Style . current . padding / 2
2022-03-01 15:59:38 +00:00
onTextChanged: {
// Update password checkers
d . containsLower = d . lowerCaseValidator ( text )
d . containsUpper = d . upperCaseValidator ( text )
d . containsNumbers = d . numbersValidator ( text )
d . containsSymbols = d . symbolsValidator ( text )
// Update strength indicator:
2022-07-20 12:34:44 +00:00
strengthInditactor . strength = d . convertStrength ( root . passwordStrengthScoreFunction ( newPswInput . text ) )
2022-10-11 13:58:34 +00:00
if ( text . length === confirmPswInput . text . length ) {
2022-05-23 12:36:23 +00:00
root . checkPasswordMatches ( false )
}
2022-03-01 15:59:38 +00:00
}
2023-04-02 18:49:43 +00:00
onAccepted: root . returnPressed ( )
2022-03-01 15:59:38 +00:00
StatusFlatRoundButton {
id: showHideNewIcon
visible: newPswInput . text !== ""
anchors.verticalCenter: parent . verticalCenter
anchors.right: parent . right
anchors.rightMargin: 16
width: 24
height: 24
icon.name: newPswInput . showPassword ? "hide" : "show"
icon.color: Theme . palette . baseColor1
onClicked: newPswInput . showPassword = ! newPswInput . showPassword
}
}
StatusPasswordStrengthIndicator {
id: strengthInditactor
2022-10-11 13:58:34 +00:00
Layout.fillWidth: true
2022-11-21 07:30:11 +00:00
value: Math . min ( Constants . minPasswordLength , newPswInput . text . length )
2022-03-01 15:59:38 +00:00
from: 0
2022-11-21 07:30:11 +00:00
to: Constants . minPasswordLength
2022-03-01 15:59:38 +00:00
labelVeryWeak: qsTr ( "Very weak" )
labelWeak: qsTr ( "Weak" )
labelSoso: qsTr ( "So-so" )
labelGood: qsTr ( "Good" )
labelGreat: qsTr ( "Great" )
}
}
StatusBaseText {
id: strengthenTxt
2022-10-11 13:58:34 +00:00
Layout.fillWidth: true
Layout.alignment: Qt . AlignHCenter
wrapMode: Text . WordWrap
2022-03-01 15:59:38 +00:00
text: root . strengthenText
font.pixelSize: 12
color: Theme . palette . baseColor1
2022-10-11 13:58:34 +00:00
clip: true
2022-03-01 15:59:38 +00:00
}
2022-10-11 13:58:34 +00:00
RowLayout {
2022-03-01 15:59:38 +00:00
spacing: Style . current . padding
2022-10-11 13:58:34 +00:00
Layout.alignment: Qt . AlignHCenter
2022-03-01 15:59:38 +00:00
StatusBaseText {
id: lowerCaseTxt
text: "• " + qsTr ( "Lower case" )
font.pixelSize: 12
color: d . containsLower ? Theme.palette.successColor1 : Theme . palette . baseColor1
}
StatusBaseText {
id: upperCaseTxt
text: "• " + qsTr ( "Upper case" )
font.pixelSize: 12
color: d . containsUpper ? Theme.palette.successColor1 : Theme . palette . baseColor1
}
StatusBaseText {
id: numbersTxt
text: "• " + qsTr ( "Numbers" )
font.pixelSize: 12
color: d . containsNumbers ? Theme.palette.successColor1 : Theme . palette . baseColor1
}
StatusBaseText {
id: symbolsTxt
text: "• " + qsTr ( "Symbols" )
font.pixelSize: 12
color: d . containsSymbols ? Theme.palette.successColor1 : Theme . palette . baseColor1
}
}
2022-10-11 13:58:34 +00:00
StatusPasswordInput {
2022-03-01 15:59:38 +00:00
id: confirmPswInput
2022-10-11 13:58:34 +00:00
objectName: "passwordViewNewPasswordConfirm"
2022-03-01 15:59:38 +00:00
property bool showPassword
z: root . zFront
2022-10-11 13:58:34 +00:00
Layout.fillWidth: true
2022-03-01 15:59:38 +00:00
placeholderText: qsTr ( "Confirm password" )
2022-10-11 13:58:34 +00:00
echoMode: showPassword ? TextInput.Normal : TextInput . Password
validator: d . validator
rightPadding: showHideConfirmIcon . width + showHideConfirmIcon . anchors . rightMargin + Style . current . padding / 2
2022-03-01 15:59:38 +00:00
2022-05-11 11:33:07 +00:00
onTextChanged: {
2022-05-18 14:30:00 +00:00
d . passwordValidation ( ) ;
2022-10-11 13:58:34 +00:00
if ( text . length === newPswInput . text . length ) {
2022-05-11 11:33:07 +00:00
root . checkPasswordMatches ( )
}
}
2022-10-11 13:58:34 +00:00
onFocusChanged: {
2022-03-01 15:59:38 +00:00
// When clicking into the confirmation input, validate if new password:
2022-10-11 13:58:34 +00:00
if ( focus ) {
2022-05-11 08:10:31 +00:00
d . passwordValidation ( )
}
2022-03-01 15:59:38 +00:00
// When leaving the confirmation input because of the button or other input component is focused, check if password matches
2022-05-11 08:10:31 +00:00
else {
root . checkPasswordMatches ( false )
}
2022-03-01 15:59:38 +00:00
}
2023-04-02 18:49:43 +00:00
onAccepted: root . returnPressed ( )
2022-03-01 15:59:38 +00:00
StatusFlatRoundButton {
id: showHideConfirmIcon
visible: confirmPswInput . text !== ""
anchors.verticalCenter: parent . verticalCenter
anchors.right: parent . right
anchors.rightMargin: 16
width: 24
height: 24
icon.name: confirmPswInput . showPassword ? "hide" : "show"
icon.color: Theme . palette . baseColor1
onClicked: confirmPswInput . showPassword = ! confirmPswInput . showPassword
}
}
StatusBaseText {
id: errorTxt
2022-10-11 13:58:34 +00:00
Layout.alignment: Qt . AlignHCenter
Layout.fillHeight: true
2022-03-01 15:59:38 +00:00
font.pixelSize: 12
color: Theme . palette . dangerColor1
}
}