Session #239
-
Preamble: First, I wanted to thank you for the work you have done with this framework, especially since it is as pleasant to use as its big brother Flask. Thank you! Environment:
Context: I am working on a POC for an e-commerce site and have encountered several issues with the use of sessions. The first issue encountered is when I secured the header: The application uses sub-applications, for example: File: main.py def create_app():
app = Microdot()
app.mount(auth, url_prefix='/auth')
return app
app = create_app()
Session(app, secret_key='top-secret')
Response.default_content_type = 'text/html'
Template.initialize(template_dir='templates/', enable_async=True) I then modified the header response to add a bit of security: File: main.py @app.after_request
async def secure_headers(req, resp):
resp.headers['Cache-Control'] = "no-store, no-cache"
resp.headers['Content-Security-Policy'] = "base-uri 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'none'; frame-src 'none'; img-src 'self'; media-src 'self'; object-src 'self'; script-src 'self'; worker-src 'self'"
resp.headers['Content-Security-Policy-Report-Only'] = "default-src 'self'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; font-src 'self'; object-src 'none';"
resp.headers['Cross-Origin-Embedder-Policy'] = "require-same-origin"
resp.headers['Cross-Origin-Opener-Policy'] = 'same-origin'
resp.headers['Cross-Origin-Resource-Policy'] = 'same-site'
resp.headers['Feature-Policy'] = "geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; usb 'none'; encrypted-media 'none';"
resp.headers['Permissions-Policy'] = "accelerometer=(), ambient-light-sensor=(), autoplay=(), camera=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), speaker=(), sync-xhr=(), usb=(), vr=()"
resp.headers['Referrer-Policy'] = "strict-origin"
resp.headers['Set-Cookie'] = "; HttpOnly; Secure; SameSite=Lax"
resp.headers['Strict-Transport-Security'] = "max-age=31536000; includeSubDomains; preload"
resp.headers['X-Content-Type-Options'] = "nosniff"
resp.headers['X-DNS-Prefetch-Control'] = 'off'
resp.headers['X-Download-Options'] = 'noopen'
resp.headers['X-Frame-Options'] = "DENY"
resp.headers['X-Permitted-Cross-Domain-Policies'] = 'none'
resp.headers['X-Xss-Protection'] = "1; mode=block"
resp.headers.pop('X-Powered-By', None) Now if I try to add values to the session in a post-login like this: File: /auth/router.py auth = Microdot()
@auth.post('/login')
@with_session
async def login(req, session):
...
# Add datas to session
session['user_id'] = str(datas['id'])
session['email'] = email
session['cart'] = datas['cart']
session.save()
return redirect('/', 303) I get the following error, which is due to the modification of the
I remind you that if I comment out line My second session problem is an issue of propagation. Here, the line When I am in my authentication and the user posts their data, the session is created correctly since I get the following response: File: /auth/router.py auth = Microdot()
@auth.post('/login')
@with_session
async def login(req, session):
...
# Add datas to session
session['user_id'] = str(datas['id'])
session['email'] = email
session['cart'] = datas['cart']
session.save()
print('Login page session (on sub-app auth):', session)
return redirect('/', 303) The response is :
But after the redirect i come to the index page at '/' File: main.py def create_app():
app = Microdot()
app.mount(auth, url_prefix='/auth')
return app
app = create_app()
Session(app, secret_key='top-secret')
Response.default_content_type = 'text/html'
Template.initialize(template_dir='templates/', enable_async=True)
@app.get('/')
@with_session
async def home(req, session):
# Get db connection
async with Database() as con:
async with con.transaction():
datas = await con.fetch('SELECT "title", "link", "infos" FROM "ProductLines" ORDER BY "link";')
print('Home page session : (on app)', session)
return await Template('/home.jinja').render_async(
title='Bienvenue', product_lines=datas, base_url=getenv('BASE_URL'), session=session
) I got an empty session response :
I conclude that the use of a session in a sub-app does not propagate correctly to the main application, which is quite inconvenient. I hope my explanations are clear enough. Best regards. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 10 replies
-
I moved the issue that you created to a discussion. Please do not write issues unless you are reporting a bug. When you need help with your application or when you are not sure if you are seeing an issue on your side or on this project then use a discussion. Regarding this line:
What do you expect this to do? I assume you agree that you are setting an invalid cookie, right? Given that Microdot supports setting cookies, I suggest you do not bypass the standard method to set them by going to the The second problem I'm not really sure. Could you share the complete code for the |
Beta Was this translation helpful? Give feedback.
-
Thank you for your reply, and I apologize for the outcome, I didn't mean to do anything wrong. Please find the code below: # -----------------------------------------------------------------------------------------------------------
# !/usr/bin/python3
# -*- coding: UTF-8 -*-
# -----------------------------------------------------------------------------------------------------------
from database.db import Database
from microdot import Microdot
from microdot import redirect
from microdot.jinja import Template
from microdot.session import with_session
from orjson import dumps
from os import getenv
from pendulum import today
from secrets import token_hex
from src.security import got_form_keys
from src.security import hash_password
from src.security import is_secure_form
from src.security import is_trash_domain_name
from src.security import verify_password
from src.utils import Alert
from uuid import uuid4
# -----------------------------------------------------------------------------------------------------------
auth = Microdot()
# -----------------------------------------------------------------------------------------------------------
# BEFORE REQUEST
# -----------------------------------------------------------------------------------------------------------
@auth.before_request
@with_session
async def check_is_logged(req, session):
if 'user_id' in session:
return redirect('/')
# -----------------------------------------------------------------------------------------------------------
@auth.get('/login')
async def login(req):
return await Template('/auth/login.jinja').render_async(
title='Connexion', base_url=getenv('BASE_URL')
)
# -----------------------------------------------------------------------------------------------------------
@auth.post('/login')
@with_session
async def login(req, session):
alert = Alert()
form = req.form
keys = ["email", "password"]
if not got_form_keys(form, keys) or not is_secure_form(form):
return "Bad Request", 400
email, password = (form[_] for _ in keys)
if not is_trash_domain_name(email):
# Get db connection
async with Database() as con:
async with con.transaction():
stmt = await con.prepare('SELECT "id", "pwd", "is_active", "cart" FROM "Users" WHERE email=$1;')
datas = await stmt.fetchrow(email)
if datas and verify_password(datas['pwd'], password):
if datas['is_active']:
# Get db connection
async with Database() as con:
async with con.transaction():
stmt = await con.prepare('UPDATE "Users" SET last_con=$1 WHERE email=$2;')
await stmt.fetchrow(today().date(), email)
# Add datas to session
session['user_id'] = str(datas['id'])
session['email'] = email
session['cart'] = datas['cart']
session.save()
return redirect('/', 303)
else:
alert: dict = alert.danger(4)
else:
alert: dict = alert.danger(3)
else:
alert: dict = alert.danger(1)
return await Template('/auth/login.jinja').render_async(
title='Connexion', base_url=getenv('BASE_URL'), alert=alert
)
# -----------------------------------------------------------------------------------------------------------
@auth.get('/register')
@with_session
async def register(req):
return await Template('/auth/register.jinja').render_async(
title='Inscription', base_url=getenv('BASE_URL')
)
# -----------------------------------------------------------------------------------------------------------
@auth.post('/register')
async def register(req):
alert = Alert()
form = req.form
keys = ["lastname", "firstname", "email", "password"]
if not got_form_keys(form, keys) or not is_secure_form(form):
return "Bad Request", 400
lastname, firstname, email, password = (form[_] for _ in keys)
if 'checkbox' in form.keys():
if not is_trash_domain_name(email):
# Get db connection
async with Database() as con:
async with con.transaction():
stmt = await con.prepare('SELECT COUNT(email) FROM "Users" WHERE email=$1;')
res = await stmt.fetchrow(email)
if not res['count']:
password: str = hash_password(password)
cart : dict = dumps({'items': []}).decode()
# Get db connection
async with Database() as con:
async with con.transaction():
stmt = await con.prepare(
'''INSERT INTO "Users" ("id", "fname", "lname", "email", "pwd", "token", "registered", "cart")
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)'''
)
await stmt.fetchrow(
uuid4(), firstname, lastname, email, password, token_hex(120), today().date(), cart
)
alert: dict = alert.success(0)
else:
alert: dict = alert.danger(2)
else:
alert: dict = alert.danger(1)
else:
alert: dict = alert.danger(0)
return await Template('/auth/register.jinja').render_async(
title='Inscription', base_url=getenv('BASE_URL'), alert=alert
)
# ----------------------------------------------------------------------------------------------------------- |
Beta Was this translation helpful? Give feedback.
-
I really don't understand the problem when I post my form and log in I can see :
{'user_id': 'eb4f8858-3999-4f2f-85b1-1443fd17e981', 'email': '[email protected]', 'cart': '{"items":[]}'}
POST /auth/login 303
@app.get('/')
@with_session
async def home(req, session):
print(session)
print(session.get('user_id'))
# Get db connection
async with Database() as con:
async with con.transaction():
datas = await con.fetch('SELECT "title", "link", "infos" FROM "ProductLines" ORDER BY "link";')
return await Template('/home.jinja').render_async(
title='Bienvenue', product_lines=datas, base_url=getenv('BASE_URL'), session=session
) the output is :
But if I go back to auth/login I get my session data back
The real data length of my session is currently 107bytes All route from the same sub-app can retrieve session datas :
Can you share with me a working example of a session with an a file (with main app) and a b file with a sub app. thank you in advance |
Beta Was this translation helpful? Give feedback.
@paroxyste-0 Give the main branch another try please and let me know if there are any remaining problems.