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

Add custom period for TOTP tokens

This commit is contained in:
Andrey Skvortsov 2022-11-14 21:23:12 +03:00
parent e3fd514d55
commit 5339deb2df
6 changed files with 59 additions and 30 deletions

View file

@ -45,7 +45,7 @@ CoverBackground {
var seconds = (curDate.getSeconds() + appWin.coverDiff) % 30 var seconds = (curDate.getSeconds() + appWin.coverDiff) % 30
if (lOTP.text == "------" || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) { if (lOTP.text == "------" || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) {
appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, appWin.coverType, appWin.coverLen, appWin.coverDiff, 0); appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, appWin.coverType, appWin.coverLen, appWin.coverDiff, 0, appWin.coverPeriod);
} }
// Change color of the OTP to red if less than 5 seconds left // Change color of the OTP to red if less than 5 seconds left

View file

@ -45,6 +45,7 @@ ApplicationWindow
property string coverOTP: "------" property string coverOTP: "------"
property int coverLen: 6 property int coverLen: 6
property int coverDiff: 0 property int coverDiff: 0
property int coverPeriod: 30
property int coverIndex: 0 property int coverIndex: 0
// Global Listmodel for Tokens // Global Listmodel for Tokens
@ -64,8 +65,8 @@ ApplicationWindow
} }
// Add an entry to the list // Add an entry to the list
function appendOTP(title, secret, type, counter, fav, len, diff) { function appendOTP(title, secret, type, counter, fav, len, diff, period) {
listModel.append({"secret": secret, "title": title, "fav": fav, "type": type, "counter": counter, "len": len, "diff": diff, "otp": "------", "itemVisible": true}); listModel.append({"secret": secret, "title": title, "fav": fav, "type": type, "counter": counter, "len": len, "diff": diff, "period": period, "otp": "------", "itemVisible": true});
} }
// Set the OTP shown on the Cover // Set the OTP shown on the Cover
@ -76,6 +77,7 @@ ApplicationWindow
coverType = listModel.get(index).type; coverType = listModel.get(index).type;
coverLen = listModel.get(index).len; coverLen = listModel.get(index).len;
coverDiff = listModel.get(index).diff; coverDiff = listModel.get(index).diff;
coverPeriod = listModel.get(index).period;
coverIndex = index; coverIndex = index;
if (coverType == "TOTP") { coverOTP = "------"; } else { coverOTP = listModel.get(index).otp; } if (coverType == "TOTP") { coverOTP = "------"; } else { coverOTP = listModel.get(index).otp; }
for (var i=0; i<listModel.count; i++) { for (var i=0; i<listModel.count; i++) {

View file

@ -77,7 +77,7 @@ var steamChars = ['2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C',
// counter: counter value for HOTP // counter: counter value for HOTP
// length: length of the returned token // length: length of the returned token
// diff: derivation of time between phone and server // diff: derivation of time between phone and server
function calcOTP(secret, type, len, diff, counter) { function calcOTP(secret, type, len, diff, counter, period) {
// Convert the key to HEX // Convert the key to HEX
var key = base32tohex(secret); var key = base32tohex(secret);
var factor = ""; var factor = "";
@ -85,8 +85,8 @@ function calcOTP(secret, type, len, diff, counter) {
if (type.substr(0, 4) == "TOTP") { if (type.substr(0, 4) == "TOTP") {
// Get current Time in UNIX Timestamp format (Seconds since 01.01.1970 00:00 UTC), and add derivation value // Get current Time in UNIX Timestamp format (Seconds since 01.01.1970 00:00 UTC), and add derivation value
var epoch = Math.round(new Date().getTime() / 1000.0) + diff; var epoch = Math.round(new Date().getTime() / 1000.0) + diff;
// Get last full 30 / 60 Seconds and convert to HEX // Get last full period Seconds and convert to HEX
factor = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0'); factor = leftpad(dec2hex(Math.floor(epoch / period)), 16, '0');
} else { } else {
factor = leftpad(dec2hex(counter), 16, '0'); factor = leftpad(dec2hex(counter), 16, '0');
} }

View file

@ -36,13 +36,13 @@ function getDB() {
if (db.version == "") { if (db.version == "") {
// Initialize an empty DB, Create the Table // Initialize an empty DB, Create the Table
db.changeVersion("", "4", db.changeVersion("", "5",
function(tx) { function(tx) {
tx.executeSql("CREATE TABLE IF NOT EXISTS OTPStorage(title TEXT, secret TEXT, type TEXT DEFAULT 'TOPT', counter INTEGER DEFAULT 0, fav INTEGER DEFAULT 0, sort INTEGER DEFAULT 0, len INTEGER default 6, diff INTEGER default 0);"); tx.executeSql("CREATE TABLE IF NOT EXISTS OTPStorage(title TEXT, secret TEXT, type TEXT DEFAULT 'TOPT', counter INTEGER DEFAULT 0, fav INTEGER DEFAULT 0, sort INTEGER DEFAULT 0, len INTEGER default 6, diff INTEGER default 0, period INTEGER default 30);");
}); });
} else if (db.version == "1.0") { } else if (db.version == "1.0") {
// Upgrade DB Schema to Version 4 // Upgrade DB Schema to Version 4
db.changeVersion("1.0", "4", db.changeVersion("1.0", "5",
function(tx) { function(tx) {
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN type TEXT DEFAULT 'TOTP';"); 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 counter INTEGER DEFAULT 0;");
@ -50,21 +50,30 @@ function getDB() {
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN sort INTEGER DEFAULT 0;"); tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN sort INTEGER DEFAULT 0;");
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN len INTEGER DEFAULT 6;"); tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN len INTEGER DEFAULT 6;");
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN diff INTEGER DEFAULT 0;"); tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN diff INTEGER DEFAULT 0;");
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN period INTEGER DEFAULT 30;");
}); });
} else if (db.version == "2") { } else if (db.version == "2") {
// Upgrade DB Schema to Version 3 // Upgrade DB Schema to Version 3
db.changeVersion("2", "4", db.changeVersion("2", "5",
function(tx) { function(tx) {
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN sort INTEGER DEFAULT 0;"); tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN sort INTEGER DEFAULT 0;");
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN len INTEGER DEFAULT 6;"); tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN len INTEGER DEFAULT 6;");
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN diff INTEGER DEFAULT 0;"); tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN diff INTEGER DEFAULT 0;");
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN period INTEGER DEFAULT 30;");
}); });
} else if (db.version == "3") { } else if (db.version == "3") {
// Upgrade DB Schema to Version 4 // Upgrade DB Schema to Version 4
db.changeVersion("3", "4", db.changeVersion("3", "5",
function(tx) { function(tx) {
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN len INTEGER DEFAULT 6;"); tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN len INTEGER DEFAULT 6;");
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN diff INTEGER DEFAULT 0;"); tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN diff INTEGER DEFAULT 0;");
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN period INTEGER DEFAULT 30;");
});
} else if (db.version == "4") {
// Upgrade DB Schema to Version 4
db.changeVersion("4", "5",
function(tx) {
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN period INTEGER DEFAULT 30;");
}); });
} }
} catch (e) { } catch (e) {
@ -83,7 +92,7 @@ function getOTP() {
function(tx) { function(tx) {
var res = tx.executeSql("select * from OTPStorage order by sort;"); var res = tx.executeSql("select * from OTPStorage order by sort;");
for (var i=0; i < res.rows.length; i++) { for (var i=0; i < res.rows.length; i++) {
appWin.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, res.rows.item(i).len, res.rows.item(i).diff); appWin.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, res.rows.item(i).len, res.rows.item(i).diff, res.rows.item(i).period);
if (res.rows.item(i).fav) appWin.setCover(i); if (res.rows.item(i).fav) appWin.setCover(i);
} }
}); });
@ -106,12 +115,13 @@ function db2json() {
"sort": res.rows.item(i).sort, "sort": res.rows.item(i).sort,
"len": res.rows.item(i).len, "len": res.rows.item(i).len,
"diff": res.rows.item(i).diff, "diff": res.rows.item(i).diff,
"period": res.rows.item(i).period,
}); });
} }
}); });
if (otpList.length > 0) { if (otpList.length > 0) {
return(JSON.stringify({"app": "sailotp", "version": 3, "otplist": otpList})); return(JSON.stringify({"app": "sailotp", "version": 4, "otplist": otpList}));
} else { } else {
return("") return("")
} }
@ -122,7 +132,7 @@ function json2db(jsonString, error) {
var json = JSON.parse(jsonString); var json = JSON.parse(jsonString);
error = ""; error = "";
if ((json.version != "1" || json.version != "2" || json.version != "3") && json.app != "sailotp" ) { if ((json.version != "1" || json.version != "2" || json.version != "3" || json.version != "4") && json.app != "sailotp" ) {
error = "Unrecognized format, file is not a SailOTP export"; error = "Unrecognized format, file is not a SailOTP export";
return(false); return(false);
} else { } else {
@ -133,13 +143,15 @@ function json2db(jsonString, error) {
var otpItem = otpList.shift(); var otpItem = otpList.shift();
if (otpItem.title != "" & otpItem.secret.length >= 16) { if (otpItem.title != "" & otpItem.secret.length >= 16) {
if (json.version == "1") { if (json.version == "1") {
addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter, 0, 6, 0); addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter, 0, 6, 0, 30);
} else if (json.version == "2") { } else if (json.version == "2") {
addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter, otpItem.sort, 6, 0); addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter, otpItem.sort, 6, 0, 30);
} else { } else if (json.version == "3") {
addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter, otpItem.sort, otpItem.len, otpItem.diff); addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter, otpItem.sort, otpItem.len, otpItem.diff, 30);
} else {
addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter, otpItem.sort, otpItem.len, otpItem.diff, otpItem.period);
} }
} }
} }
parentPage.refreshOTPList(); parentPage.refreshOTPList();
return(true); return(true);
@ -151,7 +163,7 @@ function json2db(jsonString, error) {
} }
// Add a new OTP // Add a new OTP
function addOTP(title, secret, type, counter, sort, len, diff) { function addOTP(title, secret, type, counter, sort, len, diff, period) {
var db = getDB(); var db = getDB();
db.transaction( db.transaction(
@ -159,7 +171,7 @@ function addOTP(title, secret, type, counter, sort, len, diff) {
if (checkOTP(title, secret)) { if (checkOTP(title, secret)) {
console.log("Token " + title + " is already in DB"); console.log("Token " + title + " is already in DB");
} else { } else {
tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?, ?, ?, ?, ?, ?, ?);", [title, secret, type, counter, 0, sort, len, diff]); tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?);", [title, secret, type, counter, 0, sort, len, diff, period]);
console.log("Token " + title + " added."); console.log("Token " + title + " added.");
} }
}); });
@ -210,12 +222,12 @@ function resetFav(title, secret) {
} }
// Change an existing OTP // Change an existing OTP
function changeOTP(title, secret, type, counter, len, diff, oldtitle, oldsecret) { function changeOTP(title, secret, type, counter, len, diff, period, oldtitle, oldsecret) {
var db = getDB(); var db = getDB();
db.transaction( db.transaction(
function(tx) { function(tx) {
tx.executeSql("UPDATE OTPStorage SET title=?, secret=?, type=?, counter=?, len=?, diff=? WHERE title=? and secret=?;", [title, secret, type, counter, len, diff, oldtitle, oldsecret]); tx.executeSql("UPDATE OTPStorage SET title=?, secret=?, type=?, counter=?, len=?, diff=?, period=? WHERE title=? and secret=?;", [title, secret, type, counter, len, diff, period, oldtitle, oldsecret]);
console.log("Token " + title + " modified."); console.log("Token " + title + " modified.");
} }
); );

View file

@ -48,6 +48,7 @@ Dialog {
property int paramLen: 6 property int paramLen: 6
property int paramDiff: 0 property int paramDiff: 0
property int paramCounter: 1 // New Counters start at 1 property int paramCounter: 1 // New Counters start at 1
property int paramPeriod: 30
property bool paramNew: false property bool paramNew: false
function checkQR() { function checkQR() {
@ -163,6 +164,20 @@ Dialog {
EnterKey.iconSource: "image://theme/icon-m-enter-accept" EnterKey.iconSource: "image://theme/icon-m-enter-accept"
EnterKey.onClicked: addOTP.accept() EnterKey.onClicked: addOTP.accept()
} }
TextField {
id: otpPeriod
width: parent.width
visible: paramType == "TOTP" ? true : false
label: qsTr("Period (Seconds)")
text: paramPeriod
placeholderText: qsTr("Period (Seconds)")
focus: true
horizontalAlignment: TextInput.AlignLeft
validator: IntValidator {}
EnterKey.iconSource: "image://theme/icon-m-enter-accept"
EnterKey.onClicked: addOTP.accept()
}
TextField { TextField {
id: otpCounter id: otpCounter
width: parent.width width: parent.width
@ -182,7 +197,7 @@ Dialog {
} }
// Check if we can Save // 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 canAccept: otpLabel.text.length > 0 && otpSecret.text.length >= 16 && otpSecret.acceptableInput && otpLen.text >= 1 && ((paramType == "TOTP" && otpDiff.text != "" && otpPeriod.text > 0) || paramType == "TOTP_STEAM" || otpCounter.text.length > 0) ? true : false
// Save if page is Left with Add // Save if page is Left with Add
onDone: { onDone: {
@ -190,10 +205,10 @@ Dialog {
// Save the entry to the Config DB // Save the entry to the Config DB
if (paramLabel != "" && paramKey != "" && !paramNew) { if (paramLabel != "" && paramKey != "" && !paramNew) {
// Parameters where filled -> Change existing entry // Parameters where filled -> Change existing entry
DB.changeOTP(otpLabel.text, otpSecret.text, paramType, otpCounter.text, otpLen.text, otpDiff.text, paramLabel, paramKey) DB.changeOTP(otpLabel.text, otpSecret.text, paramType, otpCounter.text, otpLen.text, otpDiff.text, otpPeriod.text, paramLabel, paramKey)
} else { } else {
// There were no parameters -> Add new entry // There were no parameters -> Add new entry
DB.addOTP(otpLabel.text, otpSecret.text, paramType, otpCounter.text, appWin.listModel.count, otpLen.text, otpDiff.text); DB.addOTP(otpLabel.text, otpSecret.text, paramType, otpCounter.text, appWin.listModel.count, otpLen.text, otpDiff.text, otpPeriod.text);
} }
// Refresh the main Page // Refresh the main Page

View file

@ -64,10 +64,10 @@ Page {
for (var i=0; i<appWin.listModel.count; i++) { for (var i=0; i<appWin.listModel.count; i++) {
if (appWin.listModel.get(i).type === "TOTP" || appWin.listModel.get(i).type === "TOTP_STEAM" ) { if (appWin.listModel.get(i).type === "TOTP" || appWin.listModel.get(i).type === "TOTP_STEAM" ) {
// Take derivation into account if set // Take derivation into account if set
var seconds = (curDate.getSeconds() + appWin.listModel.get(i).diff) % 30; var seconds = (curDate.getSeconds() + appWin.listModel.get(i).diff) % appWin.listModel.get(i).period;
// 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) // Only update on full period Seconds or if last run of the Functions is more than 2s in the past (e.g. app was in background)
if (appWin.listModel.get(i).otp === "------" || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) { if (appWin.listModel.get(i).otp === "------" || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) {
var curOTP = OTP.calcOTP(appWin.listModel.get(i).secret, appWin.listModel.get(i).type, appWin.listModel.get(i).len, appWin.listModel.get(i).diff, 0); var curOTP = OTP.calcOTP(appWin.listModel.get(i).secret, appWin.listModel.get(i).type, appWin.listModel.get(i).len, appWin.listModel.get(i).diff, 0, appWin.listModel.get(i).period);
appWin.listModel.setProperty(i, "otp", curOTP); appWin.listModel.setProperty(i, "otp", curOTP);
} else if (appWin.coverType === "HOTP" && (curDate.getTime() - lastUpdated > 2000) && appWin.listModel.get(i).fav === 1) { } else if (appWin.coverType === "HOTP" && (curDate.getTime() - lastUpdated > 2000) && appWin.listModel.get(i).fav === 1) {
// If we are coming back from the CoverPage update OTP value if current favourite is HOTP // If we are coming back from the CoverPage update OTP value if current favourite is HOTP
@ -311,7 +311,7 @@ Page {
MenuItem { MenuItem {
text: qsTr("Edit") text: qsTr("Edit")
onClicked: { onClicked: {
pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage, paramLabel: title, paramKey: secret, paramType: type, paramLen: len, paramDiff: diff, paramCounter: DB.getCounter(title, secret, false)}) pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage, paramLabel: title, paramKey: secret, paramType: type, paramLen: len, paramDiff: diff, paramCounter: DB.getCounter(title, secret, false), paramPeriod: period})
} }
} }
MenuItem { MenuItem {