mirror of
https://github.com/seiichiro0185/sailotp.git
synced 2024-11-25 00:29:42 +00:00
Version 0.2
* Added possibility to edit existing entries * Better error handling for invalid secrets * Better input checks on add/edit
This commit is contained in:
parent
0c0f10f7ab
commit
631868ddba
12 changed files with 415 additions and 295 deletions
26
COPYING
Normal file
26
COPYING
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
Copyright (c) 2013, 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.
|
36
README.md
Normal file
36
README.md
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# SailOTP
|
||||||
|
|
||||||
|
SailOTP is a Sailfish Implementation of the Google-Authenticator algorithm,
|
||||||
|
also known as Timebased One Time Pad (TOPT) as described in RFC 6238. A growing
|
||||||
|
number of sites uses this algorithm for two-factor-authentication, including
|
||||||
|
Github, Linode and several Google services.
|
||||||
|
|
||||||
|
At the moment the App is quite basic. One can add new OTP-entries using the
|
||||||
|
pulley-menu. The main view of the app will show a List off all entries and
|
||||||
|
their current One-Time-Tokens. The entries will be regenerated every 30 seconds, the remaining time for the current tokens is shown through a progress bar at the top of the app. An entry can be deleted by long-pressing on it.
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
At the moment the only way to insert new entries into the app is to insert the
|
||||||
|
title and secret key by hand. It's not possible to use the QR-Codes some sites
|
||||||
|
provide directly.
|
||||||
|
|
||||||
|
## Contact and Issues
|
||||||
|
|
||||||
|
If you find any bugs or want to suggest a feature, feel free to use Githubs
|
||||||
|
Issues feature.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
SailOTP is licensed under a 3-Clause BSD-License. See COPYING for details.
|
||||||
|
|
||||||
|
## Accnowledgements
|
||||||
|
|
||||||
|
SailOTP uses the SHA-1 and HMAC-Implementation from
|
||||||
|
|
||||||
|
<a href="https://github.com/Caligatio/jsSHA" target="_blank">https://github.com/Caligatio/jsSHA</a>
|
||||||
|
|
||||||
|
The implementation of the TOTP-algorithm was inspired by:
|
||||||
|
|
||||||
|
<a href="http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/" target="_blank">http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/</a>
|
||||||
|
|
|
@ -30,19 +30,22 @@
|
||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
|
// Define the Layout of the Active Cover
|
||||||
CoverBackground {
|
CoverBackground {
|
||||||
|
|
||||||
Image {
|
// Show the SailOTP Logo
|
||||||
id: logo
|
Image {
|
||||||
source: "../sailotp.png"
|
id: logo
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
source: "../sailotp.png"
|
||||||
anchors.top: parent.top
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.topMargin: 48
|
anchors.top: parent.top
|
||||||
}
|
anchors.topMargin: 48
|
||||||
|
}
|
||||||
|
|
||||||
Label {
|
// Show the Application Name
|
||||||
id: label
|
Label {
|
||||||
anchors.centerIn: parent
|
id: label
|
||||||
text: "SailOTP"
|
anchors.centerIn: parent
|
||||||
}
|
text: "SailOTP"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,8 @@ import "pages"
|
||||||
|
|
||||||
ApplicationWindow
|
ApplicationWindow
|
||||||
{
|
{
|
||||||
initialPage: Component { MainView { } }
|
initialPage: Component { MainView { } }
|
||||||
cover: Qt.resolvedUrl("cover/CoverPage.qml")
|
cover: Qt.resolvedUrl("cover/CoverPage.qml")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -29,49 +29,66 @@
|
||||||
|
|
||||||
.import "./sha.js" as SHA
|
.import "./sha.js" as SHA
|
||||||
|
|
||||||
// Helper Functions
|
// *** Helper Functions *** //
|
||||||
|
|
||||||
|
// Decimal to HEX
|
||||||
function dec2hex(s) { return (s < 15.5 ? '0' : '') + Math.round(s).toString(16); }
|
function dec2hex(s) { return (s < 15.5 ? '0' : '') + Math.round(s).toString(16); }
|
||||||
|
|
||||||
|
// HEX to Decimal
|
||||||
function hex2dec(s) { return parseInt(s, 16); }
|
function hex2dec(s) { return parseInt(s, 16); }
|
||||||
|
|
||||||
|
// Convert Base32-secret to HEX Value
|
||||||
function base32tohex(base32) {
|
function base32tohex(base32) {
|
||||||
var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||||
var bits = "";
|
var bits = "";
|
||||||
var hex = "";
|
var hex = "";
|
||||||
|
|
||||||
for (var i = 0; i < base32.length; i++) {
|
for (var i = 0; i < base32.length; i++) {
|
||||||
var val = base32chars.indexOf(base32.charAt(i).toUpperCase());
|
var val = base32chars.indexOf(base32.charAt(i).toUpperCase());
|
||||||
bits += leftpad(val.toString(2), 5, '0');
|
bits += leftpad(val.toString(2), 5, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i+4 <= bits.length; i+=4) {
|
|
||||||
var chunk = bits.substr(i, 4);
|
|
||||||
hex = hex + parseInt(chunk, 2).toString(16) ;
|
|
||||||
}
|
|
||||||
return hex;
|
|
||||||
|
|
||||||
|
for (var i = 0; i+4 <= bits.length; i+=4) {
|
||||||
|
var chunk = bits.substr(i, 4);
|
||||||
|
hex = hex + parseInt(chunk, 2).toString(16) ;
|
||||||
|
}
|
||||||
|
return hex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pad Strings to given length
|
||||||
function leftpad(str, len, pad) {
|
function leftpad(str, len, pad) {
|
||||||
if (len + 1 >= str.length) {
|
if (len + 1 >= str.length) {
|
||||||
str = Array(len + 1 - str.length).join(pad) + str;
|
str = Array(len + 1 - str.length).join(pad) + str;
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// *** Main Function *** //
|
||||||
|
|
||||||
// Calculate an OTP-Value from the given secret
|
// Calculate an OTP-Value from the given secret
|
||||||
|
// Parameter is the secret key in Base32-notation
|
||||||
function calcOTP(secret) {
|
function calcOTP(secret) {
|
||||||
var key = base32tohex(secret);
|
// Convert the key to HEX
|
||||||
var epoch = Math.round(new Date().getTime() / 1000.0);
|
var key = base32tohex(secret);
|
||||||
var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0');
|
// Get current Time in UNIX Timestamp format (Seconds since 01.01.1970 00:00 UTC)
|
||||||
|
var epoch = Math.round(new Date().getTime() / 1000.0);
|
||||||
|
// Get last full 30 / 60 Seconds and convert to HEX
|
||||||
|
var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Calculate the SHA-1 HMAC Value from time and key
|
||||||
var hmacObj = new SHA.jsSHA(time, 'HEX');
|
var hmacObj = new SHA.jsSHA(time, 'HEX');
|
||||||
var hmac = hmacObj.getHMAC(key, 'HEX', 'SHA-1', "HEX");
|
var hmac = hmacObj.getHMAC(key, 'HEX', 'SHA-1', "HEX");
|
||||||
|
|
||||||
|
// Finally convert the HMAC-Value to the corresponding 6-digit token
|
||||||
var offset = hex2dec(hmac.substring(hmac.length - 1));
|
var offset = hex2dec(hmac.substring(hmac.length - 1));
|
||||||
|
|
||||||
var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + '';
|
var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + '';
|
||||||
otp = (otp).substr(otp.length - 6, 6);
|
otp = (otp).substr(otp.length - 6, 6);
|
||||||
return otp;
|
} catch (e) {
|
||||||
|
otp = "Invalid Secret!"
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the calculated token
|
||||||
|
return otp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,52 +31,63 @@
|
||||||
|
|
||||||
// Get DB Connection
|
// Get DB Connection
|
||||||
function getDB() {
|
function getDB() {
|
||||||
return LS.LocalStorage.openDatabaseSync("harbour-sailotp", "1.0", "SailOTP Config Storage", 1000000);
|
return LS.LocalStorage.openDatabaseSync("harbour-sailotp", "1.0", "SailOTP Config Storage", 1000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Table if not exists
|
// Initialize Table if not exists
|
||||||
function initialize() {
|
function initialize() {
|
||||||
var db = getDB();
|
var db = getDB();
|
||||||
|
|
||||||
db.transaction(
|
db.transaction(
|
||||||
function(tx) {
|
function(tx) {
|
||||||
tx.executeSql("CREATE TABLE IF NOT EXISTS OTPStorage(title TEXT, secret TEXT);");
|
tx.executeSql("CREATE TABLE IF NOT EXISTS OTPStorage(title TEXT, secret TEXT);");
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all OTPs into the list model
|
// Get all OTPs into the list model
|
||||||
function getOTP() {
|
function getOTP() {
|
||||||
var db = getDB();
|
var db = getDB();
|
||||||
|
|
||||||
db.transaction(
|
db.transaction(
|
||||||
function(tx) {
|
function(tx) {
|
||||||
var res = tx.executeSql("select * from OTPStorage;");
|
var res = tx.executeSql("select * from OTPStorage;");
|
||||||
for (var i=0; i < res.rows.length; i++) {
|
for (var i=0; i < res.rows.length; i++) {
|
||||||
mainPage.appendOTP(res.rows.item(i).title, res.rows.item(i).secret);
|
mainPage.appendOTP(res.rows.item(i).title, res.rows.item(i).secret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new OTP
|
// Add a new OTP
|
||||||
function addOTP(title, secret) {
|
function addOTP(title, secret) {
|
||||||
var db = getDB();
|
var db = getDB();
|
||||||
|
|
||||||
db.transaction(
|
db.transaction(
|
||||||
function(tx) {
|
function(tx) {
|
||||||
tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?);", [title, secret]);
|
tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?);", [title, secret]);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove an existing OTP
|
// Remove an existing OTP
|
||||||
function removeOTP(title, secret) {
|
function removeOTP(title, secret) {
|
||||||
var db = getDB();
|
var db = getDB();
|
||||||
|
|
||||||
db.transaction(
|
db.transaction(
|
||||||
function(tx) {
|
function(tx) {
|
||||||
tx.executeSql("DELETE FROM OTPStorage WHERE title=? and secret=?;", [title, secret]);
|
tx.executeSql("DELETE FROM OTPStorage WHERE title=? and secret=?;", [title, secret]);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change an existing OTP
|
||||||
|
function changeOTP(title, secret, oldtitle, oldsecret) {
|
||||||
|
var db = getDB();
|
||||||
|
|
||||||
|
db.transaction(
|
||||||
|
function(tx) {
|
||||||
|
tx.executeSql("UPDATE OTPStorage SET title=?, secret=? WHERE title=? and secret=?;", [title, secret, oldtitle, oldsecret]);
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,56 +30,54 @@
|
||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
|
// Define the Layout of the About Page
|
||||||
Page {
|
Page {
|
||||||
id: aboutPage
|
id: aboutPage
|
||||||
|
Image {
|
||||||
Image {
|
id: logo
|
||||||
id: logo
|
source: "../sailotp.png"
|
||||||
source: "../sailotp.png"
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
y: 200
|
||||||
y: 200
|
}
|
||||||
|
Label {
|
||||||
|
id: name
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
y: 320
|
||||||
|
font.bold: true
|
||||||
|
text: "SailOTP 0.2"
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
id: desc
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: name.bottom
|
||||||
|
anchors.topMargin: 20
|
||||||
|
text: "A Simple Sailfish TOTP Generator<br />(RFC 6238 compatible)"
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
id: copyright
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: desc.bottom
|
||||||
|
anchors.topMargin: 20
|
||||||
|
text: "Copyright: Stefan Brand<br />License: BSD (3-clause)"
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
id: homepage
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: copyright.bottom
|
||||||
|
anchors.topMargin: 20
|
||||||
|
text: "<a href=\"https://github.com/seiichiro0185/sailotp\">SailOTP on Github</a>"
|
||||||
|
onClicked: {
|
||||||
|
Qt.openUrlExternally("https://github.com/seiichiro0185/sailotp")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Label {
|
Text {
|
||||||
id: name
|
id: accnowledgement
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
y: 320
|
anchors.top: homepage.bottom
|
||||||
font.bold: true
|
anchors.topMargin: 20
|
||||||
text: "SailOTP 0.1"
|
text: "SailOTP uses the SHA-1 Implementation<br />from http://caligatio.github.io/jsSHA/"
|
||||||
}
|
color: "white"
|
||||||
Text {
|
}
|
||||||
id: desc
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: name.bottom
|
|
||||||
anchors.topMargin: 20
|
|
||||||
text: "A Simple Sailfish TOTP Generator<br />(RFC 6238 compatible)"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
id: copyright
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: desc.bottom
|
|
||||||
anchors.topMargin: 20
|
|
||||||
text: "Copyright: Stefan Brand<br />License: BSD (3-clause)"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
id: homepage
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: copyright.bottom
|
|
||||||
anchors.topMargin: 20
|
|
||||||
text: "<a href=\"https://github.com/seiichiro0185/sailotp\">SailOTP on Github</a>"
|
|
||||||
onClicked: {
|
|
||||||
Qt.openUrlExternally("https://github.com/seiichiro0185/sailotp")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
id: accnowledgement
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.top: homepage.bottom
|
|
||||||
anchors.topMargin: 20
|
|
||||||
text: "SailOTP uses the SHA-1 Implementation<br />from http://caligatio.github.io/jsSHA/"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,52 +30,69 @@
|
||||||
|
|
||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
import "../lib/storage.js" as DB
|
import "../lib/storage.js" as DB // Import the storage library for Config-Access
|
||||||
|
|
||||||
|
// Define Layout of the Add OTP Dialog
|
||||||
Dialog {
|
Dialog {
|
||||||
id: addOTP
|
id: addOTP
|
||||||
|
|
||||||
property QtObject parentPage: null
|
// We get the Object of the parent page on call to refresh it after adding a new Entry
|
||||||
|
property QtObject parentPage: null
|
||||||
|
|
||||||
SilicaFlickable {
|
// If we want to edit a Key we get title and key from the calling page
|
||||||
id: addOtpList
|
property string paramLabel: ""
|
||||||
anchors.fill: parent
|
property string paramKey: ""
|
||||||
|
|
||||||
VerticalScrollDecorator {}
|
SilicaFlickable {
|
||||||
|
id: addOtpList
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
Column {
|
VerticalScrollDecorator {}
|
||||||
anchors.fill: parent
|
|
||||||
DialogHeader {
|
|
||||||
acceptText: "Add"
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField {
|
Column {
|
||||||
id: otpLabel
|
anchors.fill: parent
|
||||||
width: parent.width
|
DialogHeader {
|
||||||
label: "Title"
|
acceptText: paramLabel != "" ? "Save" : "Add"
|
||||||
placeholderText: "Title for the OTP"
|
}
|
||||||
focus: true
|
TextField {
|
||||||
horizontalAlignment: TextInput.AlignLeft
|
id: otpLabel
|
||||||
}
|
width: parent.width
|
||||||
|
label: "Title"
|
||||||
TextField {
|
placeholderText: "Title for the OTP"
|
||||||
id: otpSecret
|
text: paramLabel != "" ? paramLabel : ""
|
||||||
width: parent.width
|
focus: true
|
||||||
label: "Secret"
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
placeholderText: "Secret OTP Key"
|
}
|
||||||
focus: true
|
TextField {
|
||||||
horizontalAlignment: TextInput.AlignLeft
|
id: otpSecret
|
||||||
}
|
width: parent.width
|
||||||
}
|
label: "Secret (at least 16 characters)"
|
||||||
|
text: paramKey != "" ? paramKey : ""
|
||||||
|
placeholderText: "Secret OTP Key"
|
||||||
|
focus: true
|
||||||
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onDone: {
|
// Check if we can Save
|
||||||
if (otpLabel.text != "" && otpSecret.text != "") {
|
canAccept: otpLabel.text.length > 0 && otpSecret.text.length >= 16 ? true : false
|
||||||
DB.addOTP(otpLabel.text, otpSecret.text);
|
|
||||||
parentPage.refreshOTPList();
|
// Save if page is Left with Add
|
||||||
}
|
onDone: {
|
||||||
|
if (result == DialogResult.Accepted) {
|
||||||
|
// Save the entry to the Config DB
|
||||||
|
if (paramLabel != "" && paramKey != "") {
|
||||||
|
// Parameters where filled -> Change existing entry
|
||||||
|
DB.changeOTP(otpLabel.text, otpSecret.text, paramLabel, paramKey)
|
||||||
|
} else {
|
||||||
|
// There were no parameters -> Add new entry
|
||||||
|
DB.addOTP(otpLabel.text, otpSecret.text);
|
||||||
|
}
|
||||||
|
// Refresh the main Page
|
||||||
|
parentPage.refreshOTPList();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,139 +34,153 @@ import "../lib/storage.js" as DB
|
||||||
import "../lib/crypto.js" as OTP
|
import "../lib/crypto.js" as OTP
|
||||||
|
|
||||||
Page {
|
Page {
|
||||||
id: mainPage
|
id: mainPage
|
||||||
|
|
||||||
ListModel {
|
ListModel {
|
||||||
id: otpListModel
|
id: otpListModel
|
||||||
|
}
|
||||||
|
|
||||||
|
// This holds the time of the last update of the page as Unix Timestamp (in Milliseconds)
|
||||||
|
property double lastUpdated: null
|
||||||
|
|
||||||
|
// Add an entry to the list
|
||||||
|
function appendOTP(title, secret) {
|
||||||
|
otpListModel.append({"secret": secret, "title": title, "otp": ""});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload the List of OTPs from storage
|
||||||
|
function refreshOTPList() {
|
||||||
|
otpListModel.clear();
|
||||||
|
DB.getOTP();
|
||||||
|
refreshOTPValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate new OTPs for every entry
|
||||||
|
function refreshOTPValues() {
|
||||||
|
// get seconds from current Date
|
||||||
|
var curDate = new Date();
|
||||||
|
var seconds = curDate.getSeconds();
|
||||||
|
|
||||||
|
// Iterate over all List entries
|
||||||
|
for (var i=0; i<otpListModel.count; i++) {
|
||||||
|
// Only update on full 30 / 60 Seconds or if last run of the Functions is more than 2s in the past (e.g. app was in background)
|
||||||
|
if (otpListModel.get(i).otp == "" || seconds == 30 || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) {
|
||||||
|
var curOTP = OTP.calcOTP(otpListModel.get(i).secret)
|
||||||
|
otpListModel.setProperty(i, "otp", curOTP);
|
||||||
|
console.log("Updating Value ", i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property double lastUpdated: null
|
// Update the Progressbar
|
||||||
|
updateProgress.value = 29 - (seconds % 30)
|
||||||
|
// Set lastUpdate property
|
||||||
|
lastUpdated = curDate.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
// Add an entry to the list
|
Timer {
|
||||||
function appendOTP(title, secret) {
|
interval: 1000
|
||||||
otpListModel.append({"secret": secret, "title": title, "otp": ""});
|
running: Qt.application.active // Timer only runs when App is active
|
||||||
|
repeat: true
|
||||||
|
onTriggered: refreshOTPValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
SilicaFlickable {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
PullDownMenu {
|
||||||
|
MenuItem {
|
||||||
|
text: "About"
|
||||||
|
onClicked: pageStack.push(Qt.resolvedUrl("About.qml"))
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Add OTP"
|
||||||
|
onClicked: pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload the List of OTPs from storage
|
ProgressBar {
|
||||||
function refreshOTPList() {
|
id: updateProgress
|
||||||
otpListModel.clear();
|
width: parent.width
|
||||||
DB.getOTP();
|
maximumValue: 29
|
||||||
refreshOTPValues();
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 48
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate new OTPs for every entry
|
SilicaListView {
|
||||||
function refreshOTPValues() {
|
id: otpList
|
||||||
var curDate = new Date();
|
header: PageHeader {
|
||||||
var seconds = curDate.getSeconds();
|
title: "SailOTP"
|
||||||
|
}
|
||||||
|
anchors.fill: parent
|
||||||
|
model: otpListModel
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
for (var i=0; i<otpListModel.count; i++) {
|
ViewPlaceholder {
|
||||||
if (otpListModel.get(i).otp == "" || seconds == 30 || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) {
|
enabled: otpList.count == 0
|
||||||
var curOTP = OTP.calcOTP(otpListModel.get(i).secret)
|
text: "Nothing here"
|
||||||
otpListModel.setProperty(i, "otp", curOTP);
|
hintText: "Pull down to add a OTP"
|
||||||
console.log("Updating Value ", i);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
delegate: ListItem {
|
||||||
|
id: otpListItem
|
||||||
|
menu: otpContextMenu
|
||||||
|
width: otpList.width
|
||||||
|
contentHeight: Theme.itemSizeMedium
|
||||||
|
|
||||||
|
function remove() {
|
||||||
|
// Show 5s countdown, then delete from DB and List
|
||||||
|
remorseAction("Deleting", function() { DB.removeOTP(title, secret); otpListModel.remove(index) })
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProgress.value = 29 - (seconds % 30)
|
ListView.onRemove: animateRemoval()
|
||||||
lastUpdated = curDate.getTime();
|
Rectangle {
|
||||||
}
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
Timer {
|
Label {
|
||||||
interval: 1000
|
id: otpLabel
|
||||||
running: Qt.application.active
|
text: model.title
|
||||||
repeat: true
|
color: Theme.secondaryColor
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
onTriggered: refreshOTPValues();
|
Label {
|
||||||
}
|
id: otpValue
|
||||||
|
anchors.top: otpLabel.bottom
|
||||||
|
text: model.otp
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
color: Theme.highlightColor
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SilicaFlickable {
|
Component {
|
||||||
anchors.fill: parent
|
id: otpContextMenu
|
||||||
|
ContextMenu {
|
||||||
PullDownMenu {
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "About"
|
text: "Edit"
|
||||||
onClicked: pageStack.push(Qt.resolvedUrl("About.qml"))
|
onClicked: {
|
||||||
|
pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage, paramLabel: title, paramKey: secret})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Add OTP"
|
text: "Delete"
|
||||||
onClicked: pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage})
|
onClicked: remove()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
VerticalScrollDecorator{}
|
||||||
|
|
||||||
ProgressBar {
|
Component.onCompleted: {
|
||||||
id: updateProgress
|
// Initialize DB (create tables etc..)
|
||||||
width: parent.width
|
DB.initialize();
|
||||||
maximumValue: 29
|
// Load list of OTP-Entries
|
||||||
anchors.top: parent.top
|
refreshOTPList();
|
||||||
anchors.topMargin: 48
|
}
|
||||||
}
|
|
||||||
|
|
||||||
SilicaListView {
|
|
||||||
id: otpList
|
|
||||||
header: PageHeader {
|
|
||||||
title: "SailOTP"
|
|
||||||
}
|
|
||||||
anchors.fill: parent
|
|
||||||
model: otpListModel
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
ViewPlaceholder {
|
|
||||||
enabled: otpList.count == 0
|
|
||||||
text: "Nothing here"
|
|
||||||
hintText: "Pull down to add a OTP"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
delegate: ListItem {
|
|
||||||
id: otpListItem
|
|
||||||
menu: otpContextMenu
|
|
||||||
width: otpList.width
|
|
||||||
contentHeight: Theme.itemSizeMedium
|
|
||||||
|
|
||||||
function remove() {
|
|
||||||
remorseAction("Deleting", function() { DB.removeOTP(title, secret); otpListModel.remove(index) })
|
|
||||||
}
|
|
||||||
|
|
||||||
ListView.onRemove: animateRemoval()
|
|
||||||
Rectangle {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: otpLabel
|
|
||||||
text: model.title
|
|
||||||
color: Theme.secondaryColor
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: otpValue
|
|
||||||
anchors.top: otpLabel.bottom
|
|
||||||
text: model.otp
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
color: Theme.highlightColor
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: otpContextMenu
|
|
||||||
ContextMenu {
|
|
||||||
MenuItem {
|
|
||||||
text: "Delete"
|
|
||||||
onClicked: remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VerticalScrollDecorator{}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
DB.initialize();
|
|
||||||
refreshOTPList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ Name: harbour-sailotp
|
||||||
%{!?qtc_make:%define qtc_make make}
|
%{!?qtc_make:%define qtc_make make}
|
||||||
%{?qtc_builddir:%define _builddir %qtc_builddir}
|
%{?qtc_builddir:%define _builddir %qtc_builddir}
|
||||||
Summary: SailOTP
|
Summary: SailOTP
|
||||||
Version: 0.1
|
Version: 0.2
|
||||||
Release: 1
|
Release: 1
|
||||||
Group: Security
|
Group: Security
|
||||||
License: BSD
|
License: BSD
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
Name: harbour-sailotp
|
Name: harbour-sailotp
|
||||||
Summary: SailOTP
|
Summary: SailOTP
|
||||||
Version: 0.1
|
Version: 0.2
|
||||||
Release: 1
|
Release: 1
|
||||||
Group: Security
|
Group: Security
|
||||||
URL: https://github.com/seiichiro0185/sailotp/
|
URL: https://github.com/seiichiro0185/sailotp/
|
||||||
|
|
|
@ -1,32 +1,31 @@
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2013 Jolla Ltd.
|
* Copyright (c) 2013, Stefan Brand <seiichiro@seiichiro0185.org>
|
||||||
Contact: Thomas Perl <thomas.perl@jollamobile.com>
|
* All rights reserved.
|
||||||
All rights reserved.
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
You may use this file under the terms of BSD license as follows:
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
Redistribution and use in source and binary forms, with or without
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
modification, are permitted provided that the following conditions are met:
|
* list of conditions and the following disclaimer.
|
||||||
* 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
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
* list of conditions and the following disclaimer in the documentation and/or other
|
||||||
notice, this list of conditions and the following disclaimer in the
|
* materials provided with the distribution.
|
||||||
documentation and/or other materials provided with the distribution.
|
*
|
||||||
* Neither the name of the Jolla Ltd nor the
|
* 3. The names of the contributors may not be used to endorse or promote products
|
||||||
names of its contributors may be used to endorse or promote products
|
* derived from this software without specific prior written permission.
|
||||||
derived from this software without specific prior written permission.
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
(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
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
*/
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef QT_QML_DEBUG
|
#ifdef QT_QML_DEBUG
|
||||||
#include <QtQuick>
|
#include <QtQuick>
|
||||||
|
@ -34,9 +33,8 @@
|
||||||
|
|
||||||
#include <sailfishapp.h>
|
#include <sailfishapp.h>
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
return SailfishApp::main(argc, argv);
|
return SailfishApp::main(argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue