QPANC - Parte 16 - Quasar - Áreas Protegidas
Tags: quasar, postgres, aspnetcore, docker
Published: 2020-04-19 23:38:51

> **QPANC** são as iniciais de **Q**uasar **P**ostgreSQL **A**SP **N**ET **C**ore.

  * [Source](https://github.com/TobyMosque/qpanc)
  * [Introdução](https://dev.to/quasar-pt-br/arquiteturando-uma-aplicacao-com-quasar-postgresql-asp-net-core-e-docker-introducao-o7o)
  * [Parte I - ASP.NET - Inicializando os Projetos](https://dev.to/quasar-pt-br/qpanc-parte-1-asp-net-inicializando-os-projetos-lkm)
  * [Parte 2 - PostgreSQL](https://dev.to/quasar-pt-br/qpanc-parte-2-postgresql-51pb)
  * [Parte 3 - ASP.NET - Registrando Serviços e Lendo Variáveis de Ambiente](https://dev.to/quasar-pt-br/qpanc-parte-3-asp-net-registrando-servicos-e-lendo-variaveis-de-ambiente-3do2)
  * [Parte 4 - ASP.NET - Entity Framework e ASP.NET Core Identity](https://dev.to/quasar-pt-br/qpanc-parte-4-asp-net-entity-framework-e-asp-net-core-identity-40f9)
  * [Parte 5 - ASP.NET - Documentação Interativa com Swagger](https://dev.to/quasar-pt-br/qpanc-parte-5-asp-net-documentacao-interativa-com-swagger-4n0o)
  * [Parte 6 - ASP.NET - Regionalização](https://dev.to/quasar-pt-br/qpanc-parte-6-asp-net-regionalizacao-13p5)
  * [Parte 7 - ASP.NET - Autenticação e Autorização](https://dev.to/quasar-pt-br/qpanc-parte-7-asp-net-autenticacao-e-autorizacao-bpd)
  * [Parte 8 - ASP.NET - CORS](https://dev.to/quasar-pt-br/qpanc-parte-8-asp-net-cors-52dn)
  * [Parte 9 - Quasar - Criação e Configuração do Projeto](https://dev.to/quasar-pt-br/qpanc-parte-9-quasar-criacao-e-configuracao-do-projeto-5hbi)
  * [Parte 10 - Quasar - Configurações e Customizações](https://dev.to/quasar-pt-br/qpanc-parte-10-quasar-configuracoes-e-customizacoes-2i86)
  * [Parte 11 - Quasar - Componentes - Diferença entre SPA e SSR](https://dev.to/quasar-pt-br/qpanc-parte-11-quasar-componentes-diferenca-entre-spa-e-ssr-1a3c)
  * [Parte 12 - Quasar - Serviços](https://dev.to/quasar-pt-br/qpanc-parte-12-quasar-servicos-4e61)
  * [Parte 13 - Quasar - Regionalização e Stores](https://dev.to/quasar-pt-br/qpanc-parte-13-quasar-regionalizacao-e-stores-1kep)
  * [Parte 14 - Quasar - Consumindo a API](https://dev.to/quasar-pt-br/qpanc-parte-14-quasar-consumindo-a-api-fn6)
  * [Parte 15 - Quasar - Login](https://dev.to/quasar-pt-br/qpanc-parte-15-quasar-login-jc6)
  * [Parte 16 - Quasar - Áreas Protegidas](https://dev.to/quasar-pt-br/qpanc-parte-16-quasar-areas-protegidas-3jkn)
  * [Parte 17 - Quasar - Registro](https://dev.to/quasar-pt-br/qpanc-parte-17-quasar-registro-3b8c)
  * [Parte 18 - Docker - Maquina Virtual Linux](https://dev.to/quasar-pt-br/qpanc-parte-18-docker-maquina-virtual-linux-g5c)
  * [Parte 19 - Docker - Registro e Build](https://dev.to/quasar-pt-br/qpanc-parte-19-docker-registro-e-build-31i4)
  * [Parte 20 - Docker - Traefik e Publicação](https://dev.to/quasar-pt-br/qpanc-parte-20-docker-traefik-e-publicacao-3g3d)
  * [Demo Online](https://qpanc.tobiasmesquita.dev)

##33 Área Principal/Protegida - Parte I.

Agora, precisamos criar um segundo layout, que será utilizado pelas paginas protegidas. 

Iremos adicionar a fonte `mdi-v5`, por ele ter um maior leques de ícones que a fonte oficial do google, isto será feito em `quasar.config.js > extras > mdi-v5`.

```js
module.exports = function (ctx) {
  return {
    extras: [
      'mdi-v5'
    ]
  }
}
```

Então crie a pasta `main` em `QPANC.App/src/layouts` e adicione os arquivos `store.js`, `index.js`, `index.sass` e `index.vue`

**QPANC.App/src/layouts/main/store.js**
```js
import { factory } from '@toby.mosque/utils'

class MainLayoutModel {
  constructor ({
    leftDrawerOpen = false
  } = {}) {
    this.leftDrawerOpen = leftDrawerOpen
  }
}

const options = {
  model: MainLayoutModel
}

export default factory.store({
  options,
  actions: {
    async initialize ({ state }, { route, next }) {
    },
    async logout (context) {
      await this.$axios.delete('/Auth/Logout')
      commit('app/token', undefined, { root: true })
      this.$router.push('/login')
    }
  }
})

export { options, MainLayoutModel }
```
**QPANC.App/src/layouts/main/index.js**
```js
import EssentialLink from 'components/EssentialLink'
import { factory } from '@toby.mosque/utils'
import store, { options } from './store'

const moduleName = 'layout-main'
export default factory.page({
  name: 'MainLayout',
  options,
  moduleName,
  storeModule: store,
  components: {
    EssentialLink
  },
  data () {
    return {
      essentialLinks: [
        {
          title: 'Docs',
          caption: 'quasar.dev',
          icon: 'school',
          link: 'https://quasar.dev'
        },
        {
          title: 'Github',
          caption: 'github.com/quasarframework',
          icon: 'code',
          link: 'https://github.com/quasarframework'
        },
        {
          title: 'Discord Chat Channel',
          caption: 'chat.quasar.dev',
          icon: 'chat',
          link: 'https://chat.quasar.dev'
        },
        {
          title: 'Forum',
          caption: 'forum.quasar.dev',
          icon: 'record_voice_over',
          link: 'https://forum.quasar.dev'
        },
        {
          title: 'Twitter',
          caption: '@quasarframework',
          icon: 'rss_feed',
          link: 'https://twitter.quasar.dev'
        },
        {
          title: 'Facebook',
          caption: '@QuasarFramework',
          icon: 'public',
          link: 'https://facebook.quasar.dev'
        }
      ]
    }
  },
  methods: {
    logout () {
      return this.$store.dispatch('layout-main/logout')
    }
  }
})
```
**QPANC.App/src/layouts/main/index.sass**
```sass
#layout-main
```
**QPANC.App/src/layouts/main/index.html**
```html
<template>
  <q-layout id="layout-main" view="lHh Lpr lFf" class="bg-main">
    <q-header elevated>
      <q-toolbar>
        <q-btn
          flat
          dense
          round
          icon="menu"
          aria-label="Menu"
          @click="leftDrawerOpen = !leftDrawerOpen"
        />

        <q-toolbar-title>
          Quasar App
        </q-toolbar-title>

        <div>
          Quasar v{{ $q.version }}
          <q-btn flat icon="mdi-exit-to-app" label="logout" @click="logout"></q-btn>
        </div>
      </q-toolbar>
    </q-header>

    <q-drawer
      v-model="leftDrawerOpen"
      show-if-above
      bordered
      content-class="bg-content"
    >
      <q-list>
        <q-item-label header>
          Essential Links
        </q-item-label>
        <EssentialLink
          v-for="link in essentialLinks"
          :key="link.title"
          v-bind="link"
        />
      </q-list>
    </q-drawer>

    <q-page-container>
      <router-view />
    </q-page-container>
  </q-layout>
</template>

<script src="./index.js"></script>
<style src="./index.sass" lang="sass"></style>
```

Note que, este é basicamente o layout padrão do quasar, adaptado para o modo `dark`/`light` e com a função de `logout`.

E agora, vamos adicionar a nossa primeira pagina que irá utilizar este layout. Crie a pasta `home` na pasta `QPANC.App/src/pages` e adicione os arquivos `store.js`, `index.js`, `index.sass` e `index.vue`

**QPANC.App/src/pages/home/store.js**
```js
import { factory } from '@toby.mosque/utils'

class HomePageModel {
}

const options = {
  model: HomePageModel
}

export default factory.store({
  options,
  actions: {
    async initialize ({ state }, { route, next }) {
    }
  }
})

export { options, HomePageModel }
```
**QPANC.App/src/pages/home/index.js**
```js
import { factory } from '@toby.mosque/utils'
import store, { options } from './store'

const moduleName = 'page-home'
export default factory.page({
  name: 'HomePage',
  options,
  moduleName,
  storeModule: store
})
```
**QPANC.App/src/pages/home/index.sass**
```sass
#page-home
```
**QPANC.App/src/pages/home/index.vue**
```html
<template>
  <q-page id="page-home" class="flex flex-center">
    Home
  </q-page>
</template>

<script src="./index.js"></script>
<style src="./index.sass" lang="sass"></style>
```

tanto trabalho para exibir a palavra `Home`, mas precisávamos de uma pagina apenas.

Agora, crie o arquivo `main.js` na pasta `QPANC.App/src/router/areas` e adicione as seguintes rotas.

**QPANC.App/src/router/areas/main.js**
```js
export default function (context) {
  return {
    path: '',
    component: () => import('layouts/main/index.vue'),
    children: [
      { name: 'home', path: 'home', component: () => import('pages/home/index.vue') }
    ],
    meta: {
      authorize: true
    }
  }
}
```

Note a presença do `meta`, nós usamos este campo, quando queremos adiciona alguma informação de controle a rota, neste caso, que o usuário precisa está logado para acessar este componente (e os seus respectivos filhos).

inclua a área `main` no arquivo `routes.js`:

**QPANC.App/src/router/routes.js**
```js
import clean from './areas/clean'
import main from './areas/main'

export default function (context) {
  const routes = [{
    path: '/',
    component: { /* ... */ },
    children: [
      {
        path: '',
        beforeEnter (to, from, next) { /* ... */ }
      },
      clean(context),
      main(context)
    ]
  }]

  // Always leave this as last one
  if (process.env.MODE !== 'ssr') { /*...*/ }

  return routes
}
```

E agora vamos criar um `navigation guard` global, que fará uso do meta `authorize`, para isto, precisamos fazer a seguinte alteração no `index.js`.

**QPANC.App/src/router/index.js**
```js
/* ... */

export default function (context) {
  context.router = new VueRouter({ /*...*/ })

  context.router.beforeEach((to, from, next) => {
    const { store } = context
    let protectedRoutes = to.matched.filter(route => route.meta.authorize)
    if (protectedRoutes.length > 0) {
      const logged = store.getters['app/isLogged']()
      if (!logged) {
        next('/login')
      }
    }
    next()
  })
  return context.router
}
```

Então execute a aplicação e tente acessar a raiz da aplicação (rota '/'), e veja que seja redirecionado para '/login' se não estiver logado e '/home' se estiver. 

Caso coloque um `console.log({ path: to.path, authorize: requireAuth })` no `beforeEach`, você verá que para o path '/' o `protectedRoutes.length` será `0`.

Caso tente acessar a pagina de login (rota `/login`) enquanto logado, você terá acesso a esta rota, o que é esperado.

Caso tente acessar a pagina home (rota `/home`) enquanto não está logado, você será enviado para a rota `/login`, o que também é esperado.

##34 Área Principal/Protegida - Parte 2.

Agora, iremos cria uma segunda pagina, só que para acessar esta pagina, o usuário além de autenticado, precisa está autorizado, neste caso, a autorização é feita apenas para os usuários que tenham a `role` `Developer`

O primeiro passo, é criar uma nova page, copie a pasta `home` e renomeie a copia para `devboard`, e claro, em prol da consistência, não esqueça de renomear qual quer incidência da palavra `home` para `devboard`.

Agora que temos um componente, temos que criar um rota para ele, isto será feito no arquivo `main.js` em `QPANC.App/src/router/areas`

**QPANC.App/src/router/areas/main.js**
```js
export default function (context) {
  return {
    path: '',
    component: () => import('layouts/main/index.vue'),
    children: [
      { name: 'home', path: 'home', component: () => import('pages/home/index.vue') },
      {
        name: 'devboard',
        path: 'devboard',
        component: () => import('pages/devboard/index.vue'),
        meta: {
          authorize: {
            roles: ['Developer']
          }
        }
      }
    ],
    meta: {
      authorize: true
    }
  }
}
```

Note que os `meta` `authorize`, tentam ter o mesmo comportamento que o `AuthorizeAttribute` no C#, desta forma, todos os componentes com `authorize` serão testando, o que na pratica combina todos os `authorize`.

O segundo ponto, é que no campo `roles` é possível declarar múltiplas `role`, porém teremos um `OR` e não um `AND`. exemplo, se tivemos `['Developer', 'Admin']`, então basta que o usuário seja um `Admin` ou `Developer` para ter acesso a esta rota.

O próximo passo, é incluir alguns `getters` na store `app`, estes `getters` serão responsável por testar de o usuário possui algumas das roles declaradas.

**QPANC.App/src/store/app.js**
```js
import { factory } from '@toby.mosque/utils'

/*...*/

const roleClaimName = 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role'
export default factory.store({
  options,
  getters: {
    /*...*/
    roles (state, getters) {
      if (!getters.decoded || !getters.decoded.exp) {
        return []
      }
      const roles = getters.decoded[roleClaimName]
      if (Array.isArray(roles)) {
        return roles
      } else {
        return [roles]
      }
    },
    isOnRoles (state, getters) {
      return (roles) => {
        return getters.roles.some(role => roles.include(role))
      }
    }
  }
})
```

Note que, ao decompor o token `JWT`, o campo `http://schemas.microsoft.com/ws/2008/06/identity/claims/role` poderá ser um `array` ou uma `string`, caso ele seja uma string, devemos retornar um `array` tendo ele como único elemento.

O `getter` `isOnRoles` testa se o usuário logado possui pelo menos uma role que pertença a lista de `roles` passada.

Agora, precisamos fazer uma alteração no `beforeEach` que está no `index.js` em `QPANC.App/src/router`, para que ele faça uso dos novos `getters`.

**QPANC.App/src/router/index.js**
```js
router.beforeEach((to, from, next) => {
  const { store } = context
  let protectedRoutes = to.matched.filter(route => route.meta.authorize)
  if (protectedRoutes.length > 0) {
    const logged = store.getters['app/isLogged']()
    if (!logged) {
      return next('/login')
    }

    protectedRoutes = protectedRoutes.filter(route => route.meta.authorize.roles)
    if (protectedRoutes.length > 0) {
      for (const protectedRoute of protectedRoutes) {
        const { roles } = protectedRoute.meta.authorize
        const isOnRoles = store.getters['app/isOnRoles'](roles)
        if (!isOnRoles) {
          return next('/home')
        }
      }
    }
  }
  next()
})
```

Perceba que, caso o usuário não esteja logado, e tenta acessar uma área que requer autenticação, ele será redirecionado para a tela de `login`, porém se estiver logado, mas não tenha autorização, ele seja redirecionado para a tela inicial (`home`).

E para testamos este fluxo, iremos criar um `QBtn` na pagina `home`, que deve ser visível apenas para usuários com a role `Developer`

**QPANC.App/src/pages/home/index.vue**
```html
<template>
  <q-page id="page-home" class="flex flex-center">
    Home
    <template v-if="isDeveloper">
      <q-btn to="/devboard" :label="$('actions.devboard')" />
    </template>
  </q-page>
</template>

<script src="./index.js"></script>
<style src="./index.sass" lang="sass"></style>
```
**QPANC.App/src/pages/home/index.js**
```js
import { factory } from '@toby.mosque/utils'
import store, { options } from './store'

const moduleName = 'page-home'
export default factory.page({
  name: 'HomePage',
  options,
  moduleName,
  storeModule: store,
  computed: {
    isOnRoles () {
      return this.$store.getters['app/isOnRoles']
    },
    isDeveloper () {
      return this.isOnRoles(['Developer'])
    }
  }
})
```
**Quasar.App/src/i18n/en-us/index.js**
```js
export default {
  actions: {
    devboard: 'Developer Board'
  }
}
```
**Quasar.App/src/i18n/pt-br/index.js**
```js
export default {
  actions: {
    devboard: 'Painel do Desenvolvedor'
  }
}
```

Agora, acesse a pagina `Home`, e desde que seja um `Developer`, você verá um `QBtn` que irá redirecionar para a pagina `Devboard`.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/agy6pcdzzndcowpp6wc4.png)
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/mh593m092z4uoi7m0rrg.png)

Agora, tente alterar a regra de autorização, para ao invés de testar se o usuário é um `Developer`, testar se é ele um `Admin` (estou suponto que o seu usuário não é um `Admin`).

Neste caso, o `QBtn` da pagina `Home` deverá desaparecer, e caso tente acessar a pagina `Devboard` diretamente, será redirecionado para a `Home`, como este redirecionamento é feito no servidor, a pagina `Devboard` sequer será renderizada.