Unable to load correct page in Bottle Python

Viewed 50

i'm having a hard time understanding why i can't load the "/ticket" page properly, but i can load the "/" fine. I should have the /ticket page running and able to type the information under username and email but i'm missing something.

I'm running Python 3.10.4

"localhost:8080/"

"localhost:8080/ticket"

import os
import bottle
import base64
import traceback

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from binascii import hexlify
from html import escape
from bottle import route, request, get, post, response


page = '''
<html>
<head><title>Welcome</title></head>
<body>
<p><h2>Welcome, %s!</h2></p>
<p><h3>There are %s tickets left</h3></p>
<p>%s</p>
</body>
</html>
'''

key = os.urandom(32)
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())


def get_ticket(username, email, amount):
    encryptor = cipher.encryptor()
    padder = padding.PKCS7(128).padder() 
    message = padder.update("%s&%s&%d" % (username, email, amount)) + padder.finalize()
    ct = encryptor.update(message) + encryptor.finalize()
    return base64.b64encode(ct)

@bottle.route('/', method=('GET', 'POST'))
def default():
    decryptor = cipher.decryptor()
    unpadder = padding.PKCS7(128).unpadder()
    try:
        ticket = bottle.request.get_cookie('ticket')
        if ticket:
            message = decryptor.update(base64.b64decode(ticket)) + decryptor.finalize()
            username, email, amount = (unpadder.update(message) + unpadder.finalize()).split('&')
            return page % (escape(username), escape(amount),
                'Already done' if amount == '1' else 
                'Go ahead')
        else:
            return '<html><body><p><a href=/ticket>Fill in the forms</a></p></body><html>\n'
    except Exception as e:
        print(traceback.format_exc())
        raise bottle.HTTPError(status=401, body=e.message)

@bottle.post('/ticket', ['GET', 'POST'])
def ticket():
    username = bottle.request.forms.get('username')
    email = bottle.request.forms.get('email')
    if username and email:
        ticket = get_ticket(username, email, 1)
        bottle.response.set_cookie('ticket', ticket)
        return '<html><body><p>Thank you!</p></body></html>\n'
    raise bottle.HTTPError(status=400, body='Please fill in the form')

bottle.run(host='localhost', port=8080)
1 Answers

The reason this code hits an error is that you never actually serve a form for the user to fill in (and thus your code correctly errors). I don't really understand your flow at all, but I think it's supposed to look something like this:

  • user navigates to /
  • if user doesn't have a ticket, user is sent to a login page
  • user logs in and gets a ticket
  • user goes back to / and requests it, this time with a ticket (authentication cookie) in the get request.

Mind you I don't understand what these tickets are, as they appear to be a hashed form of whatever the user submits and the constant 1, but I guess there's more logic coming along later.

Currently there are quite a few problems with the setup:

  • / accepts post requests, but never does anything with them. It also doesn't make sense for it to accept post requests, since POST is to send data to a server.
  • your /ticket endpoint would do what you want if you sent it a form, but you never serve a form for the user to fill in.
  • your /ticket endpoint needs to return one thing if you make a GET request to it, another completely different thing if you make a POST request. This is possible, but it's a bad way to build these endpoints in general.
  • after getting the cookie the browser is simply going to throw it away. If you want to do cookie-based authentication, you need to store the cookie and handle it (normally you use this with JS in the webbrowser, and take care to add it to all requests).

If we add a login endpoint:

@bottle.route("/login")
def login():
    return """<html><body>
    <form action="./ticket" method="POST">
    Username: <input type="text" name="username"><br>
    Email: <input type="text" name="email"><br>
    <input type="submit">
    </form>
    </body></html>"""

And edit the previous href to point us to /login, we now get a form. Filling that form in and pressing 'submit' correctly sends the data to the /ticket endpoint, calling the code to get the ticket. Unfortunately that fails for unrelated reasons (your hashing needs bytes, not strings) but that's a different question.

I suggest you have a think about the overall flow of this application---what are these tickets for, and where is the user supposed to end up? Regardless you will need to serve some kind of login form at some point. You probably want this to be a static file, by the way, and served as such---see the bottle docs.

Related