QPANC são as iniciais de Quasar PostgreSQL ASP NET Core.
- Source
- Introdução
- Parte I - ASP.NET - Inicializando os Projetos
- Parte 2 - PostgreSQL
- Parte 3 - ASP.NET - Registrando Serviços e Lendo Variáveis de Ambiente
- Parte 4 - ASP.NET - Entity Framework e ASP.NET Core Identity
- Parte 5 - ASP.NET - Documentação Interativa com Swagger
- Parte 6 - ASP.NET - Regionalização
- Parte 7 - ASP.NET - Autenticação e Autorização
- Parte 8 - ASP.NET - CORS
- Parte 9 - Quasar - Criação e Configuração do Projeto
- Parte 10 - Quasar - Configurações e Customizações
- Parte 11 - Quasar - Componentes - Diferença entre SPA e SSR
- Parte 12 - Quasar - Serviços
- Parte 13 - Quasar - Regionalização e Stores
- Parte 14 - Quasar - Consumindo a API
- Parte 15 - Quasar - Login
- Parte 16 - Quasar - Áreas Protegidas
- Parte 17 - Quasar - Registro
- Parte 18 - Docker - Maquina Virtual Linux
- Parte 19 - Docker - Registro e Build
- Parte 20 - Docker - Traefik e Publicação
- Demo Online
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
.
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
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
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
#layout-main
QPANC.App/src/layouts/main/index.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
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
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
#page-home
QPANC.App/src/pages/home/index.vue
<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
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
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
/* ... */
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
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
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
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
<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
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
export default {
actions: {
devboard: 'Developer Board'
}
}
Quasar.App/src/i18n/pt-br/index.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
.
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.
Top comments (0)