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