Prevent navigation to certain pages/routes - javascript

I have a Next.js (fix may not necessarily have anything to do with Next.js) app that I want to limit some navigation. This page in the Next.js docs mentions "you should guard against navigation to routes you do not want programmatically", but I'm unsure how to do this. Let's say I have the following pages:
/bikes
/boats
/cars
and I only want to allow a user to see /bikes. How would I be able to do this. I'm able to redirect from an undesired page to an acceptable page, but only after the undesired page loads. Is there a way to redirect before this (maybe by changing the URL as soon as it is changed)?

I appreciate that this is an old question, however, I wish I had known this answer to it earlier than I did, so I would like to answer it for the record.
Our next.js app had a relatively complex set of permissions associated with accessing each page (with the ability to access the page, or different data presented on it) based on the authentication status of a user as well as the authorization scopes granted to them.
The solution that we used was the to return a redirect from getServerSideProps(). Normally one would return something like the following:
const getServerSideProps = (context) => {
return {
props: {
myProp: 1
}
};
}
However, it is also possible to return a redirect. This redirect is performed on the server side prior to the page content being sent to the browser:
const getServerSideProps = (context) => {
return {
redirect: '/login'
};
}
This is relatively new functionality - it was only pulled in towards the end of last year, and there isn't much documentation for it anywhere, but it perfectly serves this use case.

Related

Spartacus SSR mode :After Applying AuthGuard on Component , login screen is displayed momentarily on refresh though user is already logged in

I am applying Spartacus AuthGuard for some component so that only logged in user can access them, if not then it will redirect Guest to the Login page. It is working fine without SSR mode but when I serve the application using SSR mode then on Refresh, for the logged-in user it redirects to the Login page for a fraction of a second before redirecting to the requisite page.
This problem persists even on Disable SSR mode for the component having AuthGuard.
If I remove the AuthGuard from the component then this problem does not happen.
AuthGuard Code :
canActivate(): Observable<boolean | UrlTree> {
return this.authService.isUserLoggedIn().pipe(
map((isLoggedIn) => {
if (!isLoggedIn) {
this.authRedirectrvice.reportAuthGuard();
return this.router.parseUrl(this.semanticPathService.get('login'));
}
return isLoggedIn;
})
);
}
I believe it is happening due to server-side rendering as it is never redirected from the browser side(I ensured it by debugging). So is there any solution to avoid redirection from the server-side for a logged user?
Thanks
Edit 2023-02-13
Not to spend unnecessary time on full server-side-rendering of a my-account/* (and similar auth-protected pages), you can return an immediate CSR fallback (with shell index.html) for certain URLs. You need to configure renderingStrategyResolver in server.ts, like in the following example:
// server.ts
const ALWAYS_CSR_PAGES: RegExp = /* your regex here */;
NgExpressEngineDecorator.get({
renderingStrategyResolver: (req: express.Request) =>
req.originalUrl.match(ALWAYS_CSR_PAGES)
? RenderingStrategy.ALWAYS_CSR
: RenderingStrategy.DEFAULT,
/*...*/
})
For more, see docs.
Original answer 2021-03-24
The UI flickering is unavoidable, becasue SSR doesn't know about the user context, by design. So in SSR you can't get the render of the logged in user. Only in CSR it's possible.
But here described the workaround, how to avoid displaying the Login Page UI for a fraction of time, but some custom, different UI instead:
https://github.com/SAP/spartacus/issues/12537

View source not displaying dynamic content

