How to redirect to another router from an AuthorizedStep - aurelia

I have two routes — one is a public router and the other is the authorized router.
In my authorized router I have an authorizeStep.
The authorizedStep checks if there is a token in localStorage and if it is returns a next().
However if it fails to find a token its supposed to stop and jump out returning to the public router.
I am having trouble stopping the authorized step and instead going to the public router.
I have:
run(navigationInstruction: NavigationInstruction, next: Next): Promise<any> {
return Promise.resolve()
.then(() => this.checkSessionExists(navigationInstruction, next)
.then(result => result || next())
);
}
checkSessionExists(navigationInstruction: NavigationInstruction, next: Next) {
const session = this.authService.getIdentity();
if (!session) {
// HOW DO I CANCEL THE NEXT HERE AND GO TO THE PUBLIC ROUTER?
return next.cancel(new Redirect('login'))
}
return next()
}
forceReturnToPublic() {
this.authService.clearIdentity();
this.router.navigate("/", { replace: true, trigger: false });
this.router.reset();
this.aurelia.setRoot("public/public/public");
}
I have the function forceReturnToPublic() however I want to go and cancel the next() then go directly to the other router... I dont want to redirect..
How do I cancel the next in the promise and reset the router?
Here is my boot.ts which should kick it back to public but I dont know how to jump out of the promise cleanly...
// After starting the aurelia, we can request the AuthService directly
// from the DI container on the aurelia object. We can then set the
// correct root by querying the AuthService's checkJWTStatus() method
// to determine if the JWT exists and is valid.
aurelia.start().then(() => {
var auth = aurelia.container.get(AuthService);
let root: string = auth.checkJWTStatus() ? PLATFORM.moduleName('app/app/app') : PLATFORM.moduleName('public/public/public');
aurelia.setRoot(root, document.body)
});
If I place the forceReturnToPublic() in place of the return next.cancel(new Redirect('login') it goes into and endless loop with errors.
EDIT
I found THIS question which indicates I should add "this.pipeLineProvider.reset()" so I did - like this...
forceReturnToPublic() {
this.pipelineProvider.reset();
this.authService.clearIdentity();
this.router.navigate("/", { replace: true, trigger: false });
this.router.reset();
this.aurelia.setRoot("public/public/public");
}
Whilst it goes straight back to the public route I get an error in the console.
aurelia-logging-console.js:47 ERROR [app-router] Error: There was no router-view found in the view for ../components/clients/clientList/clientList.
at _loop (aurelia-router.js:281)
at NavigationInstruction._commitChanges (aurelia-router.js:307)
at CommitChangesStep.run (aurelia-router.js:143)
at next (aurelia-router.js:112)
at iterate (aurelia-router.js:1272)
at processActivatable (aurelia-router.js:1275)
at ActivateNextStep.run (aurelia-router.js:1161)
at next (aurelia-router.js:112)
at iterate (aurelia-router.js:1191)
at processDeactivatable (aurelia-router.js:1194)
I was clicking on the clientList nav link which does have a router-view..
How/where do I place the pipelineProvider.reset()? (if this is the problem)
But what I really want is...
How do I stop this router and cleanly move to the other router?

As you do I keep trying things.. This worked for me as I believe the pipelineProvider needed to finish before moving to the next step.. So I used a promise like so:
forceReturnToPublic(): Promise<any> {
return Promise.resolve()
.then(() => this.pipelineProvider.reset())
.then(() => this.authService.clearIdentity())
.then(() => this.router.navigate("/", { replace: true, trigger: false }))
.then(() => this.router.reset())
.then(() => this.aurelia.setRoot(PLATFORM.moduleName('public/public/public')));
}
...and no errors.

Related

Vue 2 / Nuxt 2 Emit From Axios With Dialog Confirm

i am using Vue 2 / nuxt to emit from a axios post* call which itself is called from a Buefy dialog confirm. The emit from this component will close the window / panel and then re-load the users.
If I call the axios request from the button, this works without any issues, but once being called from the dialog, it just don't work?
*most likely this will be updated to a delete request, just not gotten to that let
See code below:
removeUser() {
this.$buefy.dialog.confirm({
message: 'Continue on this task?',
onConfirm: () => {
this.removeUserFunc()
}
})
},
removeUserFunc() {
// console.log(that)
const that = this
// Build URL
const EndPoint = '/remove_user/' + this.id
this.$axios.post(EndPoint).then((res) => {
// User Remove Message
UserRemoved(this.$swal)
that.$parent.$emit('completed')
// console.log(this.$emit('complete'))
// // Emit 'completed' Message
console.log(that.$emit('completed'))
console.log(that)
}).catch((res) => {
console.log(res)
// Check For Errors
GeneralError(this.$swal)
})
}
I was thinking it was losing access to the correct this, so i was trying to pass that back in, but not sure that is the case?
I have also tried with await, while that sort of works? I think is firing the emit too fast, as it re-loads the users but it still includes the user that as just been deleted?
removeUser() {
this.$buefy.dialog.confirm({
message: 'Continue on this task?',
onConfirm: async() => {
this.removeUserFunc()
await this.$emit('completed')
}
})
},
The this keyword refers to the object the function belongs to, or the window object if the function belongs to no object.
Try to use .bind and use a ES5 function
removeUser() {
this.$buefy.dialog.confirm({
message: 'Continue on this task?',
onConfirm: function() {
this.removeUserFunc()
}.bind(this)
})
},

ASync/Await is not working as expected in router.BeforeEach guard in vue?

this is my router guard :
router.beforeEach(async (to,from,next)=>{
await store.dispatch('GetPermission');
if(to.matched.some(record => record.meta.requireAuth)){
let permissions=store.state.permissions; //getting empty
console.log(permissions);
if(permissions.filter(per => (per.name === 'read_list').length!=0)){
next({
path:'/dashboard/create'
})
}
else{
next()
}
}
// else if(to.matched.some(record => record.meta.requireAuth)){
// if(store.token!=null){
// next({
// path:'/dashboard'
// })
// }
// else{
// next()
// }
// }
else{
next()
}
});
problem is here though i m using await in dispatch method , i m not getting state value of permissions which is initially empty
here is vuex store code :
GetPermission(context){
axios.defaults.headers.common['Authorization']='Bearer ' + context.state.token
axios.get('http://127.0.0.1:8000/api/user').then((response)=>{
console.log(response)
context.commit('Permissions',response.data.permission)
})
//mutation:
Permissions(state,payload){
state.permissions=payload
}
//state
state:{
error:'',
token:localStorage.getItem('token') || null,
permissions:'',
success:'',
isLoggedin:'',
LoggedUser:{}
}
help me to solve it please ??
actions in Vuex are asynchronous. The only way to let the calling function (initiator of action) to know that an action is complete - is by returning a Promise and resolving it later.
Here is an example: myAction returns a Promise, makes a http call and resolves or rejects the Promise later - all asynchronously
actions: {
myAction(context, data) {
return new Promise((resolve, reject) => {
// Do something here... lets say, a http call using vue-resource
this.$http("/api/something").then(response => {
// http success, call the mutator and change something in state
resolve(response); // Let the calling function know that http is done. You may send some data back
}, error => {
// http failed, let the calling function know that action did not work out
reject(error);
})
})
}
}
Now, when your Vue component initiates myAction, it will get this Promise object and can know whether it succeeded or not. Here is some sample code for the Vue component:
export default {
mounted: function() {
// This component just got created. Lets fetch some data here using an action
this.$store.dispatch("myAction").then(response => {
console.log("Got some data, now lets show something in this component")
}, error => {
console.error("Got nothing from server. Prompt user to check internet connection and try again")
})
}
}
Also,you are calling same route when no permission match, in that case it always call your same route and make infinite loop.
Redirect to access denied page if permission denied.

vue-router — Uncaught (in promise) Error: Redirected from "/login" to "/" via a navigation guard

Why is vue-router giving me this error? To be clear, the login flow works as intended but I want to a) get rid of the errro and b) understand why the error is happening.
Error:
Uncaught (in promise) Error: Redirected from "/login" to "/" via a navigation guard.
Login flow
start logged out, but enter a URL that requires auth (i.e. anything besides "/login")
get redirected to "/login" (as expected).
login
successfully get redirected to starting Url from step #1, except with the above error.
Login action:
doLogin({ commit }, loginData) {
commit("loginStart");
axiosClient
.post("/jwt-auth/v1/token", {
username: loginData.username,
password: loginData.password,
})
.then((response) => {
commit("loginStop", null);
commit("setUserData", response.data);
this.categories = airtableQuery.getTable("Categories");
commit("setCategories", this.categories);
this.locations = airtableQuery.getTable("Locations");
commit("setLocations", this.locations);
router.push("/"); // push to site root after authentication
})
.catch((error) => {
console.log(error.response.data.message);
commit("loginStop", error.response.data.message);
commit("delUserData");
});
},
Router:
const routes = [
{
path: "/login",
name: "Login",
component: Login,
meta: { requiresAuth: false },
},
{
path: "/",
name: "Home",
component: Home,
meta: { requiresAuth: true },
},
];
let entryUrl = null;
router.beforeEach((to, from, next) => {
let localStorageUserData = JSON.parse(localStorage.getItem("userData"));
let storeUserData = state.getters.getUserData;
let userData = localStorageUserData || storeUserData;
let isAuthenticated = userData.token !== "" && userData.token !== undefined;
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (!isAuthenticated) {
if (to.name !== "Login" && to.name !== "Home") {
entryUrl = to.fullPath;
}
next({ name: "Login" });
} else if (entryUrl) {
let url = entryUrl;
entryUrl = null;
next(url);
} else {
next();
}
} else {
next();
}
});
I spent hours debugging this and got to the following results for the ugly Uncaught (in promise) Error: Redirected when going from ... Error.
Note that the error is not for the "redirect". It's for the initial caller of the first navigation. Keep reading...
It's by design. Why?
Read this comment.
TL;DR: Let's say you are on page A, and click on a button to take you to page B (kinda like method: goToB() { router.push('/B'); } on page A). But there is a Navigation Guard for page B, that sends you to page C.
This error is a way for letting that goToB() function know that the router hasn't been able to fulfill the desired task, and the user hasn't landed on /B.
It's nasty, but informative
The biggest confusion here is that the redirect (landing on Page C) is, both:
an "expected" outcome to you, the architect of the system. But, at the same time,
an "unexpected" event to the caller of goToB in page A (i.e. router.push), who expects the router to go to page B.
That's why when it's popped as Error, it's confusing and frustrating to "you", who looks at the system entirely and thinks nothing is wrong or erroneous!
Urrrgh... So, what should I do?
Solution 1: Use router-link if you can
I ran into a case that <router-link> was working fine, but router.push was complaining. (I think router-link internally suppresses such errors.)
Solution 2.1: Individual suppress errors on each router.push call
The router.push function is returning a Promise (as it can be considered, or will be, an asynchronous job). All you need to do is to suppress any Error it might throw via
router.push('/B').catch(() => {});
// Add this: ^^^^^^^^^^^^^^^^
Solution 2.2: Augment Router.prototype.push to always suppress errors
If you think you have multiple instances of this, you can augment the push function on the prototype of the Router via the snippet on the same comment to apply this to all the router.push calls on the entire app.
The good news is it's giving you granularity level to choose which error you want to suppress (e.g. only NavigationFailureTypes.redirected ones, for example. The enum is here)
If you are on TypeScript, be my guest on the conversion and typing https://gist.github.com/eyedean/ce6ab6a5108a1bd19ace64382144b5b0 :)
Other tips:
Upgrade your vue-router! Your case might be solved by the time you read this. (As they have a plan to do so, apparently.)
Make sure you are not forking or reaching to dead-end in your Navigation Guards, if you have multiple ones. Follow them one by one and track them step by step. Note that, double redirecting is fine (thanks to this answer), you just need to be double careful!
I also got a playground here: https://codepen.io/eyedean/pen/MWjmKjV You can start mimicking this to your need to figure out where your problem happens in the first place.
The error message is getting updated in the next version of vue-router. The error will read:
Redirected when going from "/login" to "/" via a navigation guard
Somewhere in your code, after being redirected to "/login", you are redirecting back to "/". And vue-router is complaining about. You'll want to make sure you only have one redirect per navigation action.
I had a similar error, but for an onboarding redirect in .beforeEach, which was resolved by replacing in the .beforeEach conditional logic:
next({ name: "Onboarding" });
with
router.push({ path: 'Onboarding' });
This error is meant to inform the caller of $router.push that the navigation didn't go to where it was initially intended. If you expect a redirection you can safely ignore the error with the following code.
import VueRouter from 'vue-router'
const { isNavigationFailure, NavigationFailureType } = VueRouter
...
this.$router.push('/')
.catch((e) => {
if (!isNavigationFailure(e, NavigationFailureType.redirected)) {
Promise.reject(e)
}
}
See https://github.com/vuejs/vue-router/issues/2932 for a discussion regarding this issue.
If your redirect is after a call to router.push('/someroute) then you can catch this error as router.push() is a promise and you can attach a catch to it as below
$router.push('/somesecureroute')
.catch(error => {
console.info(error.message)
})
I have the same error. This error created by router.push("/"); row - it trying to say you that pushing to home was interrupted by redirection in navigation guard.
But actually, it's not an error because it is an expected behaviour.
I made ignoring of such errors by the following way:
const router = new VueRouter({
mode: 'history',
routes: _routes,
});
/**
* Do not throw an exception if push is rejected by redirection from navigation guard
*/
const originalPush = router.push;
router.push = function push(location, onResolve, onReject) {
if (onResolve || onReject) {
return originalPush.call(this, location, onResolve, onReject);
}
return originalPush.call(this, location).catch((err) => {
let reg = new RegExp('^Redirected when going from "[a-z_.\\/]+" to "[a-z_.\\/]+" via a navigation guard.$');
if (reg.test(err.message)) {
// If pushing interrupted because of redirection from navigation guard - ignore it.
return Promise.resolve(false);
}
// Otherwise throw error
return Promise.reject(err);
});
};
I had the same issue, i thought it was config problem but it was not
You can try this code
async doLogin({ commit, dispatch }, loginData) {
commit("loginStart");
let response = await axiosClient
.post("/jwt-auth/v1/token", {
username: loginData.username,
password: loginData.password,
})
return dispacth('attempt', response)
}
async attempt({ commit }, response) {
try {
commit("loginStop", null);
commit("setUserData", response.data);
this.categories = airtableQuery.getTable("Categories");
commit("setCategories", this.categories);
this.locations = airtableQuery.getTable("Locations");
commit("setLocations", this.locations);
}
catch( (error) => {
console.log(error.response.data.message);
commit("loginStop", error.response.data.message);
commit("delUserData");
})
}
And in the component where doLogin action is called
this.doLogin(this.loginData)
.then( () => {
this.$router.replace('/')
.catch( error => {
console.log(error)
})
})
.catch( e => {
console.log(e)
})
It happened to me on application boot, during an authorization check I tried to push to '/auth' but that navigation was cancelled by another call to '/', which occurred just after mine.
So in the end I found that is possible to listen for readiness (https://router.vuejs.org/api/#isready) of vue-router using:
await router.isReady()
or
router.isReady().then(...)
I had the exact same issue triggered by two specific pages, for the two pages that were being redirected due to a login:
if (isAdmin){
router.push({name: 'page'}).catch(error=>{
console.info(error.message)})
else...
otherwise everyone else who is a regular user gets pushed to a different page using "router.push"
ONLY on the redirects that were throwing the original error/warning. Suppressing the warning as suggested in an earlier comment:
if (!to.matched.length) console.warn('no match');
next()
allowed for users to sign in and access pages without proper permissions.
Catching the errors appears to be the way to go, per the suggestion of: kissu

vue router next() function don't work in Promise in router.beforeEach

I have the following code:
router.beforeEach((to, from, next) => {
if (to.name !== from.name) {
store
.dispatch("fetchCurrentUser")
.then(() => {
console.log('then');
// do something
next();
})
.catch(() => {
console.log('catch');
router.push("/login");
next();
});
} else {
next();
}
// next();
});
I'm trying to get the current user, and if this succeeds, then do something with this data, and if the request is not successful, then redirect the user to the login page. But next () calls do not work, I get the "then" or "catch" in the console, but the redirect does not occur and an infinite loop begins. But if I take next () from condition (commented row) the redirect works fine.
To redirect you should use next('/') or next({ path: '/' }).
From the documentation:
next: Function: this function must be called to resolve the hook. The
action depends on the arguments provided to next:
next(): move on to the next hook in the pipeline. If no hooks are
left, the navigation is confirmed.
next(false): abort the current navigation. If the browser URL was
changed (either manually by the user or via back button), it will be
reset to that of the from route.
next('/') or next({ path: '/' }): redirect to a different location.
The current navigation will be aborted and a new one will be started.
You can pass any location object to next, which allows you to specify
options like replace: true, name: 'home' and any option used in
router-link's to prop or router.push
The promise resolves after the function ends.
This means that the commented next happens regardless of the result of the promise result. Then the promise resolves and you call another next.
The bottom line is that you don't need the commented next and should just cover the promise resolve.
I was able to implement an async validation inside beforeEach, authentication in my case.
export async function onBeforeEach(to, from, next) {
let { someUserToken } = to.query
if (someUserToken) {
let nextRoute = await authenticate(to)
next(nextRoute)
} else {
const userToken = store.state.application.userToken
if (!to.meta.public && !userToken) {
next({ name: 'Forbidden' })
} else {
next()
}
}
}
async function authenticate(to) {
let { someUserToken, ...rest } = to.query
return store
.dispatch('application/authenticate', {
someUserToken
})
.then(() => {
return {
name: 'Home',
query: {
...rest
}
}
})
.catch(() => {
return { name: 'Forbidden' }
})
}
I hope this helps.

VueJS check periodically global function

I have a VueJS project where I need to check periodically a function to see if a token has expired once the user login to the app successfully and if the token has expired have to show a modal message to user.
I have my Singin.vue file that contains the following code:
....
methods: {
...mapActions(['authorize']),
submit() {
this.$validator.validateAll().then(result => {
if (result) {
this.error = null;
this.processing = true;
this.authorize(this.credentials).then(() => {
// ***********
// HERE I have to check periodically if the token has expired
// ***********
this.$router.push({name: 'home'});
}).catch(error => {
console.warn('error message', error);
this.error = error.response.data.message;
this.processing = false;
});
}
});
}
When this.authorize happens I route to home, but before that happens I need to start calling a function periodically. Then If user Logoff then I have to clear the interval.
So first, I don't know where is the best place to have this TokenExpiration function code. Does it make sense to have it in a store file?
This is my api.js store file where I have my authorize function and my logout function, does it make sense to have the tokenExpirationCheck function here also?
There are several ways of doing it, but I would probably solve this using a plugin, because timers should not be in the store, and the behavior is global to the application, so I wouldn't put it into any single component.
The pugin would have a vuex.watch on the stoere's logged-in flag. When it goes from false => true, remove the timer (if active) and if it goes from false => true, add the timer. The timer function can then call the vuex dispatch to handle the functionality.