added more users and anonymous API
reduced time window to 3 seconds
This commit is contained in:
@@ -23,7 +23,10 @@ class SessionData:
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
users = {
|
users = {
|
||||||
"user": "password",
|
"user1": "password",
|
||||||
|
"user2": "password",
|
||||||
|
"user3": "password",
|
||||||
|
"user4 ": "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
sessions: Dict[bytes, SessionData] = dict()
|
sessions: Dict[bytes, SessionData] = dict()
|
||||||
@@ -67,8 +70,9 @@ def token_required(f):
|
|||||||
response.status = 401
|
response.status = 401
|
||||||
response.data = "Token is invalid"
|
response.data = "Token is invalid"
|
||||||
return response
|
return response
|
||||||
current_tick: int = int(time()) // 10
|
current_tick: int = int(time()) // 3
|
||||||
valid_tokens = [
|
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.to_bytes(8)).digest(),
|
||||||
sha256(session.nonce + (current_tick - 1).to_bytes(8)).digest()
|
sha256(session.nonce + (current_tick - 1).to_bytes(8)).digest()
|
||||||
]
|
]
|
||||||
@@ -86,20 +90,34 @@ def login():
|
|||||||
response = flask.Response()
|
response = flask.Response()
|
||||||
if request.headers.get('Content-Type') != 'application/json':
|
if request.headers.get('Content-Type') != 'application/json':
|
||||||
response.status = 415
|
response.status = 415
|
||||||
|
response.data = "Wrong request content type"
|
||||||
return response
|
return response
|
||||||
payload = json.loads(request.data)
|
payload = json.loads(request.data)
|
||||||
user = payload.get('username')
|
user = payload.get('username')
|
||||||
if not user:
|
if not user:
|
||||||
response.status = 401
|
response.status = 401
|
||||||
|
response.data = "Missing username from request"
|
||||||
return response
|
return response
|
||||||
password = users.get(user)
|
password = users.get(user)
|
||||||
if not password or password != payload.get('password'):
|
if not password:
|
||||||
response.status = 401
|
response.status = 401
|
||||||
|
response.data = "Wrong username"
|
||||||
return response
|
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()
|
sr = random.SystemRandom()
|
||||||
nonce = sr.randbytes(16)
|
nonce = sr.randbytes(16)
|
||||||
public_key_header = request.headers.get('public-key', None)
|
public_key_header = request.headers.get('public-key', None)
|
||||||
if not public_key_header:
|
if not public_key_header:
|
||||||
|
response.data = "Missing public key header"
|
||||||
response.status = 400
|
response.status = 400
|
||||||
return response
|
return response
|
||||||
pem_key = f'-----BEGIN PUBLIC KEY-----\n{public_key_header}\n-----END PUBLIC KEY-----\n'
|
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)
|
return send_from_directory('static/css', path)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/hello')
|
@app.route('/api/whoami')
|
||||||
@token_required
|
@token_required
|
||||||
def send_hello(user):
|
def whoami(user):
|
||||||
return f'hello {user}'
|
return f'hello {user}'
|
||||||
|
|
||||||
|
@app.route('/api/hello')
|
||||||
|
def hello():
|
||||||
|
return 'hello anonymous'
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app.run(host='0.0.0.0', port=1443, ssl_context='adhoc')
|
app.run(host='0.0.0.0', port=1443, ssl_context='adhoc')
|
||||||
|
@@ -67,6 +67,11 @@ loginButton.addEventListener('click', async evt => {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({ username: loginForm.username.value, password: loginForm.password.value})
|
body: JSON.stringify({ username: loginForm.username.value, password: loginForm.password.value})
|
||||||
}).then(async response => {
|
}).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 nonceHeader = response.headers.get('nonce');
|
||||||
const encryptedNonce = atob(nonceHeader);
|
const encryptedNonce = atob(nonceHeader);
|
||||||
const privateKey = (await keyPair).privateKey;
|
const privateKey = (await keyPair).privateKey;
|
||||||
@@ -74,26 +79,55 @@ loginButton.addEventListener('click', async evt => {
|
|||||||
const encryptedBuffer = Uint8Array.from(atob(nonceHeader), c => c.charCodeAt(0));
|
const encryptedBuffer = Uint8Array.from(atob(nonceHeader), c => c.charCodeAt(0));
|
||||||
nonce = await crypto.decrypt({ name: "RSA-OAEP" }, privateKey, encryptedBuffer)
|
nonce = await crypto.decrypt({ name: "RSA-OAEP" }, privateKey, encryptedBuffer)
|
||||||
.then(it => new Uint8Array(it));
|
.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');
|
let paragraph = document.createElement('p');
|
||||||
paragraph.textContent = text;
|
paragraph.textContent = text;
|
||||||
document.body.appendChild(paragraph);
|
document.body.appendChild(paragraph);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let button = document.createElement('button');
|
let helloButton = document.createElement('button');
|
||||||
button.textContent = 'Press me'
|
helloButton.textContent = 'hello'
|
||||||
document.body.appendChild(button);
|
div.appendChild(helloButton);
|
||||||
|
|
||||||
button.addEventListener('click', async evt => {
|
helloButton.addEventListener('click', async evt => {
|
||||||
let header = {};
|
const token = await computeToken();
|
||||||
if(nonce != null) {
|
let headers = {};
|
||||||
const crypto = window.crypto.subtle;
|
if (token != null) {
|
||||||
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(''));
|
|
||||||
headers = {
|
headers = {
|
||||||
'x-token': token
|
'x-token': token
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user