1
0
Fork 0
mirror of https://github.com/seiichiro0185/sailotp.git synced 2024-11-22 07:39:42 +00:00

Version 0.3

* Added possibility to "star" an entry to show it on the ActiveCover
* Tokens can be copied to the clipboard by tapping on them
This commit is contained in:
seiichiro 2014-01-10 19:57:42 +01:00
parent 631868ddba
commit f0b6a630dc
7 changed files with 168 additions and 40 deletions

View file

@ -5,9 +5,16 @@ 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 number of sites uses this algorithm for two-factor-authentication, including
Github, Linode and several Google services. Github, Linode and several Google services.
At the moment the App is quite basic. One can add new OTP-entries using the One can add new OTP-entries using the pulley-menu. The main view of the app will show a list
pulley-menu. The main view of the app will show a List off all entries and off all entries and their current One-Time-Tokens. The entries will be regenerated every 30 seconds,
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. the remaining time for the current tokens is shown through a progress bar at the top of the app.
An entry can be edited or deleted by long-pressing on it.
One entry can be stared by tapping the star icon on the left. the stared item will be shown
on the ActiveCover and refreshed every 30 seconds. 5 seconds before the token changes it's
color will change to red. The Item can be unstared by tapping it again.
From the main view a token can be copied to the clipboard by tapping on it.
## Known Limitations ## Known Limitations

View file

@ -29,9 +29,37 @@
import QtQuick 2.0 import QtQuick 2.0
import Sailfish.Silica 1.0 import Sailfish.Silica 1.0
import "../lib/crypto.js" as OTP
// Define the Layout of the Active Cover // Define the Layout of the Active Cover
CoverBackground { CoverBackground {
id: coverPage
property double lastUpdated: 0
Timer {
interval: 1000
// Timer runs only when cover is visible and favourite is set
running: !Qt.application.active && appWin.coverSecret != ""
repeat: true
onTriggered: {
// get seconds from current Date
var curDate = new Date();
if (lOTP.text == "" || curDate.getSeconds() == 30 || curDate.getSeconds() == 0 || (curDate.getTime() - lastUpdated > 2000)) {
appWin.coverOTP = OTP.calcOTP(appWin.coverSecret);
}
// Change color of the OTP to red if less than 5 seconds left
if (29 - (curDate.getSeconds() % 30) < 5) {
lOTP.color = "red"
} else {
lOTP.color = Theme.highlightColor
}
lastUpdated = curDate.getTime();
}
}
// Show the SailOTP Logo // Show the SailOTP Logo
Image { Image {
@ -42,10 +70,22 @@ CoverBackground {
anchors.topMargin: 48 anchors.topMargin: 48
} }
// Show the Application Name Column {
anchors.top: logo.bottom
anchors.topMargin: 48
anchors.horizontalCenter: parent.horizontalCenter
Label { Label {
id: label text: appWin.coverTitle
anchors.centerIn: parent anchors.horizontalCenter: parent.horizontalCenter
text: "SailOTP" color: Theme.secondaryColor
}
Label {
id: lOTP
text: appWin.coverOTP
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.highlightColor
font.pixelSize: Theme.fontSizeExtraLarge
}
} }
} }

View file

@ -33,6 +33,13 @@ import "pages"
ApplicationWindow ApplicationWindow
{ {
id: appWin
// Properties to pass values between MainPage and Cover
property string coverTitle: "SailOTP"
property string coverSecret: ""
property string coverOTP: ""
initialPage: Component { MainView { } } initialPage: Component { MainView { } }
cover: Qt.resolvedUrl("cover/CoverPage.qml") cover: Qt.resolvedUrl("cover/CoverPage.qml")
} }

View file

