mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-27 14:23:18 +03:00
94edf799b6
- New design based on Material Style - Light mode and Dark mode - Add support for custom CCID readers - New tray functionality - Support for favorite credentials Co-authored-by: Rikard <rikard@yubico.com>
321 lines
14 KiB
QML
321 lines
14 KiB
QML
import QtQuick 2.9
|
|
import QtQuick.Controls 2.2
|
|
import QtQuick.Layouts 1.3
|
|
import QtQuick.Controls.Material 2.2
|
|
import QtGraphicalEffects 1.0
|
|
|
|
ScrollView {
|
|
|
|
readonly property int dynamicWidth: 864
|
|
readonly property int dynamicMargin: 32
|
|
|
|
id: newCredentialViewId
|
|
objectName: 'newCredentialView'
|
|
property string title: "New Credential"
|
|
|
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
|
ScrollBar.vertical.width: 8
|
|
|
|
property var credential
|
|
property bool manualEntry
|
|
|
|
contentWidth: app.width
|
|
contentHeight: content.implicitHeight + dynamicMargin
|
|
|
|
function acceptableInput() {
|
|
if (settings.otpMode) {
|
|
return secretKeyLbl.text.length > 0
|
|
// TODO: check maxlength of secret, 20 bytes?
|
|
} 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)
|
|
navigator.snackBar("Credential added")
|
|
} else {
|
|
navigator.snackBarError(navigator.getErrorMessage(
|
|
resp.error_id))
|
|
console.log("addCredential failed:", resp.error_id)
|
|
}
|
|
}
|
|
|
|
function _otpAddCredential() {
|
|
yubiKey.otpAddCredential(otpSlotComboBox.currentText,
|
|
secretKeyLbl.text,
|
|
requireTouchCheckBox.checked, callback)
|
|
}
|
|
|
|
if (acceptableInput()) {
|
|
if (settings.otpMode) {
|
|
yubiKey.otpSlotStatus(function (resp) {
|
|
if (resp.success) {
|
|
if (resp.status[parseInt(
|
|
otpSlotComboBox.currentText) - 1]) {
|
|
navigator.confirm(
|
|
"Overwrite?",
|
|
"The slot is already configured, do you want to overwrite it?",
|
|
_otpAddCredential)
|
|
} else {
|
|
_otpAddCredential()
|
|
}
|
|
} else {
|
|
navigator.snackBarError(navigator.getErrorMessage(
|
|
resp.error_id))
|
|
}
|
|
})
|
|
} else {
|
|
yubiKey.ccidAddCredential(nameLbl.text, secretKeyLbl.text,
|
|
issuerLbl.text,
|
|
oathTypeComboBox.currentText,
|
|
algoComboBox.currentText,
|
|
digitsComboBox.currentText,
|
|
periodLbl.text,
|
|
requireTouchCheckBox.checked, callback)
|
|
}
|
|
settings.requireTouch = requireTouchCheckBox.checked
|
|
}
|
|
}
|
|
|
|
|
|
Keys.onEscapePressed: navigator.home()
|
|
|
|
spacing: 8
|
|
padding: 0
|
|
|
|
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 {
|
|
label: "Make sure QR code is fully visible on screen"
|
|
description: "Press the button to scan when ready."
|
|
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
|
|
text: "Scan"
|
|
toolTipText: "Scan a QR code on the screen"
|
|
focus: true
|
|
onClicked: yubiKey.scanQr(true)
|
|
Keys.onReturnPressed: yubiKey.scanQr(true)
|
|
Keys.onEnterPressed: yubiKey.scanQr(true)
|
|
}
|
|
StyledButton {
|
|
text: "Enter manually"
|
|
toolTipText: "Enter credential details manually"
|
|
flat: true
|
|
onClicked: manualEntryPane.expandAction()
|
|
Material.foreground: formText
|
|
Keys.onReturnPressed: manualEntryPane.expandAction()
|
|
Keys.onEnterPressed: manualEntryPane.expandAction()
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledStepperPanel {
|
|
label: "Add credential"
|
|
description: !manualEntry ? "Edit and confirm settings" : "Use manual entry if there's no QR code available."
|
|
id: manualEntryPane
|
|
|
|
ColumnLayout {
|
|
Layout.topMargin: 8
|
|
|
|
StyledTextField {
|
|
id: issuerLbl
|
|
labelText: "Issuer"
|
|
Layout.fillWidth: true
|
|
text: credential
|
|
&& credential.issuer ? credential.issuer : ""
|
|
visible: !settings.otpMode
|
|
onSubmit: addCredential()
|
|
}
|
|
StyledTextField {
|
|
id: nameLbl
|
|
labelText: "Account name"
|
|
Layout.fillWidth: true
|
|
required: true
|
|
text: credential && credential.name ? credential.name : ""
|
|
visible: !settings.otpMode
|
|
onSubmit: addCredential()
|
|
}
|
|
StyledTextField {
|
|
id: secretKeyLbl
|
|
labelText: "Secret key"
|
|
Layout.fillWidth: true
|
|
required: true
|
|
text: credential
|
|
&& credential.secret ? credential.secret : ""
|
|
visible: manualEntry
|
|
validateText: "Invalid Base32 format (valid characters are A-Z and 2-7)"
|
|
validateRegExp: /^[2-7a-zA-Z]+=*$/
|
|
Layout.bottomMargin: 12
|
|
onSubmit: addCredential()
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
StyledComboBox {
|
|
label: "Slot"
|
|
id: otpSlotComboBox
|
|
model: getEnabledOtpSlots()
|
|
}
|
|
visible: settings.otpMode
|
|
}
|
|
|
|
RowLayout {
|
|
CheckBox {
|
|
id: requireTouchCheckBox
|
|
checked: settings.requireTouch
|
|
text: "Require touch"
|
|
padding: 0
|
|
indicator.width: 16
|
|
indicator.height: 16
|
|
Material.foreground: formText
|
|
}
|
|
visible: yubiKey.supportsTouchCredentials()
|
|
|| settings.otpMode
|
|
}
|
|
|
|
StyledExpansionPanel {
|
|
id: advancedSettingsPanel
|
|
label: "Advanced settings"
|
|
description: "Note: Changing these may result in unexpected behavior."
|
|
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
|
|
label: "Algorithm"
|
|
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
|
|
labelText: "Period"
|
|
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
|
|
label: "Digits"
|
|
model: ["6", "7", "8"]
|
|
selectedValue: credential && credential.digits ? credential.digits : ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledButton {
|
|
id: addBtn
|
|
text: "Add"
|
|
toolTipText: "Add credential to YubiKey"
|
|
enabled: settings.otpMode ? secretKeyLbl.validated && acceptableInput() : secretKeyLbl.validated && acceptableInput() && nameLbl.validated
|
|
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
|
onClicked: addCredential()
|
|
Layout.bottomMargin: -16
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|