I created a directive with name 'can' to check user permissions and then renders some content only for users who have required access but it doesn't work.
Here is what I have tried
nuxt.config.js
module.export = {
// other configs
ssr: true,
render: {
bundleRenderer: {
directives: {
can (elm, binding, vnode) {
// check if the user has the permission render the element
// const has_permission = check user have my-permission;
if (!has_permission) {
elm.remove();
}
}
}
}
},
}
my-component.vue
<nuxt-link v-can="'my-permission'"></nuxt-link>
Related
I am using VueJS with webpack, vuex and vue-router. I try to check if the user is registered or not by using the computed functionality in a child component. The computed functionality trys to look at the user-property in my vuex. This works fine, if i open my webapplication by this URL http://localhost:8080. The Problem occurs only if i call my child component directly by this URL: http://localhost:8080/meetup/id. It appears an error of: TypeError: Cannot read property 'registeredMeetups' of null, because user-property doesnt even exist at this time.
Child component computed Code:
computed: {
userIsRegistered() {
console.log(this.$store.getters.getUser)
if (this.$store.getters.getUser.registeredMeetups.findIndex(meetupId => {return meetupId === this.meetupId;}) >= 0) {
return true;
} else { return false; }
}
}
The user properties are set in main.js in Vue-instance:
new Vue({
router,
store,
vuetify,
render: function (h) { return h(App) },
created () {
this.initialFirebaseConfig();
this.isUserAuthSignIn();
this.$store.dispatch('loadMeetups');
},
methods: {
initialFirebaseConfig() {
const firebaseConfig = {
...
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
},
isUserAuthSignIn() {
firebase.auth().onAuthStateChanged((user) => {
if(user) {
this.$store.dispatch('autoSignIn', user)
}
});
}
}
The vue-router Code:
{
path: "/meetup/:id",
name: "Meetup",
props: true,
component: () => import(/* webpackChunkName: "meetup" */ '../components/Meetup/Meetup.vue')
},
Goal: The computed functionality from the child component should run after the functionality of the vue-instance from main.js.
const user = this.$store.getters.getUser
if (user && user.registeredMeetups.findIndex(meetupId => {return meetupId === this.meetupId;}) >= 0) {
return true;
} else {
return false;
}
I'm just started learning Vue and I don't understand how to make "structure" of components, I mean: my login page should be as a main component which is loaded as first page in application, then after success login user should be redirected to main components (core application).
Can someone simply explain how to do that? What should be in App.vue? Where to place router-view responsible for login-view and where to place routers-views which are responsible for views which can be showed just after loggin?
Here's couple of pieces of my code:
App.vue
<template>
<div>
<left-menu></left-menu>
<nav-menu></nav-menu>
<router-view></router-view>
</div>
</template>
<script>
import NavMenu from './NavMenu.vue';
import LeftMenu from './LeftMenu.vue';
export default {
components: {
navMenu: NavMenu,
leftMenu: LeftMenu
},
computed: {
auth() {
return this.$store.getters.isAuthenticated;
}
}
}
</script>
That's my routes.js, every component has same structure:
(basically that particular component should be loaded after loggin)
{
path: '/orders',
component: Orders,
beforeEnter(to, from, next) {
if (store.state.jwt) {
next();
} else {
next('/login')
}
}
}
and here's my Login component:
<template>
<div class="login-content">
<form #submit.prevent="submit">
<label>Podaj login</label>
<input type="text" v-model="login">
<br>
<label>Podaj hasło</label>
<input type="password" v-model="password">
<button type="submit">Zaloguj</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
login: '',
password: ''
}
},
methods: {
submit() {
const formData = {
login: this.login,
password: this.password
}
this.$store.dispatch('login', {
login: formData.login,
password: formData.password
})
}
}
}
With those setting, my Login component is loaded when user isn't authenticated, but the content appear on components which shouldn't be available to see before loggin.
I've seen Redirect to requested page after login using vue-router but I don't get it.
Redirecting to the home page after successful login can be done in the method itself.
submit() {
const formData = {
login: this.login,
password: this.password
}
this.$store.dispatch('login', formData)
.then(response=>{
if(response.data.status){
this.$router.push({name:'home'})
}
})
}
Now you need to handle the access to the login page. I managed this by adding meta values to the router objects.
{
name: 'login',
path: '/login',
component: Login,
meta: {
public: true,
disableIfLoggedIn: true
}
},
public : If this value set to true then, this page can be accessed by without authentication.
disableIfLoggedIn: If this value is set to true, then the page cannot access once authenticated.
These values are checked and managed the redirection in the beforeEach section like follows
router.beforeEach((to, from, next) => {
// if the route is not public
if (!to.meta.public) {
// if the user authenticated
if (store.getters.isAuthenticated) {
// continue to the route
next();
} else {
// set redirect to path after login
let redirect = {};
if (to.name !== 'home') {
redirect = {redirect: to.path};
}
// redirect to login
next({name: 'login', query: redirect});
}
}
// if the user is authenticated and the route is disabled for authenticated window
if (store.getters.isAuthenticated && to.meta.disableIfLoggedIn) {
// redirect to home
next({name: 'home'});
}
next();
});
This will check before every router navigation and control the access to the routes.
Note
The redirect section can be removed. This is to handle redirect to specific path after login.
I am trying to use a global eventbus in VueJs but have been unsuccessful so far.
I have the following code. When I navigate from ResetPassword to Login screen, I should see the successMessage with a Your password has been changed successfully. Please login to continue but it always shows a blank.
What could I be doing wrong?
plugins.js:
Vue.prototype.$eventHub = new Vue();
ChangePassword.vue:
methods:
{
ChangePassword()
{
this.$eventHub.$emit('navigation-message', 'Your password has been changed successfully. Please login to continue.');
this.$router.push({ name: 'login'});
},
},
Login.vue:
data() {
return {
successMessage:'',
};
},
created ()
{
this.$eventHub.$once('navigation-message', this.successMessage);
},
beforeDestroy()
{
this.$eventHub.$off('navigation-message');
},
Update: 12/8/2019: I changed the login.vue as per comment by #tony19 but the issue still exists.
Login.vue:
created ()
{
this.$eventHub.$once('navigation-message', (payload)=>
{
updateSuccessMessage(payload);
});
},
methods:
{
updateSuccessMessage(payload)
{
this.successMessage=payload;
},
You need to add this.
created () {
this.$eventHub.$on('navigation-message', payload => {
this.updateSuccessMessage(payload)
})
},
methods: {
updateSuccessMessage(payload) {
this.successMessage = payload
}
}
Also make sure you're actually importing plugin.js globally (e.g. inside your main file where you import Vue) and make sure your components have access to it.
Try this:
created() {
console.log(this.$eventHub)
}
I am working with Vue, by means of Quasar, with the pages being rendered via SSR. This works well enough, but I have a component that doesn't seem to behaving properly.
The issue is that the content is rendered correctly on the server side (verified by checking network log in Chrome), with the axios call loading in the data into an element using v-html, but when we get to the browser the state seems to be reset and server side rendered content gets lost, when using the 'elements' tab in the inspector.
Any ideas?
The Vue component is as follows:
<template>
<div class="dy-svg" v-html="svgData"></div>
</template>
<script>
/**
* This provides a way of loading an SVG and embedding it straight into
* the page, so that it can have css applied to it. Note, since we are
* using XHR to load the SVG, any non-local resource will have to deal
* with CORS.
*/
import axios from 'axios';
export default {
props: {
src: String,
prefetch: {
type: Boolean,
default: true
}
},
data() {
return {
svgData: undefined,
};
},
async serverPrefetch() {
if (this.prefetch) {
await this.loadImage();
}
},
async mounted() {
// if (!this.svgData) {
// await this.loadImage();
// }
},
methods: {
async loadImage() {
try {
let url = this.src;
if (url && url.startsWith('/')) {
url = this.$appConfig.baseUrl + url;
}
const response = await axios.get(url);
let data = response.data;
const idx = data.indexOf('<svg');
if (idx > -1) {
data = data.substring(idx, data.length);
}
this.svgData = data;
} catch (error) {
console.error(error);
}
}
}
};
</script>
Note, I did try add the v-once attribute to the div, but it seems to have no impact.
Environment:
Quasar 1.1.0
#quasar/cli 1.0.0
#quasar/app 1.0.6
NodeJS 10.15.3
Vue 2.6.10 (dependency via Quasar)
The fetched data needs to live outside the view components, in a dedicated data store, or a "state container". On the server, you should pre-fetch and fill data into the store while rendering. For this you can use Vuex.
Example Vuex store file:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
// import example from './module-example'
Vue.use(Vuex)
export default function ( /* { ssrContext } */ ) {
const Store = new Vuex.Store({
state: () => ({
entities: {}
}),
actions: {
async get({
commit
}) {
await axios.get('https://example.com/api/items')
.then((res) => {
if (res.status === 200) {
commit('set', res.data.data)
}
})
}
},
mutations: {
set(state, entities) {
state.entities = entities
},
},
modules: {},
// enable strict mode (adds overhead!)
// for dev mode only
strict: process.env.DEV
})
return Store
}
Example Vue page script:
export default {
name: 'PageIndex',
computed: {
// display the item from store state.
entities: {
get() {
return this.$store.state.entities
}
}
},
serverPrefetch() {
return this.fetchItem()
},
mounted() {
if (!this.entities) {
this.fetchItem()
}
},
methods: {
fetchItem() {
return this.$store.dispatch('get')
}
}
}
This should solve the issue you're facing.
DESCRIPTION
I have a pretty standard SPA built with vue.js where I'm using Auth0 to handle the authentication part by following the official example. The app flow is as follows:
Register in the Initial.vue via Auth0 lock -> Callback is called -> User's redirected to /home
Everything in the above flow works fine but here's the problem:
PROBLEM
Once the user's registered and in the /home I want him to be able to access all the other routes (e.g. /doctors) if authenticated and if not he should be prompter to login again. According to the above link this is handled in the router.beforeEach function.
My problem appears when accessing the / login page (Initialvue). When the user's already registered and trying to access that route I want him to get redirected to the /home and skip the login page. I've tried implementing this with a beforeEnter route but auth.isAuthenticated fails due to the tokenExpiry being null (even-though user's authenticated!
CODE
My AuthService.js:
import auth0 from 'auth0-js';
import EventEmitter from 'events';
import authConfig from '../config/auth_config.json';
const webAuth = new auth0.WebAuth({
domain: authConfig.domain,
redirectUri: `${window.location.origin}/callback`,
clientID: authConfig.clientId,
responseType: 'id_token',
scope: 'openid profile email'
});
const localStorageKey = 'loggedIn';
const loginEvent = 'loginEvent';
class AuthService extends EventEmitter {
idToken = null;
profile = null;
tokenExpiry = null;
// Starts the user login flow
login(customState) {
webAuth.authorize({
appState: customState
});
}
// Handles the callback request from Auth0
handleAuthentication() {
return new Promise((resolve, reject) => {
webAuth.parseHash((err, authResult) => {
if (err) {
reject(err);
} else {
this.localLogin(authResult);
resolve(authResult.idToken);
}
});
});
}
localLogin(authResult) {
// console.log(authResult); TODO-me: Handle this
this.idToken = authResult.idToken;
this.profile = authResult.idTokenPayload;
// Convert the JWT expiry time from seconds to milliseconds
this.tokenExpiry = new Date(this.profile.exp * 1000);
localStorage.setItem(localStorageKey, 'true');
this.emit(loginEvent, {
loggedIn: true,
profile: authResult.idTokenPayload,
state: authResult.appState || {}
});
}
renewTokens() {
return new Promise((resolve, reject) => {
if (localStorage.getItem(localStorageKey) !== "true") {
return reject("Not logged in");
}``;
webAuth.checkSession({}, (err, authResult) => {
if (err) {
reject(err);
} else {
this.localLogin(authResult);
resolve(authResult);
}
});
});
}
logOut() {
localStorage.removeItem(localStorageKey);
this.idToken = null;
this.tokenExpiry = null;
this.profile = null;
webAuth.logout({
returnTo: window.location.origin
});
this.emit(loginEvent, { loggedIn: false });
}
isAuthenticated() {
console.log('In tokenExp is:');
console.log(this.tokenExpiry); //THIS returns null when /home -> /
return (
Date.now() < this.tokenExpiry &&
localStorage.getItem(localStorageKey) === 'true'
);
}
}
export default new AuthService();
My Initial.vue:
<template>
<v-container
app
fluid
>
<v-parallax
src="https://cdn.vuetifyjs.com/images/backgrounds/vbanner.jpg"
height="1000"
>
<v-layout
row
wrap
>
<!-- LOGIN-->
<v-toolbar
flat
light
dense
color="transparent"
>
<v-spacer></v-spacer>
<v-toolbar-items>
<v-btn
medium
color="lime lighten-2"
#click="login"
class="font-weight-bold title text-uppercase"
>
Login
</v-btn>
</v-toolbar-items>
</v-toolbar>
<v-layout
align-center
column
>
<h1 class="display-2 font-weight-thin mb-3 text-uppercase lime--text lighten-2" >Pulse</h1>
<h4 class="subheading">A digital intelligent insurance built for you!</h4>
</v-layout>
</v-layout>
</v-parallax>
</v-container>
</template>
<script>
import VContainer from "vuetify/lib/components/VGrid/VContainer";
import VFlex from "vuetify/lib/components/VGrid/VFlex";
import VLayout from "vuetify/lib/components/VGrid/VLayout";
import VBtn from "vuetify/lib/components/VBtn/VBtn";
import VToolbar from "vuetify/lib/components/VToolbar/VToolbar";
import VParallax from "vuetify/lib/components/VParallax/VParallax";
export default {
name: "Initial",
components: {
VContainer,
VLayout,
VFlex,
VBtn,
VToolbar,
VParallax
},
data() {
return {
isAuthenticated: false
};
},
async created() {
try {
await this.$auth.renewTokens();
} catch (e) {
// console.log(e);
}
},
methods: {
login() {
this.$auth.login();
},
// logout() {
// this.$auth.logOut();
// },
handleLoginEvent(data) {
this.isAuthenticated = data.loggedIn;
this.profile = data.profile;
}
}
}
</script>
<style scoped>
</style>
My Callback.vue:
<template>
<div>
<p>Loading...</p>
</div>
</template>
<script>
export default {
methods: {
handleLoginEvent(data) {
console.log('State.target is:');
console.log(data.state.target);
//If user has just signed up redirect to complete-signup form
if ((data.profile['<AUTH_DOMAIN>'].justSignedUp) && (data.state.target===undefined)){
// this.$router.push(data.state.target || "/complete-signup");
this.$router.push('/complete-signup');
}else {
// this.$router.push('/home');
this.$router.push(data.state.target);
}
}
},
created() {
this.$auth.handleAuthentication();
}
}
</script>
<style scoped>
</style>
My router.js:
import Vue from 'vue';
import Router from 'vue-router';
import auth from '../auth/AuthService';
import Callback from '../components/Callback';
Vue.use(Router)
// export default new Router({
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
// {
// path: '/',
// name: 'login',
// component: () => import('#/views/Login')
// },
{
path: '/',
name: 'initial',
component: () => import('#/views/Initial'),
// meta: {isAuth: true},
beforeEnter: ((to, from, next) => {
// if (to.matched.some(record => record.meta.isAuth)) {
console.log(auth.isAuthenticated()); //THIS is false for the above scenario
if (auth.isAuthenticated()) {
next({
path: '/home',
query: {redirect: to.fullPath}
})
} else {
next()
}
// }
})
},
{
path: '/callback',
name: 'callback',
component: Callback
},
{
path: '/home',
name: 'home',
component: () => import('#/views/Home')
},
{
path: '/doctors',
name: 'doctors',
component: () => import('#/views/Doctors')
},
{
path: '/complete-signup',
name: 'complete-signup',
component: () => import('#/views/CompleteSignup')
},
]
});
// Add a `beforeEach` handler to each route
router.beforeEach((to, from, next) => {
if (to.path === "/" || to.path === "/callback" || auth.isAuthenticated()) {
return next();
}
// Specify the current path as the customState parameter, meaning it
// will be returned to the application after auth
console.log('OUT beforeach if');
auth.login({ target: to.path });
});
The 'CompleteSignupis my signup form after registering where the user's is filling out a form and then posting it viaaxiosand then redirected to/home`:
//Form data before
methods: {
this.$store.dispatch(REGISTER,registerFormData)
.then(() => this.$router.push('/home'));
}
I'm also using vuetify and my main App.vue component is:
<template>
<v-app
style= "background: #E0EAFC; /* fallback for old browsers */
background: -webkit-linear-gradient(to left, #CFDEF3, #E0EAFC); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to left, #CFDEF3, #E0EAFC); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
"
>
<v-content>
<router-view></router-view>
</v-content>
</v-app>
</template>
<script>
export default {
name: 'App',
components: {
}
};
</script>
<style>
</style>
You can simplify the problem by making the default behaviour the one where your user is logged in and then protect the respective routes with the route guard.
1) point your / to /home
2) create separate route for login/"intial"
3) use the beforeEach hook to ensure a user is authenticated and if not redirect him to your Initial.vue (or trigger auth.login() directly)
...
{
path: '/',
redirect: 'home'
},
{
path: '/initial',
...
...
}
...
router.beforeEach((to, from, next) => {
if(to.name == 'callback' || auth.isAuthenticated()) {
next()
} else { // trigger auth0 login or redirect to your Initial.vue
auth.login()
// next({ path: '/initial' })
}
})
I figured it out. The problem lies in the fact that the this.tokenExpiry, this.idToken, this.profile values are being assigned a value within the callback view which comes after the login one and in addition to that these values are tied to the specific Vue prototype instance that I'd defined in a mixin. So when navigating back to the initial page this is undefined since it's not associated to a specific Vue instance.
I had been in the same situation (with React) and I will try to explain how to solve it.
First, in Authentication, it is necessary to store the token on the client side (localStorage / cookie can be used)
You need a SecureComponent, this component will only check if the stored token exists
FetchHandler this component handles eachProtected Application
and will inspect the response code, and if it is 401 (for example) it will redirect a User to the Authentication Component. This step can be added to SecureComponent as an additional layer.