I am making very simple next js application, where everything is working fine but except the view source.
I am making a promise and there is a delay in retrieving content and after when those content loaded and if I view source (ctrl + u) in chrome, I couldn't get those dynamic content loaded in the source.
So it is reproduceable in the link,
Step 1) Just click on the codesandbox link: https://3re10.sse.codesandbox.io
Step 2) After that choose view source (ctrl + u), and it gives page like,
Here you could clearly see that there is no element with text My name is Jared and all other text which is intended to be there but it is not.
Only Loading... text is available in page source which comes on page load.
The entire application working code is available here: https://codesandbox.io/s/nextjs-typescript-template-u8evx
Please help me how could I reflect all the dynamic content in view source in Next Js application.
I could understand that this is due to behaviour of async .. But really I couldn't understand the way to overcome this and display the dynamic content once loaded.. Please help me, I am stuck with this for very long..
A big thanks in advance..
You're explicitly telling React to fetch a user on a client-side.
function Profile() {
const { data, revalidate } = useSWR("/api/user", fetch);
}
If you need to prerender a user info on the server you can do it with one of the following functions:
getStaticProps (Static Generation): Fetch data at build time
getServerSideProps (Server-side Rendering): Fetch data on each request.
As you fetching a user info, I assume that it should be requested on each request, so use getServerSideProps.
const URL = 'api/user/'
export default function Profile({ initialData }) {
const { data } = useSWR(URL, fetcher, { initialData })
return (
// render
)
}
export async function getServerSideProps() {
const data = await fetcher(URL)
return { props: { initialData: data } }
}
This way you would fetch a user info on the server and give it to React with first render. Also, you would have useSWR on a client side that will periodically revalidate data.
Suggested reading:
Data fetching
If you use nextjs you must run
yarn build
and
yarn export
then you have directory 'out' with your exported static content.
Because now your example is CSR (client side rendering)
Other answers already quite clear telling you the reason why your dynamic content doesn't appear on source code.
First, there are two kinds of rendering: server side and client side.
Server side / SSR is when your server render your app and then send it to the client (browser).
Client side / CSR is when your app reach the browser, it will be rendered again (but this time only render what is necessary if you have activated SSR, which NextJS has as default).
If you want your dynamic content to be appear at the source code, you should call your api on the server side like #Nikolai Kiselev has mentioned.
NextJS provides function getServerSideProps()(for the component level) to be used if developers want to fetch info on the server side.
If you put your profile() function as a page, you could also use getInitialProps() function to fetch your api from server side.
Please take a look on NextJS doc, they have given the examples you need.

Gating content with JavaScript (Netlify Identity): Content flashes in slow connections, how to only load it after log in check?

