I have two middlewares:
Login page middleware pushes logged in user to his maximum's role allowed dashboard (example: user, owner -> owner / user -> user)
Panel middleware checks user roles and if there's no permission to visit the page it redirects back to login (worker will be redirected to login page if he tries to watch owner dashboard)
To make logout function I created query path in the first middleware (login?logout=true) and when user wants to logout, dashboard should redirect him to login page with this query, though it doesn't
The thing is that when I do redirection, console.log(to) method on the first middleware prints me:
fullPath: "/dashboard/owner"
Redirect method
this.$router.replace({ name: 'login', query: { 'logout': true } })
First middleware
import { authoritiesRoutes } from "~~/middleware/panel"
import { useAuthStore } from "~~/store/auth"
export default defineNuxtRouteMiddleware(async ({ $pinia }, to, from) => {
const token = useCookie('accessToken')
console.log(to)
if (to.query.logout === 'true') {
token.value = null
return navigateTo('/login')
}
if (token.value) {
const store = useAuthStore($pinia)
await store.retrieveUser(token.value)
if (store.user != null) {
const permissionLevel = store.getUser.authorities.length
const routeName = authoritiesRoutes[permissionLevel]
const route = '/' + routeName.replace('-', '/')
return route;
}
}
})
Second middleware
import { useAuthStore } from '~~/store/auth'
export enum authoritiesRoutes {
'panel' = 1,
'dashboard' = 2,
'dashboard-owner' = 3
}
export default defineNuxtRouteMiddleware(async ({ $pinia, $event }, to) => {
const token = useCookie('accessToken')
if (!token.value) {
return navigateTo('/login')
}
const store = useAuthStore($pinia)
await store.retrieveUser(token.value)
if (store.user == null) {
return navigateTo('/login')
}
const permissionLevel = authoritiesRoutes[to.name];
if (store.getUser.authorities.length < permissionLevel) {
return navigateTo("/login?error=you%20don't%20have%20permission%20to%20watch%20this%20page")
}
})
I figured out that (async ({ $pinia }, to, from) in the middlewares should be (async (to, { $pinia })
I also found out that useCookie method works only when page is reloaded so I did my task this way:
First middleware
export default defineNuxtRouteMiddleware(async (to, { $pinia }) => {
var token = useCookie('accessToken')
const store = useAuthStore($pinia)
if (to.query.logout === 'true') {
if (!token.value) {
return navigateTo('/login')
}
token.value = null
if (process.client) {
store.logout()
}
}
if (token.value) {
await store.retrieveUser(token.value)
if (store.user != null) {
const permissionLevel = store.getUser.authorities.length
const routeName = authoritiesRoutes[permissionLevel]
const route = '/' + routeName.replace('-', '/')
return route;
}
}
})
Second middleware stays the same
Login.vue now has created method that looks like this:
created () {
if (this.$route.query.logout == 'true') {
this.$router.go(0)
}
}
Related
I'm fetching page data from a cms, so far I have only one page in pages/posts.
pages/posts/[slug].js
import { getAllPostsWithSlug, getPostAndMorePosts } from '../../lib/api';
export default function Post({ post }) {
const router = useRouter();
const { slug } = router.query;
return (
<div>
<p>
title: {typeof post == 'undefined' ? 'no post' : post.title}
</p>
</div>
);
}
export async function getStaticProps({ params, preview = null }) {
const data = await getPostAndMorePosts(params.slug, preview);
const content = await markdownToHtml(data?.posts[0]?.content || '');
return {
props: {
preview,
post: {
...data?.posts[0],
content,
},
morePosts: data?.morePosts,
},
};
}
export async function getStaticPaths() {
const allPosts = await getAllPostsWithSlug();
return {
paths: allPosts?.map((post) => `/posts/${post.slug}`) || [],
fallback: true,
};
}
That will correctly display post.title, but if I access the property directly with
<p>title: {post.title}</p>
I get the build error:
post undefined
Is next trying to build a page out of the template with no data? When the build succeeds I only have one route in /posts.
I have a uid from firebase, but it is stored like this:
checkout.js
firebase.auth().onAuthStateChanged((user) => {
if(user) {
console.log(user.uid)
}
});
the user.uid stores the uid I need to pass to another page. this uid is stored in checkout.js, and I need to get it to profile.js to use the uid there. how would I do this?
please let me know if more code is needed. I am using react / js
I literally just copy and pasted that code into the profile.js and it worked. 😑
use a context store
create the store and the hook
const FirebaseAuthContext = createContext()
export function useAuth() {
const context = useContext(FirebaseAuthContext)
if (!context && typeof window !== 'undefined') {
throw new Error(`useAuth must be used within a FirebaseAuthContext`)
}
return context
}
Create the Provider
export function FirebaseAuthProvider(props) {
const [authState, setAuthState] = useState({
isLoggedIn: false,
currentUser: null,
pending: true,
})
useEffect(() => {
const unregisterAuthObserver = auth().onAuthStateChanged(async (currentUser) => {
if (currentUser) {
setAuthState({. isLoggedIn: true, pending: false, currentUser})
} else {
setAuthState({ ...authState, currentUser: null, pending: false })
}
})
return () => unregisterAuthObserver()
}, [])
return <FirebaseAuthContext.Provider value={authState}>{props.children}</FirebaseAuthContext.Provider>
Wrap your app with provider
<FirebaseAuthProvider><App /></FirebaseAuthProvider>
Then wherever you need to validate the user.
const { currentUser, pending } = useAuth();
if (currentUser && pending) //pending authentication
if (currentUser) // user is login, uid is in currentUser.uid
I am trying to pass the username from signup1 to signup 2:
handleSignUp = () => {
console.log(this.state.username) <------------- This logs correctly
try {
this.props.navigation.navigate('Signup2', {
username: this.state.username, <------------- Passing here
});
} catch (error) {
console.log(error);
}
}
Here is the code from Signup2:
const Signup2 = (props, route, navigation) => {
const { username } = route.params; <----------- Trying to access it here
I get the following error:
undefined is not an object (evaluating 'route.params.username')
I have also tried this:
const { username } = props.route.params
And neither work.
How can I access the route params?
I believe you would need to destructure your route and navigation properties if you'd like to access them as intended.
You can go for:
const Signup2 = ({route, navigation}) => {
const { username } = route.params
}
Or maybe:
const Signup2 = (props) => {
const { username } = props.route.params
}
My SPA has this working component that fetches an access token that will be encrypted and passed to other components via props. This is it:
import React, { Component } from 'react';
//import { Redirect } from 'react-router-dom';
import axios from 'axios';
import Credentials from './spotify-auth.js'
import './Spotify.css'
class SpotifyAuth extends Component {
constructor (props) {
super(props);
this.state = {
isAuthenticatedWithSpotify: false,
};
this.state.handleRedirect = this.handleRedirect.bind(this);
};
generateRandomString(length) {
let text = '';
const possible =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
getHashParams() {
const hashParams = {};
const r = /([^&;=]+)=?([^&;]*)/g;
const q = window.location.hash.substring(1);
let e = r.exec(q);
while (e) {
hashParams[e[1]] = decodeURIComponent(e[2]);
e = r.exec(q);
}
return hashParams;
}
componentDidMount() {
//if (this.props.isAuthenticated) {
const params = this.getHashParams();
const access_token = params.access_token;
const state = params.state;
const storedState = localStorage.getItem(Credentials.stateKey);
localStorage.setItem('spotifyAuthToken', access_token);
localStorage.getItem('spotifyAuthToken');
if (window.localStorage.getItem('authToken')) {
this.setState({ isAuthenticatedWithSpotify: true });
};
if (access_token && (state == null || state !== storedState)) {
alert('Click "ok" to finish authentication with Spotify');
} else {
localStorage.removeItem(Credentials.stateKey);
}
// DO STUFF WITH ACCEES TOKEN HERE
this.props.onConnectWithSpotify(access_token);
};
handleRedirect(event) {
event.preventDefault()
this.props.createMessage('You linked your Spotify account!', 'success');
// get client features at authenticating
const params = this.getHashParams();
const access_token = params.access_token;
console.log(access_token);
const state = this.generateRandomString(16);
localStorage.setItem(Credentials.stateKey, state);
let url = 'https://accounts.spotify.com/authorize';
url += '?response_type=token';
url += '&client_id=' + encodeURIComponent(Credentials.client_id);
url += '&scope=' + encodeURIComponent(Credentials.scope);
url += '&redirect_uri=' + encodeURIComponent(Credentials.redirect_uri);
url += '&state=' + encodeURIComponent(state);
window.location = url;
};
render() {
return (
<div className="button_container">
<h1 className="title is-3"><font color="#C86428">{"Welcome"}</font></h1>
<div className="Line" /><br/>
<button className="sp_button" onClick={(event) => this.handleRedirect(event)}>
<strong>LINK YOUR SPOTIFY ACCOUNT</strong>
</button>
</div>
)
}
}
export default SpotifyAuth;
This token credentials last for 60 minutes.
I've learned that the standard option for SPAs is to use iframes to silently renew tokens and not use refresh tokens at all.
How do one spin up an iframe and silently get a new access token every hour in a React component like the one above? I have looked everywhere for this and haven't found anything.
You can do the following:
Create some watcher function, which checks the expiration time of the access token. If the token is about to expire, it is time to renew it.
Render an iframe tag, the src should be the same URL which you are using for redirecting to the Auth server, with one difference: change the return URL to a static file, let's call it redirect.html. The server should know about the user calling this URL, from the stored cookie, so it should just simply redirect you to the redirect.html file, now with a fresh access token.
In this redirect.html write a short script, which takes out the token from the URL and override it with the one you already have in local storage.
Destroy the iframe.
This is about it, the token is renewd. Keep the watcher going and renew it everytime its about to expire (do it like 5 minutes before it expire).
An example implementation for the AccessToken component in React, most parts of the code will actually work, but you need to replace the constants with your stuff. Also, some functions like extractTokenFromUrl are missing, but that should be easy enough to make:
import React, { Component } from 'react'
export class SilentTokenRenew extends Component {
constructor(props) {
super(props)
this.state = { renewing: false }
this.currentAttempt = 0
this.maxNumberOfAttempts = 20
}
shouldComponentUpdate(nextProps, nextState) {
return this.state.renewing !== nextState.renewing
}
componentDidMount() {
this.timeInterval = setInterval(this.handleCheckToken, 20000)
}
componentWillUnmount() {
clearInterval(this.timeInterval)
}
willTokenExpire = () => {
const token = YOUR_ACCESS_TOKEN_OBJECT // { accessToken, expirationTime }
const threshold = 300 // 300s = 5 minute threshold for token expiration
const hasToken = token && token.accessToken
const now = (Date.now() / 1000) + threshold
return !hasToken || (now > token.accessToken.expirationTime)
}
handleCheckToken = () => {
if (this.willTokenExpire()) {
this.setState({ renewing: true })
clearInterval(this.timeInterval)
}
}
silentRenew = () => {
return new Promise((resolve, reject) => {
const checkRedirect = () => {
// This can be e
const redirectUrl = localStorage[YOUR_REDIRECT_URL_FROM_THE_REDIRECT_HTML_FILE] // /redirect.html#access_token=......
if (!redirectUrl) {
this.currentAttempt += 1
if (this.currentAttempt > this.maxNumberOfAttempts) {
reject({
message: 'Silent renew failed after maximum number of attempts.',
short: 'max_number_of_attempts_reached',
})
return
}
setTimeout(() => checkRedirect(), 500)
return
}
// Clean up your localStorage for the next silent renewal
localStorage.removeItem(YOUR_REDIRECT_URL_FROM_THE_REDIRECT_HTML_FILE)
// Put some more error handlers here
// Silent renew worked as expected, lets update the access token
const session = extractTokenFromUrl(redirectUrl) // write some function to get out the access token from the URL
// Following your code you provided, here is the time to set
// the extracted access token back to your localStorage under a key Credentials.stateKey
localStorage.setItem(Credentials.stateKey, JSON.stringify(session))
resolve(session)
}
checkRedirect()
})
}
handleOnLoad = () => {
this.silentRenew()
.then(() => {
this.setState({ renewing: false })
this.currentAttempt = 0
this.timeInterval = setInterval(this.handleCheckToken, 60000)
// Access token renewed silently.
})
.catch(error => {
this.setState({ renewing: false })
// handle the errors
})
}
renderIframe = () => {
const url = new URL(YOUR_AUTHORIZE_URL_TO_TH_AUTH_SERVER)
url.searchParams.set('redirect_uri', 'http://localhost:3000/redirect.html') // the redirect.html file location
url.searchParams.set('prompt', 'none')
return (
<iframe
style={{ width: 0, height: 0, position: 'absolute', left: 0, top: 0, display: 'none', visibility: 'hidden' }}
width={0}
height={0}
title="silent-token-renew"
src={url.href}
onLoad={this.handleOnLoad}
/>
)
}
render() {
const { renewing } = this.state
return renewing ? this.renderIframe() : null
}
}
Sample code fore the redirect.html file:
<!DOCTYPE html>
<html>
<head>
<title>OAuth - Redirect</title>
</head>
<body>
<p>Renewing...</p>
<script>
// Get name of window which was set by the parent to be the unique request key
// or if no parameter was specified, we have a silent renew from iframe
const requestKey = YOUR_REDIRECT_URL_FROM_THE_REDIRECT_HTML_FILE;
// Update corresponding entry with the redirected url which should contain either access token or failure reason in the query parameter / hash
window.localStorage.setItem(requestKey, window.location.href);
window.close();
</script>
</body>
</html>
I have this function get the token from api and check if I have admin permission. The problem is my export its firing before of the checking of the function.
const async = [
{ path: '*', redirect: '/404', hidden: true }
]
var hasAdmin = getAdmin()
if (hasAdmin === undefined || hasAdmin === null) {
var token = null
var url = null
var uri = window.location.href.split('?')
if (uri.length === 2) {
var vars = uri[1].split('&')
var getVars = {}
var tmp = ''
vars.forEach(function(v) {
tmp = v.split('=')
if (tmp.length === 2) {
getVars[tmp[0]] = tmp[1]
}
token = getVars.AUTH_ID
url = getVars.DOMAIN
})
getUserAdmin(url, token)
.then(response => {
var hasAdmin = response.result
if (hasAdmin === true) {
console.log('hasAdmin: ' + hasAdmin)
async.push(adminRouter)
}
})
.catch(error => {
console.log(error)
})
}
} else if (hasAdmin === true) {
async.push(adminRouter)
}
export const asyncRoutes = async
console.log('hasAdmin2: ' + hasAdmin)
Here is how Im using the const asyncRoutes:
import { asyncRoutes, constantRoutes } from '#/router'
/**
* Use meta.role to determine if the current user has permission
* #param roles
* #param route
*/
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
return true
}
}
/**
* Filter asynchronous routing tables by recursion
* #param routes asyncRoutes
* #param roles
*/
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
const state = {
routes: [],
addRoutes: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
generateRoutes({ commit }, roles) {
return new Promise(resolve => {
let accessedRoutes
if (roles.includes('admin')) {
accessedRoutes = asyncRoutes || []
} else {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
I always need reload(f5) the page in first access to gets the menu admin.
Await from promise dont works because de export should be always in the top. Some idea how can I fix this?
obs: attention in variable hasAdmin