mirror of
https://github.com/seiichiro0185/sailotp.git
synced 2024-11-01 09:38:26 +00:00
Stefan Brand
0339d7dc2d
Added possibility to show a Token as QR-Code Added possibility to hide Tokens by default and reveal on tap Added Base32 input validation for secret Fix progressbar placement for different screen sizes / ambiances Fix About-Page styling for light ambiances
203 lines
7.7 KiB
QML
203 lines
7.7 KiB
QML
/*
|
|
* Copyright (c) 2014, Stefan Brand <seiichiro@seiichiro0185.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice, this
|
|
* list of conditions and the following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* 3. The names of the contributors may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
|
|
import QtQuick 2.0
|
|
import Sailfish.Silica 1.0
|
|
import "../lib/storage.js" as DB // Import the storage library for Config-Access
|
|
|
|
// Define Layout of the Add OTP Dialog
|
|
Dialog {
|
|
id: addOTP
|
|
|
|
allowedOrientations: Orientation.All
|
|
|
|
// We get the Object of the parent page on call to refresh it after adding a new Entry
|
|
property QtObject parentPage: null
|
|
|
|
// If we want to edit a Key we get title and key from the calling page
|
|
property string paramType: "TOTP"
|
|
property string paramLabel: ""
|
|
property string paramKey: ""
|
|
property int paramLen: 6
|
|
property int paramDiff: 0
|
|
property int paramCounter: 1 // New Counters start at 1
|
|
property bool paramNew: false
|
|
|
|
function checkQR() {
|
|
if (paramLabel != "" && paramKey != "" && !paramNew) {
|
|
return(true);
|
|
} else {
|
|
return(false);
|
|
}
|
|
}
|
|
|
|
SilicaFlickable {
|
|
id: addOtpList
|
|
anchors.fill: parent
|
|
contentHeight: dialog.height
|
|
|
|
PullDownMenu {
|
|
visible: checkQR()
|
|
MenuItem {
|
|
text: qsTr("Show QR-Code")
|
|
onClicked: {
|
|
if (((paramType == "TOTP" || paramType == "TOTP_STEAM") && (otpLabel.text == "" || otpSecret.text == "")) || (paramType == "HOTP" && (otpLabel.text == "" || otpSecret.text == "" || otpCounter.text <= 0))) {
|
|
notify.show(qsTr("Can't create QR-Code from incomplete settings!"), 4000);
|
|
} else {
|
|
var otpurl = "";
|
|
if (paramType == "TOTP") {
|
|
if (otpLabel.text != "" && otpSecret.text != "")
|
|
otpurl = "otpauth://totp/"+otpLabel.text+"?secret="+otpSecret.text+"&digits="+otpLen.text;
|
|
} else if (paramType == "HOTP") {
|
|
if (paramLabel != "" && paramKey != "" && paramCounter > 0)
|
|
otpurl = "otpauth://hotp/"+otpLabel.text+"?secret="+otpSecret.text+"&counter="+otpCounter.text+"&digits="+otpLen.text;
|
|
}
|
|
if (otpurl != "") {
|
|
pageStack.push(Qt.resolvedUrl("QRPage.qml"), {paramQrsource: otpurl, paramLabel: otpLabel.text, paramQRId: -1});
|
|
} else {
|
|
notify.show(qsTr("Can't create QR-Code from incomplete settings!"), 4000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
VerticalScrollDecorator {}
|
|
|
|
Column {
|
|
id: dialog
|
|
width: parent.width
|
|
DialogHeader {
|
|
acceptText: paramNew ? qsTr("Add") : qsTr("Save")
|
|
}
|
|
|
|
ComboBox {
|
|
id: typeSel
|
|
label: qsTr("Type")
|
|
menu: ContextMenu {
|
|
MenuItem { text: qsTr("Time-based (TOTP)"); onClicked: { paramType = "TOTP" } }
|
|
MenuItem { text: qsTr("Counter-based (HOTP)"); onClicked: { paramType = "HOTP" } }
|
|
MenuItem { text: qsTr("Steam Guard"); onClicked: { paramType = "TOTP_STEAM" } }
|
|
}
|
|
}
|
|
TextField {
|
|
id: otpLabel
|
|
width: parent.width
|
|
label: qsTr("Title")
|
|
placeholderText: qsTr("Title for the OTP")
|
|
text: paramLabel != "" ? paramLabel : ""
|
|
focus: true
|
|
horizontalAlignment: TextInput.AlignLeft
|
|
|
|
EnterKey.enabled: text.length > 0
|
|
EnterKey.iconSource: "image://theme/icon-m-enter-next"
|
|
EnterKey.onClicked: otpSecret.focus = true
|
|
}
|
|
TextField {
|
|
id: otpSecret
|
|
width: parent.width
|
|
label: qsTr("Secret (at least 16 characters)")
|
|
text: paramKey != "" ? paramKey : ""
|
|
placeholderText: qsTr("Secret OTP Key")
|
|
focus: true
|
|
validator: RegExpValidator { regExp: /^(?:[A-Za-z2-7]{8})*(?:[A-Za-z2-7]{2}={6}|[A-Za-z2-7]{4}={4}|[A-Za-z2-7]{5}={3}|[A-Za-z2-7]{7}=)?$/ }
|
|
inputMethodHints: Qt.ImhNoPredictiveText
|
|
horizontalAlignment: TextInput.AlignLeft
|
|
|
|
EnterKey.enabled: text.length > 15
|
|
EnterKey.iconSource: "image://theme/icon-m-enter-next"
|
|
EnterKey.onClicked: otpLen.focus = true
|
|
}
|
|
TextField {
|
|
id: otpLen
|
|
width: parent.width
|
|
visible: paramType != "TOTP_STEAM" ? true : false
|
|
label: qsTr("Length")
|
|
text: paramLen
|
|
placeholderText: qsTr("Length of the Token")
|
|
focus: true
|
|
horizontalAlignment: TextInput.AlignLeft
|
|
validator: IntValidator { bottom: 1 }
|
|
|
|
EnterKey.iconSource: "image://theme/icon-m-enter-next"
|
|
EnterKey.onClicked: paramType == "HOTP" ? otpCounter.focus = true : otpDiff.focus = true
|
|
}
|
|
TextField {
|
|
id: otpDiff
|
|
width: parent.width
|
|
visible: paramType == "TOTP" ? true : false
|
|
label: qsTr("Time Derivation (Seconds)")
|
|
text: paramDiff
|
|
placeholderText: qsTr("Time Derivation (Seconds)")
|
|
focus: true
|
|
horizontalAlignment: TextInput.AlignLeft
|
|
validator: IntValidator {}
|
|
|
|
EnterKey.iconSource: "image://theme/icon-m-enter-accept"
|
|
EnterKey.onClicked: addOTP.accept()
|
|
}
|
|
TextField {
|
|
id: otpCounter
|
|
width: parent.width
|
|
visible: paramType == "HOTP" ? true : false
|
|
label: qsTr("Next Counter Value")
|
|
text: paramCounter
|
|
placeholderText: qsTr("Next Value of the Counter")
|
|
focus: true
|
|
horizontalAlignment: TextInput.AlignLeft
|
|
validator: IntValidator { bottom: 0 }
|
|
|
|
EnterKey.iconSource: "image://theme/icon-m-enter-accept"
|
|
EnterKey.onClicked: addOTP.accept()
|
|
}
|
|
Component.onCompleted: { typeSel.currentIndex = paramType == "HOTP" ? 1 : 0 }
|
|
}
|
|
}
|
|
|
|
// Check if we can Save
|
|
canAccept: otpLabel.text.length > 0 && otpSecret.text.length >= 16 && otpSecret.acceptableInput && otpLen.text >= 1 && ((paramType == "TOTP" && otpDiff.text != "") || paramType == "TOTP_STEAM" || otpCounter.text.length > 0) ? true : false
|
|
|
|
// Save if page is Left with Add
|
|
onDone: {
|
|
if (result == DialogResult.Accepted) {
|
|
// Save the entry to the Config DB
|
|
if (paramLabel != "" && paramKey != "" && !paramNew) {
|
|
// Parameters where filled -> Change existing entry
|
|
DB.changeOTP(otpLabel.text, otpSecret.text, paramType, otpCounter.text, otpLen.text, otpDiff.text, paramLabel, paramKey)
|
|
} else {
|
|
// There were no parameters -> Add new entry
|
|
DB.addOTP(otpLabel.text, otpSecret.text, paramType, otpCounter.text, appWin.listModel.count, otpLen.text, otpDiff.text);
|
|
}
|
|
|
|
// Refresh the main Page
|
|
parentPage.refreshOTPList();
|
|
}
|
|
}
|
|
}
|