Pular para o conteúdo

Programando Soluções

Cadastro e autenticação de usuário – ToDo List parte 9

  • por
autenticação

Introdução

Neste post vou te mostrar como criar o cadastro, autenticação de usuário e o vínculo dos usuários com as tarefas.

Este é uma continuação do Projeto ToDo List com VueJS, caso você queira pegar o código fonte atual do projeto, clique aqui. Ou se preferir ver a playlist com o desenvolvimento do projeto, clique aqui.

Vou implementar a tela de cadastro, para que um novo usuário consiga se registrar.

A autenticação para que consiga acessar o sistema.

E o vínculo do usuário com as tarefas, para quando o usuário se logar, ver somente as suas tarefas.

Cadastro de usuário

A primeira etapa será criar a model de user, que ficará em /src/models/UsersModel.js

import Model from "./Model";

export default class Users extends Model {
    resource() {
        return "users";
    }
}

Agora vou criar o componente em /src/views/Register.vue.

Ele vai ter uma estrutura parecida com a do Login, porém vou inverter a ordem das colunas, somente para diferenciar da tela de Login.

Vou utilizar uma imagem diferente também, então vou no unDraw, pesquisei por create e peguei a primeira imagem.

Salvei a imagem no computador, troquei de nome e movi para a pasta /src/assets/images/register.vue

O formulário terá quatro campos, sendo eles de Nome, E-mail, Senha e Confirmação de senha.

Criei os quatro campos e ajustei a validação, para fazer a validação do e-mail e a validação da senha e confirmação de senha.

Depois disso criei o método register, que valida os dados, cria um novo usuário e redireciona para a tela de login.

Segue o código fonte completo do Register.vue

<template>
  <b-row class="vh-100 vw-100 row-login">
    <b-col
      sm="7"
      class="side-login d-flex justify-content-center align-items-center"
    >
      <img src="../assets/images/register.svg" class="img-register" />
    </b-col>
    <b-col
      sm="5"
      class="d-flex justify-content-center align-items-center right-register"
    >
      <div class="col-8">
        <h2 class="text-center mb-5 title-login">Faça o seu cadastro</h2>
        <b-form>
          <b-form-group label="Nome" label-for="name">
            <b-form-input
              id="name"
              type="text"
              placeholder="João Silva"
              autocomplete="off"
              v-model.trim="$v.form.name.$model"
              :state="getValidation('name')"
            ></b-form-input>
          </b-form-group>

          <b-form-group label="E-mail" label-for="email">
            <b-form-input
              id="email"
              type="email"
              placeholder="joaosilva@email.com"
              autocomplete="off"
              v-model.trim="$v.form.email.$model"
              :state="getValidation('email')"
            ></b-form-input>
          </b-form-group>

          <b-form-group label="Senha" label-for="password">
            <b-form-input
              id="password"
              type="password"
              placeholder="Digite aqui a sua senha"
              v-model.trim="$v.form.password.$model"
              :state="getValidation('password')"
            ></b-form-input>
          </b-form-group>

          <b-form-group label="Confirme sua senha" label-for="confirmPassword">
            <b-form-input
              id="confirmPassword"
              type="password"
              placeholder="Confirme sua senha"
              v-model.trim="$v.form.confirmPassword.$model"
              :state="getValidation('confirmPassword')"
            ></b-form-input>
          </b-form-group>

          <b-button type="button" variant="primary" block @click="register"
            ><i class="fas fa-sign-in-alt"></i> Cadastrar</b-button
          >

          <hr />

          <b-button
            type="button"
            variant="outline-secondary"
            block
            @click="goToLogin"
            ><i class="fas fa-arrow-left"></i> Voltar</b-button
          >
        </b-form>
      </div>
    </b-col>
  </b-row>
</template>

<script>
import { required, minLength, email, sameAs } from "vuelidate/lib/validators";
import UsersModel from "@/models/UsersModel";
import ToastMixin from "@/mixins/toastMixin.js";

export default {
  mixins: [ToastMixin],

  data() {
    return {
      form: {
        name: "",
        email: "",
        password: "",
        confirmPassword: "",
      },
    };
  },

  validations: {
    form: {
      name: {
        required,
        minLength: minLength(3),
      },

      email: {
        required,
        email,
      },

      password: {
        required,
        minLength: minLength(6),
      },

      confirmPassword: { 
        required, 
        sameAsPassword: sameAs('password') 
      }
    },
  },

  methods: {
    register() {
      this.$v.$touch();
      if (this.$v.$error) return;

      const user = new UsersModel(this.form);
      user.save();

      this.showToast("success", "Sucesso!", "Usuário criado com sucesso");
      this.goToLogin();
    },

    getValidation(field) {
      if (this.$v.form.$dirty === false) {
        return null;
      }

      return !this.$v.form[field].$error;
    },

    goToLogin() {
      this.$router.push({ name: "login" });
    },
  },
};
</script>

