Querying data

In this section, you learn how to query data from your client application and implement attribute-based access control (ABAC).

“Attribute-based access control, also known as policy-based access control for IAM, defines an access control paradigm whereby access rights are granted to users through the use of policies which combine attributes together.” – Wikipedia

In the previous section, you were able to log in a user and save their session in browser cookies. To retrieve data using the access token (saved in session cookies) you first need to define a role in Fauna. This role will specify the resources you can interact with.

Navigate to Fauna dashboard. Select Security > Roles > New Role to create a new role.

  1. Name your new role AuthRole since all authenticated users will assume this role.
  2. In the collection section Add Owner and Store. Provide read privilege to both Owner and Store collections.
  3. In the Indexes section add findOwnerByEmail and owner_stores_by_owner index. Give read privilege to both of these indexes.
  4. Navigate to the membership tab.
specify privileges

The findOwnerByEmail query lets you query a user by email using the generated access token. The owner_stores_by_owner lets you query the stores that belong to a particular user.

Membership in Fauna specific identities that should have the specified privileges. In this scenario all the records in the Owner collection are members.

  1. In the membership tab add Owner collection as the member collection.
  2. Select Save.
select membership

When an owner logs in to your application you want to show that user their basic information (i.e. username, email) and all the stores that belong to that owner. To do so you can make the following GraphQL query with the owner’s email as a parameter.

query findbyEmail($email: String!) {
  findOwnerByEmail(email: $email) {
    _id
    name
    email
    stores {
      data {
        _id
        name
      }
    }
  }
}

You use the secret retrieved from the login function to make the previous query. The secret is saved in the browser session cookies when a user logs in. Therefore, you can retrieve it from the cookies. Once you retrieve the session secret, you can pass it down to the GraphQL client and set the header.

Create a new function called clientWithCookieSession in your src/client.js file as follows. The clientWithCookieSession takes in a token as parameter and sets up the @urql/svelte client.

...
export const clientWithCookieSession = token => createClient({
  url,
  
  fetchOptions: () => {
    console.log('token', token);
    return {
      headers: { authorization: token ? `Bearer ${token}` : '' },
    };
  },
});

Next, replace the contents of src/routes/index.svelte with the following code.

<script>
  import { operationStore, query, setClient } from '@urql/svelte';
	import {clientWithCookieSession} from '../client';
  import Cookie from 'js-cookie';

  const cookies = Cookie.get('fauna-session');

  const { email, secret} = cookies ? JSON.parse(cookies) : {};
  setClient(clientWithCookieSession(secret));

  const findCurrentOwner = operationStore(`
    query findbyEmail($email: String!) {
      findOwnerByEmail(email: $email) {
        _id
        name
        email
        stores {
          data {
            _id
            name
          }
        }
      }
    }
  `,
  {
    email,
  },
  { requestPolicy: 'network-only' }
  );
  query(findCurrentOwner);
</script>

