Vuex state not updating - javascript

I am trying to render different UIs depending on whether a user is a retailer or not. I think I have mostly everything correct, but when the app initializes, I want to check whether the user is a retailer or not (This dispatches the checkAuth action).
I try running the following to get the value of the isRetailer property from Firebase.
firebase.database().ref('/users/' + user.uid + '/isRetailer').once('value').then(snapshot => snapshot.val()
Then I dispatch some actions based on the result. For some reason, the isRetailer part of my Vuex state is not updating from false even when the users isRetailer property is true.
I'm not really sure why but I'm guessing it has something to do w/ my firebase reference. My full code is below. Thanks!
import firebase from 'firebase'
const authentication = {
state: {
isAuthed: false,
authId: '',
isRetailer: false
},
mutations: {
authUser (state, user) {
state.isAuthed = true;
state.authId = user.uid;
},
notAuthed (state) {
state.isAuthed = false;
state.authId = '';
state.isRetailer = false
},
isRetailer (state) {
state.isRetailer = true
},
isNotRetailer (state) {
state.isRetailer = false
}
},
actions: {
checkUser (context, user) {
if (!user.uid) {
// Do nothing.
} else if (firebase.database().ref('/users/' + user.uid + '/isRetailer').once('value').then(snapshot => snapshot.val()) === true) {
context.commit('authUser', user);
context.commit('isRetailer');
} else {
context.commit('authUser', user);
context.commit('isNotRetailer');
};
},
signOut (context) {
context.commit('notAuthed')
},
setRetailer (context, payload) {
// Set retailer info in Firebase.
var uid = payload.user.uid;
firebase.database().ref('/users/' + uid).set({
name: payload.name,
email: payload.email,
location: payload.retailerLocation,
isRetailer: payload.isRetailer
});
},
setNewUser (context, payload) {
// Sets a user in firebase w/ a isRetailer value of false.
var uid = payload.user.uid;
firebase.database().ref('/users/' + uid).set({
name: payload.name,
email: payload.email,
isRetailer: payload.isRetailer
});
}
},
getters: {}
};
export default authentication;

This might be happening because you have given same name to mutation and state variable: isRetailer, I don't have any reference right now, but this might be the case for mutation not triggering, You can try once by setting your mutation by different name such as:
mutations: {
...
...
setIsRetailer (state) {
state.isRetailer = true
},
isNotRetailer (state) {
state.isRetailer = false
}
},
and use this in actions:
actions: {
checkUser (context, user) {
...
...
context.commit('authUser', user);
context.commit('setIsRetailer');
}

Related

Vue: Set user in vuex store before registration

Hi Stackoverflow Community,
I have a Vue.js application where a user can register. The registration is displayed in three different components. Register 1 (email, password), Register 2 (personal information) and Register 3 (preferences).
I implemented an api post request after the user press register on the first page according to bezkoder: https://www.bezkoder.com/vue-3-authentication-jwt/
What I am trying to do now is instead of register the user directly, I want to save the user data in my vuex store and send the api post request in Register 3 instead of Register 1, when I have all the user information.
Unfortunately, I am not able to create a new state in my store. This is my auth.module.js Code (store):
import AuthService from "../services/auth.service";
const user = JSON.parse(localStorage.getItem("user"));
const initialState = user
? { status: { loggedIn: true }, user }
: { status: { loggedIn: false }, user: null };
export const auth = {
namespaced: true,
state: initialState,
actions: {
login({ commit }, user) {
return AuthService.login(user).then(
(user) => {
commit("loginSuccess", user);
return Promise.resolve(user);
},
(error) => {
commit("loginFailure");
return Promise.reject(error);
}
);
},
logout({ commit }) {
AuthService.logout();
commit("logout");
},
register({ commit }, user) {
return AuthService.register(user).then(
(response) => {
commit("registerSuccess");
return Promise.resolve(response.data);
},
(error) => {
commit("registerFailure");
return Promise.reject(error);
}
);
},
},
mutations: {
loginSuccess(state, user) {
state.status.loggedIn = true;
state.user = user;
},
loginFailure(state) {
state.status.loggedIn = false;
state.user = null;
},
logout(state) {
state.status.loggedIn = false;
state.user = null;
},
registerSuccess(state) {
state.status.loggedIn = true;
},
registerFailure(state) {
state.status.loggedIn = false;
},
},
};
So I need to create a state for my userdata (email, password, preferences) and then an action and a method to save my userdata from Register 1 and 2.
Does someone has a clue how I could implement this? Or do you have a better idea how to create my registration?
Glad for all your tips and tricks :)
I could fix it. I created a new file named "patient.module.js" and there I could set the states. My code is the following:
export const patient = {
namespaced: true,
state: {
email: null,
password: null,
location: [],
specialty: [],
attribute: [],
language: [],
gender: [],
},
actions: {
register({ commit }, patient) {
return new Promise((resolve) => {
commit("setPatientData", patient);
resolve();
});
},
},
mutations: {
setPatientData(state, patient) {
Object.assign(state, patient);
},
},
};

How to authenticate Nuxt on server side?

I have spent the night looking for solutions to this issue, it seems like a lot of people have it and the best advice is often "just switch to SPA mode", which is not an option for me.
I have JWT for authentication, using the JWTSessions gem for Rails.
On the frontend, I have Nuxt with nuxt-auth, using a custom scheme, and the following authorization middleware:
export default function ({ $auth, route, redirect }) {
const role = $auth.user && $auth.user.role
if (route.meta[0].requiredRole !== role) {
redirect('/login')
}
}
The symptom I have is as follows: if I log in and navigate around restricted pages, everything works as expected. I even have fetchOnServer: false for restricted pages, as I only need SSR for my public ones.
However, once I refresh the page or just navigate directly to a restricted URL, I get immediately redirected to the login page by the middleware. Clearly, the user that's authenticated on the client side is not being authenticated on the server side too.
I have the following relevant files.
nuxt.config.js
...
plugins: [
// ...
{ src: '~/plugins/axios' },
// ...
],
// ...
modules: [
'cookie-universal-nuxt',
'#nuxtjs/axios',
'#nuxtjs/auth'
],
// ...
axios: {
baseURL: process.env.NODE_ENV === 'production' ? 'https://api.example.com/v1' : 'http://localhost:3000/v1',
credentials: true
},
auth: {
strategies: {
jwtSessions: {
_scheme: '~/plugins/auth-jwt-scheme.js',
endpoints: {
login: { url: '/signin', method: 'post', propertyName: 'csrf' },
logout: { url: '/signin', method: 'delete' },
user: { url: '/users/active', method: 'get', propertyName: false }
},
tokenRequired: true,
tokenType: false
}
},
cookie: {
options: {
maxAge: 64800,
secure: process.env.NODE_ENV === 'production'
}
}
},
auth-jwt-scheme.js
const tokenOptions = {
tokenRequired: true,
tokenType: false,
globalToken: true,
tokenName: 'X-CSRF-TOKEN'
}
export default class LocalScheme {
constructor (auth, options) {
this.$auth = auth
this.name = options._name
this.options = Object.assign({}, tokenOptions, options)
}
_setToken (token) {
if (this.options.globalToken) {
this.$auth.ctx.app.$axios.setHeader(this.options.tokenName, token)
}
}
_clearToken () {
if (this.options.globalToken) {
this.$auth.ctx.app.$axios.setHeader(this.options.tokenName, false)
this.$auth.ctx.app.$axios.setHeader('Authorization', false)
}
}
mounted () {
if (this.options.tokenRequired) {
const token = this.$auth.syncToken(this.name)
this._setToken(token)
}
return this.$auth.fetchUserOnce()
}
async login (endpoint) {
if (!this.options.endpoints.login) {
return
}
await this._logoutLocally()
const result = await this.$auth.request(
endpoint,
this.options.endpoints.login
)
if (this.options.tokenRequired) {
const token = this.options.tokenType
? this.options.tokenType + ' ' + result
: result
this.$auth.setToken(this.name, token)
this._setToken(token)
}
return this.fetchUser()
}
async setUserToken (tokenValue) {
await this._logoutLocally()
if (this.options.tokenRequired) {
const token = this.options.tokenType
? this.options.tokenType + ' ' + tokenValue
: tokenValue
this.$auth.setToken(this.name, token)
this._setToken(token)
}
return this.fetchUser()
}
async fetchUser (endpoint) {
if (this.options.tokenRequired && !this.$auth.getToken(this.name)) {
return
}
if (!this.options.endpoints.user) {
this.$auth.setUser({})
return
}
const user = await this.$auth.requestWith(
this.name,
endpoint,
this.options.endpoints.user
)
this.$auth.setUser(user)
}
async logout (endpoint) {
if (this.options.endpoints.logout) {
await this.$auth
.requestWith(this.name, endpoint, this.options.endpoints.logout)
.catch(() => {})
}
return this._logoutLocally()
}
async _logoutLocally () {
if (this.options.tokenRequired) {
this._clearToken()
}
return await this.$auth.reset()
}
}
axios.js
export default function (context) {
const { app, $axios, redirect } = context
$axios.onResponseError(async (error) => {
const response = error.response
const originalRequest = response.config
const access = app.$cookies.get('jwt_access')
const csrf = originalRequest.headers['X-CSRF-TOKEN']
const credentialed = (process.client && csrf) || (process.server && access)
if (credentialed && response.status === 401 && !originalRequest.headers.REFRESH) {
if (process.server) {
$axios.setHeader('X-CSRF-TOKEN', csrf)
$axios.setHeader('Authorization', access)
}
const newToken = await $axios.post('/refresh', {}, { headers: { REFRESH: true } })
if (newToken.data.csrf) {
$axios.setHeader('X-CSRF-TOKEN', newToken.data.csrf)
$axios.setHeader('Authorization', newToken.data.access)
if (app.$auth) {
app.$auth.setToken('jwt_access', newToken.data.csrf)
app.$auth.syncToken('jwt_access')
}
originalRequest.headers['X-CSRF-TOKEN'] = newToken.data.csrf
originalRequest.headers.Authorization = newToken.data.access
if (process.server) {
app.$cookies.set('jwt_access', newToken.data.access, { path: '/', httpOnly: true, maxAge: 64800, secure: false, overwrite: true })
}
return $axios(originalRequest)
} else {
if (app.$auth) {
app.$auth.logout()
}
redirect(301, '/login')
}
} else {
return Promise.reject(error)
}
})
}
This solution is already heavily inspired by material available under other threads and at this point I am pretty much clueless regarding how to authenticate my users universally across Nuxt. Any help and guidance much appreciated.
In order for You not to lose Your authentication session in the system, You first need to save your JWT token to some storage on the client: localStorage or sessionStorage or as well as token data can be saved in cookies.
For to work of the application will be optimally, You also need to save the token in the store of Nuxt. (Vuex)
If You save Your token only in srore of Nuxt and use only state, then every time You refresh the page, Your token will be reset to zero, since the state will not have time to initialize. Therefore, you are redirected to the page /login.
To prevent this from happening, after you save Your token to some storage, You need to read it and reinitialize it in the special method nuxtServerInit(), in the universal mode his will be work on the server side the very first. (Nuxt2)
Then, accordingly, You use Your token when sending requests to the api server, adding to each request that requires authorization, a header of the Authorization type.
Since Your question is specific to the Nuxt2 version, for this version a working code example using cookies to store the token would be:
/store/auth.js
import jwtDecode from 'jwt-decode'
export const state = () => ({
token: null
})
export const getters = {
isAuthenticated: state => Boolean(state.token),
token: state => state.token
}
export const mutations = {
SET_TOKEN (state, token) {
state.token = token
}
}
export const actions = {
autoLogin ({ dispatch }) {
const token = this.$cookies.get('jwt-token')
if (isJWTValid(token)) {
dispatch('setToken', token)
} else {
dispatch('logout')
}
},
async login ({ commit, dispatch }, formData) {
const { token } = await this.$axios.$post('/api/auth/login', formData, { progress: false })
dispatch('setToken', token)
},
logout ({ commit }) {
this.$axios.setToken(false)
commit('SET_TOKEN', null)
this.$cookies.remove('jwt-token')
},
setToken ({ commit }, token) {
this.$axios.setToken(token, 'Bearer')
commit('SET_TOKEN', token)
this.$cookies.set('jwt-token', token, { path: '/', expires: new Date('2024') })
// <-- above use, for example, moment or add function that will computed date
}
}
/**
* Check valid JWT token.
*
* #param token
* #returns {boolean}
*/
function isJWTValid (token) {
if (!token) {
return false
}
const jwtData = jwtDecode(token) || {}
const expires = jwtData.exp || 0
return new Date().getTime() / 1000 < expires
}
/store/index.js
export const state = () => ({
// ... Your state here
})
export const getters = {
// ... Your getters here
}
export const mutations = {
// ... Your mutations here
}
export const actions = {
nuxtServerInit ({ dispatch }) { // <-- init auth
dispatch('auth/autoLogin')
}
}
/middleware/isGuest.js
export default function ({ store, redirect }) {
if (store.getters['auth/isAuthenticated']) {
redirect('/admin')
}
}
/middleware/auth.js
export default function ({ store, redirect }) {
if (!store.getters['auth/isAuthenticated']) {
redirect('/login')
}
}
/pages/login.vue
<template>
<div>
<!-- Your template here-->
</div>
</template>
<script>
export default {
name: 'Login',
layout: 'empty',
middleware: ['isGuest'], // <-- if the user is authorized, then he should not have access to the page !!!
data () {
return {
controls: {
login: '',
password: ''
},
rules: {
login: [
{ required: true, message: 'login is required', trigger: 'blur' }
],
password: [
{ required: true, message: 'password is required', trigger: 'blur' },
{ min: 6, message: 'minimum 6 length', trigger: 'blur' }
]
}
}
},
head: {
title: 'Login'
},
methods: {
onSubmit () {
this.$refs.form.validate(async (valid) => { // <-- Your validate
if (valid) {
// here for example: on loader
try {
await this.$store.dispatch('auth/login', {
login: this.controls.login,
password: this.controls.password
})
await this.$router.push('/admin')
} catch (e) {
// eslint-disable-next-line no-console
console.error(e)
} finally {
// here for example: off loader
}
}
})
}
}
}
</script>
! - You must have the following packages installed:
cookie-universal-nuxt
jsonwebtoken
jwt-decode
I think you will find my answer helpful. If something is not clear, ask!

Keeping User state when refreshing or changing the page in vuex

As you can see my user state is null at the beginning but when the user logs in, I need to user id for my action called setFavoriteCrypto
So I can retrieve the specific data that is related to the user connected but since vuex initialize the state every time there's a refresh of the page. the user becomes null
I know there's a package called persist state which is something I could work with.
But I would like to know if there's a different way of managing my user connected without depending on a package?
I am using node.js as my backend for this app.
import Api from '#/services/Api'
import axios from 'axios'
import router from '#/router'
const state = {
localStorageToken: localStorage.getItem('user-token') || null,
token: null,
user: null,
favoriteCrypto: []
}
const mutations = {
setToken(state, token) {
state.localStorageToken = token
state.token = token
},
setUser(state, user) {
state.user = user
},
setFavoritecrypto(state, crypto) {
state.favoriteCrypto = crypto
}
}
const actions = {
setToken({commit}, token) {
commit('setToken', token)
},
setUser({commit}, user) {
commit('setUser', user)
},
setFavoriteCrypto({commit}, token) {
if(state.user) {
return Api().get('getuserfavoritescoins', {
params: {
userId: state.user.id
}
})
.then(response => response.data.coins)
.then(cryptoFav => {
commit('setFavoritecrypto', cryptoFav)
})
}
else{
console.log("there's not user state")
}
},
loginUser({commit}, payload) {
return Api().post('login', payload)
.then(response => {
commit('setUser', response.data.user)
commit('setToken', response.data.token)
localStorage.setItem('user-token', response.data.token)
axios.defaults.headers.common['Authorization'] = response.data.token
router.push('/main').catch(e => {})
})
},
logoutUser({commit, dispatch}) {
localStorage.removeItem('user-token')
commit('setUser', null)
commit('setToken', null)
delete axios.defaults.headers.common['Authorization']
}
}
const getters = {
isAuthenticated: state => !!state.localStorageToken,
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

Vuex - ...mapActions call from template: Cannot read property 'dispatch' of undefined

I'm new to Vue, i'm just trying to figure out how it is working.
I use router and i have a parent and a child element.
In the child, i have this simple HTML element in the child component:
<button #click="consoleLog">consoleLog</button>
The store at the main.js have this action:
export const store = new Vuex.Store({
state: {
userName: undefined,
password: undefined,
authenticated: false
},
mutations: {
changeUserNameMutation(state, userName) {
state.userName = userName;
},
changePasswordMutation(state, password) {
state.password = password;
Vue.axios.defaults.headers['Authorization'] = password;
},
changeAuthenticatedMutation(state, isAuthenticated) {
state.authenticated = isAuthenticated;
}
},
actions: {
changeUserName({ commit }, userName) {
commit('changeUserNameMutation', userName);
},
changePassword({ commit }, password) {
commit('changePasswordMutation', password);
Vue.axios.defaults.headers['Authorization'] = password;
},
changeAuthenticated({ commit }, isAuthenticated) {
commit('changeAuthenticatedMutation', isAuthenticated);
},
consoleLog({ commit }) {
console.log('sad');
}
}
Nothing more really in the main.js, just the new Vue call, Vue.use() calls and the imports.
In the child component, i have this actions:
methods: {
changeUserName(userName) {
store.dispatch('changeUserName', userName);
},
changePassword(password) {
store.dispatch('changePassword', password);
},
login() {
this.axios.get('http://localhost:3050/admin/login')
.then((data) => {
console.log(data);
})
.catch(() => {
console.log(data);
});
},
...mapActions(['consoleLog'])
}
The first 2 dispatch working, but when i hit the consoleLog method, it's says:
Cannot read property 'dispatch' of undefined at VueComponent.mappedAction
I've tried all other way to call ...mapActions(), but not working this way somehow for me.
(this is my first day in Vue, trying how it's working)

Cannot access state in re-base syncState, after authentication with Firebase

I have a React app with the following shallow database model, hooked up to Firebase:
{
"users": {
"userid1": {
"Full name": "Patrick Bateman",
},
},
"posts": {
"userid1": {
"post-1": {
text: "Lorem ipsum",
...
},
"post-2": {
text: "Cats sure are swell",
...
},
},
"userid2": {
...
}
}
}
and in React, I'm using the re-base library's syncState() to fetch initial content and keep the local state and firebase in sync.
In the re-base docs and examples, they use syncState() in componentDidMount. This makes sense, but I don't want to sync all the firebase posts; only those belonging to the user, so my instinct was to do something like this:
componentDidMount() {
this.refs = base.syncState(`posts/${this.state.uid}`, {
context: this,
state: 'posts',
});
}
The problem here is that this.state.uid is null at this stage in the lifecycle.
My question is this: how do I access the uid when I call syncState?
Here is the rest of my code for reference, show auth too:
init() {
// I need the uid here!!!!!! 😫
console.log(this.state.uid); // null
this.refs = base.syncState(`posts/${this.state.uid}`, {
context: this,
state: 'posts',
});
}
componentDidMount() {
this.unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) {
window.localStorage.setItem(storageKey, user.uid);
this.setState({
uid: user.uid,
user
});
} else {
window.localStorage.removeItem(storageKey);
this.setState({
uid: null,
user: null,
posts: null,
});
}
});
// re-base firebase sync
this.init();
}
componentWillUnmount() {
base.removeBinding(this.ref);
this.unsubscribe();
}
authenticate = (provider) => {
if (provider === 'twitter') {
provider = new firebase.auth.TwitterAuthProvider();
} else if (provider === 'google') {
provider = new firebase.auth.GoogleAuthProvider();
provider.addScope('https://www.googleapis.com/auth/plus.login');
}
firebase.auth().signInWithPopup(provider).then((authData) => {
// get the of the firebase.User instance
const user = authData.user;
// Set local storage to preserve login
window.localStorage.setItem(storageKey, user.uid);
// Set the state of the current user
this.setState({
uid: user.uid,
user,
});
}).catch(function(error) {
console.log(error);
});
};
With the above code, my app creates local state correctly:
State
posts: {
post-1: {
...
}
}
but then on firebase, predicately adds them with a null user, since the uid is not available.
database: {
posts: {
null: {
post-1: {
...
}
}
}
}
You can just call this.init() after the state sets. this.setState has a second argument that is a callback. This way you can be sure that the uid will not be null.
Example
this.setState({
uid: user.uid,
user
}, () => {
this.init();
});

Categories