<style>
*,
*:after,
*:before {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  text-decoration: none;
}

.row-login {
  margin-left: 0 !important;
}

.img-register {
  width: 600px;
  height: 600px;
}

.right-register {
  background-color: #f2f2f2;
}

.title-login {
  font-weight: bold;
}
</style>

Vou registrar esse componente dentro do vue-router para poder acessar a rota /register e mostrar esse componente.

import Vue from 'vue'
import VueRouter from 'vue-router'
import List from '../views/List.vue'
import Form from '../views/Form.vue'
import Login from '../views/Login.vue'
import Register from '../views/Register.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'list',
    component: List
  },
  {
    path: '/form',
    name: 'form',
    component: Form
  },
  {
    path: '/login',
    name: 'login',
    component: Login
  },
  {
    path: '/register',
    name: 'register',
    component: Register
  },
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

No Login.vue, vou criar um método goToRegister, e vou utilizar ele no botão “não tenho conta” para quando o usuário clicar nesse botão, redirecionar para a tela de registro.

<!-- Login.vue -->
<b-button 
        type="button" 
        variant="outline-secondary"  
        block
        @click="goToRegister"
        ><i class="fas fa-user-plus"></i> Não tenho conta</b-button>
//Login.vue
goToRegister() {
      this.$router.push({ name: "register" });
    }

Ao acessar a rota /register, ou clicar no botão “não tenho conta” na tela de login será mostrado o componente de cadastro.

Autenticação

Com o usuário cadastrado, será possível realizar a autenticação dele.

No login será preciso buscar o usuário, caso ele exista vou verificar se a senha digitada é igual a senha salva.

Caso seja, vou salvar esse usuário no localStorage, para conseguir identificar que ele está logado e depois ter o id dele para salvar nas tarefas.

Também vou fazer o redirecionamento do usuário para a lista de tarefas.

O método de autenticação fica assim:

//Login.vue
async login() {
      this.$v.$touch();
      if (this.$v.$error) return;

      let user = await UsersModel.params({email: this.form.email}).get();

      if(!user || !user[0] || !user[0].email) {
        this.showToast("danger", "Erro!", "Usuário e/ou senha incorretos");
        this.clearForm();
        return;
      }

      user = user[0];
      if(user.password !== this.form.password) {
        this.showToast("danger", "Erro!", "Usuário e/ou senha incorretos");
        this.clearForm();
        return;
      }

      localStorage.setItem('authUser', JSON.stringify(user));
      this.$router.push({name: "list"});
    },

    clearForm() {
      this.form = {
        email: "",
        password: ""
      }
    },

Adicionei um método chamado clearForm, caso o usuário erre os dados de autenticação, uso esse método para limpar o formulário.

Vou implementar agora a proteção das rotas privadas, que vão ser a de listagem e cadastro das tarefas.

Para isso vou alterar o index.js do vue-router.

Nele vou fazer uma função que vai ser executada antes do usuário entrar em qualquer rota.

E nessa função vou verificar, se não é tela de login ou tela de cadastro, e se ele já está com autenticação.

Caso não esteja com a autenticação e esteja tentando acessar a listagem ou cadastro, vou redirecionar para a tela de login.

//index.js
router.beforeEach((to, from, next) => {
  if(to.name !== 'login' && to.name !== 'register' && !localStorage.getItem('authUser')) {
    next({ name: 'login' });
  } else {
    next();
  }
})

Agora se o usuário tentar acessar a tela de listagem ou de cadastro de tarefas, sem estar com a autenticação, irá ser redirecionado para o login.

Vincular usuário com as tarefas

Nesse ponto, o usuário já pode se cadastrar no sistema, e também realizar a sua autenticação.

Agora vou vincular cada tarefa a um usuário, para que quando ele acesse o sistema, ele veja somente as tarefas dele.

No Form.vue, no data do componente, vou recuperar o id que estará no localStorage, e vou colocar junto dos campos que são preenchidos no formulário.

Assim quando o usuário submeter o formulário, irá enviar o seu id junto com os dados da tarefa, salvando assim seu id na tarefa.

//Form.vue
data() {
    return {
      form: {
        subject: "",
        description: "",
        status: Status.OPEN,
        dateOverdue: "",
        userId: JSON.parse(localStorage.getItem('authUser')).id
      },
      ...
    };
  },

Se já tiver tarefas cadastradas, você pode excluir todas manualmente, ou colocar o userId em cada uma.

As próximas tarefas cadastradas ficarão com o userId.

Com o userId para cada tarefa, vou alterar o List.vue, fazendo com que a busca das tarefas, procure somente as tarefas que contém o userId do usuário logado.

E cada usuário terá acesso somente as suas tarefas.

Criei um novo método para isolar toda a lógica de filtro, que agora vai ser sempre passado o userId e também por padrão os status aberto e fechado.

