User sessions
This commit is contained in:
58
examples/login.py
Normal file
58
examples/login.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from microdot import Microdot, Response, redirect
|
||||
from microdot_session import set_session_secret_key, with_session, \
|
||||
update_session, delete_session
|
||||
|
||||
BASE_TEMPLATE = '''<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Microdot login example</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Microdot login example</h1>
|
||||
{content}
|
||||
</body>
|
||||
</html>'''
|
||||
|
||||
LOGGED_OUT = '''<p>You are not logged in.</p>
|
||||
<form method="POST">
|
||||
<p>
|
||||
Username:
|
||||
<input type="text" name="username" autofocus />
|
||||
</p>
|
||||
<input type="submit" value="Submit" />
|
||||
</form>'''
|
||||
|
||||
LOGGED_IN = '''<p>Hello <b>{username}</b>!</p>
|
||||
<form method="POST" action="/logout">
|
||||
<input type="submit" value="Logout" />
|
||||
</form>'''
|
||||
|
||||
app = Microdot()
|
||||
set_session_secret_key('top-secret')
|
||||
Response.default_content_type = 'text/html'
|
||||
|
||||
|
||||
@app.get('/')
|
||||
@app.post('/')
|
||||
@with_session
|
||||
def index(req, session):
|
||||
username = session.get('username')
|
||||
if req.method == 'POST':
|
||||
username = req.form.get('username')
|
||||
update_session(req, {'username': username})
|
||||
return redirect('/')
|
||||
if username is None:
|
||||
return BASE_TEMPLATE.format(content=LOGGED_OUT)
|
||||
else:
|
||||
return BASE_TEMPLATE.format(content=LOGGED_IN.format(
|
||||
username=username))
|
||||
|
||||
|
||||
@app.post('/logout')
|
||||
def logout(req):
|
||||
delete_session(req)
|
||||
return redirect('/')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
55
src/microdot_session.py
Normal file
55
src/microdot_session.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import jwt
|
||||
|
||||
secret_key = None
|
||||
|
||||
|
||||
def set_session_secret_key(key):
|
||||
global secret_key
|
||||
secret_key = key
|
||||
|
||||
|
||||
def get_session(request):
|
||||
global secret_key
|
||||
if not secret_key:
|
||||
raise ValueError('The session secret key is not configured')
|
||||
session = request.cookies.get('session')
|
||||
if session is None:
|
||||
return {}
|
||||
try:
|
||||
session = jwt.decode(session, secret_key, algorithms=['HS256'])
|
||||
except jwt.exceptions.PyJWTError: # pragma: no cover
|
||||
raise
|
||||
return {}
|
||||
return session
|
||||
|
||||
|
||||
def update_session(request, session):
|
||||
if not secret_key:
|
||||
raise ValueError('The session secret key is not configured')
|
||||
|
||||
encoded_session = jwt.encode(session, secret_key, algorithm='HS256')
|
||||
|
||||
@request.after_request
|
||||
def _update_session(request, response):
|
||||
response.set_cookie('session', encoded_session, http_only=True)
|
||||
return response
|
||||
|
||||
|
||||
def delete_session(request):
|
||||
@request.after_request
|
||||
def _delete_session(request, response):
|
||||
response.set_cookie('session', '', http_only=True,
|
||||
expires='Thu, 01 Jan 1970 00:00:01 GMT')
|
||||
return response
|
||||
|
||||
|
||||
def with_session(f):
|
||||
def wrapper(request, *args, **kwargs):
|
||||
return f(request, get_session(request), *args, **kwargs)
|
||||
|
||||
for attr in ['__name__', '__doc__', '__module__', '__qualname__']:
|
||||
try:
|
||||
setattr(wrapper, attr, getattr(f, attr))
|
||||
except AttributeError: # pragma: no cover
|
||||
pass
|
||||
return wrapper
|
||||
72
tests/test_session.py
Normal file
72
tests/test_session.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import unittest
|
||||
from microdot import Microdot
|
||||
from microdot_session import set_session_secret_key, get_session, \
|
||||
update_session, delete_session, with_session
|
||||
from microdot_test_client import TestClient
|
||||
|
||||
set_session_secret_key('top-secret!')
|
||||
|
||||
|
||||
class TestSession(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.app = Microdot()
|
||||
self.client = TestClient(self.app)
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_session(self):
|
||||
@self.app.get('/')
|
||||
def index(req):
|
||||
session = get_session(req)
|
||||
return str(session.get('name'))
|
||||
|
||||
@self.app.get('/with')
|
||||
@with_session
|
||||
def session_context_manager(req, session):
|
||||
return str(session.get('name'))
|
||||
|
||||
@self.app.post('/set')
|
||||
def set_session(req):
|
||||
update_session(req, {'name': 'joe'})
|
||||
return 'OK'
|
||||
|
||||
@self.app.post('/del')
|
||||
def del_session(req):
|
||||
delete_session(req)
|
||||
return 'OK'
|
||||
|
||||
res = self.client.get('/')
|
||||
self.assertEqual(res.text, 'None')
|
||||
res = self.client.get('/with')
|
||||
self.assertEqual(res.text, 'None')
|
||||
|
||||
res = self.client.post('/set')
|
||||
self.assertEqual(res.text, 'OK')
|
||||
|
||||
res = self.client.get('/')
|
||||
self.assertEqual(res.text, 'joe')
|
||||
res = self.client.get('/with')
|
||||
self.assertEqual(res.text, 'joe')
|
||||
|
||||
res = self.client.post('/del')
|
||||
self.assertEqual(res.text, 'OK')
|
||||
|
||||
res = self.client.get('/')
|
||||
self.assertEqual(res.text, 'None')
|
||||
res = self.client.get('/with')
|
||||
self.assertEqual(res.text, 'None')
|
||||
|
||||
def test_session_no_secret_key(self):
|
||||
set_session_secret_key(None)
|
||||
|
||||
@self.app.get('/')
|
||||
def index(req):
|
||||
self.assertRaises(ValueError, get_session, req)
|
||||
self.assertRaises(ValueError, update_session, req, {})
|
||||
return ''
|
||||
|
||||
res = self.client.get('/')
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
set_session_secret_key('top-secret!')
|
||||
Reference in New Issue
Block a user