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
31 - Componente de Login - Part 2
Agora que terminamos de configurar os boots
, podemos retornar ao componente de Login
, e fazer a integração dele com a API
.
O primeiro passo, é adicionar a escuta ao evento unprocessable
, que é emitido pelo axios
sempre que ocorre um erro 422
.
QPANC.App/src/pages/login/index.js
export default factory.page({ name: 'LoginPage', created () { if (process.env.CLIENT) { this.$root.$on('unprocessable', this.unprocessable) } }, destroy () { if (process.env.CLIENT) { this.$root.$off('unprocessable', this.unprocessable) } }, methods: { unprocessable (errors) { console.log(errors) } } })
O segundo passo, é adicionar a regra server
aos campos que sofrem validação.:
QPANC.App/src/pages/login/index.js
export default factory.page({ name: 'LoginPage', data () { const self = this const validation = validations(self, { userName: ['required', 'email', 'server'], password: ['required', 'server'] }) return { validation, validationArgs: { userName: { server: true }, password: { server: true } } } }, })
Como as validações feitas sobre o userName
no lado do servidor também são realizadas pelo front
e de se esperar que a API nunca retorne um erro para este campo. Então, você pode remover esta validação ou manter.
Agora, adicione o evento @blur=validationArgs.${name}.server = true
aos componentes que são validados no servidor.:
QPANC.App/src/pages/login/index.vue
<q-form ref="form" class="row q-col-gutter-sm"> <div class="col col-12"> <q-input v-model="userName" :label="$t('fields.userName')" :rules="validation.userName" @blur="validationArgs.userName.server = true"></q-input> </div> <div class="col col-12"> <q-input type="password" v-model="password" :label="$t('fields.password')" :rules="validation.password" @blur="validationArgs.password.server = true"></q-input> </div> ... </q-form>
Como as validações feitas no servidor são exibidas de forma estática, precisamos limpar elas manualmente, por isto a necessidade do evento @blur
, assim como do this.validation.resetServer()
antes de validar novamente.
QPANC.App/src/pages/login/index.js
export default factory.page({ name: 'LoginPage', methods: { async login () { this.validation.resetServer() const isValid = await this.$refs.form.validate() if (isValid) { await this.$store.dispatch(`${moduleName}/login`) } } } })
O ultimo passo, e atualizar o argumento server, sempre que o evento unprocessable
ocorrer:
QPANC.App/src/pages/login/index.js
export default factory.page({ name: 'LoginPage', methods: { unprocessable (errors) { switch (true) { case !!errors.UserName: this.validationArgs.userName.server = errors.UserName[0]; break case !!errors.Password: this.validationArgs.password.server = errors.Password[0]; break } this.$refs.form.validate() } } })
Note que estamos chamando o this.$refs.form.validate()
, para forçar que os campos sejam revalidados, assim exibindo a mensagem de erro.
Agora que o componente está aplicando as validações, tanto as feitas no lado do cliente, quanto as remotas, podemos fazer as alterações necessárias na store
.
QPANC.App/src/pages/login/index.js
export default factory.store({ actions: { async login ({ state, commit }) { const { data: token } = await this.$axios.post('/Auth/Login', state) commit('app/token', token, { root: true }) this.$router.push('/home') } } })
Caso ocorra algum erro na API, não precisa se preocupar, pois o interceptor do axios
irá lidar com ele.
32 Lendo o Token JWT
Agora que já temos a nossa tela de Login, a já persistimos o token
, precisamos adicionar uma forma de ler ele. para isto, iremos adicionar o pacote jwt-decode
.
yarn add jwt-decode
O próximo passo é incrementar o nosso modulo app
com alguns getters
:
import { factory } from '@toby.mosque/utils' import jwtDecode from 'jwt-decode' class AppStoreModel { constructor ({ token = '', localeOs = '', localeUser = '' } = {}) { this.token = token this.localeOs = localeOs this.localeUser = localeUser } } const options = { model: AppStoreModel } export default factory.store({ options, getters: { decoded (state) { if (!state.token) { return undefined } return jwtDecode(state.token) }, expireAt (state, getters) { if (!getters.decoded || !getters.decoded.exp) { return undefined } const expiration = getters.decoded.exp * 1000 return new Date(expiration) }, isLogged (state, getters) { return function () { const now = new Date() return getters.expireAt && getters.expireAt > now } }, locale (state) { return state.localeUser || state.localeOs } } }) export { options, AppStoreModel }
O getter decoded
vai retornar o token
decodificado, o getter expireAt
vai retornar quando o token
expira, e por fim o isLogged
testa se a data de validade do token
é maior do que agora.
Para o isLogged
usamos um getter
que retorna uma function
ao invés de uma action
, pois precisamos que este método seja chamado de forma síncrona. e actions
são chamadas de forma assíncrona.
O isLogged
retorna uma função ao invés de realizar o teste de forma direta, isto é necessário, por que o new Date
não é reativo, desta forma ele seria executado apenas uma vez para cada token
, fazendo que o token
fosse sempre valido, mesmo expirado.
Uma forma de contornar isto, seria adicionar a hora atual ao state de outro modulo (não deve ser feito no app
, para que esta data não seja persistida em um Cookie
), então atualizar ele a cada segundo, porém isto adicionaria um custo extra, que seria justificável, caso precise exibir à hora atual na aplicação, ou tenhas mais regras que dependam a hora atual.
store/clock.js
import { factory } from '@toby.mosque/utils' class CloseStoreModel { constructor ({ now = '', interval = 0 } = {}) { this.now = now || new Date().toISOString() this.interval = interval } } const options = { model: CloseStoreModel } export default factory.store({ options, actions: { config ({ commit }) { if (process.env.CLIENT) { const interval = setInterval (() => { commit('now', new Date().toISOString()) }, 1000) commit('interval', interval) }() return jwtDecode(state.token) }, destroy ({ state, commit }) { clearInterval(state.interval) commit('interval', 0) } } }) export { options, CloseStoreModel }
isLogged (state, getters, rootState) { const now = new Date(rootState.clock.now) return getters.expireAt && getters.expireAt > now }
Note que no exemplo acima, armazenamos no state uma string no formato ISO, ao invés do objeto date, isto é necessário, pois é provável que o servidor esteja em um horário diferente do usuário, um outro complicador, é a implementação do Date, que é diferente no Browser (cliente) quando comparado ao NodeJS (servidor), desta forma, deve-se SEMPRE armazenar datas como strings nas stores e nunca como objetos.
Caso decida inspecionar os valores retornados por estes getters
, pode fazer o seguinte.:
const logged = store.getters['app/isLogged']() console.log({ token: store.state.app.token, decoded: store.getters['app/decoded'], expireAt: store.getters['app/expireAt'], isLogged: store.getters['app/isLogged'], logged: logged })
O resultado deverá ser algo semelhante ao exibido na imagem abaixo.:
E por fim, um exemplo de uso, vai condicionar o redirecionamento da rota '/' ao fato do usuário está logado ou não, para tal, vamos modificar o arquivo src/router/routes.js
.
import clean from './areas/clean' export default function (context) { const routes = [{ path: '/', component: { /* ... */ }, children: [ { path: '', beforeEnter (to, from, next) { const { store } = context const logged = store.getters['app/isLogged']() if (logged) { next('/home') } else { next('/login') } } }, clean(context) ] }] // Always leave this as last one if (process.env.MODE !== 'ssr') { /* ... */ } return routes }
O nosso único impeditivo agora, é o fato que não temos uma rota /home
.
Top comments (0)