//List.vue
async getTasks() {
      this.isLoading = true;
      let self = this;
      await setTimeout(function(){ self.isLoading = false; }, 1000);
      return await TasksModel.params({ userId: JSON.parse(localStorage.getItem('authUser')).id, status: this.statusList }).get();
    }

E por fim vou utilizar esta busca em todos os métodos do List.vue que faz a busca pelas tarefas.

Como mudei em muitos métodos, vou deixar abaixo toda a parte do Javascript do List.vue.

//List.vue
<script>
import TasksModel from "@/models/TasksModel";
import ToastMixin from "@/mixins/toastMixin.js";
import Status from "@/valueObjects/status";

export default {
  name: "List",

  mixins: [ToastMixin],

  data() {
    return {
      tasks: [],
      taskSelected: [],
      statusList: [Status.OPEN, Status.FINISHED],
      status: Status,
      filter: {
        status: null,
        subject: null,
      },
      optionsStatus: [
        { value: null, text: "Selecione um status" },
        { value: Status.OPEN, text: "Aberto" },
        { value: Status.FINISHED, text: "Finalizado" },
        { value: Status.ARCHIVED, text: "Arquivado" },
      ],
      isLoading: false
    };
  },

  async created() {
    this.tasks = await this.getTasks();
  },

  methods: {
    edit(taskId) {
      this.$router.push({ name: "form", params: { taskId } });
    },

    async remove(taskId) {
      this.taskSelected = await TasksModel.find(taskId);
      this.$refs.modalRemove.show();
    },

    hideModal() {
      this.$refs.modalRemove.hide();
    },

    async confirmRemoveTask() {
      this.taskSelected.delete();
      this.tasks = await this.getTasks();
      this.hideModal();
    },

    async updateStatus(taskId, status) {
      let task = await TasksModel.find(taskId);
      task.status = status;
      await task.save();

      this.tasks = await this.getTasks();
      this.showToast(
        "success",
        "Sucesso!",
        "Status da tarefa atualizado com sucesso"
      );
    },

    isFinished(task) {
      return task.status === Status.FINISHED;
    },

    async filterTasks() {
      let filters = this.clean(this.filter);
      filters.userId = JSON.parse(localStorage.getItem('authUser')).id;
      this.tasks = await TasksModel.params(filters).get();
    },

    clean(obj) {
      for (var propName in obj) {
        if (obj[propName] === null || obj[propName] === undefined) {
          delete obj[propName];
        }
      }
      return obj;
    },

    async clearFilter() {
      this.filter = {
        status: null,
        subject: null,
      };
      this.tasks = await this.getTasks();
    },

    overduePresenter(dateOverdue) {
      if(!dateOverdue) {
        return;
      }
      return dateOverdue.split('-').reverse().join('/');
    },

    variantOverdue(dateOverdue, taskStatus) {
      if(!dateOverdue) {
        return 'light';
      }

      if(taskStatus === this.status.FINISHED){
        return 'success';
      }
      
      let dateNow = new Date().toISOString().split('T')[0];
      if(dateOverdue === dateNow) {
        return 'warning';
      }

      if(dateOverdue < dateNow) {
        return 'danger';
      }

      return 'light';
    },

    async getTasks() {
      this.isLoading = true;
      let self = this;
      await setTimeout(function(){ self.isLoading = false; }, 1000);
      return await TasksModel.params({ userId: JSON.parse(localStorage.getItem('authUser')).id, status: this.statusList }).get();
    }
  },

  computed: {
    isTasksEmpty() {
      return this.tasks.length === 0;
    },
  },
};
</script>

Agora, caso o usuário crie tarefas, toda vez que fizer a autenticação no sistema, só irá conseguir as tarefas que cadastrou.

Caso outro usuário faça a autenticação, não irá conseguir ver essas tarefas, cada um só poderá ver o seu.

Vídeo

https://youtu.be/fP2uvsttw5s

Código fonte

O código fonte está no meu CodeSandbox, neste link.

Para ver outros canais onde o posto conteúdo sobre VueJS, veja os Links do Programando Soluções.

Conclusão

Agora o projeto tem o módulo de usuário, onde ele pode se cadastrar, fazer a autenticação, e ver somente as suas tarefas.

Um ponto importante é que a parte de login e autenticação, foi criada pensando somente nesse projeto, onde há só um frontend para simular comportamentos.

Caso você utilize login e autenticação em outros projetos, considere fazer pesquisas para buscar melhores práticas de segurança dos dados de usuário.

Referências

https://undraw.co/search

https://router.vuejs.org/

Este conteúdo te ajudou de alguma forma?

Marcações:
Usamos cookies para lhe proporcionar a melhor experiência possível no nosso site. Ao continuar a usar este site, você concorda com o uso de cookies.
Ok