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 {
|
.card {
|
||||||
border: 0;
|
border: 0;
|
||||||
box-shadow: 0 .25rem 1rem rgba(48,55,66,.15);
|
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 ===
|
// === Core functions ===
|
||||||
var api_base = "http://localhost:5000";
|
var api_base = "http://localhost:5000";
|
||||||
var app_token = null;
|
var app_token = null;
|
||||||
|
var app_key_id = null;
|
||||||
|
|
||||||
function do_request(method, url, data, onsuccess, onerror) {
|
function do_request(method, url, data, onsuccess, onerror) {
|
||||||
var req = new XMLHttpRequest();
|
var req = new XMLHttpRequest();
|
||||||
|
@ -26,9 +34,16 @@ function oninit() {
|
||||||
window.location.href = "#tracker"
|
window.location.href = "#tracker"
|
||||||
|
|
||||||
app_token = _get_token();
|
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)
|
if (window.location.href.search("#keyholder") > 0)
|
||||||
keyholder_onload();
|
keyholder_onload();
|
||||||
|
|
||||||
|
update_key_name();
|
||||||
update_key_status();
|
update_key_status();
|
||||||
load_saved_tracker_data();
|
load_saved_tracker_data();
|
||||||
|
|
||||||
|
@ -43,31 +58,31 @@ function oninit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function tracker_onsend(e) {
|
function tracker_onsend(e) {
|
||||||
ui_tracker_noerror();
|
ui_tracker_error(false);
|
||||||
|
|
||||||
var name = document.getElementById("input-name").value;
|
var name = document.getElementById("input-name").value;
|
||||||
var contact = document.getElementById("input-contact").value;
|
var contact = document.getElementById("input-contact").value;
|
||||||
var save_data = document.getElementById("input-tracker-save").checked;
|
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")
|
ui_tracker_error("Name und Kontakt müssen mindestens 3 Zeichen lang sein")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app_token === null) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("btn-tracker").classList.add("loading");
|
ui_tracker_send_loading(true);
|
||||||
do_request(
|
do_request(
|
||||||
"POST",
|
"POST",
|
||||||
"/claim",
|
"/claim",
|
||||||
"name="+window.encodeURI(name)+"&contact="+window.encodeURI(contact),
|
"name="+window.encodeURI(name)+"&contact="+window.encodeURI(contact),
|
||||||
function(r) {
|
function(r) {
|
||||||
document.getElementById("btn-tracker").classList.remove("loading");
|
ui_tracker_send_loading(true);
|
||||||
if (r.status == 200) {
|
if (r.status == 200) {
|
||||||
localStorage.setItem("last_cid", r.response);
|
localStorage.setItem(app_key_id + "_last_cid", r.response);
|
||||||
if (save_data) {
|
if (save_data) {
|
||||||
localStorage.setItem("name", name);
|
localStorage.setItem("name", name);
|
||||||
localStorage.setItem("contact", contact);
|
localStorage.setItem("contact", contact);
|
||||||
|
@ -83,7 +98,7 @@ function tracker_onsend(e) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function(r) {
|
function(r) {
|
||||||
document.getElementById("btn-tracker").classList.remove("loading");
|
ui_tracker_send_loading(true);
|
||||||
ui_tracker_error("Fehlgeschlagen: Bitte überprüfe deine Internetverbindung");
|
ui_tracker_error("Fehlgeschlagen: Bitte überprüfe deine Internetverbindung");
|
||||||
console.log("Claim request failed");
|
console.log("Claim request failed");
|
||||||
console.log(r);
|
console.log(r);
|
||||||
|
@ -92,14 +107,16 @@ function tracker_onsend(e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyholder_onload() {
|
function keyholder_onload() {
|
||||||
ui_password_noerror();
|
ui_password_error(false);
|
||||||
ui_keyholder_noerror();
|
ui_keyholder_error(false);
|
||||||
|
ui_keyholder_refresh_loading(true);
|
||||||
|
|
||||||
do_request(
|
do_request(
|
||||||
"GET",
|
"GET",
|
||||||
"/keyholder",
|
"/keyholder",
|
||||||
null,
|
null,
|
||||||
function(r) {
|
function(r) {
|
||||||
|
ui_keyholder_refresh_loading(false);
|
||||||
if (r.status == 200) {
|
if (r.status == 200) {
|
||||||
ui_show_keyholder(JSON.parse(r.response));
|
ui_show_keyholder(JSON.parse(r.response));
|
||||||
} else if (r.status == 403) {
|
} else if (r.status == 403) {
|
||||||
|
@ -111,6 +128,7 @@ function keyholder_onload() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function(r) {
|
function(r) {
|
||||||
|
ui_keyholder_refresh_loading(false);
|
||||||
ui_keyholder_error("Fehlgeschlagen: Bitte überprüfe deine Internetverbindung");
|
ui_keyholder_error("Fehlgeschlagen: Bitte überprüfe deine Internetverbindung");
|
||||||
console.log("Keyholder request failed");
|
console.log("Keyholder request failed");
|
||||||
console.log(r);
|
console.log(r);
|
||||||
|
@ -119,8 +137,8 @@ function keyholder_onload() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function password_submit() {
|
function password_submit() {
|
||||||
ui_password_noerror();
|
ui_password_error(false);
|
||||||
ui_keyholder_noerror();
|
ui_keyholder_error(false);
|
||||||
|
|
||||||
var pass = document.getElementById("input-pass").value;
|
var pass = document.getElementById("input-pass").value;
|
||||||
var save_token = document.getElementById("input-pass-save").checked;
|
var save_token = document.getElementById("input-pass-save").checked;
|
||||||
|
@ -133,17 +151,28 @@ function password_submit() {
|
||||||
function(r) {
|
function(r) {
|
||||||
document.getElementById("btn-send-pw").classList.remove("loading");
|
document.getElementById("btn-send-pw").classList.remove("loading");
|
||||||
if (r.status == 200) {
|
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;
|
app_token = r.response;
|
||||||
if (save_token)
|
if (save_token) {
|
||||||
localStorage.setItem("token", app_token);
|
if (localStorage.getItem("token") !== null)
|
||||||
else
|
localStorage.setItem("token", localStorage.getItem("token") + "," + app_token);
|
||||||
localStorage.removeItem("token");
|
else
|
||||||
|
localStorage.setItem("token", app_token)
|
||||||
|
}
|
||||||
|
|
||||||
ui_keyholder_loading();
|
ui_keyholder_loading();
|
||||||
keyholder_onload();
|
keyholder_onload();
|
||||||
|
|
||||||
} else if (r.status == 401) {
|
} else if (r.status == 401) {
|
||||||
ui_password_error();
|
ui_password_error(true);
|
||||||
} else {
|
} else {
|
||||||
ui_keyholder_error("Fehlgeschlagen: Status code " + r.status);
|
ui_keyholder_error("Fehlgeschlagen: Status code " + r.status);
|
||||||
console.log("Auth request failed (Status code)");
|
console.log("Auth request failed (Status code)");
|
||||||
|
@ -162,10 +191,11 @@ function password_submit() {
|
||||||
|
|
||||||
// === Helper functions ===
|
// === Helper functions ===
|
||||||
function update_key_status() {
|
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(
|
do_request(
|
||||||
"GET",
|
"GET",
|
||||||
"/status/" + localStorage.getItem("last_cid"),
|
"/status/" + last_cid,
|
||||||
null,
|
null,
|
||||||
function(r) {
|
function(r) {
|
||||||
if (r.status == 200 && r.response == "latest")
|
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() {
|
function load_saved_tracker_data() {
|
||||||
if (localStorage.getItem("name") !== null)
|
if (localStorage.getItem("name") !== null)
|
||||||
document.getElementById("input-name").value = localStorage.getItem("name");
|
document.getElementById("input-name").value = localStorage.getItem("name");
|
||||||
|
@ -186,19 +242,44 @@ function load_saved_tracker_data() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _get_token() {
|
function _get_token() {
|
||||||
if (localStorage.getItem("token") === null) {
|
// A token in the URL is always required to know the key
|
||||||
// Very dirty parsing as of now
|
var token = null;
|
||||||
var params_str = window.location.search;
|
// Very dirty parsing as of now
|
||||||
if (params_str.search("&") >= 0)
|
var params_str = window.location.search;
|
||||||
return null;
|
if (params_str.search("&") >= 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
var parts = params_str.split("token=");
|
var parts = params_str.split("token=");
|
||||||
if (parts.length != 2)
|
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
|
return null
|
||||||
else
|
else
|
||||||
return parts[1];
|
return parts[0];
|
||||||
} else {
|
|
||||||
return localStorage.getItem("token");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,6 +303,11 @@ function ui_set_has_not_key() {
|
||||||
document.getElementById("tracker-formgroup").style.opacity = 1
|
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() {
|
function ui_keyholder_loading() {
|
||||||
document.getElementById("keyholder-loading").style.display = "block"
|
document.getElementById("keyholder-loading").style.display = "block"
|
||||||
document.getElementById("btn-send-pw").style.display = "none"
|
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"
|
document.getElementById("keyholder-table-container").style.display = "block"
|
||||||
}
|
}
|
||||||
|
|
||||||
function ui_tracker_error(msg) {
|
function ui_keyholder_refresh_loading(is_loading) {
|
||||||
document.getElementById("tracker-error").innerHTML = msg;
|
if (is_loading)
|
||||||
document.getElementById("tracker-error").style.display = "block";
|
document.getElementById("btn-update").classList.add("loading");
|
||||||
|
else
|
||||||
|
document.getElementById("btn-update").classList.remove("loading");
|
||||||
}
|
}
|
||||||
|
|
||||||
function ui_tracker_noerror(msg) {
|
function ui_tracker_send_loading(is_loading) {
|
||||||
document.getElementById("tracker-error").style.display = "none";
|
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) {
|
function ui_keyholder_error(msg) {
|
||||||
document.getElementById("keyholder-error").innerHTML = msg;
|
if (msg === false) {
|
||||||
document.getElementById("keyholder-error").style.display = "block";
|
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) {
|
function ui_password_error(is_error) {
|
||||||
document.getElementById("keyholder-error").style.display = "none";
|
if (is_error)
|
||||||
}
|
document.getElementById("input-pass").classList.add("is-error");
|
||||||
|
else
|
||||||
function ui_password_error() {
|
document.getElementById("input-pass").classList.remove("is-error");
|
||||||
document.getElementById("input-pass").classList.add("is-error")
|
|
||||||
}
|
|
||||||
|
|
||||||
function ui_password_noerror() {
|
|
||||||
document.getElementById("input-pass").classList.remove("is-error")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
<div class="card" id="tracker">
|
<div class="card" id="tracker">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title h5">Schlüsseltracker</div>
|
<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>
|
||||||
<div class="card-body">
|
<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>
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="keyholder-table-container" style="display: none">
|
<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">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<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 import Flask, request, abort
|
||||||
from flask.json import jsonify
|
from flask.json import jsonify
|
||||||
|
|
||||||
DB_PATH = "history.db"
|
|
||||||
PASS_FILE = "pass"
|
import configparser
|
||||||
PASS_SALT = b"IgTp9iQH"
|
config = configparser.ConfigParser()
|
||||||
CLAIM_TOKEN_FILE = "claim_token"
|
config.read("config")
|
||||||
CORS_ORIGIN = "*"
|
|
||||||
|
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"])
|
@app.route("/auth", methods=["POST"])
|
||||||
def auth():
|
def auth():
|
||||||
if "pass" in request.form:
|
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"),
|
request.form["pass"].encode("utf-8"),
|
||||||
PASS_SALT,
|
PASS_SALT,
|
||||||
10000)
|
10000).hex()
|
||||||
with open(PASS_FILE, "rb") as f:
|
|
||||||
if hashed == f.read():
|
if key_id in KEYS:
|
||||||
new_token = secrets.token_hex(16)
|
if hashed == KEYS[key_id]["pass_hash"]:
|
||||||
|
new_token = "{}:{}".format(key_id, secrets.token_hex(12))
|
||||||
_add_token(new_token, "claim,query")
|
_add_token(new_token, "claim,query")
|
||||||
return new_token
|
return new_token
|
||||||
else:
|
else:
|
||||||
abort(401)
|
abort(401)
|
||||||
|
else:
|
||||||
|
abort(400)
|
||||||
else:
|
else:
|
||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/claim", methods=["POST"])
|
@app.route("/claim", methods=["POST"])
|
||||||
def claim():
|
def claim():
|
||||||
permissions = _check_token()
|
key_id, permissions = _check_token()
|
||||||
if "claim" not in permissions:
|
if "claim" not in permissions:
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
|
@ -47,10 +56,10 @@ def claim():
|
||||||
contact = request.form["contact"].strip()
|
contact = request.form["contact"].strip()
|
||||||
|
|
||||||
# These are arbitrary values but there to prevent sending an empty form
|
# 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)
|
abort(400)
|
||||||
|
|
||||||
claim_id = _add_claim(name, contact)
|
claim_id = _add_claim(key_id, name, contact)
|
||||||
return claim_id
|
return claim_id
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,13 +68,22 @@ def status(cid):
|
||||||
return _get_claim_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")
|
@app.route("/keyholder")
|
||||||
def keyholder():
|
def keyholder():
|
||||||
permissions = _check_token()
|
key_id, permissions = _check_token()
|
||||||
if "query" not in permissions:
|
if "query" not in permissions:
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
return jsonify(_get_keyholder())
|
return jsonify(_get_keyholder(key_id))
|
||||||
|
|
||||||
|
|
||||||
@app.after_request
|
@app.after_request
|
||||||
|
@ -81,6 +99,7 @@ def _init_db():
|
||||||
c.execute("""
|
c.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS History (
|
CREATE TABLE IF NOT EXISTS History (
|
||||||
Id INTEGER PRIMARY KEY,
|
Id INTEGER PRIMARY KEY,
|
||||||
|
KId TEXT NOT NULL,
|
||||||
CId TEXT NOT NULL,
|
CId TEXT NOT NULL,
|
||||||
Name TEXT NOT NULL,
|
Name TEXT NOT NULL,
|
||||||
Contact TEXT NOT NULL,
|
Contact TEXT NOT NULL,
|
||||||
|
@ -95,11 +114,11 @@ def _init_db():
|
||||||
Timestamp INTEGER NOT NULL
|
Timestamp INTEGER NOT NULL
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
with open(CLAIM_TOKEN_FILE) as f:
|
for key_id, key_data in KEYS.items():
|
||||||
c.execute("""
|
c.execute("""
|
||||||
INSERT OR IGNORE INTO Token (Token, Permissions, Timestamp)
|
INSERT OR IGNORE INTO Token (Token, Permissions, Timestamp)
|
||||||
VALUES (?,?,?)
|
VALUES (?,?,?)
|
||||||
""", (f.read(), "claim", datetime.now().timestamp()))
|
""", (f"{key_id}:{key_data['claim_token']}", "claim", datetime.now().timestamp()))
|
||||||
c.commit()
|
c.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@ -119,25 +138,29 @@ def _check_token():
|
||||||
else:
|
else:
|
||||||
abort(401)
|
abort(401)
|
||||||
|
|
||||||
|
parts = token.split(":")
|
||||||
|
if len(parts) != 2 or len(parts[0]) == 0:
|
||||||
|
abort(400)
|
||||||
|
|
||||||
c = sqlite3.connect(DB_PATH)
|
c = sqlite3.connect(DB_PATH)
|
||||||
conn = c.cursor()
|
conn = c.cursor()
|
||||||
conn.execute("SELECT Permissions FROM Token WHERE Token=?", (token,))
|
conn.execute("SELECT Permissions FROM Token WHERE Token=?", (token,))
|
||||||
row = conn.fetchone()
|
row = conn.fetchone()
|
||||||
if row is None:
|
if row is None:
|
||||||
return set()
|
return None, set()
|
||||||
else:
|
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)
|
claim_id = secrets.token_hex(8)
|
||||||
|
|
||||||
c = sqlite3.connect(DB_PATH)
|
c = sqlite3.connect(DB_PATH)
|
||||||
conn = c.cursor()
|
conn = c.cursor()
|
||||||
conn.execute("""
|
conn.execute("""
|
||||||
INSERT INTO History (CId, Name, Contact, Timestamp)
|
INSERT INTO History (CId, KId, Name, Contact, Timestamp)
|
||||||
VALUES (?,?,?,?)
|
VALUES (?,?,?,?,?)
|
||||||
""", (claim_id, name, contact, datetime.now().timestamp()))
|
""", (claim_id, key_id, name, contact, datetime.now().timestamp()))
|
||||||
c.commit()
|
c.commit()
|
||||||
|
|
||||||
return claim_id
|
return claim_id
|
||||||
|
@ -146,24 +169,28 @@ def _add_claim(name, contact):
|
||||||
def _get_claim_status(claim_id):
|
def _get_claim_status(claim_id):
|
||||||
c = sqlite3.connect(DB_PATH)
|
c = sqlite3.connect(DB_PATH)
|
||||||
conn = c.cursor()
|
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()
|
row = conn.fetchone()
|
||||||
if row is None:
|
if row is None:
|
||||||
return "unknown"
|
return "unknown"
|
||||||
else:
|
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:
|
if row[0] == claim_id:
|
||||||
return "latest"
|
return "latest"
|
||||||
else:
|
else:
|
||||||
return "outdated"
|
return "outdated"
|
||||||
|
|
||||||
|
|
||||||
def _get_keyholder():
|
def _get_keyholder(key_id):
|
||||||
c = sqlite3.connect(DB_PATH)
|
c = sqlite3.connect(DB_PATH)
|
||||||
conn = c.cursor()
|
conn = c.cursor()
|
||||||
conn.execute("""
|
conn.execute("""
|
||||||
SELECT Name, Contact, Timestamp
|
SELECT Name, Contact, Timestamp FROM History
|
||||||
FROM History ORDER BY Timestamp DESC LIMIT 3
|
WHERE KId=?
|
||||||
""")
|
ORDER BY Timestamp DESC LIMIT 3
|
||||||
|
""", (key_id,))
|
||||||
# Timestamp precision is one minute
|
# Timestamp precision is one minute
|
||||||
keyholder = [{"name": row[0], "contact": row[1], "timestamp": int(row[2] // 60 * 60)}
|
keyholder = [{"name": row[0], "contact": row[1], "timestamp": int(row[2] // 60 * 60)}
|
||||||
for row in conn]
|
for row in conn]
|
||||||
|
@ -171,5 +198,12 @@ def _get_keyholder():
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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()
|
_init_db()
|
||||||
app.run()
|
app.run(port=PORT)
|
||||||
|
|
Loading…
Reference in a new issue