commit
ac16c46e01
@ -0,0 +1 @@ |
|||||||
|
__pycache__ |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
from datetime import datetime,timedelta |
||||||
|
from uuid import uuid1 |
||||||
|
import secrets |
||||||
|
|
||||||
|
class Nounce(object): |
||||||
|
def __init__(self): |
||||||
|
self.id = uuid1() |
||||||
|
self.expiration = datetime.now()+timedelta(seconds=60) |
||||||
|
self.token = secrets.token_urlsafe() |
||||||
|
|
||||||
|
def expired(self): |
||||||
|
return datetime.now() > self.expiration |
||||||
|
|
||||||
|
def bump(self): |
||||||
|
self.expiration = datetime.now()+timedelta(seconds=60) |
||||||
|
|
||||||
|
class AuthSession(object): |
||||||
|
def __init__(self, username): |
||||||
|
self.id = uuid1() |
||||||
|
self.expiration = datetime.now()+timedelta(minutes=30) |
||||||
|
self.username = username |
||||||
|
|
||||||
|
def expired(self): |
||||||
|
return datetime.now() > self.expiration |
||||||
|
|
||||||
|
def bumpExpiration(self): |
||||||
|
self.expiration = datetime.now()+timedelta(minutes=30) |
||||||
@ -0,0 +1,109 @@ |
|||||||
|
from flask import * |
||||||
|
import secrets, hashlib |
||||||
|
from model.models import * |
||||||
|
import json |
||||||
|
|
||||||
|
app = Blueprint('nounce', __name__) |
||||||
|
|
||||||
|
SID = 'SID' |
||||||
|
nonces = {} |
||||||
|
authSessions = {} |
||||||
|
users = {} |
||||||
|
|
||||||
|
def loadUsers(path): |
||||||
|
global users |
||||||
|
with open(path) as json_file: |
||||||
|
users = json.load(json_file) |
||||||
|
|
||||||
|
@app.route('/nonce', methods=['GET']) |
||||||
|
def nounce(): |
||||||
|
n = tryGetSessionNonce() |
||||||
|
if n == None or n.expired(): |
||||||
|
n = generateSessionNonce() |
||||||
|
n.bump() |
||||||
|
return jsonify(n.__dict__),201 |
||||||
|
|
||||||
|
@app.route('/login', methods=['POST']) |
||||||
|
def login(): |
||||||
|
if isLoggedIn(): |
||||||
|
return 'already logged in', 400 |
||||||
|
|
||||||
|
n = tryGetSessionNonce() |
||||||
|
if n == None: |
||||||
|
return 'no nonce was generated', 400 |
||||||
|
if n.expired(): |
||||||
|
clearSessionNonce() |
||||||
|
return 'nonce expired', 408 |
||||||
|
|
||||||
|
user = request.form['user'] |
||||||
|
cnonce = request.form['cnonce'] |
||||||
|
pwdhash = request.form['pwdhash'] |
||||||
|
|
||||||
|
pwdHashed_sha256 = '' |
||||||
|
if user in users: |
||||||
|
pwdHashed_sha256 = users[user] |
||||||
|
else: |
||||||
|
abort(403) |
||||||
|
|
||||||
|
hash = generateHash(pwdHashed_sha256, cnonce, n.token) |
||||||
|
if hash == pwdhash: |
||||||
|
s = generateAuthSession(user) |
||||||
|
return jsonify(s.__dict__),201 |
||||||
|
else: |
||||||
|
abort(403) |
||||||
|
|
||||||
|
@app.route('/check-login', methods=['GET']) |
||||||
|
def checkLogin(): |
||||||
|
if isLoggedIn(): |
||||||
|
asession = authSessions[session[SID]] |
||||||
|
asession.bumpExpiration() |
||||||
|
return jsonify(asession.__dict__) |
||||||
|
else: |
||||||
|
return abort(403) |
||||||
|
|
||||||
|
@app.route('/login-page', methods=['GET']) |
||||||
|
def loginPage(): |
||||||
|
return render_template('login-page.html') |
||||||
|
|
||||||
|
def isLoggedIn(): |
||||||
|
if SID in session and session[SID] in authSessions: |
||||||
|
return not(authSessions[session[SID]].expired()) |
||||||
|
return False |
||||||
|
|
||||||
|
def generateAuthSession(username): |
||||||
|
asession = AuthSession(username) |
||||||
|
authSessions[session[SID]] = asession |
||||||
|
return asession |
||||||
|
|
||||||
|
def getAuthSession(): |
||||||
|
if not SID in session: |
||||||
|
return None |
||||||
|
if not session[SID]: |
||||||
|
return None |
||||||
|
return authSessions[session[SID]] |
||||||
|
|
||||||
|
def clearSessionNonce(): |
||||||
|
if not SID in session: |
||||||
|
return |
||||||
|
if not session[SID] in nonces: |
||||||
|
return |
||||||
|
del nonces[session[SID]] |
||||||
|
|
||||||
|
def tryGetSessionNonce(): |
||||||
|
if not SID in session: |
||||||
|
return None |
||||||
|
if not session[SID] in nonces: |
||||||
|
return None |
||||||
|
return nonces[session[SID]] |
||||||
|
|
||||||
|
def generateSessionNonce(): |
||||||
|
n = Nounce() |
||||||
|
session[SID] = secrets.token_urlsafe() |
||||||
|
nonces[session[SID]] = n |
||||||
|
return n |
||||||
|
|
||||||
|
def generateHash(pwdsha256,cnonce,nonce): |
||||||
|
all = pwdsha256+cnonce+nonce |
||||||
|
sha256 = hashlib.sha256( ) |
||||||
|
sha256.update( all.encode('utf8') ) |
||||||
|
return sha256.hexdigest() |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
from flask import * |
||||||
|
import secrets |
||||||
|
import nonce |
||||||
|
import os.path |
||||||
|
|
||||||
|
app = Flask(__name__) |
||||||
|
app.secret_key = secrets.token_urlsafe() |
||||||
|
app.register_blueprint(nonce.app) |
||||||
|
|
||||||
|
ROUTES = {'routes': ['/', '/nonce'] } |
||||||
|
@app.route('/') |
||||||
|
def home(): |
||||||
|
return jsonify(ROUTES) |
||||||
|
|
||||||
|
nonce.loadUsers( os.path.join('data','userlist.json') ) |
||||||
|
if __name__ == '__main__': |
||||||
|
app.run( host='0.0.0.0', port=7070) |
||||||
|
|
||||||
@ -0,0 +1,179 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<script |
||||||
|
src="https://code.jquery.com/jquery-3.4.1.min.js" |
||||||
|
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" |
||||||
|
crossorigin="anonymous"></script> |
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsSHA/2.3.1/sha256.js" |
||||||
|
integrity="sha384-rxrcz1OyCVG21aEKlNBwgap2/r4pSV/RrN6eEMQhLriiTZnnzhCFZr+ZKGPJNSJX" |
||||||
|
crossorigin="anonymous"></script> |
||||||
|
|
||||||
|
<script> |
||||||
|
|
||||||
|
function generateCnonce() { |
||||||
|
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
||||||
|
var text = ""; |
||||||
|
for(var i = 0; i < 43; i++) { |
||||||
|
text += possible.charAt(Math.floor(Math.random() * possible.length)); |
||||||
|
} |
||||||
|
return text; |
||||||
|
} |
||||||
|
|
||||||
|
function submit() { |
||||||
|
var user = $("#user").val(); |
||||||
|
var pwd = $("#pwd").val(); |
||||||
|
jQuery.get( 'nonce', {}, function(data){ |
||||||
|
var nonce = data.token; |
||||||
|
var cnonce = generateCnonce(); |
||||||
|
|
||||||
|
var sha256 = new jsSHA('SHA-256', 'TEXT'); //B64 |
||||||
|
sha256.update(pwd); |
||||||
|
var pwdHashed_sha256 = sha256.getHash('HEX') |
||||||
|
|
||||||
|
sha256 = new jsSHA('SHA-256', 'TEXT'); |
||||||
|
sha256.update( pwdHashed_sha256+cnonce+nonce ); |
||||||
|
const pwdhash= sha256.getHash('HEX') |
||||||
|
const payload = { |
||||||
|
user: user, |
||||||
|
cnonce: cnonce, |
||||||
|
pwdhash: pwdhash |
||||||
|
} |
||||||
|
jQuery.post('login', payload, |
||||||
|
function(data){ |
||||||
|
location.href = '/'; |
||||||
|
} |
||||||
|
).fail(function() { |
||||||
|
var el = $('.error_msg'); |
||||||
|
el.css('animation-name',''); |
||||||
|
void el[0].offsetWidth; //trigger reflow |
||||||
|
el.css('animation-name','fade'); |
||||||
|
}); |
||||||
|
} ); |
||||||
|
} |
||||||
|
|
||||||
|
$( document ).ready(function() { |
||||||
|
$("body").keypress( function(e) { |
||||||
|
if(e.key=='Enter') { |
||||||
|
submit(); |
||||||
|
} |
||||||
|
}); |
||||||
|
$("#login").click( submit ); |
||||||
|
}); |
||||||
|
</script> |
||||||
|
<style> |
||||||
|
input { |
||||||
|
border: 1px solid lightgray; |
||||||
|
border-radius: 5px; |
||||||
|
padding: 5px 5px 5px 5px; |
||||||
|
/*width: 150px;*/ |
||||||
|
width:100%; |
||||||
|
display: block; |
||||||
|
margin: 1px auto 3px auto; |
||||||
|
box-sizing: border-box; |
||||||
|
} |
||||||
|
|
||||||
|
button { |
||||||
|
border: none; |
||||||
|
border-radius: 5px; |
||||||
|
padding: 5px 5px 5px 5px; |
||||||
|
/*width: 150px;*/ |
||||||
|
width:100%; |
||||||
|
display: block; |
||||||
|
margin: 1px auto 3px auto; |
||||||
|
box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.75); |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
button:hover { |
||||||
|
background-color: lightgray; |
||||||
|
} |
||||||
|
|
||||||
|
button:active { |
||||||
|
transform: translateY(1px); |
||||||
|
box-shadow: 0px 1px 2px 0px rgba(0,0,0,0.75); |
||||||
|
} |
||||||
|
|
||||||
|
button:focus { |
||||||
|
outline: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.error_msg { |
||||||
|
border: none; |
||||||
|
border-radius: 5px; |
||||||
|
display: block; |
||||||
|
background-color: #CC3333; |
||||||
|
color: white; |
||||||
|
animation-duration: 2.5s; |
||||||
|
opacity: 0; |
||||||
|
padding: 5px; |
||||||
|
width:100%; |
||||||
|
box-sizing: border-box; |
||||||
|
font: 400 13.3333px Arial; |
||||||
|
font-weight: 600; |
||||||
|
text-align: center; |
||||||
|
margin: 1px auto 3px auto; |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes fade { |
||||||
|
0% {opacity: 1;} |
||||||
|
25% {opacity: 1;} |
||||||
|
100% {opacity: 0;} |
||||||
|
} |
||||||
|
|
||||||
|
.centered { |
||||||
|
width: 180px; |
||||||
|
margin: 0; |
||||||
|
position: absolute; |
||||||
|
top: 50%; |
||||||
|
left: 50%; |
||||||
|
transform: translate(-50%, -50%); |
||||||
|
} |
||||||
|
|
||||||
|
body { |
||||||
|
background: |
||||||
|
linear-gradient(27deg, |
||||||
|
#151515 5px, |
||||||
|
transparent 5px |
||||||
|
) 0 5px, |
||||||
|
linear-gradient(207deg, |
||||||
|
#151515 5px, |
||||||
|
transparent 5px |
||||||
|
) 10px 0px, |
||||||
|
linear-gradient(27deg, |
||||||
|
#222 5px, |
||||||
|
transparent 5px |
||||||
|
) 0px 10px, |
||||||
|
linear-gradient(207deg, |
||||||
|
#222 5px, |
||||||
|
transparent 5px |
||||||
|
) 10px 5px, |
||||||
|
linear-gradient(90deg, |
||||||
|
#1b1b1b 10px, |
||||||
|
transparent 10px |
||||||
|
), |
||||||
|
linear-gradient( |
||||||
|
#1d1d1d 25%, |
||||||
|
#1a1a1a 25%, |
||||||
|
#1a1a1a 50%, |
||||||
|
transparent 50%, |
||||||
|
transparent 75%, |
||||||
|
#242424 75%, |
||||||
|
#242424 |
||||||
|
); |
||||||
|
background-color: #131313; |
||||||
|
background-size: 20px 20px; |
||||||
|
background-clip: content-box; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div class="centered"> |
||||||
|
<input id="user" type="text" /> |
||||||
|
<input id="pwd" type="password" /> |
||||||
|
<button id="login">Login</button> |
||||||
|
<div class="error_msg">Login failed!</div> |
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
||||||
Loading…
Reference in new issue