2019-09-09 15:12:58 +03:00
import QtQuick 2.9
import QtQuick . Controls 2.2
import QtQuick . Layouts 1.3
import QtQuick . Controls . Material 2.2
import QtGraphicalEffects 1.0
2019-10-29 11:52:47 +03:00
Flickable {
2019-09-09 15:12:58 +03:00
readonly property int dynamicWidth: 864
readonly property int dynamicMargin: 32
id: newCredentialViewId
objectName: 'newCredentialView'
2019-10-08 15:56:42 +03:00
property string title: qsTr ( "Add Account" )
2019-09-09 15:12:58 +03:00
2019-10-29 11:52:47 +03:00
ScrollBar.vertical: ScrollBar {
width: 8
anchors.top: parent . top
anchors.right: parent . right
anchors.bottom: parent . bottom
hoverEnabled: true
z: 2
}
boundsBehavior: Flickable . StopAtBounds
2019-09-09 15:12:58 +03:00
property var credential
property bool manualEntry
contentWidth: app . width
contentHeight: content . implicitHeight + dynamicMargin
function acceptableInput ( ) {
if ( settings . otpMode ) {
2019-09-09 17:09:25 +03:00
return secretKeyLbl . text . length > 0 && secretKeyLbl . text . length <= 32
2019-09-09 15:12:58 +03:00
} else {
var nameAndKey = nameLbl . text . length > 0
&& secretKeyLbl . text . length > 0
var okTotalLength = ( nameLbl . text . length + issuerLbl . text . length ) < 60
return nameAndKey && okTotalLength
}
}
function addCredential ( ) {
function callback ( resp ) {
if ( resp . success ) {
yubiKey . calculateAll ( navigator . goToCredentials )
2019-11-05 18:43:17 +03:00
if ( ! settings . hideBackupReminder ) {
navigator . confirm ( {
"heading" : qsTr ( "Account added. Create backup?" ) ,
"message" : qsTr ( "Secrets are stored safely on YubiKey. Backups can only be created during setup." ) ,
"description" : qsTr ( "To create a backup, change YubiKey and repeat the 'Add account' procedure BEFORE verifying with the issuer. The secret key may also be copied and stored somewhere safe." ) ,
"warning" : false ,
"copySecret" : true ,
"buttons" : false ,
"acceptedCb" : function ( ) {
clipBoard . push ( secretKeyLbl . text )
navigator . snackBar ( qsTr ( "Secret copied to clipboard" ) )
}
} )
} else {
navigator . snackBar ( qsTr ( "Account added" ) )
}
2019-09-09 15:12:58 +03:00
} else {
2019-09-18 11:48:42 +03:00
if ( resp . error_id === 'credential_already_exists' ) {
2019-11-05 18:43:17 +03:00
navigator . confirm ( {
"heading" : qsTr ( "Overwrite?" ) ,
"message" : qsTr ( "An account with this name already exists, do you want to overwrite it?" ) ,
"acceptedCb" : _ccidAddCredentialOverwrite
} )
2019-09-18 11:48:42 +03:00
} else {
navigator . snackBarError ( navigator . getErrorMessage ( resp . error_id ) )
console . log ( "addCredential failed:" , resp . error_id )
}
2019-09-09 15:12:58 +03:00
}
}
function _otpAddCredential ( ) {
yubiKey . otpAddCredential ( otpSlotComboBox . currentText ,
secretKeyLbl . text ,
requireTouchCheckBox . checked , callback )
}
2019-09-18 11:48:42 +03:00
function _ccidAddCredential ( overwrite ) {
yubiKey . ccidAddCredential ( nameLbl . text , secretKeyLbl . text ,
issuerLbl . text ,
oathTypeComboBox . currentText ,
algoComboBox . currentText ,
digitsComboBox . currentText ,
periodLbl . text ,
requireTouchCheckBox . checked ,
overwrite ,
callback )
}
function _ccidAddCredentialOverwrite ( ) {
_ccidAddCredential ( true )
}
function _ccidAddCredentialNoOverwrite ( ) {
_ccidAddCredential ( false )
}
2019-09-09 15:12:58 +03:00
if ( acceptableInput ( ) ) {
if ( settings . otpMode ) {
yubiKey . otpSlotStatus ( function ( resp ) {
if ( resp . success ) {
if ( resp . status [ parseInt (
otpSlotComboBox . currentText ) - 1 ] ) {
2019-11-05 18:43:17 +03:00
navigator . confirm ( {
"heading" : qsTr ( "Overwrite?" ) ,
"message" : qsTr ( "This slot is already configured, do you want to overwrite it?" ) ,
"acceptedCb" : _otpAddCredential
} )
2019-09-09 15:12:58 +03:00
} else {
_otpAddCredential ( )
}
} else {
navigator . snackBarError ( navigator . getErrorMessage (
resp . error_id ) )
}
} )
} else {
2019-09-18 11:48:42 +03:00
_ccidAddCredentialNoOverwrite ( )
2019-09-09 15:12:58 +03:00
}
settings . requireTouch = requireTouchCheckBox . checked
}
}
Keys.onEscapePressed: navigator . home ( )
function getEnabledOtpSlots ( ) {
var res = [ ]
if ( settings . slot1digits ) {
res . push ( 1 )
}
if ( settings . slot2digits ) {
res . push ( 2 )
}
return res
}
ColumnLayout {
anchors.fill: parent
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: 0
Pane {
id: content
Layout.alignment: Qt . AlignCenter | Qt . AlignTop
Layout.fillWidth: true
Layout.maximumWidth: dynamicWidth + dynamicMargin
Layout.topMargin: 0
background: Rectangle {
color: isDark ( ) ? defaultDarkLighter : defaultLightDarker
layer.enabled: true
layer.effect: DropShadow {
radius: 3
samples: radius * 2
verticalOffset: 2
horizontalOffset: 0
color: formDropShdaow
transparentBorder: true
}
}
ColumnLayout {
width: app . width - dynamicMargin
< dynamicWidth ? app . width - dynamicMargin : dynamicWidth
spacing: 0
StyledStepperContainer {
Layout.fillWidth: true
initialStep: ! manualEntry ? 2 : 1
StyledStepperPanel {
2019-10-09 13:11:16 +03:00
label: qsTr ( "Make sure QR code is fully visible" )
2019-09-09 17:59:30 +03:00
description: qsTr ( "Press the button to scan when ready." )
2019-09-09 15:12:58 +03:00
id: retryPane
Layout.fillWidth: true
Component.onCompleted: retry . forceActiveFocus ( )
StyledImage {
source: "../images/qr-monitor.svg"
color: app . isDark ( ) ? defaultLightForeground : defaultLightOverlay
iconWidth: 140
Layout.alignment: Qt . AlignHCenter | Qt . AlignVCenter
Layout.margins: 16
}
RowLayout {
Layout.alignment: Qt . AlignLeft | Qt . AlignTop
StyledButton {
id: retry
2019-09-09 17:59:30 +03:00
text: qsTr ( "Scan" )
toolTipText: qsTr ( "Scan a QR code on the screen" )
2019-09-09 15:12:58 +03:00
focus: true
onClicked: yubiKey . scanQr ( true )
Keys.onReturnPressed: yubiKey . scanQr ( true )
Keys.onEnterPressed: yubiKey . scanQr ( true )
}
StyledButton {
2019-09-09 17:59:30 +03:00
text: qsTr ( "Enter manually" )
2019-10-08 15:56:42 +03:00
toolTipText: qsTr ( "Enter account details manually" )
2019-09-09 15:12:58 +03:00
flat: true
onClicked: manualEntryPane . expandAction ( )
Material.foreground: formText
Keys.onReturnPressed: manualEntryPane . expandAction ( )
Keys.onEnterPressed: manualEntryPane . expandAction ( )
}
}
}
StyledStepperPanel {
2019-10-08 15:56:42 +03:00
label: qsTr ( "Add account" )
2019-09-09 17:59:30 +03:00
description: ! manualEntry ? qsTr ( "Edit and confirm settings" ) : qsTr ( "Use manual entry if there's no QR code available." )
2019-09-09 15:12:58 +03:00
id: manualEntryPane
ColumnLayout {
Layout.topMargin: 8
StyledTextField {
id: issuerLbl
2019-09-09 17:59:30 +03:00
labelText: qsTr ( "Issuer" )
2019-09-09 15:12:58 +03:00
Layout.fillWidth: true
text: credential
&& credential . issuer ? credential.issuer : ""
visible: ! settings . otpMode
onSubmit: addCredential ( )
}
StyledTextField {
id: nameLbl
2019-09-09 17:59:30 +03:00
labelText: qsTr ( "Account name" )
2019-09-09 15:12:58 +03:00
Layout.fillWidth: true
required: true
text: credential && credential . name ? credential.name : ""
visible: ! settings . otpMode
onSubmit: addCredential ( )
}
StyledTextField {
id: secretKeyLbl
2019-09-09 17:59:30 +03:00
labelText: qsTr ( "Secret key" )
2019-09-09 15:12:58 +03:00
Layout.fillWidth: true
required: true
text: credential
&& credential . secret ? credential.secret : ""
visible: manualEntry
2019-10-09 14:01:47 +03:00
validateText: qsTr ( "Invalid Base32 format (A-Z and 2-7)" )
2019-09-09 15:12:58 +03:00
validateRegExp: /^[2-7a-zA-Z]+=*$/
Layout.bottomMargin: 12
onSubmit: addCredential ( )
}
RowLayout {
Layout.fillWidth: true
StyledComboBox {
2019-09-09 17:59:30 +03:00
label: qsTr ( "Slot" )
2019-09-09 15:12:58 +03:00
id: otpSlotComboBox
model: getEnabledOtpSlots ( )
}
visible: settings . otpMode
}
RowLayout {
CheckBox {
id: requireTouchCheckBox
checked: settings . requireTouch
2019-09-09 17:59:30 +03:00
text: qsTr ( "Require touch" )
2019-09-09 15:12:58 +03:00
padding: 0
indicator.width: 16
indicator.height: 16
Material.foreground: formText
2019-10-02 15:40:08 +03:00
font.pixelSize: 13
2019-09-09 15:12:58 +03:00
}
visible: yubiKey . supportsTouchCredentials ( )
|| settings . otpMode
}
StyledExpansionPanel {
id: advancedSettingsPanel
2019-09-09 17:59:30 +03:00
label: qsTr ( "Advanced settings" )
2019-10-02 14:59:32 +03:00
description: qsTr ( "Changing these may result in unexpected behavior." )
2019-09-09 15:12:58 +03:00
visible: manualEntry && ! settings . otpMode
dropShadow: false
backgroundColor: "transparent"
ColumnLayout {
Layout.fillWidth: true
RowLayout {
StyledComboBox {
label: "Type"
id: oathTypeComboBox
model: [ "TOTP" , "HOTP" ]
selectedValue: credential && credential . oath_type ? credential.oath_type : ""
}
Item {
width: 16
}
StyledComboBox {
id: algoComboBox
2019-09-09 17:59:30 +03:00
label: qsTr ( "Algorithm" )
2019-09-09 15:12:58 +03:00
model: {
var algos = [ "SHA1" , "SHA256" ]
if ( yubiKey . supportsOathSha512 ( ) ) {
algos . push ( "SHA512" )
}
return algos
}
selectedValue: credential && credential . algorithm ? credential.algorithm : ""
}
}
RowLayout {
StyledTextField {
id: periodLbl
visible: oathTypeComboBox . currentIndex === 0
2019-09-09 17:59:30 +03:00
labelText: qsTr ( "Period" )
2019-09-09 15:12:58 +03:00
text: credential && credential . period ? credential.period : "30"
horizontalAlignment: Text . Alignleft
validator: IntValidator {
bottom: 15
top: 60
}
}
Item {
visible: oathTypeComboBox . currentIndex === 0
width: 16
}
StyledComboBox {
id: digitsComboBox
2019-09-09 17:59:30 +03:00
label: qsTr ( "Digits" )
2019-09-09 15:12:58 +03:00
model: [ "6" , "7" , "8" ]
selectedValue: credential && credential . digits ? credential.digits : ""
}
}
}
}
StyledButton {
id: addBtn
2019-09-09 17:59:30 +03:00
text: qsTr ( "Add" )
2019-10-08 15:56:42 +03:00
toolTipText: qsTr ( "Add account to YubiKey" )
2019-09-09 15:12:58 +03:00
enabled: settings . otpMode ? secretKeyLbl . validated && acceptableInput ( ) : secretKeyLbl . validated && acceptableInput ( ) && nameLbl . validated
Layout.alignment: Qt . AlignLeft | Qt . AlignVCenter
onClicked: addCredential ( )
Layout.bottomMargin: - 16
}
}
}
}
}
}
}
}