mirror of
https://github.com/seiichiro0185/sailotp.git
synced 2024-11-14 21:16:42 +00:00
Merge pull request #65 from AndreySV/custom-period
Implement custom period for TOTP tokens
This commit is contained in:
commit
020d917fe8
7 changed files with 65 additions and 30 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -2,6 +2,12 @@
|
|||
*.pro.user
|
||||
*.pro.user.*
|
||||
*.autosave
|
||||
*.o
|
||||
moc_*
|
||||
documentation.list
|
||||
harbour-sailotp
|
||||
RPMS
|
||||
Makefile
|
||||
rpm/harbour-sailotp.spec
|
||||
rpm/harbour-sailotp.spec.*
|
||||
translations/*.qm
|
||||
|
|
|
@ -45,7 +45,7 @@ CoverBackground {
|
|||
var seconds = (curDate.getSeconds() + appWin.coverDiff) % 30
|
||||
|
||||
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
|
||||
|
|
|
@ -45,6 +45,7 @@ ApplicationWindow
|
|||
property string coverOTP: "------"
|
||||
property int coverLen: 6
|
||||
property int coverDiff: 0
|
||||
property int coverPeriod: 30
|
||||
property int coverIndex: 0
|
||||
|
||||
// Global Listmodel for Tokens
|
||||
|
@ -64,8 +65,8 @@ ApplicationWindow
|
|||
}
|
||||
|
||||
// Add an entry to the list
|
||||
function appendOTP(title, secret, type, counter, fav, len, diff) {
|
||||
listModel.append({"secret": secret, "title": title, "fav": fav, "type": type, "counter": counter, "len": len, "diff": diff, "otp": "------", "itemVisible": true});
|
||||
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, "period": period, "otp": "------", "itemVisible": true});
|
||||
}
|
||||
|
||||
// Set the OTP shown on the Cover
|
||||
|
@ -76,6 +77,7 @@ ApplicationWindow
|
|||
coverType = listModel.get(index).type;
|
||||
coverLen = listModel.get(index).len;
|
||||
coverDiff = listModel.get(index).diff;
|
||||
coverPeriod = listModel.get(index).period;
|
||||
coverIndex = index;
|
||||
if (coverType == "TOTP") { coverOTP = "------"; } else { coverOTP = listModel.get(index).otp; }
|
||||
for (var i=0; i<listModel.count; i++) {
|
||||
|
|
|
@ -77,7 +77,7 @@ var steamChars = ['2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C',
|
|||
// counter: counter value for HOTP
|
||||
// length: length of the returned token
|
||||
// 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
|
||||
var key = base32tohex(secret);
|
||||
var factor = "";
|
||||
|
@ -85,8 +85,8 @@ function calcOTP(secret, type, len, diff, counter) {
|
|||
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
|
||||
var epoch = Math.round(new Date().getTime() / 1000.0) + diff;
|
||||
// Get last full 30 / 60 Seconds and convert to HEX
|
||||
factor = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0');
|
||||
// Get last full period Seconds and convert to HEX
|
||||
factor = leftpad(dec2hex(Math.floor(epoch / period)), 16, '0');
|
||||
} else {
|
||||
factor = leftpad(dec2hex(counter), 16, '0');
|
||||
}
|
||||
|
|
|
@ -36,13 +36,13 @@ function getDB() {
|
|||
|
||||
if (db.version == "") {
|
||||
// Initialize an empty DB, Create the Table
|
||||
db.changeVersion("", "3",
|
||||
db.changeVersion("", "5",
|
||||
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") {
|
||||
// Upgrade DB Schema to Version 4
|
||||
db.changeVersion("1.0", "4",
|
||||
db.changeVersion("1.0", "5",
|
||||
function(tx) {
|
||||
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN type TEXT DEFAULT 'TOTP';");
|
||||
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 len INTEGER DEFAULT 6;");
|
||||
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") {
|
||||
// Upgrade DB Schema to Version 3
|
||||
db.changeVersion("2", "4",
|
||||
db.changeVersion("2", "5",
|
||||
function(tx) {
|
||||
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 diff INTEGER DEFAULT 0;");
|
||||
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN period INTEGER DEFAULT 30;");
|
||||
});
|
||||
} else if (db.version == "3") {
|
||||
// Upgrade DB Schema to Version 4
|
||||
db.changeVersion("3", "4",
|
||||
db.changeVersion("3", "5",
|
||||
function(tx) {
|
||||
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 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) {
|
||||
|
@ -83,7 +92,7 @@ function getOTP() {
|
|||
function(tx) {
|
||||
var res = tx.executeSql("select * from OTPStorage order by sort;");
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
@ -106,12 +115,13 @@ function db2json() {
|
|||
"sort": res.rows.item(i).sort,
|
||||
"len": res.rows.item(i).len,
|
||||
"diff": res.rows.item(i).diff,
|
||||
"period": res.rows.item(i).period,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (otpList.length > 0) {
|
||||
return(JSON.stringify({"app": "sailotp", "version": 3, "otplist": otpList}));
|
||||
return(JSON.stringify({"app": "sailotp", "version": 4, "otplist": otpList}));
|
||||
} else {
|
||||
return("")
|
||||
}
|
||||
|
@ -122,7 +132,7 @@ function json2db(jsonString, error) {
|
|||
var json = JSON.parse(jsonString);
|
||||
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";
|
||||
return(false);
|
||||
} else {
|
||||
|
@ -133,13 +143,15 @@ function json2db(jsonString, error) {
|
|||
var otpItem = otpList.shift();
|
||||
if (otpItem.title != "" & otpItem.secret.length >= 16) {
|
||||
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") {
|
||||
addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter, otpItem.sort, 6, 0);
|
||||
} else {
|
||||
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, 6, 0, 30);
|
||||
} else if (json.version == "3") {
|
||||
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();
|
||||
return(true);
|
||||
|
@ -151,7 +163,7 @@ function json2db(jsonString, error) {
|
|||
}
|
||||
|
||||
// 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();
|
||||
|
||||
db.transaction(
|
||||
|
@ -159,7 +171,7 @@ function addOTP(title, secret, type, counter, sort, len, diff) {
|
|||
if (checkOTP(title, secret)) {
|
||||
console.log("Token " + title + " is already in DB");
|
||||
} 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.");
|
||||
}
|
||||
});
|
||||
|
@ -210,12 +222,12 @@ function resetFav(title, secret) {
|
|||
}
|
||||
|
||||
// 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();
|
||||
|
||||
db.transaction(
|
||||
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.");
|
||||
}
|
||||
);
|
||||
|
|
|
@ -48,6 +48,7 @@ Dialog {
|
|||
property int paramLen: 6
|
||||
property int paramDiff: 0
|
||||
property int paramCounter: 1 // New Counters start at 1
|
||||
property int paramPeriod: 30
|
||||
property bool paramNew: false
|
||||
|
||||
function checkQR() {
|
||||
|
@ -163,6 +164,20 @@ Dialog {
|
|||
EnterKey.iconSource: "image://theme/icon-m-enter-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 {
|
||||
id: otpCounter
|
||||
width: parent.width
|
||||
|
@ -182,7 +197,7 @@ Dialog {
|
|||
}
|
||||
|
||||
// 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
|
||||
onDone: {
|
||||
|
@ -190,10 +205,10 @@ Dialog {
|
|||
// 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)
|
||||
DB.changeOTP(otpLabel.text, otpSecret.text, paramType, otpCounter.text, otpLen.text, otpDiff.text, otpPeriod.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);
|
||||
DB.addOTP(otpLabel.text, otpSecret.text, paramType, otpCounter.text, appWin.listModel.count, otpLen.text, otpDiff.text, otpPeriod.text);
|
||||
}
|
||||
|
||||
// Refresh the main Page
|
||||
|
|
|
@ -64,10 +64,10 @@ Page {
|
|||
for (var i=0; i<appWin.listModel.count; i++) {
|
||||
if (appWin.listModel.get(i).type === "TOTP" || appWin.listModel.get(i).type === "TOTP_STEAM" ) {
|
||||
// Take derivation into account if set
|
||||
var seconds = (curDate.getSeconds() + appWin.listModel.get(i).diff) % 30;
|
||||
// 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)
|
||||
var seconds = (curDate.getSeconds() + appWin.listModel.get(i).diff) % appWin.listModel.get(i).period;
|
||||
// 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)) {
|
||||
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);
|
||||
} 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
|
||||
|
@ -311,7 +311,7 @@ Page {
|
|||
MenuItem {
|
||||
text: qsTr("Edit")
|
||||
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 {
|
||||
|
|
Loading…
Reference in a new issue