@ -29,20 +29,34 @@
.import QtQuick.LocalStorage 2.0 as LS .import QtQuick.LocalStorage 2.0 as LS
// Get DB Connection // Get DB Connection, Initialize or Upgrade DB
function getDB() { function getDB() {
return LS.LocalStorage.openDatabaseSync("harbour-sailotp", "1.0", "SailOTP Config Storage", 1000000); try {
} var db = LS.LocalStorage.openDatabaseSync("harbour-sailotp", "", "SailOTP Config Storage", 1000000);
// Initialize Table if not exists if (db.version == "") {
function initialize() { // Initialize an empty DB, Create the Table
var db = getDB(); db.changeVersion("", "2",
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, type TEXT, counter INTEGER, fav INTEGER);");
} }
) );
} else if (db.version == "1.0") {
// Upgrade DB Schema to Version 2
db.changeVersion("1.0", "2",
function(tx) {
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN type TEXT DEFAULT 'TOTP';");
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN counter INTEGER DEFAULT 0;");
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN fav INTEGER DEFAULT 0;");
}
);
}
} catch (e) {
// DB Failed to open
console.log("Could not open DB: " + e);
}
return db;
} }
// Get all OTPs into the list model // Get all OTPs into the list model
@ -53,7 +67,8 @@ function getOTP() {
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, res.rows.item(i).type, res.rows.item(i).counter, res.rows.item(i).fav);
if (res.rows.item(i).fav) mainPage.setCoverOTP(res.rows.item(i).title, res.rows.item(i).secret);
} }
} }
) )
@ -65,7 +80,7 @@ function addOTP(title, secret) {
db.transaction( db.transaction(
function(tx) { function(tx) {
tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?);", [title, secret]); tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?, ?, ?, ?);", [title, secret, 'TOTP', 0, 0]);
} }
) )
} }
@ -81,6 +96,27 @@ function removeOTP(title, secret) {
) )
} }
function setFav(title, secret) {
var db = getDB();
db.transaction(
function(tx) {
tx.executeSql("UPDATE OTPStorage set fav = 0");
tx.executeSql("UPDATE OTPStorage set fav = 1 WHERE title=? and secret=?;", [title, secret]);
}
)
}
function resetFav(title, secret) {
var db = getDB();
db.transaction(
function(tx) {
tx.executeSql("UPDATE OTPStorage set fav = 0");
}
)
}
// Change an existing OTP // Change an existing OTP
function changeOTP(title, secret, oldtitle, oldsecret) { function changeOTP(title, secret, oldtitle, oldsecret) {
var db = getDB(); var db = getDB();

View file

@ -44,7 +44,7 @@ Page {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
y: 320 y: 320
font.bold: true font.bold: true
text: "SailOTP 0.2" text: "SailOTP 0.3"
} }
Text { Text {
id: desc id: desc

View file

@ -41,18 +41,27 @@ Page {
} }
// This holds the time of the last update of the page as Unix Timestamp (in Milliseconds) // This holds the time of the last update of the page as Unix Timestamp (in Milliseconds)
property double lastUpdated: null property double lastUpdated: 0
// Add an entry to the list // Add an entry to the list
function appendOTP(title, secret) { function appendOTP(title, secret, type, counter, fav) {
otpListModel.append({"secret": secret, "title": title, "otp": ""}); otpListModel.append({"secret": secret, "title": title, "fav": fav, "otp": ""});
}
// Hand favorite over to the cover
function setCoverOTP(title, secret) {
appWin.coverTitle = title
appWin.coverSecret = secret
if (secret == "") appWin.coverOTP = ""
} }
// Reload the List of OTPs from storage // Reload the List of OTPs from storage
function refreshOTPList() { function refreshOTPList() {
otpList.visible = false;
otpListModel.clear(); otpListModel.clear();
DB.getOTP(); DB.getOTP();
refreshOTPValues(); refreshOTPValues();
otpList.visible = true;
} }
// Calculate new OTPs for every entry // Calculate new OTPs for every entry
@ -67,7 +76,6 @@ Page {
if (otpListModel.get(i).otp == "" || seconds == 30 || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) { if (otpListModel.get(i).otp == "" || seconds == 30 || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) {
var curOTP = OTP.calcOTP(otpListModel.get(i).secret) var curOTP = OTP.calcOTP(otpListModel.get(i).secret)
otpListModel.setProperty(i, "otp", curOTP); otpListModel.setProperty(i, "otp", curOTP);
console.log("Updating Value ", i);
} }
} }
@ -79,7 +87,8 @@ Page {
Timer { Timer {
interval: 1000 interval: 1000
running: Qt.application.active // Timer only runs when App is active // Timer only runs when app is acitive and we have entries
running: Qt.application.active && otpListModel.count
repeat: true repeat: true
onTriggered: refreshOTPValues(); onTriggered: refreshOTPValues();
} }
@ -104,6 +113,8 @@ Page {
maximumValue: 29 maximumValue: 29
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 48 anchors.topMargin: 48
// Only show when there are enries
visible: otpListModel.count
} }
SilicaListView { SilicaListView {
@ -121,21 +132,50 @@ Page {
hintText: "Pull down to add a OTP" hintText: "Pull down to add a OTP"
} }
delegate: ListItem { delegate: ListItem {
id: otpListItem id: otpListItem
menu: otpContextMenu menu: otpContextMenu
width: otpList.width
contentHeight: Theme.itemSizeMedium contentHeight: Theme.itemSizeMedium
width: parent.width
function remove() { function remove() {
// Show 5s countdown, then delete from DB and List // Show 5s countdown, then delete from DB and List
remorseAction("Deleting", function() { DB.removeOTP(title, secret); otpListModel.remove(index) }) remorseAction("Deleting", function() { DB.removeOTP(title, secret); otpListModel.remove(index) })
} }
onClicked: {
Clipboard.text = otp
}
ListView.onRemove: animateRemoval() ListView.onRemove: animateRemoval()
Rectangle { Rectangle {
id: listRow
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
IconButton {
icon.source: fav == 1 ? "image://theme/icon-m-favorite-selected" : "image://theme/icon-m-favorite"
anchors.left: parent.left
onClicked: {
if (fav == 0) {
DB.setFav(title, secret)
setCoverOTP(title, secret)
for (var i=0; i<otpListModel.count; i++) {
if (i != index) {
otpListModel.setProperty(i, "fav", 0);
} else {
otpListModel.setProperty(i, "fav", 1);
}
}
} else {
DB.resetFav(title, secret)
setCoverOTP("SailOTP", "")
otpListModel.setProperty(index, "fav", 0);
}
}
}
Column {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
Label { Label {
@ -147,11 +187,11 @@ Page {
Label { Label {
id: otpValue id: otpValue
anchors.top: otpLabel.bottom
text: model.otp text: model.otp
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.highlightColor color: Theme.highlightColor
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
anchors.horizontalCenter: parent.horizontalCenter
}
} }
} }
@ -174,8 +214,6 @@ Page {
VerticalScrollDecorator{} VerticalScrollDecorator{}
Component.onCompleted: { Component.onCompleted: {
// Initialize DB (create tables etc..)
DB.initialize();
// Load list of OTP-Entries // Load list of OTP-Entries
refreshOTPList(); refreshOTPList();
} }

View file

@ -1,6 +1,6 @@
Name: harbour-sailotp Name: harbour-sailotp
Summary: SailOTP Summary: SailOTP
Version: 0.2 Version: 0.3
Release: 1 Release: 1
Group: Security Group: Security
URL: https://github.com/seiichiro0185/sailotp/ URL: https://github.com/seiichiro0185/sailotp/