Tutorial 5

This tutorial builds on the previous tutorial. Please complete it before proceeding.

Autogeneration

In the previous tutorial, we manually created a website with a login and signup system. But what if I told you that we could do all of this with just the CLI? Let’s create a new project from scratch:

python3 -m libmercury init

After navigating to your project folder, you can use the CLI to generate a login system:

python3 -m libmercury generate control:login

You will then be prompted with several questions to configure your login system:

[GENERATOR] Starting generation task
What should be the name of the controller: Login
What should be the route of the controller: /api/login
What should be the success redirect of the controller: /dashboard
What should be the name of the validator: Login
What is the name of the model to sign into (leave blank to create a new one):
What should the table's name be: user
What should the username field be called: username
What should the password field be called: password
Is the password hashed? (y/n): y
Is there an email field? (y/n): y
What should the email field be called: email
[CODEGEN] Successfully created src/cargo/userModel.py
What should the salt be called: salt
[GENERATOR] Successfully created model
What is the name of the model to sign into (leave blank to create a new one): user
What is the name of the model's password field: password
What is the name of the model's username field: username
Is the password hashed? (y/n): y
What is the name of the validator function (check_password if you used this for the model): check_password
What should be the name of the JWT: Main
What kind of key do you want to generate? (HMAC/RSA): rsa
[KEYGEN] RSA private key saved to src/.vault/MainPrivate_key.pem
[KEYGEN] RSA public key saved to src/.vault/MainPublic_key.pem
[CODEGEN] Successfully created src/cargo/MainJwt.py
[CODEGEN] Successfully created src/controllers/LoginController.py
[CODEGEN] Successfully created src/validators/LoginValidator.py
[GENERATOR] Successfully created login controller

With just a few commands, a user model and corresponding table are created. Next, we need to set up the migration in our database, which can be done using the CLI:

python3 -m libmercury create migration first_migration

Now, let’s program the migration to create the table:

from libmercury.db import Integer, MigrationWrapper, String, Column

_version = '1'
_prev_version = None

def upgrade(url):
    wrapper = MigrationWrapper(url)
    wrapper.create_table("user", [
        Column("id", Integer, primary_key=True),
        Column("username", String(20)),
        Column("password", String(50)),
        Column("salt", String, nullable=False),
        Column("email", String(318)),
    ])

def downgrade(url):
    wrapper = MigrationWrapper(url)
    wrapper.delete_table("user")

To apply the migration, use the following CLI command:

python3 -m libmercury migrate

Now that our model is created, we can easily generate a registration system using the CLI:

python3 -m libmercury generate control:register

You will be prompted with configuration questions for the registration system:

[GENERATOR] Starting generation task
What should be the name of the controller: Register
What should be the route of the controller: /api/register
What should be the success redirect of the controller: /login
What should be the name of the validator: Register
What is the name of the model to register for (leave blank to create a new one): user
Provide the name of your JWT to ensure that users are not signed in (leave blank to create a new one): Main
What is the name of the model's username field: username
Is the password hashed? (y/n): y
What is the name of the model's password field: password
What is the name of the model's password setter field: set_password
Where should the controller redirect if the user is already signed in: /dashboard
What is the name of the model's primary key: id
[CODEGEN] Successfully created src/validators/RegisterValidator.py
[CODEGEN] Successfully created src/controllers/RegisterController.py

Making it Pretty

With the back-end in place, we can now set up all our pages. Let’s start by creating a sign-in template in src/templates/login.html:

<html>
<body>
{% if error %}
    <div class="error">{{ error }}</div>
{% endif %}
<form action="/api/login" method="post">
    <input type="text" name="username">
    <input type="password" name="password">
    <input type="submit">
</form>
</body>
</html>

Next, create a sign-up template in src/templates/register.html:

<html>
<body>
    {% if error %}
        <div class="error">{{ error }}</div>
    {% endif %}
<form action="/api/register" method="post">
    <input type="text" name="username">
    <input type="password" name="password">
    <input name="email">
    <input type="submit">
</form>
</body>
</html>

Finally, create a protected endpoint that displays information about the signed-in user in src/templates/dashboard.html:

<html>
<body>
    <h1>Hello {{username}}</h1>
</body>
</html>

Now let’s connect these views to a controller named PagesController. To create this controller, run the following command:

python3 -m libmercury controller Pages

Then, add the following code to your controller:

from libmercury import GETRoute, Request, Response, use_template, useAuthorization, redirect
from libmercury.security.jwt import JWT
from src.security.MainJwt import MainJwt

class PagesController:
    @staticmethod
    @GETRoute("/login")
    def signin(request: Request) -> Response:
        return use_template("login.html", error=request.args.get("error"))

    @staticmethod
    @GETRoute("/register")
    def signup(request: Request) -> Response:
        return use_template("register.html", error=request.args.get("error"))

    @staticmethod
    @GETRoute("/dashboard")
    @useAuthorization(MainJwt, cookie="token", error=lambda: redirect("/login"))
    def protected(request: Request) -> Response:
        return use_template("dashboard.html", username=JWT(request.cookies["token"]).payload["username"])

With this setup, if you visit your page while the server is running, you should be able to use your website.

Next tutorial: Tutorial 6