So, I already implemented private routing in React.js with the help of library from react-aad-msal. When the user isn't authorized, it will show a landing page to ask the user to log in. The next step is we want to limit what the user can and cannot see. We have a table of users that contains Email and what role the user belongs to. We also have another table security roles detail, this table will return all the routes the user roles has. So we created an API that will return all the Routes the user has in the React app, and the returned data from API will filter the main ListRouter array in the React app. I already implemented all that but I just want to be certain that this is the best practice and doesn't have a flaw.
My code is looking like this:
export const ListRouter = [
{
id: "1",
title: "Menu1",
linkto: "/Menu1",
paths: '/Menu1',
elements: <Menu1/>
},
{
id: "2",
title: "Menu2",
linkto: "/Menu2",
paths: '/Menu2',
elements: <Menu2/>
},
{
id: "3",
title: "Menu3",
linkto: "/Menu3",
paths: '/Menu3',
elements: <Menu3/>
}
]
And then in App.js, we called an API to return all the available Routers:
// API Returns
["/menu1", "/menu2"]
After we got the data from the API then we filter the ListRouter based on the returned data in useEffect:
const routerResult = await getFormListRouter().then(x => {
var userModule = x.data;
var module = ListRouter.filter((x) => {
if (userModule.indexOf(x.linkto.toString().toLowerCase()) > -1) {
return x;
}
});
setRouters(module);
});
After that we use the state in the component to render all the Routers so when user don't have the permission to the page it will return no router matches.
<Routes>
{routers.map((listmap) => (
<Route
path={listmap.paths}
element={listmap.elements}
key={listmap.id}
/>
))}
</Routes>
For now this already works, the user can't open the menu in the browser. From this implementation, is there anything should I be concerned of, or can I add something to make it more secure?
Related
When navigating from a link in the same web app to the dynamically routed page clicking on a link the result is as intended: I navigate to the page for a product (http://localhost/1).
But when I directly navigate by naming the product number specifically in the search bar (navigating to http://localhost/2), I get the following error:
Server Error
TypeError: Cannot read property 'image' of undefined
> | <Image src={"/../public/images/" + p.image}
^
So far I've tried making the types match and reading the Next JS docs on dynamically routing.
I've removed the array zero from the filter but still no resolution.
Could it be possible that the routing only works when clicking on a link in Next JS? Is there some missing setting I've neglected?
pages/[pid].js
import { useRouter } from 'next/router'
import Image from 'next/image'
import data from '../products.json'
export default function Template() {
const router = useRouter()
const { pid } = router.query
const p = data.filter(product => product._id == pid)[0] // Choose one result
return (
<Image src={"/../public/images/" + p.image}
height="500px"
width="500px" />
)
}
products.json
[
{
"_id": 1,
"name": "Toyota",
"image": "toyota.png"
},
{
"_id": 2,
"name": "BMW",
"image": "bmw.png"
}
]
Update: I've tried to hardcode the src attribute in the Image tag and the new error says the other references are the issue. So I can safely say the issue is to do with no object returned when the data object is called.
I solved the issue!
It was not enough to use Dynamic Routes by using the 'useRouter()' function. I also had to define these two functions:
export async function getStaticProps(context) {
// No-op since getStaticPaths needs getStaticProps to be called.
return { props: {} }
}
export async function getStaticPaths() {
const dynamicFiles = products.map(product => (
{
params: { pid: String(product._id) }, // Product IDs are ints in JSON file
}
))
return {
paths: dynamicFiles,
fallback: false
}
}
This makes sense since you wouldn't want random paths to be used as a variable. For example, then a user would be able to specify http://localhost/1234 when 1234 is not a valid option.
https://nextjs.org/learn/basics/dynamic-routes/implement-getstaticprops
I just started working with Gatsby to see if it would be a good choice to rebuild my company's CraftCMS website with Craft as the backend and Gatsby as the frontend. So far everything has been working well until it came time to query for the individual entries inside our "campaign" channel.
For the record, I have been able to render a complete list using .map() for each of my campaign entries on a "overall view" page to see all the campaigns. I have also been able to recursively build out each campaign page so that it calls my /src/templates/campaign-page.js template and has the correct slug pulled from my site's Craft API with no issue. For some reason, I just can't get my individual campaign data to query inside the campaign-page.js template.
I've read just about every page in the Gatsby docs and every tutorial that currently exists, but for the life of me I can't figure out why my GraphQL query will not filter for my individual campaign entries. It just keeps telling me, "GraphQL Error Expected type [String], found {eq: $slug}."
I've also tried wrapping my "slug: {eq: $slug} in a "filter:" based on some markdown docs, but that just tells me "filter" does not exist. I'm beginning to think the issue is in my gatsby-node.js file, but I'm not seeing any issue when I compare it to the docs.
Gatsby-node.js
const path = require(`path`)
exports.createPages = async ({ actions, graphql }) => {
const { data } = await graphql(`
query {
api {
entries(section: "campaigns") {
slug
}
}
}
`)
data.api.entries.forEach(({ slug }) => {
actions.createPage({
path: "/campaigns/" + slug,
component: path.resolve(`./src/templates/campaign-page.js`),
context: {
slug: slug,
},
})
})
}
Campaign-page.js
export default ({data}) => {
const post = data.api.entries
return(
<div className={"campaign-page-single"} style={{marginTop: '-21px,'}}>
<Header/>
<div>
<h1>{post.id}</h1>
</div>
</div>
)
}
export const campaignQuery = graphql`
query ($slug: String!){
api{
entries (slug: { eq: $slug }){
slug
id
... on API_campaigns_campaigns_Entry {
id
campaignTitle
slug
}
}
}
}
`
For reference, here's what a typical working query looks like on my main campaigns.js page that lists all available campaigns:
query = {graphql`
{
api {
entries(section: "campaigns") {
... on API_campaigns_campaigns_Entry {
id
campaignTitle
uri
slug
}
}
}
}
`}
I expect my /src/templates/campaign-page.js template to render the individual campaign data.
I finally had one of my coworkers take a look at my code. All I had to do was wrap my $slug variable in brackets as so:
entries (section: "campaigns", slug: [$slug] )
That's two days I wish I could have back.
Ok I'll try to explain this the best I can. I have a ResourceInfo component that posts data to the /resources/ path and /users/ + uid + /created-resources path using newPostKeyand update.
I also have a QuizBuilder component. I want to post data from this component to a /resources/ + newPostKey + /quiz/ path. However, I don't know how to get the newPostKeyor key from that particular path I created in ResourceInfo from the QuizBuilder component.
Here are the two components. First the user adds info using the ResourceInfo component. Once they hit submit they go to the QuizBuilder component where they create the quiz.
ResourceInfo.vue
export default {
name: 'resource-info',
data () {
return {
header: 'Before you build your quiz we just need some quick info.',
sharedState: store.state,
resource: {
type: '',
title: '',
url: '',
desc: '',
timesPassed: 0,
authorId: store.state.userInfo.uid,
authorName: store.state.userInfo.displayName,
authorImage: store.state.userInfo.photoURL
},
}
},
methods: {
saveToFirebase () {
var newPostKey = firebase.database().ref().child('resources').push().key;
var updates = {};
updates['/resources/' + newPostKey] = this.resource;
updates['/users/' + store.state.userInfo.uid + '/created-resources/' + newPostKey] = this.resource;
// Clear inputs
this.resource.title = '',
this.resource.type = '',
this.resource.desc = '',
this.resource.url = ''
console.log("Saving resource data...")
return firebase.database().ref().update(updates);
}
}
}
QuizBuilder.vue
export default {
name: "quiz-builder",
data () {
return {
questions: [createNewQuestion()],
showQuestions: false
}
},
methods: {
addQuestion () {
this.questions.push(createNewQuestion())
},
addOption (question) {
question.options.push(createNewOption())
},
saveToFirebase (e) {
e.preventDefault();
var questions = this.questions;
this.firebaseRef = db.ref('a/path/here'); // /resources/ + that resources id + /quiz/
this.firebaseRef.push({ // Should I use set or push here?
questions
})
console.log('Saving quiz data...')
}
}
}
The answer depends on how the transition between the components/pages are made.
If you're building a single page app with vue-router or something, then the transition is replacing the former component with the latter, which all happens on the index.html, with no request sent(simplest situation). To still keep the generated key within our grasp after the first component is gone, you need to save it on a common parent of the two components. To be specific, add a key in the parent's data, and let the ResourceInfo emit a custom event with the generated key to notify the parent to set its key. See http://vuejs.org/guide/components.html#Using-v-on-with-Custom-Events .
If you refreshes the page when jumping from ResourceInfo to to Quiz, with server-side rendering (which should be really rare, since it requires more effort compared to the single-page way, and has an inferior performance), then it's irrelavent to vue and rather simple: redirect the user to Quiz after ResourceInfo is saved, with the key as a url param.
Edit upon OP's using store.js:
Just store the key in LocalStorage(store.js) and retrive it from another component should work since LocalStorage is available globally and even across pages/sessions.
Some thought: main.js just be the parent is in some sense right. There's no real parent vue component here, but our main.js is evaled by the browser in the global scope, so it's true that main.js is the root entry of our app, aka parent.
A while ago we started with our first web-based single page application. We started with React and Flux (Facebook implementation), but recently switched to Reflux for the Flux implementation. While the Fb Flux implementation knows the concept of 'named' store events, this concept is not available in Reflux. As a result, every component that listens to a store always reacts on all events. Below there is reference image that displays the complexity of our user interface.
As you can see we have a lot of repeating components (multiple tabs with the same sort of info, multiple components with customer details and multiple reapeating lists (even nested) with details.
This application is a new front-end (UI) for an existing .Net application that maintains all te data (opened invoices etc.). The web application can communicate with this back-end by use of web services (MVC web API 2).
Given the user interface as shown in the picture, I would like to know whether I could better go for a store per invoice that holds all the invoice data (with an aggregated store that references them all), or for a store per invoice that holds only the invoice header info and some other stores that hold the information for each of the detail panes.
Thanks in advance for your reactions and suggestions!
edit: I misread your question, seems like you wanted a Reflux answer while I only gave a React answer. I will keep this up anyway for future reference.
The way I would do it is a single store that stores an array of invoices. So I would make my server API output something like this:
[
{
invoice_no: 1,
delivery: {
// delivery details
},
customer: {
// customer details
}
products: [
{
product_no: 1,
product_details: {
...
}
},
{
product_no: 5,
product_details: {
...
}
}
]
},
{
invoice_no: 2,
...
}
]
And then store that in a <App> component's state. My React code would look roughly like this:
var App = React.createClass({
getInitialState: function() {
return { invoices: [] };
},
onComponentDidMount: function() {
// make an ajax call to server to get
// invoices data. Then on success:
// this.setState({ invoices: data });
},
render: function() {
<div>
<InvoiceTabs invoices={this.state.invoices} />
</div>
}
});
// Everything here on out is just using props, so it's immutable data.
var InvoiceTabs = React.createClass({
render: function() {
var InvoiceComponents = this.props.invoices.map(function(invoice) {
return (
<Invoice invoice={invoice} />
);
});
}
});
var Invoice = React.createClass({
render: function() {
var invoice = this.props.invoice;
return (
<li>
<ProductsList products={invoice.products} />
<DeliveryInfo delivery={invoice.delivery} />
<InvoiceCalculator />
</li>
)
}
});
// various other components
Backbone routing allows us to route to different pages.
var Workspace = Backbone.Router.extend({
routes: {
"help": "help", // #help
"search/:query": "search", // #search/kiwis
"search/:query/p:page": "search" // #search/kiwis/p7
},
help: function() {
...
},
search: function(query, page) {
...
}
});
My question is instead of writing different functions for different routes, why not write a single function for all the routes and use a switch statement to determine the exact route and performing tasks based on the route.
It would look something like this.
var Workspace = Backbone.Router.extend({
routes: {
"help": "main", // #help
"search/:query": "main", // #search/kiwis
"search/:query/p:page": "main" // #search/kiwis/p7
},
main: function() {
...
switch(){
case("help") : ...;
case("search") : ...;
}
}
});
I don't know the exact implementation. I just gave a brief idea. Is this possible in Backbone routing?
Because that will lead to a nightmare hell as soon as you have more than 2 o 3 routes/functions, or you need anything more that 2 lines to setup the data and views for each route.
Also, it's much much easier to test your route handlers if you can simply call one function.
If you need one function per your requirements, then what's wrong is your route definition! I assume you are modeling a single page with search functionality and pagination of those search results. Let's suppose that page is accesed with a url like "yourapp/#page":
Enter optional parameters my friend: :)
http://backbonejs.org/#Router-routes
var Workspace = Backbone.Router.extend({
routes: {
"page(/search/:query)(/:page)": "main"
},
main: function(query, page) {
if(query) {
//you're searching
if(page) {
//display specific page
}
else {
//show first results page
}
}
else {
//show you initial views/models
}
}
});
That route will handle: page, page/search/apples and page/search/apples/4