In this section, you learn how to query data from your client application and implement attribute-based access control (ABAC).
“Attribute-based access control, also known as policy-based access control for IAM, defines an access control paradigm whereby access rights are granted to users through the use of policies which combine attributes together.” – Wikipedia
In the previous section, you were able to log in a user and save their session in browser cookies. To retrieve data using the access token (saved in session cookies) you first need to define a role in Fauna. This role will specify the resources you can interact with.
Navigate to Fauna dashboard. Select Security > Roles > New Role to create a new role.
AuthRole
since all authenticated users will assume this role.findOwnerByEmail
and owner_stores_by_owner
index. Give read privilege to both of these indexes.The findOwnerByEmail query lets you query a user by email using the generated access token. The owner_stores_by_owner lets you query the stores that belong to a particular user.
Membership in Fauna specific identities that should have the specified privileges. In this scenario all the records in the Owner collection are members.
When an owner logs in to your application you want to show that user their basic information (i.e. username, email) and all the stores that belong to that owner. To do so you can make the following GraphQL query with the owner’s email as a parameter.
query findbyEmail($email: String!) {
findOwnerByEmail(email: $email) {
_id
name
email
stores {
data {
_id
name
}
}
}
}
Make the following changes to your components/Dashboard.js
file to run this query when your dashboard component loads.
// components/Dashboard.js
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import Cookie from 'js-cookie';
import { useLazyQuery, gql } from '@apollo/client'
const FindOwnerByEmail = gql`
query findbyEmail($email: String!) {
findOwnerByEmail(email: $email) {
_id
name
email
stores {
data {
_id
name
}
}
}
}
`;
export default function Dashboard() {
const router = useRouter()
const [getCurrentUser, { data, error, loading }] = useLazyQuery(FindOwnerByEmail);
const cookies = Cookie.get('fauna-session');
useEffect(() => {
if(!cookies) {
router.push('/login')
}
const current_user_email = JSON.parse(cookies).email;
console.log('Find Email', JSON.parse(cookies));
getCurrentUser({
variables: {
email: current_user_email
}
})
}, [cookies])
if(loading) {
return <div>Loading...</div>
}
if(data?.findOwnerByEmail?.stores?.data) {
const stores = data?.findOwnerByEmail?.stores?.data
const ownerInfo = data.findOwnerByEmail;
return (
<>
<h4>{ownerInfo.name}</h4>
<div><b>Email:</b> {ownerInfo.email}</div>
<h6>Your Stores: </h6>
<div style={{
marginTop: '20px',
overflow: 'auto',
minWidth: '270px',
maxHeight: '60vh',
display: 'flex',
justifyContent: 'center'
}}>
<ul className="uk-list uk-list-large uk-list-striped">
{
Object.keys(stores).map((_, index) => {
const store = stores[index]
return (
<li key={store._id}>
<div className="container">
<div>{store.name}</div>
<p uk-margin>
<button
className="uk-button uk-button-secondary uk-button-small"
onClick={() => {
router.push(`/store/${store._id}/edit`)
}}
>
Edit</button>
<button
className="uk-button uk-button-danger uk-button-small"
onClick={() => {
alert('Delete not implemented yet')
}}
>
Delete
</button>
</p>
</div>
</li>
)
})
}
</ul>
</div>
</>
)
}
return <div>Dashboard</div>
}
}
Run your application with npm run dev
command and make sure everything is working as intended.
Let’s refactor the UI to make things tidy. Add a navigation bar and logout button. When a user logs out you delete the session cookies. Create a new component components/Navbar.js and the following code in there.
// components/Navbar.js
import { useRouter } from 'next/router'
import Link from 'next/link'
import Cookie from 'js-cookie'
export default function Navbar() {
const router = useRouter()
const logOut = () => {
Cookie.remove('fauna-session')
router.push('/login')
}
return (
<nav className="uk-navbar-container" style={{ display: 'flex'}}>
<div className="uk-navbar-left">
<ul className="uk-navbar-nav">
<li className="uk-active"><Link href='/'>Fauna E-Com</Link></li>
</ul>
</div>
<div className="uk-navbar-right">
<ul className="uk-navbar-nav">
<li >
<Link href='/store/new' className="uk-button uk-button-primary" style={{ color: 'white'}}>
Add Store
</Link>
</li>
<li >
<a onClick={logOut} className="uk-button uk-button-danger" style={{ color: 'white'}}>
Logout
</a>
</li>
</ul>
</div>
</nav>
)
}
Next, create a Layout component. Create a new file components/layout.js
and add the following code.
// components/layout.js
import Navbar from './Navbar'
export default function Layout({ children }) {
return (
<>
<Navbar />
{children}
</>
)
}
Wrap your pages/index.js
page component with the new layout you created. Make the following changes.
import Dashboard from '../components/Dashboard'
import Layout from '../components/Layout'
import styles from '../styles/Home.module.css'
export default function Home() {
return (
<Layout>
<div className={styles.container}>
<Dashboard />
</div>
</Layout>
)
}
Navigate to your browser and notice that the new layout is now applied.
That’s all for this section. In the next section, you implement Create Delete and Update stores. You also do a deep dive into custom resolvers and Fauna Query Language (FQL).