Building the Admin Section

Last edit: Oct 25, 2019
  • Contributors:
  • pavelloz

This guide will help you set up a secured section on your e-commerce site, where you can manage all products and orders in your online store.

Note

This guide is part of a tutorial series on building an e-commerce website powered by platformOS. Find the first part of the series here.

Requirements

To follow this tutorial, you should be familiar with basic platformOS concepts, and the topics in the Get Started section. You should have configured the data model, as described in a previous part of this tutorial series.

Steps

Building a secured admin section is a five-step process:

Step 1: Create an admin user

Your admin panel should be accessible only to admin users, but you don’t have one just yet. After creating the instance, your user from the Partner Portal has been automatically added to your Instance. It’s now necessary to grant admin credentials to that user. You can use migrations to do that.

Go to the terminal and run the following command to create your first migration:

pos-cli migrations generate development add_admin

This should create a new file in your migrations folder.

app/migrations/{TIMESTAMP}_add_admin.liquid


{% graphql user_id = 'get_user', email: "your-email@example.com" | fetch: "users" | first | fetch: "id" %}
{% graphql res = "promote_user_to_admin", id: user_id %}
{% graphql res = "set_user_password", id: user_id, password: 'admin' %}

Note

{TIMESTAMP} is going to be a 12 digit number representing the date and time when the migration was created. You will use this in a moment to run the migration.

You need three more files to make it work. First, create a generic get_user query.

app/graphql/get_user.graphql

query get_user($id: ID, $email: String, $slug: String) {
  user(id: $id, email: $email, slug: $slug) {
    id
    name
    email
    profile(profile_type: "default") {
      properties
    }
  }
}

Note

You can build queries with additional parameters, to make them more versatile. Empty parameters are omitted when query is executed.

Note

properties is a special keyword, that will list all of custom properties attached to the object, so you do not have to list them all one by one.

app/graphql/user/mutations/promote_user_to_admin.graphql

mutation promote_user_to_admin($id: ID) {
  user_update(
    form_name: "promote_user_to_admin_form"
    id: $id
    user: {
      profiles: [
        {
          name: "default"
          values: { properties: [{ name: "admin", value: "true" }] }
        }
      ]
    }
  ) {
    id
  }
}

app/forms/user/promote_user_to_admin_form.liquid

---
name: promote_user_to_admin_form
resource_owner: "anyone"
resource: User
fields:
  profiles:
    default:
      properties:
        admin:
---

There is one more thing to do. When the Instance was created, your user has been copied, but for security reasons its password was set to a random string. You have to update it as well. You need to add one more mutation and form configuration to do just that.

app/graphql/user/mutations/set_user_password.graphql

mutation set_user_password($id: ID, $password: String) {
  user_update(
    form_name: "set_user_password_form"
    id: $id
    user: { password: $password }
  ) {
    id
  }
}

app/forms/user/set_user_password_form.liquid

---
name: set_user_password_form
resource_owner: "anyone"
resource: User
fields:
  password:
---

Important

Right now this form configuration is not secure. You had to set resource_owner to anyone so it can be executed by a logged out user, but it should be set to self once the migration is done. This way the user will only be able to change their own password.

Once all of these files are ready, you can run the migration with the pos-cli command. Substitute TIMESTAMP with migration ID from your own file.

pos-cli migrations run development TIMESTAMP

Step 2: Create an Authorization Policy

Your user now has the required admin credential. This allows you to create your first Authorization Policy, that you can use to secure the form you’ve just created in the previous step and reuse it to limit access to all resources in the admin panel.

app/authorization_policies/admin_user.liquid

---
name: admin_user
redirect_to: /unauthorized
flash_alert: Sorry, you have to be an admin user to access this page.
---

{% graphql g = 'current_user' %}
{% if g.current_user.profile.properties.admin %}true{% endif %}

app/graphql/current_user.graphql

query current_user {
  current_user {
    id
    name
    email
    profile(profile_type: "default") {
      properties
    }
  }
}

Note

You’ll notice, that this query is identical to the get_user query apart from having no parameters, and using the current_user endpoint instead. It always returns the currently authorized user.

Now, that the Authorization Policy is in place, you can go ahead and modify the promote_user_to_admin_form form, so that only admins can promote other users to admins from now on.

app/forms/user/promote_user_to_admin_form.liquid

name: promote_user_to_admin_form
resource_owner: "anyone"
resource: User
fields:
  profiles:
    default:
      properties:
        admin:
authorization_policies: [admin_user]
---

Step 3: Create a sign-in form

You are ready to create the sign-in form for your application. Sign-up will come later, after you’ve finished the management section of the app.

The whole process has been described in our documentation in the Signing in User Manually article.

You’ll need a new page and form configuration file.

app/views/pages/sign-in.html.liquid


---
slug: sign-in
layout_name: authorization
metadata:
  title: Sign in
---
{% include_form 'sign_in_form' %}

<hr>

<p>
  Don’t have an account yet?
  <a href="/sign-up">Sign up as a new user</a>
</p>


Note

You can also use a new layout file views/layouts/authorization.html.liquid to make the authorization screens a bit more compact.

app/forms/session/sign_in_form.liquid


---
name: sign_in_form
resource: Session
flash_notice: 'You are now logged in'
redirect_to: >
  {%- graphql g = 'current_user' -%}
  {%- if g.current_user.profile.properties.admin -%}
    /admin
  {%- else -%}
    /
  {%- endif -%}
fields:
  email:
  password:
---
{% form %}
  <div class="form-group">
    <label for="sign-in-email">Email</label>

    <input
      type="email"
      class="form-control{% if form.errors.email != blank %} is-invalid{% endif %}"
      id="sign-in-email"
      value="{{ form.fields.email.value }}"
      name="{{ form.fields.email.name }}"
    />

    {% if form.errors.email %}
      <div class="invalid-feedback">{{ form.errors.email }}</div>
    {% endif %}
  </div>
  <div class="form-group">
    <label for="sign-in-password">Password</label>

    <input
      type="password"
      class="form-control{% if form.errors.email != blank %} is-invalid{% endif %}"
      id="sign-in-password"
      name="{{ form.fields.password.name }}"
    />

    {% if form.errors.password %}
      <div class="invalid-feedback">{{ form.errors.password }}</div>
    {% endif %}
  </div>

  <div class="form-group row">
    <div class="col-sm-12">
      <button type="submit" class="btn btn-primary">Sign in</button>
    </div>
  </div>
{% endform %}


Note

Input fields are a prime candidate for refactoring, as there is a lot of repetition and common logic that can be implemented with shared partials.

Step 4: Create a sign-out form

Now that you can sign in, you should be able to sign out as well. Logging Out an Authenticated User describes well what needs to be done. You’ll add the form configuration now, so that it’s ready when you are working on the top navigation and the sign out button.

app/forms/session/sign_out_form.liquid


---
name: sign_out_form
resource: Session
---
{% form method: 'delete' %}
  <button type="submit" class="dropdown-item">Log out</button>
{% endform %}

Step 5: Create a protected page

All that is left is to add an admin index page and make sure only admins can access it.

app/views/pages/admin/index.liquid

---
slug: admin
authorization_policies: [admin_user]
metadata:
  title: Admin panel
---
<p>Only admins can access this page.</p>

Next steps

Congratulations! You’ve promoted your user to an admin, created sign-in and sign-out forms and added a protected area that will be used to add all administration functionalities for the website.

You are ready to do some views refactoring with shared partials to make sure that further development goes smoothly.

Questions?

We are always happy to help with any questions you may have.