user logins
This commit is contained in:
1
examples/login/README.md
Normal file
1
examples/login/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This directory contains examples that demonstrate user logins.
|
||||
123
examples/login/login.py
Normal file
123
examples/login/login.py
Normal file
@@ -0,0 +1,123 @@
|
||||
from microdot import Microdot, redirect
|
||||
from microdot.session import Session
|
||||
from microdot.login import Login
|
||||
from pbkdf2 import generate_password_hash, check_password_hash
|
||||
|
||||
# this example provides an implementation of the generate_password_hash and
|
||||
# check_password_hash functions that can be used in MicroPython. On CPython
|
||||
# there are many other options for password hashisng so there is no need to use
|
||||
# this custom solution.
|
||||
|
||||
|
||||
class User:
|
||||
def __init__(self, id, username, password):
|
||||
self.id = id
|
||||
self.username = username
|
||||
self.password_hash = self.create_hash(password)
|
||||
|
||||
def create_hash(self, password):
|
||||
return generate_password_hash(password)
|
||||
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
|
||||
USERS = {
|
||||
'user001': User('user001', 'susan', 'hello'),
|
||||
'user002': User('user002', 'david', 'bye'),
|
||||
}
|
||||
|
||||
app = Microdot()
|
||||
Session(app, secret_key='top-secret!')
|
||||
login = Login()
|
||||
|
||||
|
||||
@login.user_loader
|
||||
async def get_user(user_id):
|
||||
return USERS.get(user_id)
|
||||
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
async def login_page(request):
|
||||
if request.method == 'GET':
|
||||
return '''
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Please Login</h1>
|
||||
<form method="POST">
|
||||
<p>
|
||||
Username<br>
|
||||
<input name="username" autofocus>
|
||||
</p>
|
||||
<p>
|
||||
Password:<br>
|
||||
<input name="password" type="password">
|
||||
<br>
|
||||
</p>
|
||||
<p>
|
||||
<input name="remember_me" type="checkbox"> Remember me
|
||||
<br>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit">Login</button>
|
||||
</p>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
''', {'Content-Type': 'text/html'}
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
remember_me = bool(request.form.get('remember_me'))
|
||||
|
||||
for user in USERS.values():
|
||||
if user.username == username:
|
||||
if user.check_password(password):
|
||||
return await login.login_user(request, user,
|
||||
remember=remember_me)
|
||||
return redirect('/login')
|
||||
|
||||
|
||||
@app.route('/')
|
||||
@login
|
||||
async def index(request):
|
||||
return f'''
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello, {request.g.current_user.username}!</h1>
|
||||
<p>
|
||||
<a href="/fresh">Click here</a> to access the fresh login page.
|
||||
</p>
|
||||
<form method="POST" action="/logout">
|
||||
<button type="submit">Logout</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
''', {'Content-Type': 'text/html'}
|
||||
|
||||
|
||||
@app.get('/fresh')
|
||||
@login.fresh
|
||||
async def fresh(request):
|
||||
return f'''
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello, {request.g.current_user.username}!</h1>
|
||||
<p>This page requires a fresh login session.</p>
|
||||
<p><a href="/">Go back</a> to the main page.</p>
|
||||
</body>
|
||||
</html>
|
||||
''', {'Content-Type': 'text/html'}
|
||||
|
||||
|
||||
@app.post('/logout')
|
||||
@login
|
||||
async def logout(request):
|
||||
await login.logout_user(request)
|
||||
return redirect('/')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
47
examples/login/pbkdf2.py
Normal file
47
examples/login/pbkdf2.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import os
|
||||
import hashlib
|
||||
|
||||
# PBKDF2 secure password hashing algorithm obtained from:
|
||||
# https://codeandlife.com/2023/01/06/how-to-calculate-pbkdf2-hmac-sha256-with-
|
||||
# python,-example-code/
|
||||
|
||||
|
||||
def sha256(b):
|
||||
return hashlib.sha256(b).digest()
|
||||
|
||||
|
||||
def ljust(b, n, f):
|
||||
return b + f * (n - len(b))
|
||||
|
||||
|
||||
def gethmac(key, content):
|
||||
okeypad = bytes(v ^ 0x5c for v in ljust(key, 64, b'\0'))
|
||||
ikeypad = bytes(v ^ 0x36 for v in ljust(key, 64, b'\0'))
|
||||
return sha256(okeypad + sha256(ikeypad + content))
|
||||
|
||||
|
||||
def pbkdf2(pwd, salt, iterations=1000):
|
||||
U = salt + b'\x00\x00\x00\x01'
|
||||
T = bytes(64)
|
||||
for _ in range(iterations):
|
||||
U = gethmac(pwd, U)
|
||||
T = bytes(a ^ b for a, b in zip(U, T))
|
||||
return T
|
||||
|
||||
|
||||
# The number of iterations may need to be adjusted depending on the hardware.
|
||||
# Lower numbers make the password hashing algorithm faster but less secure, so
|
||||
# the largest number that can be tolerated should be used.
|
||||
def generate_password_hash(password, salt=None, iterations=100000):
|
||||
salt = salt or os.urandom(16)
|
||||
dk = pbkdf2(password.encode(), salt, iterations)
|
||||
return f'pbkdf2-hmac-sha256:{salt.hex()}:{iterations}:{dk.hex()}'
|
||||
|
||||
|
||||
def check_password_hash(password_hash, password):
|
||||
algorithm, salt, iterations, dk = password_hash.split(':')
|
||||
iterations = int(iterations)
|
||||
if algorithm != 'pbkdf2-hmac-sha256':
|
||||
return False
|
||||
return pbkdf2(password.encode(), salt=bytes.fromhex(salt),
|
||||
iterations=iterations) == bytes.fromhex(dk)
|
||||
@@ -1,3 +1,6 @@
|
||||
# This is a simple example that demonstrates how to use the user session, but
|
||||
# is not intended as a complete login solution. See the login subdirectory for
|
||||
# a more complete example.
|
||||
from microdot import Microdot, Response, redirect
|
||||
from microdot.session import Session, with_session
|
||||
|
||||
|
||||
Reference in New Issue
Block a user