2017-02-02 16:16:52 +03:00
|
|
|
import QtQuick 2.6
|
2017-01-27 15:55:38 +03:00
|
|
|
import QtQuick.Controls 1.4
|
|
|
|
import QtQuick.Layouts 1.1
|
2017-02-02 16:16:52 +03:00
|
|
|
import QtQuick.Controls.Styles 1.4
|
2017-01-27 15:55:38 +03:00
|
|
|
import QtQuick.Dialogs 1.2
|
|
|
|
|
|
|
|
ApplicationWindow {
|
2017-02-06 17:00:11 +03:00
|
|
|
id: appWindow
|
2017-02-02 16:16:52 +03:00
|
|
|
width: 300
|
|
|
|
height: 400
|
2017-02-04 00:28:57 +03:00
|
|
|
minimumHeight: 400
|
|
|
|
minimumWidth: 300
|
2017-01-27 15:55:38 +03:00
|
|
|
visible: true
|
2017-01-27 16:28:06 +03:00
|
|
|
title: qsTr("Yubico Authenticator")
|
2017-02-07 12:12:22 +03:00
|
|
|
property var device: yk
|
2017-02-02 16:16:52 +03:00
|
|
|
property int expiration: 0
|
2017-02-07 12:12:22 +03:00
|
|
|
property var credentials: device.credentials
|
2017-02-03 15:58:22 +03:00
|
|
|
property var selectedCredential
|
2017-02-07 16:17:54 +03:00
|
|
|
property bool validated: device.validated
|
|
|
|
property bool hasDevice: device.hasDevice
|
|
|
|
|
|
|
|
PasswordPrompt {
|
|
|
|
id: passwordPrompt
|
|
|
|
}
|
|
|
|
|
2017-02-08 18:47:28 +03:00
|
|
|
onHasDeviceChanged: {
|
2017-02-07 16:17:54 +03:00
|
|
|
if (device.hasDevice) {
|
2017-02-09 12:57:28 +03:00
|
|
|
device.promptOrSkip(passwordPrompt)
|
2017-02-09 14:45:04 +03:00
|
|
|
} else {
|
|
|
|
passwordPrompt.close()
|
|
|
|
addCredential.close()
|
2017-02-07 16:17:54 +03:00
|
|
|
}
|
|
|
|
}
|
2017-02-02 16:16:52 +03:00
|
|
|
|
|
|
|
onCredentialsChanged: {
|
|
|
|
updateExpiration()
|
|
|
|
touchYourYubikey.close()
|
|
|
|
console.log('CREDENTIALS ', JSON.stringify(credentials))
|
|
|
|
}
|
|
|
|
|
2017-02-04 00:54:13 +03:00
|
|
|
SystemPalette {
|
|
|
|
id: palette
|
|
|
|
}
|
|
|
|
|
|
|
|
TextEdit {
|
|
|
|
id: clipboard
|
|
|
|
visible: false
|
|
|
|
function setClipboard(value) {
|
|
|
|
text = value
|
|
|
|
selectAll()
|
|
|
|
copy()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-27 15:55:38 +03:00
|
|
|
menuBar: MenuBar {
|
|
|
|
|
|
|
|
Menu {
|
|
|
|
title: qsTr("File")
|
2017-02-03 11:58:17 +03:00
|
|
|
MenuItem {
|
2017-02-10 14:44:10 +03:00
|
|
|
text: qsTr('Add credential...')
|
2017-02-06 11:41:00 +03:00
|
|
|
onTriggered: addCredential.open()
|
2017-02-03 11:58:17 +03:00
|
|
|
}
|
2017-02-10 14:44:10 +03:00
|
|
|
MenuItem {
|
|
|
|
text: qsTr('Set password...')
|
|
|
|
onTriggered: setPassword.open()
|
|
|
|
}
|
2017-01-27 15:55:38 +03:00
|
|
|
MenuItem {
|
|
|
|
text: qsTr("Exit")
|
|
|
|
onTriggered: Qt.quit()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Menu {
|
|
|
|
title: qsTr("Help")
|
|
|
|
MenuItem {
|
2017-01-27 16:28:06 +03:00
|
|
|
text: qsTr("About Yubico Authenticator")
|
2017-01-27 15:55:38 +03:00
|
|
|
onTriggered: aboutPage.show()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AboutPage {
|
|
|
|
id: aboutPage
|
|
|
|
}
|
|
|
|
|
2017-02-03 11:58:17 +03:00
|
|
|
AddCredential {
|
|
|
|
id: addCredential
|
|
|
|
}
|
|
|
|
|
2017-02-10 14:44:10 +03:00
|
|
|
SetPassword {
|
|
|
|
id: setPassword
|
2017-02-10 16:01:49 +03:00
|
|
|
onAccepted: passwordUpdated.open()
|
2017-02-10 14:44:10 +03:00
|
|
|
}
|
2017-02-07 16:17:54 +03:00
|
|
|
|
2017-02-10 16:01:49 +03:00
|
|
|
MessageDialog {
|
|
|
|
id: passwordUpdated
|
|
|
|
icon: StandardIcon.Information
|
|
|
|
title: qsTr("Password set")
|
|
|
|
text: qsTr("A new password has been set.")
|
|
|
|
standardButtons: StandardButton.Ok
|
|
|
|
}
|
|
|
|
|
2017-02-03 11:58:17 +03:00
|
|
|
MouseArea {
|
2017-02-07 12:12:22 +03:00
|
|
|
enabled: device.hasDevice
|
2017-02-03 18:23:28 +03:00
|
|
|
anchors.fill: parent
|
|
|
|
acceptedButtons: Qt.RightButton
|
|
|
|
onClicked: contextMenu.popup()
|
2017-02-03 11:58:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
Menu {
|
|
|
|
id: contextMenu
|
2017-02-03 18:23:28 +03:00
|
|
|
MenuItem {
|
|
|
|
text: qsTr('Add...')
|
|
|
|
onTriggered: addCredential.open()
|
2017-02-03 11:58:17 +03:00
|
|
|
}
|
2017-02-03 18:23:28 +03:00
|
|
|
}
|
2017-02-03 11:58:17 +03:00
|
|
|
|
2017-02-03 15:58:22 +03:00
|
|
|
Menu {
|
|
|
|
id: credentialMenu
|
2017-02-03 18:23:28 +03:00
|
|
|
MenuItem {
|
|
|
|
text: qsTr('Copy')
|
2017-02-16 11:55:47 +03:00
|
|
|
shortcut: StandardKey.Copy
|
|
|
|
onTriggered: {
|
|
|
|
if (selectedCredential != null) {
|
|
|
|
clipboard.setClipboard(selectedCredential.code)
|
|
|
|
}
|
|
|
|
}
|
2017-02-03 18:23:28 +03:00
|
|
|
}
|
|
|
|
MenuItem {
|
|
|
|
visible: selectedCredential != null
|
2017-02-16 11:55:47 +03:00
|
|
|
&& (selectedCredential.oath_type === "hotp"
|
|
|
|
|| selectedCredential.touch === true)
|
2017-02-03 18:23:28 +03:00
|
|
|
text: qsTr('Generate code')
|
2017-02-16 11:55:47 +03:00
|
|
|
shortcut: "Space"
|
2017-02-03 18:23:28 +03:00
|
|
|
onTriggered: calculateCredential(selectedCredential)
|
|
|
|
}
|
|
|
|
MenuItem {
|
|
|
|
text: qsTr('Delete')
|
2017-02-16 11:55:47 +03:00
|
|
|
shortcut: StandardKey.Delete
|
2017-02-03 18:23:28 +03:00
|
|
|
onTriggered: confirmDeleteCredential.open()
|
2017-02-03 15:58:22 +03:00
|
|
|
}
|
2017-02-03 18:23:28 +03:00
|
|
|
}
|
2017-02-03 11:58:17 +03:00
|
|
|
|
2017-02-06 17:00:11 +03:00
|
|
|
ColumnLayout {
|
2017-02-06 15:42:17 +03:00
|
|
|
anchors.fill: parent
|
2017-02-06 17:00:11 +03:00
|
|
|
spacing: 0
|
|
|
|
|
|
|
|
ProgressBar {
|
|
|
|
id: progressBar
|
2017-02-09 14:45:04 +03:00
|
|
|
visible: hasDevice
|
2017-02-06 17:00:11 +03:00
|
|
|
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
|
|
|
Layout.maximumHeight: 10
|
|
|
|
Layout.minimumHeight: 10
|
|
|
|
Layout.minimumWidth: 300
|
|
|
|
Layout.fillWidth: true
|
|
|
|
maximumValue: 30
|
|
|
|
minimumValue: 0
|
|
|
|
|
|
|
|
style: ProgressBarStyle {
|
|
|
|
progress: Rectangle {
|
|
|
|
color: "#9aca3c"
|
|
|
|
}
|
2017-02-02 16:16:52 +03:00
|
|
|
|
2017-02-06 17:00:11 +03:00
|
|
|
background: Rectangle {
|
|
|
|
color: palette.mid
|
2017-02-02 16:16:52 +03:00
|
|
|
}
|
2017-02-06 15:42:17 +03:00
|
|
|
}
|
2017-02-06 17:00:11 +03:00
|
|
|
}
|
2017-02-02 16:16:52 +03:00
|
|
|
|
2017-02-06 17:00:11 +03:00
|
|
|
ScrollView {
|
|
|
|
id: scrollView
|
|
|
|
Layout.fillHeight: true
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
|
|
|
ColumnLayout {
|
|
|
|
width: scrollView.viewport.width
|
|
|
|
id: credentialsColumn
|
|
|
|
spacing: 0
|
2017-02-07 12:12:22 +03:00
|
|
|
visible: device.hasDevice
|
2017-02-06 17:07:43 +03:00
|
|
|
anchors.right: appWindow.right
|
|
|
|
anchors.left: appWindow.left
|
|
|
|
anchors.top: appWindow.top
|
2017-02-06 17:00:11 +03:00
|
|
|
|
|
|
|
Repeater {
|
2017-02-06 17:57:32 +03:00
|
|
|
id: repeater
|
|
|
|
model: filteredCredentials(credentials, search.text)
|
2017-02-06 17:00:11 +03:00
|
|
|
|
|
|
|
Rectangle {
|
|
|
|
id: credentialRectangle
|
2017-02-16 11:18:52 +03:00
|
|
|
color: {
|
|
|
|
if (selectedCredential != null) {
|
|
|
|
if (selectedCredential.name == modelData.name) {
|
|
|
|
return palette.dark
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (index % 2 == 0) {
|
|
|
|
return "#00000000"
|
|
|
|
}
|
|
|
|
return palette.alternateBase
|
|
|
|
}
|
2017-02-06 17:00:11 +03:00
|
|
|
Layout.fillWidth: true
|
|
|
|
Layout.minimumHeight: 70
|
|
|
|
Layout.alignment: Qt.AlignTop
|
|
|
|
|
|
|
|
MouseArea {
|
|
|
|
anchors.fill: parent
|
|
|
|
onClicked: {
|
2017-02-16 11:18:52 +03:00
|
|
|
if (mouse.button & Qt.LeftButton) {
|
|
|
|
if (selectedCredential != null
|
|
|
|
&& selectedCredential.name == modelData.name) {
|
|
|
|
selectedCredential = null
|
|
|
|
} else {
|
|
|
|
selectedCredential = modelData
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (mouse.button & Qt.RightButton) {
|
|
|
|
selectedCredential = modelData
|
|
|
|
credentialMenu.popup()
|
|
|
|
}
|
2017-02-06 17:00:11 +03:00
|
|
|
}
|
2017-02-16 11:18:52 +03:00
|
|
|
acceptedButtons: Qt.RightButton | Qt.LeftButton
|
2017-02-06 15:42:17 +03:00
|
|
|
}
|
|
|
|
|
2017-02-06 17:00:11 +03:00
|
|
|
ColumnLayout {
|
|
|
|
anchors.leftMargin: 10
|
|
|
|
spacing: -15
|
|
|
|
anchors.fill: parent
|
|
|
|
Text {
|
|
|
|
visible: hasIssuer(modelData.name)
|
|
|
|
text: qsTr('') + parseIssuer(modelData.name)
|
|
|
|
font.pointSize: 13
|
|
|
|
}
|
2017-02-15 17:14:34 +03:00
|
|
|
Text {
|
2017-02-06 17:00:11 +03:00
|
|
|
visible: modelData.code != null
|
|
|
|
text: qsTr('') + modelData.code
|
|
|
|
font.family: "Verdana"
|
|
|
|
font.pointSize: 22
|
|
|
|
}
|
|
|
|
Text {
|
|
|
|
text: hasIssuer(
|
|
|
|
modelData.name) ? qsTr(
|
|
|
|
'') + parseName(
|
|
|
|
modelData.name) : modelData.name
|
|
|
|
font.pointSize: 13
|
|
|
|
}
|
2017-02-06 15:42:17 +03:00
|
|
|
}
|
2017-02-02 16:16:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-02-06 17:00:11 +03:00
|
|
|
|
2017-02-06 17:07:43 +03:00
|
|
|
TextField {
|
|
|
|
id: search
|
2017-02-09 14:45:04 +03:00
|
|
|
visible: hasDevice
|
2017-02-06 17:07:43 +03:00
|
|
|
placeholderText: 'Search...'
|
|
|
|
Layout.fillWidth: true
|
2017-02-06 17:00:11 +03:00
|
|
|
}
|
2017-02-02 16:16:52 +03:00
|
|
|
}
|
2017-02-06 17:57:32 +03:00
|
|
|
|
2017-02-02 16:16:52 +03:00
|
|
|
MessageDialog {
|
|
|
|
id: touchYourYubikey
|
|
|
|
icon: StandardIcon.Information
|
|
|
|
title: qsTr("Touch your YubiKey")
|
|
|
|
text: qsTr("Touch your YubiKey to generate the code.")
|
|
|
|
standardButtons: StandardButton.NoButton
|
|
|
|
}
|
|
|
|
|
2017-02-03 18:23:28 +03:00
|
|
|
MessageDialog {
|
|
|
|
id: confirmDeleteCredential
|
|
|
|
icon: StandardIcon.Warning
|
|
|
|
title: qsTr("Delete credential?")
|
|
|
|
text: qsTr("Are you sure you want to delete the credential?")
|
|
|
|
standardButtons: StandardButton.Ok | StandardButton.Cancel
|
|
|
|
onAccepted: {
|
2017-02-07 12:12:22 +03:00
|
|
|
device.deleteCredential(selectedCredential)
|
|
|
|
device.refreshCredentials()
|
2017-02-03 18:23:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-27 15:55:38 +03:00
|
|
|
// @disable-check M301
|
|
|
|
YubiKey {
|
|
|
|
id: yk
|
|
|
|
onError: {
|
2017-01-30 16:59:58 +03:00
|
|
|
errorBox.text = traceback
|
2017-01-27 15:55:38 +03:00
|
|
|
errorBox.open()
|
|
|
|
}
|
2017-02-08 18:47:28 +03:00
|
|
|
onWrongPassword: {
|
2017-02-10 14:44:10 +03:00
|
|
|
passwordPrompt.open()
|
2017-02-08 18:47:28 +03:00
|
|
|
}
|
2017-01-27 15:55:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
Timer {
|
2017-02-02 16:16:52 +03:00
|
|
|
id: ykTimer
|
2017-01-27 15:55:38 +03:00
|
|
|
triggeredOnStart: true
|
|
|
|
interval: 500
|
|
|
|
repeat: true
|
|
|
|
running: true
|
2017-02-07 12:12:22 +03:00
|
|
|
onTriggered: device.refresh()
|
2017-01-27 15:55:38 +03:00
|
|
|
}
|
|
|
|
|
2017-02-02 16:16:52 +03:00
|
|
|
Timer {
|
|
|
|
id: progressBarTimer
|
|
|
|
interval: 100
|
|
|
|
repeat: true
|
|
|
|
running: true
|
|
|
|
triggeredOnStart: true
|
|
|
|
onTriggered: {
|
|
|
|
var timeLeft = expiration - (Date.now() / 1000)
|
|
|
|
if (timeLeft <= 0 && progressBar.value > 0) {
|
2017-02-07 12:12:22 +03:00
|
|
|
device.refresh()
|
2017-02-02 16:16:52 +03:00
|
|
|
}
|
|
|
|
progressBar.value = timeLeft
|
|
|
|
}
|
2017-01-27 15:55:38 +03:00
|
|
|
}
|
|
|
|
|
2017-02-02 16:16:52 +03:00
|
|
|
Text {
|
2017-02-07 12:12:22 +03:00
|
|
|
visible: !device.hasDevice
|
2017-02-02 16:16:52 +03:00
|
|
|
id: noLoadedDeviceMessage
|
2017-02-07 12:12:22 +03:00
|
|
|
text: if (device.nDevices == 0) {
|
2017-02-02 16:16:52 +03:00
|
|
|
qsTr("No YubiKey detected")
|
2017-02-07 12:12:22 +03:00
|
|
|
} else if (device.nDevices == 1) {
|
2017-02-02 16:16:52 +03:00
|
|
|
qsTr("Connecting to YubiKey...")
|
|
|
|
} else {
|
|
|
|
qsTr("Multiple YubiKeys detected!")
|
|
|
|
}
|
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
2017-01-27 15:55:38 +03:00
|
|
|
}
|
|
|
|
|
2017-02-02 16:16:52 +03:00
|
|
|
MessageDialog {
|
|
|
|
id: errorBox
|
|
|
|
icon: StandardIcon.Critical
|
|
|
|
title: qsTr("Error!")
|
|
|
|
text: ""
|
|
|
|
standardButtons: StandardButton.Ok
|
|
|
|
}
|
2017-01-27 15:55:38 +03:00
|
|
|
|
2017-02-06 17:57:32 +03:00
|
|
|
function filteredCredentials(creds, search) {
|
|
|
|
var result = []
|
|
|
|
if (creds != null) {
|
|
|
|
for (var i = 0; i < creds.length; i++) {
|
|
|
|
var cred = creds[i]
|
|
|
|
if (cred.name.toLowerCase().indexOf(search.toLowerCase(
|
|
|
|
)) !== -1) {
|
|
|
|
result.push(creds[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2017-02-02 17:44:49 +03:00
|
|
|
function hasIssuer(name) {
|
|
|
|
return name.indexOf(':') !== -1
|
|
|
|
}
|
|
|
|
function parseName(name) {
|
|
|
|
return name.split(":").slice(1).join(":")
|
|
|
|
}
|
|
|
|
function parseIssuer(name) {
|
|
|
|
return name.split(":", 1)
|
|
|
|
}
|
|
|
|
|
2017-02-02 16:16:52 +03:00
|
|
|
function calculateCredential(credential) {
|
2017-02-07 12:12:22 +03:00
|
|
|
device.calculate(credential)
|
2017-02-02 16:16:52 +03:00
|
|
|
if (credential.touch) {
|
|
|
|
touchYourYubikey.open()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateExpiration() {
|
|
|
|
var maxExpiration = 0
|
|
|
|
if (credentials !== null) {
|
|
|
|
for (var i = 0; i < credentials.length; i++) {
|
|
|
|
var exp = credentials[i].expiration
|
|
|
|
if (exp !== null && exp > maxExpiration) {
|
|
|
|
maxExpiration = exp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
expiration = maxExpiration
|
2017-01-27 15:55:38 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|