From 852c3e82fc5068e0dbfa578dc4e3c86ea411a5c2 Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Wed, 14 Feb 2024 13:29:57 +0800 Subject: [PATCH] added more users and anonymous API reduced time window to 3 seconds --- sspoc/server.py | 31 +++++++++++++++++---- sspoc/static/js/sspoc.js | 60 +++++++++++++++++++++++++++++++--------- 2 files changed, 73 insertions(+), 18 deletions(-) diff --git a/sspoc/server.py b/sspoc/server.py index 381f096..c765bf7 100644 --- a/sspoc/server.py +++ b/sspoc/server.py @@ -23,7 +23,10 @@ class SessionData: app = Flask(__name__) users = { - "user": "password", + "user1": "password", + "user2": "password", + "user3": "password", + "user4 ": "password", } sessions: Dict[bytes, SessionData] = dict() @@ -67,8 +70,9 @@ def token_required(f): response.status = 401 response.data = "Token is invalid" return response - current_tick: int = int(time()) // 10 + current_tick: int = int(time()) // 3 valid_tokens = [ + sha256(session.nonce + (current_tick + 1).to_bytes(8)).digest(), sha256(session.nonce + current_tick.to_bytes(8)).digest(), sha256(session.nonce + (current_tick - 1).to_bytes(8)).digest() ] @@ -86,20 +90,34 @@ def login(): response = flask.Response() if request.headers.get('Content-Type') != 'application/json': response.status = 415 + response.data = "Wrong request content type" return response payload = json.loads(request.data) user = payload.get('username') if not user: response.status = 401 + response.data = "Missing username from request" return response password = users.get(user) - if not password or password != payload.get('password'): + if not password: response.status = 401 + response.data = "Wrong username" return response + suppliedPassword = payload.get('password') + if not suppliedPassword: + response.status = 401 + response.data = "Missing password from request" + return response + elif suppliedPassword != password: + response.status = 401 + response.data = "Wrong password" + return response + sr = random.SystemRandom() nonce = sr.randbytes(16) public_key_header = request.headers.get('public-key', None) if not public_key_header: + response.data = "Missing public key header" response.status = 400 return response pem_key = f'-----BEGIN PUBLIC KEY-----\n{public_key_header}\n-----END PUBLIC KEY-----\n' @@ -142,11 +160,14 @@ def send_css(path): return send_from_directory('static/css', path) -@app.route('/api/hello') +@app.route('/api/whoami') @token_required -def send_hello(user): +def whoami(user): return f'hello {user}' +@app.route('/api/hello') +def hello(): + return 'hello anonymous' def main(): app.run(host='0.0.0.0', port=1443, ssl_context='adhoc') diff --git a/sspoc/static/js/sspoc.js b/sspoc/static/js/sspoc.js index 306a0da..2d68171 100644 --- a/sspoc/static/js/sspoc.js +++ b/sspoc/static/js/sspoc.js @@ -67,6 +67,11 @@ loginButton.addEventListener('click', async evt => { }, body: JSON.stringify({ username: loginForm.username.value, password: loginForm.password.value}) }).then(async response => { + if (!response.ok) { + let paragraph = document.createElement('p'); + paragraph.textContent = await response.text(); + document.body.appendChild(paragraph); + } const nonceHeader = response.headers.get('nonce'); const encryptedNonce = atob(nonceHeader); const privateKey = (await keyPair).privateKey; @@ -74,26 +79,55 @@ loginButton.addEventListener('click', async evt => { const encryptedBuffer = Uint8Array.from(atob(nonceHeader), c => c.charCodeAt(0)); nonce = await crypto.decrypt({ name: "RSA-OAEP" }, privateKey, encryptedBuffer) .then(it => new Uint8Array(it)); - return response.text(); - }).then(text => { + }); +}); + +async function computeToken() { + if(nonce != null) { + const crypto = window.crypto.subtle; + const epochTick = Math.floor(new Date().getTime() / 3000); + const data = concatenateUInt8Arrays(nonce, integerToBytes(epochTick, 8)); + const hash = new Uint8Array(await crypto.digest("SHA-256", data)); + const token = btoa(Array.from(hash, byte => String.fromCharCode(byte)).join('')); + return token; + } else { + return null; + } +} + +let div = document.createElement('div'); +document.body.appendChild(div); + +let whoamiButton = document.createElement('button'); +whoamiButton.textContent = 'whoami' +div.appendChild(whoamiButton); + +whoamiButton.addEventListener('click', async evt => { + const token = await computeToken(); + let headers = {}; + if (token != null) { + headers = { + 'x-token': token + }; + } + fetch('api/whoami', { + method: 'GET', + headers + }).then(response => response.text()).then(text => { let paragraph = document.createElement('p'); paragraph.textContent = text; document.body.appendChild(paragraph); }); }); -let button = document.createElement('button'); -button.textContent = 'Press me' -document.body.appendChild(button); +let helloButton = document.createElement('button'); +helloButton.textContent = 'hello' +div.appendChild(helloButton); -button.addEventListener('click', async evt => { - let header = {}; - if(nonce != null) { - const crypto = window.crypto.subtle; - const epochTick = Math.floor(new Date().getTime() / 10000) - const data = concatenateUInt8Arrays(nonce, integerToBytes(epochTick, 8)) - const hash = new Uint8Array(await crypto.digest("SHA-256", data)); - const token = btoa(Array.from(hash, byte => String.fromCharCode(byte)).join('')); +helloButton.addEventListener('click', async evt => { + const token = await computeToken(); + let headers = {}; + if (token != null) { headers = { 'x-token': token };