Add support for multiple keys and move config to config file
This commit is contained in:
parent
c3fd3beaea
commit
d55fe53181
7 changed files with 263 additions and 76 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
server/config
|
||||
server/history.db
|
|
@ -1,3 +1,7 @@
|
|||
a:visited {
|
||||
color: #1b7340;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: 0;
|
||||
box-shadow: 0 .25rem 1rem rgba(48,55,66,.15);
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
// polyfills for older browsers
|
||||
if (typeof String.prototype.startsWith === 'undefined') {
|
||||
String.prototype.startsWith = function (needle) {
|
||||
return this.indexOf(needle) === 0;
|
||||
};
|
||||
}
|
||||
|
||||
// === Core functions ===
|
||||
var api_base = "http://localhost:5000";
|
||||
var app_token = null;
|
||||
var app_key_id = null;
|
||||
|
||||
function do_request(method, url, data, onsuccess, onerror) {
|
||||
var req = new XMLHttpRequest();
|
||||
|
@ -26,9 +34,16 @@ function oninit() {
|
|||
window.location.href = "#tracker"
|
||||
|
||||
app_token = _get_token();
|
||||
app_key_id = _get_key_id(app_token);
|
||||
if (app_token === null || app_key_id === null) {
|
||||
ui_tracker_error("Interner Fehler: kein/ungültiges Token. Bitte stelle sicher, dass du diese Seite durch Scannen des QR Codes erreicht hast");
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.location.href.search("#keyholder") > 0)
|
||||
keyholder_onload();
|
||||
|
||||
update_key_name();
|
||||
update_key_status();
|
||||
load_saved_tracker_data();
|
||||
|
||||
|
@ -43,31 +58,31 @@ function oninit() {
|
|||
}
|
||||
|
||||
function tracker_onsend(e) {
|
||||
ui_tracker_noerror();
|
||||
ui_tracker_error(false);
|
||||
|
||||
var name = document.getElementById("input-name").value;
|
||||
var contact = document.getElementById("input-contact").value;
|
||||
var save_data = document.getElementById("input-tracker-save").checked;
|
||||
|
||||
if (name.length < 3 || contact.length < 3) {
|
||||
if (name.length < 2 || contact.length < 2) {
|
||||
ui_tracker_error("Name und Kontakt müssen mindestens 3 Zeichen lang sein")
|
||||
return;
|
||||
}
|
||||
|
||||
if (app_token === null) {
|
||||
ui_tracker_error("Interner Fehler: kein Token. Bitte stelle sicher, dass du diese Seite durch Scannen den QR Codes erreicht hast");
|
||||
ui_tracker_error("Interner Fehler: kein Token. Bitte stelle sicher, dass du diese Seite durch Scannen des QR Codes erreicht hast");
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById("btn-tracker").classList.add("loading");
|
||||
ui_tracker_send_loading(true);
|
||||
do_request(
|
||||
"POST",
|
||||
"/claim",
|
||||
"name="+window.encodeURI(name)+"&contact="+window.encodeURI(contact),
|
||||
function(r) {
|
||||
document.getElementById("btn-tracker").classList.remove("loading");
|
||||
ui_tracker_send_loading(true);
|
||||
if (r.status == 200) {
|
||||
localStorage.setItem("last_cid", r.response);
|
||||
localStorage.setItem(app_key_id + "_last_cid", r.response);
|
||||
if (save_data) {
|
||||
localStorage.setItem("name", name);
|
||||
localStorage.setItem("contact", contact);
|
||||
|
@ -83,7 +98,7 @@ function tracker_onsend(e) {
|
|||
}
|
||||
},
|
||||
function(r) {
|
||||
document.getElementById("btn-tracker").classList.remove("loading");
|
||||
ui_tracker_send_loading(true);
|
||||
ui_tracker_error("Fehlgeschlagen: Bitte überprüfe deine Internetverbindung");
|
||||
console.log("Claim request failed");
|
||||
console.log(r);
|
||||
|
@ -92,14 +107,16 @@ function tracker_onsend(e) {
|
|||
}
|
||||
|
||||
function keyholder_onload() {
|
||||
ui_password_noerror();
|
||||
ui_keyholder_noerror();
|
||||
ui_password_error(false);
|
||||
ui_keyholder_error(false);
|
||||
ui_keyholder_refresh_loading(true);
|
||||
|
||||
do_request(
|
||||
"GET",
|
||||
"/keyholder",
|
||||
null,
|
||||
function(r) {
|
||||
ui_keyholder_refresh_loading(false);
|
||||
if (r.status == 200) {
|
||||
ui_show_keyholder(JSON.parse(r.response));
|
||||
} else if (r.status == 403) {
|
||||
|
@ -111,6 +128,7 @@ function keyholder_onload() {
|
|||
}
|
||||
},
|
||||
function(r) {
|
||||
ui_keyholder_refresh_loading(false);
|
||||
ui_keyholder_error("Fehlgeschlagen: Bitte überprüfe deine Internetverbindung");
|
||||
console.log("Keyholder request failed");
|
||||
console.log(r);
|
||||
|
@ -119,8 +137,8 @@ function keyholder_onload() {
|
|||
}
|
||||
|
||||
function password_submit() {
|
||||
ui_password_noerror();
|
||||
ui_keyholder_noerror();
|
||||
ui_password_error(false);
|
||||
ui_keyholder_error(false);
|
||||
|
||||
var pass = document.getElementById("input-pass").value;
|
||||
var save_token = document.getElementById("input-pass-save").checked;
|
||||
|
@ -133,17 +151,28 @@ function password_submit() {
|
|||
function(r) {
|
||||
document.getElementById("btn-send-pw").classList.remove("loading");
|
||||
if (r.status == 200) {
|
||||
// Delete previously used token
|
||||
if (app_token !== null && localStorage.getItem("token") !== null) {
|
||||
var tokenlist = localStorage.getItem("token").split(",");
|
||||
if (tokenlist.indexOf(app_token) !== -1) {
|
||||
tokenlist.splice(tokenlist.indexOf(app_token), 1);
|
||||
localStorage.setItem("token", tokenlist.join(","));
|
||||
}
|
||||
}
|
||||
|
||||
app_token = r.response;
|
||||
if (save_token)
|
||||
localStorage.setItem("token", app_token);
|
||||
else
|
||||
localStorage.removeItem("token");
|
||||
if (save_token) {
|
||||
if (localStorage.getItem("token") !== null)
|
||||
localStorage.setItem("token", localStorage.getItem("token") + "," + app_token);
|
||||
else
|
||||
localStorage.setItem("token", app_token)
|
||||
}
|
||||
|
||||
ui_keyholder_loading();
|
||||
keyholder_onload();
|
||||
|
||||
} else if (r.status == 401) {
|
||||
ui_password_error();
|
||||
ui_password_error(true);
|
||||
} else {
|
||||
ui_keyholder_error("Fehlgeschlagen: Status code " + r.status);
|
||||
console.log("Auth request failed (Status code)");
|
||||
|
@ -162,10 +191,11 @@ function password_submit() {
|
|||
|
||||
// === Helper functions ===
|
||||
function update_key_status() {
|
||||
if (localStorage.getItem("last_cid") !== null) {
|
||||
var last_cid = localStorage.getItem(app_key_id + "_last_cid");
|
||||
if (last_cid !== null) {
|
||||
do_request(
|
||||
"GET",
|
||||
"/status/" + localStorage.getItem("last_cid"),
|
||||
"/status/" + last_cid,
|
||||
null,
|
||||
function(r) {
|
||||
if (r.status == 200 && r.response == "latest")
|
||||
|
@ -178,6 +208,32 @@ function update_key_status() {
|
|||
}
|
||||
}
|
||||
|
||||
function update_key_name() {
|
||||
// For perception purposes, the last key name is cached but
|
||||
// updated as soon as possible.
|
||||
var last_key_name = localStorage.getItem(app_key_id + "_last_key_name");
|
||||
if (last_key_name !== null)
|
||||
ui_set_key_name(last_key_name);
|
||||
do_request(
|
||||
"GET",
|
||||
"/keyname",
|
||||
null,
|
||||
function(r) {
|
||||
if (r.status == 200) {
|
||||
ui_set_key_name(r.response);
|
||||
localStorage.setItem(app_key_id + "_last_key_name", r.response);
|
||||
} else {
|
||||
ui_set_key_name("?");
|
||||
console.log("Keyname request failed (status code " + r.status + ")");
|
||||
}
|
||||
},
|
||||
function(r) {
|
||||
ui_set_key_name("?");
|
||||
console.log("Keyname request failed"); console.log(r)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function load_saved_tracker_data() {
|
||||
if (localStorage.getItem("name") !== null)
|
||||
document.getElementById("input-name").value = localStorage.getItem("name");
|
||||
|
@ -186,19 +242,44 @@ function load_saved_tracker_data() {
|
|||
}
|
||||
|
||||
function _get_token() {
|
||||
if (localStorage.getItem("token") === null) {
|
||||
// Very dirty parsing as of now
|
||||
var params_str = window.location.search;
|
||||
if (params_str.search("&") >= 0)
|
||||
return null;
|
||||
// A token in the URL is always required to know the key
|
||||
var token = null;
|
||||
// Very dirty parsing as of now
|
||||
var params_str = window.location.search;
|
||||
if (params_str.search("&") >= 0)
|
||||
return null;
|
||||
|
||||
var parts = params_str.split("token=");
|
||||
if (parts.length != 2)
|
||||
var parts = params_str.split("token=");
|
||||
if (parts.length != 2)
|
||||
return null
|
||||
else
|
||||
token = parts[1];
|
||||
|
||||
if (localStorage.getItem("token") === null) {
|
||||
return token;
|
||||
} else {
|
||||
var key_id = token.split(":")[0];
|
||||
|
||||
var tokenlist = localStorage.getItem("token").split(",");
|
||||
// This returns the last stored token for this key.
|
||||
// Generally it should not happen (tm) that there is more than one token.
|
||||
for (var i = tokenlist.length - 1; i >= 0; i--) {
|
||||
if (tokenlist[i].indexOf(key_id + ":") === 0)
|
||||
return tokenlist[i];
|
||||
}
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
function _get_key_id(token) {
|
||||
if (token === null) {
|
||||
return null;
|
||||
} else {
|
||||
var parts = token.split(":");
|
||||
if (parts.length !== 2)
|
||||
return null
|
||||
else
|
||||
return parts[1];
|
||||
} else {
|
||||
return localStorage.getItem("token");
|
||||
return parts[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -222,6 +303,11 @@ function ui_set_has_not_key() {
|
|||
document.getElementById("tracker-formgroup").style.opacity = 1
|
||||
}
|
||||
|
||||
function ui_set_key_name(name) {
|
||||
document.getElementById("tracker-key-name").textContent = name;
|
||||
document.getElementById("keyholder-key-name").textContent = name;
|
||||
}
|
||||
|
||||
function ui_keyholder_loading() {
|
||||
document.getElementById("keyholder-loading").style.display = "block"
|
||||
document.getElementById("btn-send-pw").style.display = "none"
|
||||
|
@ -265,28 +351,41 @@ function ui_show_keyholder(keyholder) {
|
|||
document.getElementById("keyholder-table-container").style.display = "block"
|
||||
}
|
||||
|
||||
function ui_tracker_error(msg) {
|
||||
document.getElementById("tracker-error").innerHTML = msg;
|
||||
document.getElementById("tracker-error").style.display = "block";
|
||||
function ui_keyholder_refresh_loading(is_loading) {
|
||||
if (is_loading)
|
||||
document.getElementById("btn-update").classList.add("loading");
|
||||
else
|
||||
document.getElementById("btn-update").classList.remove("loading");
|
||||
}
|
||||
|
||||
function ui_tracker_noerror(msg) {
|
||||
document.getElementById("tracker-error").style.display = "none";
|
||||
function ui_tracker_send_loading(is_loading) {
|
||||
if (is_loading)
|
||||
document.getElementById("btn-tracker").classList.add("loading");
|
||||
else
|
||||
document.getElementById("btn-tracker").classList.remove("loading");
|
||||
}
|
||||
|
||||
function ui_tracker_error(msg) {
|
||||
if (msg === false) {
|
||||
document.getElementById("tracker-error").style.display = "none";
|
||||
} else {
|
||||
document.getElementById("tracker-error").innerHTML = msg;
|
||||
document.getElementById("tracker-error").style.display = "block";
|
||||
}
|
||||
}
|
||||
|
||||
function ui_keyholder_error(msg) {
|
||||
document.getElementById("keyholder-error").innerHTML = msg;
|
||||
document.getElementById("keyholder-error").style.display = "block";
|
||||
if (msg === false) {
|
||||
document.getElementById("keyholder-error").style.display = "none";
|
||||
} else {
|
||||
document.getElementById("keyholder-error").innerHTML = msg;
|
||||
document.getElementById("keyholder-error").style.display = "block";
|
||||
}
|
||||
}
|
||||
|
||||
function ui_keyholder_noerror(msg) {
|
||||
document.getElementById("keyholder-error").style.display = "none";
|
||||
}
|
||||
|
||||
function ui_password_error() {
|
||||
document.getElementById("input-pass").classList.add("is-error")
|
||||
}
|
||||
|
||||
function ui_password_noerror() {
|
||||
document.getElementById("input-pass").classList.remove("is-error")
|
||||
function ui_password_error(is_error) {
|
||||
if (is_error)
|
||||
document.getElementById("input-pass").classList.add("is-error");
|
||||
else
|
||||
document.getElementById("input-pass").classList.remove("is-error");
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<div class="card" id="tracker">
|
||||
<div class="card-header">
|
||||
<div class="card-title h5">Schlüsseltracker</div>
|
||||
<div class="card-subtitle text-gray">für: <span id="tracker-key-name">...</span></div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span id="has-key" style="display: none"><i class="icon icon-check"></i> Der Schlüssel ist aktuell auf dich eingetragen</span>
|
||||
|
@ -54,7 +55,10 @@
|
|||
</label>
|
||||
</div>
|
||||
<div id="keyholder-table-container" style="display: none">
|
||||
<p>Folgende Personen hatten zuletzt den Schlüssel</p>
|
||||
<p>
|
||||
Folgende Personen hatten zuletzt den Schlüssel für:
|
||||
<span id="keyholder-key-name">...</span>
|
||||
</p>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
19
server/example-config
Normal file
19
server/example-config
Normal file
|
@ -0,0 +1,19 @@
|
|||
[Default]
|
||||
# The Port of the server when running in development mode
|
||||
Port = 5000
|
||||
|
||||
# This is the path to the sqlite database.
|
||||
# If it does not exist yet, it will be created on startup.
|
||||
DbPath = history.db
|
||||
|
||||
# Optionally, the keytracker can provide a CORS Header in case
|
||||
# the Website is deployed on a different (sub)domain than the API.
|
||||
# Use CorsOrigin = * to make it publically available (not advised).
|
||||
CorsOrigin = off
|
||||
|
||||
# In the following section you can configure the keys that can be tracked.
|
||||
# Use the script generate_key.py to generate the configuration line
|
||||
# (each line is one key).
|
||||
[Keys]
|
||||
8174875f7d85 = Chris Büro;49c5dbda74fe86eae0dd1ce6;247f16f579033a6a947b3be301407319cd9bfe14f11554d71ea3190e04f7cb91
|
||||
7fc944c9e632 = Test=2;74fffaf6e463950fc6da3fd3;95313e37ff448b1a19b133fd8067c160f9f1c6d417f5d8dbec6f4f931097d389
|
25
server/generate_key.py
Normal file
25
server/generate_key.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import hashlib
|
||||
import secrets
|
||||
|
||||
from server import PASS_SALT
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
name = input("Key for (the description of the key): ")
|
||||
if ";" in name:
|
||||
print("The character ';' is not allowed in the key name")
|
||||
exit(1)
|
||||
|
||||
password = input("Password (no hidden input here): ")
|
||||
hashed = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), PASS_SALT, 10000).hex()
|
||||
|
||||
key_id = secrets.token_hex(6)
|
||||
claim_token = secrets.token_hex(12)
|
||||
|
||||
print("The configuration line is:")
|
||||
print("{} = {};{};{}".format(key_id, name, claim_token, hashed))
|
||||
print("The claim token is:")
|
||||
print("{}:{}".format(key_id, claim_token))
|
||||
except KeyboardInterrupt:
|
||||
print("Cancelled")
|
|
@ -6,37 +6,46 @@ import secrets
|
|||
from flask import Flask, request, abort
|
||||
from flask.json import jsonify
|
||||
|
||||
DB_PATH = "history.db"
|
||||
PASS_FILE = "pass"
|
||||
PASS_SALT = b"IgTp9iQH"
|
||||
CLAIM_TOKEN_FILE = "claim_token"
|
||||
CORS_ORIGIN = "*"
|
||||
|
||||
import configparser
|
||||
config = configparser.ConfigParser()
|
||||
config.read("config")
|
||||
|
||||
PORT = config["Default"]["Port"]
|
||||
DB_PATH = config["Default"]["DbPath"]
|
||||
CORS_ORIGIN = None if config["Default"]["CorsOrigin"].lower() == "off" else config["Default"]["CorsOrigin"]
|
||||
KEYS = {}
|
||||
PASS_SALT = b"IgTp9iQH" # Static for now
|
||||
|
||||
|
||||
app = Flask("Schluesselverfolgung")
|
||||
app = Flask("Schluesseltracker")
|
||||
|
||||
|
||||
@app.route("/auth", methods=["POST"])
|
||||
def auth():
|
||||
if "pass" in request.form:
|
||||
hashed = hashlib.pbkdf2_hmac("sha512",
|
||||
key_id, permissions = _check_token()
|
||||
hashed = hashlib.pbkdf2_hmac("sha256",
|
||||
request.form["pass"].encode("utf-8"),
|
||||
PASS_SALT,
|
||||
10000)
|
||||
with open(PASS_FILE, "rb") as f:
|
||||
if hashed == f.read():
|
||||
new_token = secrets.token_hex(16)
|
||||
10000).hex()
|
||||
|
||||
if key_id in KEYS:
|
||||
if hashed == KEYS[key_id]["pass_hash"]:
|
||||
new_token = "{}:{}".format(key_id, secrets.token_hex(12))
|
||||
_add_token(new_token, "claim,query")
|
||||
return new_token
|
||||
else:
|
||||
abort(401)
|
||||
else:
|
||||
abort(400)
|
||||
else:
|
||||
abort(400)
|
||||
|
||||
|
||||
@app.route("/claim", methods=["POST"])
|
||||
def claim():
|
||||
permissions = _check_token()
|
||||
key_id, permissions = _check_token()
|
||||
if "claim" not in permissions:
|
||||
abort(403)
|
||||
|
||||
|
@ -47,10 +56,10 @@ def claim():
|
|||
contact = request.form["contact"].strip()
|
||||
|
||||
# These are arbitrary values but there to prevent sending an empty form
|
||||
if len(name) < 3 or len(contact) < 3:
|
||||
if len(name) < 2 or len(contact) < 2:
|
||||
abort(400)
|
||||
|
||||
claim_id = _add_claim(name, contact)
|
||||
claim_id = _add_claim(key_id, name, contact)
|
||||
return claim_id
|
||||
|
||||
|
||||
|
@ -59,13 +68,22 @@ def status(cid):
|
|||
return _get_claim_status(cid)
|
||||
|
||||
|
||||
@app.route("/keyname")
|
||||
def keyname():
|
||||
key_id, permissions = _check_token()
|
||||
if key_id in KEYS:
|
||||
return KEYS[key_id]["name"]
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
@app.route("/keyholder")
|
||||
def keyholder():
|
||||
permissions = _check_token()
|
||||
key_id, permissions = _check_token()
|
||||
if "query" not in permissions:
|
||||
abort(403)
|
||||
|
||||
return jsonify(_get_keyholder())
|
||||
return jsonify(_get_keyholder(key_id))
|
||||
|
||||
|
||||
@app.after_request
|
||||
|
@ -81,6 +99,7 @@ def _init_db():
|
|||
c.execute("""
|
||||
CREATE TABLE IF NOT EXISTS History (
|
||||
Id INTEGER PRIMARY KEY,
|
||||
KId TEXT NOT NULL,
|
||||
CId TEXT NOT NULL,
|
||||
Name TEXT NOT NULL,
|
||||
Contact TEXT NOT NULL,
|
||||
|
@ -95,11 +114,11 @@ def _init_db():
|
|||
Timestamp INTEGER NOT NULL
|
||||
)
|
||||
""")
|
||||
with open(CLAIM_TOKEN_FILE) as f:
|
||||
for key_id, key_data in KEYS.items():
|
||||
c.execute("""
|
||||
INSERT OR IGNORE INTO Token (Token, Permissions, Timestamp)
|
||||
VALUES (?,?,?)
|
||||
""", (f.read(), "claim", datetime.now().timestamp()))
|
||||
""", (f"{key_id}:{key_data['claim_token']}", "claim", datetime.now().timestamp()))
|
||||
c.commit()
|
||||
|
||||
|
||||
|
@ -119,25 +138,29 @@ def _check_token():
|
|||
else:
|
||||
abort(401)
|
||||
|
||||
parts = token.split(":")
|
||||
if len(parts) != 2 or len(parts[0]) == 0:
|
||||
abort(400)
|
||||
|
||||
c = sqlite3.connect(DB_PATH)
|
||||
conn = c.cursor()
|
||||
conn.execute("SELECT Permissions FROM Token WHERE Token=?", (token,))
|
||||
row = conn.fetchone()
|
||||
if row is None:
|
||||
return set()
|
||||
return None, set()
|
||||
else:
|
||||
return set(row[0].split(","))
|
||||
return parts[0], set(row[0].split(","))
|
||||
|
||||
|
||||
def _add_claim(name, contact):
|
||||
def _add_claim(key_id, name, contact):
|
||||
claim_id = secrets.token_hex(8)
|
||||
|
||||
c = sqlite3.connect(DB_PATH)
|
||||
conn = c.cursor()
|
||||
conn.execute("""
|
||||
INSERT INTO History (CId, Name, Contact, Timestamp)
|
||||
VALUES (?,?,?,?)
|
||||
""", (claim_id, name, contact, datetime.now().timestamp()))
|
||||
INSERT INTO History (CId, KId, Name, Contact, Timestamp)
|
||||
VALUES (?,?,?,?,?)
|
||||
""", (claim_id, key_id, name, contact, datetime.now().timestamp()))
|
||||
c.commit()
|
||||
|
||||
return claim_id
|
||||
|
@ -146,24 +169,28 @@ def _add_claim(name, contact):
|
|||
def _get_claim_status(claim_id):
|
||||
c = sqlite3.connect(DB_PATH)
|
||||
conn = c.cursor()
|
||||
conn.execute("SELECT CId FROM History ORDER BY Timestamp DESC")
|
||||
conn.execute("SELECT KId FROM History WHERE CId=?", (claim_id,))
|
||||
row = conn.fetchone()
|
||||
if row is None:
|
||||
return "unknown"
|
||||
else:
|
||||
key_id = row[0]
|
||||
conn.execute("SELECT CId FROM History WHERE KId=? ORDER BY Timestamp DESC", (key_id,))
|
||||
row = conn.fetchone()
|
||||
if row[0] == claim_id:
|
||||
return "latest"
|
||||
else:
|
||||
return "outdated"
|
||||
|
||||
|
||||
def _get_keyholder():
|
||||
def _get_keyholder(key_id):
|
||||
c = sqlite3.connect(DB_PATH)
|
||||
conn = c.cursor()
|
||||
conn.execute("""
|
||||
SELECT Name, Contact, Timestamp
|
||||
FROM History ORDER BY Timestamp DESC LIMIT 3
|
||||
""")
|
||||
SELECT Name, Contact, Timestamp FROM History
|
||||
WHERE KId=?
|
||||
ORDER BY Timestamp DESC LIMIT 3
|
||||
""", (key_id,))
|
||||
# Timestamp precision is one minute
|
||||
keyholder = [{"name": row[0], "contact": row[1], "timestamp": int(row[2] // 60 * 60)}
|
||||
for row in conn]
|
||||
|
@ -171,5 +198,12 @@ def _get_keyholder():
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for key_id in config["Keys"]:
|
||||
parts = config["Keys"][key_id].split(";")
|
||||
if len(parts) != 3:
|
||||
print("Invalid key configuration")
|
||||
exit(1)
|
||||
KEYS[key_id] = {"name": parts[0], "claim_token": parts[1], "pass_hash": parts[2]}
|
||||
|
||||
_init_db()
|
||||
app.run()
|
||||
app.run(port=PORT)
|
||||
|
|
Loading…
Reference in a new issue