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
|
|
|
|
2020-01-14 17:13:35 +03:00
|
|
|
readonly property int dynamicWidth: 648
|
2019-09-09 15:12:58 +03:00
|
|
|
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() {
|
2019-11-29 00:54:29 +03:00
|
|
|
// trim spaces to accurately count length, parse_b32_key later trims them
|
|
|
|
var secretKeyTrimmed = secretKeyLbl.text.replace(/ /g, "")
|
2019-09-09 15:12:58 +03:00
|
|
|
if (settings.otpMode) {
|
2019-11-29 00:54:29 +03:00
|
|
|
return secretKeyTrimmed.length > 0 && secretKeyTrimmed.length <= 32
|
2019-09-09 15:12:58 +03:00
|
|
|
} else {
|
|
|
|
var nameAndKey = nameLbl.text.length > 0
|
2019-11-29 00:54:29 +03:00
|
|
|
&& secretKeyTrimmed.length > 0
|
2019-09-09 15:12:58 +03:00
|
|
|
var okTotalLength = (nameLbl.text.length + issuerLbl.text.length) < 60
|
|
|
|
return nameAndKey && okTotalLength
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-27 16:44:27 +03:00
|
|
|
function addCredentialNoCopy() {
|
|
|
|
addCredential(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
function addCredential(copy = false) {
|
2019-09-09 15:12:58 +03:00
|
|
|
|
|
|
|
function callback(resp) {
|
|
|
|
if (resp.success) {
|
2020-01-14 17:13:35 +03:00
|
|
|
yubiKey.calculateAll(navigator.goToCredentials)
|
|
|
|
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
|
2019-11-27 16:44:27 +03:00
|
|
|
Material.elevation: 1
|
|
|
|
Material.background: defaultElevated
|
2019-09-09 15:12:58 +03:00
|
|
|
|
|
|
|
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"
|
2019-11-27 16:44:27 +03:00
|
|
|
color: defaultImageOverlay
|
2019-09-09 15:12:58 +03:00
|
|
|
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-11-27 16:44:27 +03:00
|
|
|
text: qsTr("Manual")
|
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()
|
|
|
|
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-11-29 00:54:29 +03:00
|
|
|
validateRegExp: /^[2-7a-zA-Z ]+[= ]*$/
|
2019-09-09 15:12:58 +03:00
|
|
|
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
|
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
|
|
|
|
}
|
2020-01-14 17:13:35 +03:00
|
|
|
Layout.maximumWidth: oathTypeComboBox.width
|
2019-09-09 15:12:58 +03:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|