mirror of
https://github.com/seiichiro0185/sailotp.git
synced 2024-11-22 07:39:42 +00:00
Added basic import and export
The DB can be exported to json file Added handling of duplicate entries
This commit is contained in:
parent
09edf6c8b8
commit
743b06f065
3 changed files with 133 additions and 58 deletions
|
@ -5,25 +5,25 @@
|
||||||
* Redistribution and use in source and binary forms, with or without modification,
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
* are permitted provided that the following conditions are met:
|
* are permitted provided that the following conditions are met:
|
||||||
*
|
*
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
* list of conditions and the following disclaimer.
|
* list of conditions and the following disclaimer.
|
||||||
*
|
*
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this
|
* 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
|
* list of conditions and the following disclaimer in the documentation and/or other
|
||||||
* materials provided with the distribution.
|
* materials provided with the distribution.
|
||||||
*
|
*
|
||||||
* 3. The names of the contributors may not be used to endorse or promote products
|
* 3. The names of the contributors may not 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 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
* FOR 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 ON ANY
|
||||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
* 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,
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||||
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -39,8 +39,7 @@ function getDB() {
|
||||||
db.changeVersion("", "2",
|
db.changeVersion("", "2",
|
||||||
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);");
|
tx.executeSql("CREATE TABLE IF NOT EXISTS OTPStorage(title TEXT, secret TEXT, type TEXT DEFAULT 'TOPT', counter INTEGER DEFAULT 0, fav INTEGER DEFAULT 0);");
|
||||||
}
|
});
|
||||||
);
|
|
||||||
} else if (db.version == "1.0") {
|
} else if (db.version == "1.0") {
|
||||||
// Upgrade DB Schema to Version 2
|
// Upgrade DB Schema to Version 2
|
||||||
db.changeVersion("1.0", "2",
|
db.changeVersion("1.0", "2",
|
||||||
|
@ -48,8 +47,7 @@ function getDB() {
|
||||||
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;");
|
||||||
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN fav INTEGER DEFAULT 0;");
|
tx.executeSql("ALTER TABLE OTPStorage ADD COLUMN fav INTEGER DEFAULT 0;");
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// DB Failed to open
|
// DB Failed to open
|
||||||
|
@ -70,8 +68,57 @@ function getOTP() {
|
||||||
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);
|
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, res.rows.item(i).type);
|
if (res.rows.item(i).fav) mainPage.setCoverOTP(res.rows.item(i).title, res.rows.item(i).secret, res.rows.item(i).type);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all OTP Values and put them into a JSON-Object
|
||||||
|
function db2json(password) {
|
||||||
|
var db = getDB();
|
||||||
|
var otpList = [];
|
||||||
|
|
||||||
|
db.transaction(
|
||||||
|
function(tx) {
|
||||||
|
var res = tx.executeSql("select * from OTPStorage;");
|
||||||
|
for (var i=0; i < res.rows.length; i++) {
|
||||||
|
otpList.push({
|
||||||
|
"title": res.rows.item(i).title,
|
||||||
|
"secret": res.rows.item(i).secret,
|
||||||
|
"type": res.rows.item(i).type,
|
||||||
|
"counter": res.rows.item(i).counter,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
)
|
});
|
||||||
|
|
||||||
|
if (otpList.length > 0) {
|
||||||
|
return(JSON.stringify({"app": "sailotp", "version": 1, "otplist": otpList}));
|
||||||
|
} else {
|
||||||
|
return("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Values from JSON and put them into the DB
|
||||||
|
function json2db(jsonString, password) {
|
||||||
|
var json = JSON.parse(jsonString);
|
||||||
|
|
||||||
|
if (json.version != "1" && json.app != "sailotp" ) {
|
||||||
|
console.log("Unrecognized JSON format");
|
||||||
|
return(false);
|
||||||
|
} else {
|
||||||
|
var otpList = [];
|
||||||
|
otpList = json.otplist;
|
||||||
|
if (otpList.length > 0) {
|
||||||
|
while(otpList.length > 0) {
|
||||||
|
var otpItem = otpList.shift();
|
||||||
|
if (otpItem.title != "" & otpItem.secret.length >= 16) {
|
||||||
|
addOTP(otpItem.title, otpItem.secret, otpItem.type, otpItem.counter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parentPage.refreshOTPList();
|
||||||
|
} else {
|
||||||
|
console.log("File contains no Items");
|
||||||
|
return(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new OTP
|
// Add a new OTP
|
||||||
|
@ -80,9 +127,26 @@ function addOTP(title, secret, type, counter) {
|
||||||
|
|
||||||
db.transaction(
|
db.transaction(
|
||||||
function(tx) {
|
function(tx) {
|
||||||
tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?, ?, ?, ?);", [title, secret, type, counter, 0]);
|
if (checkOTP(title, secret)) {
|
||||||
}
|
console.log("Token " + title + " is already in DB");
|
||||||
)
|
} else {
|
||||||
|
tx.executeSql("INSERT INTO OTPStorage VALUES(?, ?, ?, ?, ?);", [title, secret, type, counter, 0]);
|
||||||
|
console.log("Token " + title + " added.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if an OTP Token already exists in the DB
|
||||||
|
function checkOTP(title, secret) {
|
||||||
|
var db = getDB();
|
||||||
|
var res
|
||||||
|
|
||||||
|
db.transaction(
|
||||||
|
function(tx) {
|
||||||
|
res = tx.executeSql("select title FROM OTPStorage WHERE title=? and secret=?;", [title, secret]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.rows.length > 0 ? true : false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove an existing OTP
|
// Remove an existing OTP
|
||||||
|
@ -92,10 +156,10 @@ function removeOTP(title, secret) {
|
||||||
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]);
|
||||||
}
|
});
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set OTP to favourite
|
||||||
function setFav(title, secret) {
|
function setFav(title, secret) {
|
||||||
var db = getDB();
|
var db = getDB();
|
||||||
|
|
||||||
|
@ -103,18 +167,17 @@ function setFav(title, secret) {
|
||||||
function(tx) {
|
function(tx) {
|
||||||
tx.executeSql("UPDATE OTPStorage set fav = 0");
|
tx.executeSql("UPDATE OTPStorage set fav = 0");
|
||||||
tx.executeSql("UPDATE OTPStorage set fav = 1 WHERE title=? and secret=?;", [title, secret]);
|
tx.executeSql("UPDATE OTPStorage set fav = 1 WHERE title=? and secret=?;", [title, secret]);
|
||||||
}
|
});
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset favourite Flag for OTP
|
||||||
function resetFav(title, secret) {
|
function resetFav(title, secret) {
|
||||||
var db = getDB();
|
var db = getDB();
|
||||||
|
|
||||||
db.transaction(
|
db.transaction(
|
||||||
function(tx) {
|
function(tx) {
|
||||||
tx.executeSql("UPDATE OTPStorage set fav = 0");
|
tx.executeSql("UPDATE OTPStorage set fav = 0");
|
||||||
}
|
});
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change an existing OTP
|
// Change an existing OTP
|
||||||
|
@ -123,12 +186,15 @@ function changeOTP(title, secret, type, counter, oldtitle, oldsecret) {
|
||||||
|
|
||||||
db.transaction(
|
db.transaction(
|
||||||
function(tx) {
|
function(tx) {
|
||||||
tx.executeSql("UPDATE OTPStorage SET title=?, secret=?, type=?, counter=? WHERE title=? and secret=?;", [title, secret, type, counter, oldtitle, oldsecret]);
|
if (checkOTP(title, secret)) {
|
||||||
}
|
console.log("Token " + title + " is already in DB");
|
||||||
)
|
} else {
|
||||||
|
tx.executeSql("UPDATE OTPStorage SET title=?, secret=?, type=?, counter=? WHERE title=? and secret=?;", [title, secret, type, counter, oldtitle, oldsecret]);
|
||||||
|
console.log("Token " + title + " modified.");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Get the counter for a HOTP value, incerment the counter on request
|
// Get the counter for a HOTP value, incerment the counter on request
|
||||||
function getCounter(title, secret, increment) {
|
function getCounter(title, secret, increment) {
|
||||||
var db = getDB();
|
var db = getDB();
|
||||||
|
@ -139,8 +205,7 @@ function getCounter(title, secret, increment) {
|
||||||
function(tx) {
|
function(tx) {
|
||||||
res = tx.executeSql("SELECT counter FROM OTPStorage where title=? and secret=?;", [title, secret]);
|
res = tx.executeSql("SELECT counter FROM OTPStorage where title=? and secret=?;", [title, secret]);
|
||||||
if (increment) tx.executeSql("UPDATE OTPStorage set counter=counter+1 WHERE title=? and secret=?;", [title, secret]);
|
if (increment) tx.executeSql("UPDATE OTPStorage set counter=counter+1 WHERE title=? and secret=?;", [title, secret]);
|
||||||
}
|
});
|
||||||
)
|
|
||||||
|
|
||||||
return res.rows.item(0).counter;
|
return res.rows.item(0).counter;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,13 +37,16 @@ import "../lib/storage.js" as DB // Import the storage library for Config-Access
|
||||||
Dialog {
|
Dialog {
|
||||||
id: exportPage
|
id: exportPage
|
||||||
|
|
||||||
|
// We get the Object of the parent page on call to refresh it after adding a new Entry
|
||||||
|
property QtObject parentPage: null
|
||||||
|
|
||||||
property string mode: "export"
|
property string mode: "export"
|
||||||
|
|
||||||
// FileIO Object for reading / writing files
|
// FileIO Object for reading / writing files
|
||||||
FileIO {
|
FileIO {
|
||||||
id: exportFile
|
id: exportFile
|
||||||
source: fileName.text
|
source: fileName.text
|
||||||
onError: console.log(msg)
|
onError: console.log(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
SilicaFlickable {
|
SilicaFlickable {
|
||||||
|
@ -90,9 +93,16 @@ Dialog {
|
||||||
canAccept: fileName.text.length > 0 && filePassword.text.length > 0 ? true : false
|
canAccept: fileName.text.length > 0 && filePassword.text.length > 0 ? true : false
|
||||||
|
|
||||||
// Do the DB-Export / Import
|
// Do the DB-Export / Import
|
||||||
|
// TODO: Error handling and enctyption
|
||||||
onDone: {
|
onDone: {
|
||||||
if (result == DialogResult.Accepted) {
|
if (result == DialogResult.Accepted) {
|
||||||
// TODO
|
if (mode == "export") {
|
||||||
|
console.log("Exporting to " + fileName.text, filePassword.text);
|
||||||
|
exportFile.write(DB.db2json());
|
||||||
|
} else if(mode == "import") {
|
||||||
|
console.log("Importing ftom " + fileName.text, filePassword.text);
|
||||||
|
DB.json2db(exportFile.read(), filePassword)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,25 +5,25 @@
|
||||||
* Redistribution and use in source and binary forms, with or without modification,
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
* are permitted provided that the following conditions are met:
|
* are permitted provided that the following conditions are met:
|
||||||
*
|
*
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
* list of conditions and the following disclaimer.
|
* list of conditions and the following disclaimer.
|
||||||
*
|
*
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this
|
* 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
|
* list of conditions and the following disclaimer in the documentation and/or other
|
||||||
* materials provided with the distribution.
|
* materials provided with the distribution.
|
||||||
*
|
*
|
||||||
* 3. The names of the contributors may not be used to endorse or promote products
|
* 3. The names of the contributors may not 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 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
* FOR 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 ON ANY
|
||||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
* 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,
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||||
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ Page {
|
||||||
id: otpListModel
|
id: otpListModel
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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: 0
|
property double lastUpdated: 0
|
||||||
|
|
||||||
// Add an entry to the list
|
// Add an entry to the list
|
||||||
|
@ -71,11 +71,11 @@ Page {
|
||||||
|
|
||||||
// Calculate new OTPs for every entry
|
// Calculate new OTPs for every entry
|
||||||
function refreshOTPValues() {
|
function refreshOTPValues() {
|
||||||
// get seconds from current Date
|
// get seconds from current Date
|
||||||
var curDate = new Date();
|
var curDate = new Date();
|
||||||
var seconds = curDate.getSeconds();
|
var seconds = curDate.getSeconds();
|
||||||
|
|
||||||
// Iterate over all List entries
|
// Iterate over all List entries
|
||||||
for (var i=0; i<otpListModel.count; i++) {
|
for (var i=0; i<otpListModel.count; i++) {
|
||||||
if (otpListModel.get(i).type == "TOTP") {
|
if (otpListModel.get(i).type == "TOTP") {
|
||||||
// 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 30 / 60 Seconds or if last run of the Functions is more than 2s in the past (e.g. app was in background)
|
||||||
|
@ -89,9 +89,9 @@ Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the Progressbar
|
// Update the Progressbar
|
||||||
updateProgress.value = 29 - (seconds % 30)
|
updateProgress.value = 29 - (seconds % 30)
|
||||||
// Set lastUpdate property
|
// Set lastUpdate property
|
||||||
lastUpdated = curDate.getTime();
|
lastUpdated = curDate.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ Page {
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Export / Import DB"
|
text: "Export / Import DB"
|
||||||
onClicked: pageStack.push(Qt.resolvedUrl("ExportPage.qml"))
|
onClicked: pageStack.push(Qt.resolvedUrl("ExportPage.qml"), {parentPage: mainPage})
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Add OTP"
|
text: "Add OTP"
|
||||||
|
@ -241,7 +241,7 @@ Page {
|
||||||
VerticalScrollDecorator{}
|
VerticalScrollDecorator{}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// Load list of OTP-Entries
|
// Load list of OTP-Entries
|
||||||
refreshOTPList();
|
refreshOTPList();
|
||||||
console.log("SailOTP Version " + Qt.application.version + " started");
|
console.log("SailOTP Version " + Qt.application.version + " started");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue