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)