<div class="uk-container wrap">
  {#if !cookies}
    <p>
      You are not Loged in.
    </p>
    <a href="/login">Login</a>
  {/if}

  {#if $findCurrentOwner.data}
    <h4>{$findCurrentOwner.data.findOwnerByEmail.name}</h4>
    <div><b>Email:</b> {$findCurrentOwner.data.findOwnerByEmail.email}</div>
    <ul class="uk-list uk-list-large uk-list-striped">
      {#each $findCurrentOwner.data.findOwnerByEmail.stores.data as store}
      <li>
        <div class="container">
          <div>{store.name}</div>
          <p uk-margin>
            <a href="/store/{store._id}">View</a>
          </p>
        </div>
      </li>
      {/each}
    </ul>
  {/if}

</div>

<style>
  .wrap {
    max-width: 350px;
  }
</style>

Run your application with npm run dev command and make sure everything is working as intended.

Application Status

Updating the app UI

Modify the layout of the application. The app right now could use a navbar for better navigation. Next, go ahead and add a Navbar.

Create a new navbar component. Create a new file src/lib/Nav.svelte and add the following code for Navbar.

<script lang="js">
  import Cookies from 'js-cookie';

  function logout() {
    Cookies.remove('fauna-session');
    alert('User Logoed Out');
  }
</script>

<nav class="uk-navbar-container wrap" >
  <div class="uk-navbar-left">
    <ul class="uk-navbar-nav">
      <li class="uk-active"><a href='/'>Fauna E-Com</a></li>
    </ul>
  </div>
  <div class="uk-navbar-right">
    <ul class="uk-navbar-nav">
      <li >
        <a href='/store/new' class="uk-button uk-button-primary add-btn">
          Add Store
        </a>
      </li>
      <li >
        <!-- svelte-ignore a11y-missing-attribute -->
        <a on:click={logout} class="uk-button uk-button-danger add-btn">
          Logout
        </a>
      </li>
    </ul>
  </div>
</nav>

<style>
.wrap {
  display: flex;
}
.add-btn {
  color: azure;
}
</style>

Apply a layout to your application, so the Navbar component appears on every page. Create a new file src/routes/__layout.svelte and add the following code snippet.

<script>
  import Nav from '$lib/Nav.svelte';
</script>
<Nav />
<slot></slot>

After you apply the changes, notice that a navbar appears in every page of your application.

Currently, the Add Store and Logout buttons are always visible. These buttons should only be visible when a user is logged in. Otherwise, the app should display the Login and Signup buttons.

You can use a Svelte writeable store to manage the state of your application dynamically. First, create a new file src/store.js and add the following code.

import { writable } from 'svelte/store';

export const userSession = writable(null);

Update the userSession in svelte-store when a user successfully logs in. Make the following changes to your src/routes/login.svelte file.

<script>
  ...
  import { userSession } from '../store';
  ...

  async function onSubmit(e) {
    const formData = new FormData(e.target);

    ...
    if(resp.data?.login) {
      alert('Login Successful');

      Cookies.set(
        'fauna-session', 
        JSON.stringify(resp.data.login),
        { expires: new Date(resp.data.login.ttl) }
      );

      userSession.update(() => (resp.data.login));

      goto('/')
    }
  }
</script>

Update the src/lib/Nav.svelte file as follows.

<script lang="js">
  import Cookies from 'js-cookie';
  import { userSession } from '../store.js';

  let user;
  userSession.subscribe(val => {
    user = val;
    const cookies = Cookies.get('fauna-session');
    if(!val && cookies) {
      user = JSON.parse(cookies);
      userSession.set(user);
    }
  });


  function logout() {
    Cookies.remove('fauna-session');
    alert('User Logoed Out');
    userSession.update(() => null);
    window.location.reload();
  }
</script>

<nav class="uk-navbar-container wrap" >
  <div class="uk-navbar-left">
    <ul class="uk-navbar-nav">
      <li class="uk-active"><a href='/'>Fauna E-Com</a></li>
    </ul>
  </div>
  <div class="uk-navbar-right">
    <ul class="uk-navbar-nav">
      {#if user}
        <li >
          <a href='/store/new' class="uk-button uk-button-primary add-btn">
            Add Store
          </a>
        </li>
        <li >
          <!-- svelte-ignore a11y-missing-attribute -->
          <a on:click={logout} class="uk-button uk-button-danger add-btn">
            Logout
          </a>
        </li>
      {:else}
        <li >
          <a href='/login' class="uk-button uk-button-primary add-btn">
            Login
          </a>
        </li>
        <li >
          <a href='/signup' class="uk-button uk-button-danger add-btn">
            Signup
          </a>
        </li>
      {/if}
    </ul>
  </div>
</nav>

<style>
.wrap {
  display: flex;
}
.add-btn {
  color: azure;
}
</style>

Now your application state will be synced when user logs in or logs out.

That’s all for this section. In the next section, you implement Create Delete and Update stores. You also do a deep dive into custom resolvers and Fauna Query Language (FQL).

Complete Code

📙 Get the final code for this section here