VTML
Github iconNPM icondownload
Tutorial
Getting startedHello worldMy first siteAdding a formRoutingAuth
Documentation
VariablesLogicPagesSQLFormsChecksPortalsJavascriptEvent streams
Reference
v-withv-ifv-unlessv-for-eachv-portalv-jsonv-yamlv-markdownv-sqlv-nodejsv-dumpv-hint-portv-pagev-exposev-set-cookiev-includeformv-outputv-check-*v-catchv-tryv-set-statusv-redirectv-subscribev-notify
warning

VTML and vtml.org is in alpha and under active development. Use at your own risk

warning

VTML is in alpha. Use at own risk.

Auth

Our dogs DB is looking great but right now anyone can view and alter our data. Although we may want to show our canine companions to the whole world anyone could add their own dog, or event worse they could start adding cats!

To rectify this we need to add an auth-barrier into our app. VTML has a simple mechanism for deciding who gets to use what.

First of all we will need to add a table to our database to store the users. We're also going to be adding session data to this table to keep it nice and simple.

After running sqlite3 dogsdb.sqlite input this SQL to create a users table with three fields:

CREATE TABLE users (name TEXT, password_hash TEXT, session_key TEXT);

info If you downloaded the example database this has already been done for you.

Next up we'll need a way to create password hashes. This will stop anyone from being able to see the user's passwords in plain text when they look at the database. To do this we'll need to call nodejs directly.

Create a new file called hash_password.js and add the following:

import crypto from 'node:crypto'

export default
function hash_passport(password) {
	return crypto.createHash('md5').update(password).digest('hex');
}

It's a very simple hashing function and shouldn't be used in the real world but it will do for now.

Let's go ahead and make this function available to VTML. At the top of your index.vtml add this tag:

<v-nodejs target="$hash_password" import="./hash_password.js" />

info We could have imported the function like so:

<v-nodejs target="$hash_password" >
import hash_password from "./hash_password"
return hash_passport
</v-nodejs>

However VTML would see that as an external data-source and execute it for every request. By importing with the --import attribute we're telling VTML that it's an external source file which only needs to be imported on application startup.

The last thing we need to do outside of VTML is add an initial user to the dataabse. We'll add a user named admin with the password password1. Not very original but that's fine for now.

INSERT INTO users (name, password_hash) VALUES ('admin', '7c6a180b36896a0a8c02787eeafb0e4c');

Once we have everything setup we're going to want to add a login page. Let's go back to our main.vtml and add a new route inside it with a login form.

<v-page path="/login" >
    <form v-name="login" method="post" >
        <input name="username" placeholder="username" />
        <input name="password" type="password" placeholder="password" />
		<button type="submit" >Login</button>

        <v-action>
            ...
        </v-action>
    </form>
</v-page>

For the action we need to do five things when the login is valid:

<v-action>

    <v-nodejs target=$hash >
        return $hash_password($.body.password)
    </v-nodejs>

    <v-sql single-row target=$user >
        SELECT * FROM users WHERE name = $.body.username AND password_hash = $hash;
    </v-sql>

    <v-check-found $user >
        <v-nodejs target=$session_key >
            return "skey_"+Math.random()
        </v-nodejs>

        <v-sql>
            UPDATE USERS SET session_key = $session_key WHERE name = $.body.username;
        </v-sql>

        <v-set-cookie name="session_key" value=$session_key max-days=1 />

        <v-redirect path="/" />
    </v-check-found>

</v-action>

The new tag that we used here is <v-check-found> which will cause the response to return a 404 status when the source variable ($user in this case) is falsy (or undefined in this case).

To better explain the v-check tags let's add another one into our application. Let's say that we want guests to the site to be able to view any of the dogs but not create or edit them.

To do so we want to use a different v-check tag <v-check-authenticated> which, similar to the v-check-found tag will return 401 when when the condition is false.

We also need to find the user in the database by the session_key passed into the request.

To apply this we need to revisit the routing block in main.vtml and change the /create page to look like so:

<v-page path="/create" >
    <v-sql single-row target=$user >
        SELECT * FROM users WHERE session_key = $.cookies.session_key;
    </v-sql>
    <v-check-authenticated $user >
        <form v-name="create_dog" >
            ...
        </form>
    </v-check-authenticated>
</v-page>

Now our application will set a 401 status and display an error message when we try to visit this page when $user is undefined.

One last thing we can do is to redirect the user to the login page when they get the 401 status.

Back in the index.vtml update the <v-catch> block with a bit of logic to check what the status is:

<v-catch>
  <v-if source=$error.code eq="401" >
  	<v-redirect path="/login" />
  </v-if>

  Error: $error.message
</v-catch>

Now when the <v-check-authenticated> throws a 401 and we end up in the <v-catch> we will be sent to the login page instead of seeing the error.

And that's it! Now users can view the site but need to be logged in with a valid user to be able to add our own pooches.