I am not really a web app developer and I would like to ask about best practices for gating website content.
I am preparing to deploy documentation created with mkdocs. It uses Netlify Identity because with that Github auth is available without any coding.
My current solution: I have added the Netlify Identity script in head and the login/logoff button via template addons in mkdocs, and then created a static document /login/ (that gets picked up automatically in mkdocs but does not get generated with template).
In the standard template there is a JS redirect to /login/ unless user is logged in:
if (window.netlifyIdentity) {
window.netlifyIdentity.on("init", user => {
if (!user) {
document.location.href = "/login/";
}
});
}
On the static page there is a redirect to / only just after user has logged in:
if (window.netlifyIdentity) {
window.netlifyIdentity.on("init", user => {
if (!user) {
window.netlifyIdentity.on("login", () => {
document.location.href = "/";
});
}
});
}
I hope this is a reasonable way to go about it. The docs do not store anything critical but I still wouldn't want that content exposed.
But I have noticed on slow connection the redirect takes a second or two so when a deep URL is accessed the content flashes on the screen before login.
What can be done to stop this and load the content only after the login check is performed?
This is not going to work as you desire and is not secure.
If I wanted to read your content without an account, I could simply disable JavaScript in my browser (a few mouse clicks) and your site would load, but the redirect would never run.
Regardless, with JavaScript enabled, the way it works is that the browser downloads the page, then downloads any resources (including scripts), and then finally runs any scripts. There is no way to change that. Of course, on a fast system, the user may not perceive a delay, as the delay is very short, but there is always a delay. That is how browsers work.
If you don't want your users to have access to the information until after they are logged in , then you must not send the information out until they are logged in. In other words, you need to configure your server to not send the page at all until it receives verification that the user has permission to receive that information. How you do that depends on which server you are using among other things, which would be the subject of a separate question.
I know this is a old post but you can use netlify functions combined with a netlify redirect file.
You would have to set a role of a user when signing up using the metadata, you could do this with a netlify function thats hooked into netlify identity, more here.
Create a function called identity-signup.js when a user signs up this function is automatically called.
exports.handler = async (event) => {
const { user } = JSON.parse(event.body)
// you could do something with the user here: eg console.log(user.email)
// or using stripe: const customer = await stripe.customers.create({ email: user.email });
return {
statusCode: 200,
body: JSON.stringify({
app_metadata: {
roles: ['free']
}
})
}
}
Once you have a role you can simply create a _redirects file like so:
/authedcontent/* 200! Role=free
/authedcontent/ / 404
Later down the line you can extend the netlify function to save the users detail in an external database or maybe setup a stripe subscription.
The only caveat is that this requires a paid netlify account.

Getting initial state using HTML5 history api

Everything I've been searching for is just a tutorial how to use pushState, replaceState, history.state, etc. Those concepts are simple but one thing I'm wondering how people solve is how to know what the initial state is.
Say you SPA is hosted at https://example.com/en-us/myapp/. Go there and your home page of the app is loaded, click around and it does a pushState to see you to https://example.com/en-us/myapp/get/users. Great, now you see a list of users and thanks to the history api, it wasn't an actual page load.
But now let's pretend a user had that https://example.com/en-us/myapp/get/users state bookmarked and the started the app off at this URL. Ok, so your server listens to that and serves up the app. My question is, how do you know that get/users is the current state and you need to show the associated view? Do you just know that your app is hosted at https://example.com/en-us/myapp/ and so you get whatever is after that to know?
Something like this:
function getState (uri) {
return uri.match(/^https:\/{2}(?:w{3}\.)?example.com\/en-us\/myapp\/?(.*)/i)[1];
}
var state = getState(location.href);
and if state is falsey then load the initial view, otherwise handle the state and show the list of users when state === 'get/users'?
Yes, that is quite right. However, you could try using location.pathname to fetch the state, so that your regex does not need to include the domain name.
For example:
function getState (uri){
var path = uri.split("myapp", 2)[1]; // This will split the pathname after 'myapp'
console.log(path) // Just for debugging purposes
// Now we can decide what to do with the path (i.e. "/get/users")
// For example, we can use a switch or a simple if statement
if (path === '/get/users'){
return true
} else {
return false
}
}
var state = getState(location.pathname);
That is just a simple example of a router. You can now try building your very own router for your SPA. Also, there are many libraries out there for you to use if you would like a different approach. You can take a look at these ones if you would like.
navigo
router.js
Also, if you are using a framework to build your SPA, they often have their own routing ability built in. These are just some of the many frameworks that have routers built in. (Sorry, I've <10 reputation so I'm not allowed more than two links).
Vue.js — vuejs.org/v2/guide/routing.html
Mithril.js — mithril.js.org/#routing
Ember.js — guides.emberjs.com/v2.13.0/routing/
Of course, it is ultimately your choice which to use. You could expand upon the example I've provided, by simply implementing a switch for different links/pages in your SPA. I wish you the best with your app!

Communicating with a web widget-Meteor, React, Node

I'm building a chat dashboard and widget with which a customer should be able to put the widget into their page. Some similar examples would be Intercom or Drift.
Currently, the "main" application is written in Meteor.js (it's front end is in React). I've written a <Widget /> component and thrown it inside a /widget directory. Inside this directory, I also have an index.jsx file, which simply contains the following:
import React from 'react';
import ......
ReactDOM.render(
<Widget/>,
document.getElementById('widget-target')
);
I then setup a webpack configuration with an entry point at index.jsx and when webpack is run spits out a bundle.js in a public directory.
This can then be included on another page by simply including a script and div:
<script src="http://localhost:3000/bundle.js" type="text/javascript"></script>
<div id="widget-target"></div>
A few questions:
What is wrong with this implementation? Are their any security issues to be aware of? Both the examples linked earlier seem make use of an iframe in one form or another.
What is the best way to communicate with my main meteor application? A REST API? Emit events with Socket.io? The widget is a chat widget, so I need to send messages back and forth.
How can I implement some sort of unique identifier/user auth for the user and the widget? Currently, the widget is precompiled.
1 What is wrong with this implementation? Are their any security issues to be aware of? Both the examples linked earlier seem make use of an iframe in one form or another.
As #JeremyK mentioned, you're safer within an iFrame. That being said, there's a middle route that many third parties (Facebook, GA, ...) are using, including Intercom:
ask users to integrate your bundled code within their webpage. It's then up to you to ensure you're not introducing a security vulnerability on their site. This code will do two things:
take care of setting up an iframe, where the main part of your service is going to happen. You can position it, style it etc. This ensure that all the logic happening in the iframe is safe and you're not exposed.
expose some API between your customer webpage and your iframe, using window messaging.
the main code (the iframe code) is then loaded by this first script asynchronously, and not included in it.
For instance Intercom ask customers to include some script on their page: https://developers.intercom.com/docs/single-page-app#section-step-1-include-intercom-js-library that's pretty small (https://js.intercomcdn.com/shim.d97a38b5.js). This loads extra code that sets the iFrame and expose their API that will make it easy to interact with the iFrame, like closing it, setting user properties etc.
2 What is the best way to communicate with my main meteor application? A REST API? Emit events with Socket.io? The widget is a chat widget, so I need to send messages back and forth.
You've three options:
Build your widget as an entire Meteor app. This will increase the size of the code that needs to be loaded. In exchange for the extra code, you can communicate with your backend through the Meteor API, like Meteor.call, get the reactivity of all data (for instance if you send a response to a user through your main Meteor application, the response would pop up on the client with no work to do as long as they are on the same database (no need to be on the same server)), and the optimistic UI. In short you've all what Meteor offers here, and it's probably going to be easier to integrate with your existing backend that I assume is Meteor.
Don't include Meteor. Since you're building a chat app, you'll probably need socket.io over a traditional REST API. For sure you can do a mix of both
Use Meteor DDP. (it's kind of like socket.io, but for Meteor. Meteor app use that for all requests to the server) This will include less things that the full Meteor and probably be easier to integrate to your Meteor backend than a REST API / socket.io, and will be some extra work over the full Meteor.
3 How can I implement some sort of unique identifier/user auth for the user and the widget?
This part should probably do some work on the customer website (vs in your iframe) so that you can set cookies on his page, and send that data to your iframe that's gonna talk to your server and identify the user. Wether you use artwells:accounts-guest (that's based on meteor:accounts-base) is going to depend on wether you decide to include Meteor in your iframe.
If you don't have Meteor in your iframe, you can do something like:
handle user creation yourself, by simply doing on your server
.
const token = createToken();
Users.insert({ tokens: [token] });
// send the token back to your iframe
// and set is as a cookie on your customer website
then for each call to your server, on your iframe:
.
let token;
const makeRequest = async (request) => {
token = token || getCookieFromCustomerWebsite();
// pass the token to your HTTP / socket.io / ... request.
// in the header of whatever
return await callServer(token, request);
};
in the server have a middleware that sets the user. Mine looks like:
.
const loginAs = (userId, cb) => {
DDP._CurrentInvocation.withValue(new DDPCommon.MethodInvocation({
isSimulation: false,
userId,
}), cb);
};
// my middleware that run on all API requests for a non Meteor client
export const identifyUserIfPossible = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return next();
}
const user = Users.findOne({ tokens: token });
if (!user) {
return next();
}
loginAs(user._id, () => {
next();
// Now Meteor.userId() === user._id from all calls made on that request
// So you can do Meteor.call('someMethod') as you'd do on a full Meteor stack
});
};
Asking your customers to embed your code like this doesn't follow the principles of Security by Design.
From their point of view, you are asking them to embed your prebundled code into their website, exposing their site up to any hidden security risks (inadvertent or deliberately malicious) that exist in your code which would have unrestricted access to their website's DOM, localstorage, etc.
This is why using an iframe is the prefered method to embed third party content in a website, as that content is sandboxed from the rest of it's host site.
Further, following the security principle of 'Least Privilege' they (with your guidance/examples) can set the sandbox attribute on the iframe, and explicitly lockdown via a whitelist the privileges the widget will have.
Loading your widget in an iframe will also give you more flexibility in how it communicates with your servers. This could now be a normal meteor client, using meteor's ddp to communicate with your servers. Your other suggestions are also possible.
User auth/identification depends on the details of your system. This could range from using Meteor Accounts which would give you either password or social auth solutions. Or you could try an anonymous accounts solution such as artwells:accounts-guest.
html5rocks article on sandboxed-iframes

Categories

Resources