I am creating a web redux-react app which will have a number of different permission levels. Many users may be interacting with one one piece of data but some may have limitations on what they can do.
To me, the obvious way to set permissions on interactions on the data (held behind the app server) would be to associate certain permissions with different redux actions. Then, when a user saves their state the client side app would bundle up the users action history and send it back to the server. These actions could then be applied to the data in the server and permissions could be checked, action by action, against a user jwt.
This would mean lots our reducer code could be used isomorphically on the server.
I cannot find any resources/disscussions on this. What is the normal way of handling complex permissions in a redux app? Having auth purely at the endpoint seems cumbersome , this would require rewriting a ton of new code that is already written in client side reducers. Is the any reason not to go ahead and create a reducer which checks auth on each action?
Points:
We must assume actions sent to the server are authenticated, but sent by users that do not have permission dispatch these actions
If the permissions have been checked and are inside the actions then the reducer can check permissions and be pure
I think it's not the responsibility of action creators to check the permissions but using a reducer and a selector is definitively the way to go. Here is one possible implementation.
The following component requires some ACL checks:
/**
* Display a user record.
*
* A deletion link is added if the logged user has sufficient permissions to
* delete the record.
*/
function UserRecord({ username, email, avatar, isGranted, deleteUser }) {
return (
<div>
<img src={avatar} />
<b>{username}</b>
{isGranted("DELETE_USER")
? <button onClick={deleteUser}>{"Delete"}</button>
: null
}
</div>
)
}
We need to connect it to our store to properly hydrate all props:
export default connect(
(state) => ({
isGranted: (perm) => state.loggedUser.permissions.has(perm),
}),
{deleteUser},
(stateProps, dispatchProps, ownProps) => ({
...stateProps,
...ownProps,
deleteUser: () => dispatchProps.deleteUser(ownProps.user)
})
)(UserRecord)
The first argument of connect will create isGranted for the logged user. This part could be done using reselect to improve performance.
The second argument will bind the actions. Nothing fancy here.
The third argument will merge all props and will pass them to the wrapped component. deleteUser is bound to the current record.
You can now use UserRecord without dealing with ACL checks since it will auto-update depending on what is stored in loggedUser.
<UserRecord user={someUser} />
In order to get the above example work you need to store the logged user in Redux's store as loggedUser. You don't need check ACL on actions since the UI won't trigger them if current user lacks of permissions. Moreover, ACL have to be checked server-side.
You can set up an helper function that would be built into actions for checking user rights (locally or remotely) where you would also provide with a callback action creator on error. Of course redux-thunk or similar would be needed so you can dispatch actions from other actions.
The key rule you should observe here is:
Reducers are pure functions.
Action creators can be impure. That means reducers always return the same value given the same arguments. Checking for ACL rights in reducer will violate that rule.
Say let's say you need to fetch the list of contacts. Your action is REQUEST_CONTACTS. The action creator would first dispatch something like:
// ACL test function
function canAccessContacts(dispatch) {
if (user !== 'cool') {
dispatch({type: 'ACCESS_DENIED'});
return false;
}
}
// Action creator
function fetchContacts() {
return (dispatch) => {
if (!canAccessContacts(dispatch)) {
return false;
}
// your logic for retrieving contacts goes here
dispatch({
type: 'RECEIVE_CONTACTS',
data: your_contacts_data_here
});
};
}
RECEIVE_CONTACTS will be fired once you have data back. Time between REQUEST_CONTACTS and RECEIVE_CONTACTS (which is likely an async call) is ian opportunity to show your loading indicator.
Of course, this is a very raw example, but it should get you going.
Related
I'm coming across code where there's an additional parameter for express route handlers beyond the path and the callback.
For example:
app.get('/path', authUser, (req,res) => {
...
}
where authUser is a function. Can anybody explain the role of such functions? To date I've only seen express routes with the two parameters.
These are middleware, which are functions that run before your route handler (your third function). You can have as many as these as possible. They basically modify/perform an action based on the request, or maybe manipulate the response.
So this is likely middleware that checks the request for an authenticated user, and will return a 401/403 if not authenticated, meaning that you can write your route handler under the assumption that you are authenticated.
For more info, check out this article
The family of methods such as app.get(), app.post(), app.use(), accept any number of request handlers as successive arguments:
app.get('/path', fn1, fn2, fn3, fn4);
These requests handlers can be used for a variety of purposes. Often times, they are what is generally referred to as middleware which prepares a request for further processing or in some cases blocks a request from further processing. But, they can also be normal request handlers too, they are not just limited to what most people call middleware.
In your specific case:
app.get('/path', authUser, (req,res) => {
...
}
We can guess by the name that authUser is checking to see if the user making the request has been properly authenticated and, if not, then an error status is probably sent as the response and the next request handler in the chain is not called. Or conversely, because authUser has already filtered out any unauthenticated users, the request handler here at the end of the chain can safely assume that the user is already authenticated. So, this particular use is a means of applying middleware to one specific route with no consequences for any other routes defined later.
But, I want to emphasize that this is a generic mechanism that is not limited to just what is classically described as middleware. It can also be used for request handlers that might execute conditionally based on other parameters. For example here's one such example where the first request handler looks the URL and decides to handle the whole request itself based on what it sees in the URL and, if not, passes it on to the next handler:
app.get('/book/:id', (req, res) => {
// check if id is purely numeric
if (/^\d+$/.test(req.params.id)) {
// this is a request for a book by numeric id
// look up the book numeric id in the database and return the meta
// data about this book
} else {
// not a book id, let next request handler have the request
next();
}
}, (req, res) => {
// must be a book title lookup
// look up the book in the title database and return the id
});
Suppose I module export "/route1" in route1.js, how would I pass parameters into this route from "/route2" defined in route2.js?
route1.js
module.exports = (app) => {
app.post('/route1', (req,res)=>{
console.log(req.body);
});
}
route2.js
const express = require('express');
const app = express();
//import route1 from route1.js
const r1 = require('./route1')(app);
app.post('/route2', (req, res) => {
//how to pass parameters?
app.use(???, r1) ?
})
In short, route 1 output depends on the input from route 2.
You don't pass parameters from one route to another. Each route is a separate client request. http, by itself, is stateless where each request stands on its own.
If you describe what the actual real-world problem you're trying to solve is, we can help you with some of the various tools there are to use for managing state from one request to the next in http servers. But, we really need to know what the REAL world problem is to know what best to suggest.
The general tools available are:
Set a cookie as part the first response with some data in the cookie. On the next request sent from that client, the prior cookie will be sent with it so the server can see what that data is.
Create a server-side session object (using express-session, probably) and set some data in the session object. In the 2nd request, you can then access the session object to get that previously set data.
Return the data to the client in the first request and have the client send that data back in the 2nd request either in query string or form fields or custom headers. This would be the truly stateless way of doing things on the server. Any required state is kept in the client.
Which option results in the best design depends entirely upon what problem you're actually trying to solve.
FYI, you NEVER embed one route in another like you showed in your question:
app.post('/route2', (req, res) => {
//how to pass parameters?
app.use(???, r1) ?
})
What that would do is to install a new permanent copy of app.use() that's in force for all incoming requests every time your app.post() route was hit. They would accumlate forever.
I'm currently working on authentication using Firebase's signInWithEmailAndPassword().
I want to check if a user logins in for the first time and recently found isNewUser.
The problem is, it always returns false because signInWithEmailAndPassword() runs in first place, making isNewUser false automatically.
Note) I don't use createuserwithemailandpassword() for registration. I manually make an account and provide it to the user.
Any suggestion?
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(user => {
if (user.additionalUserInfo.isNewUser) {
// Want to redirect to Terms of service
// But it always returns false
}})
When you create a new user account with createUserWithEmailAndPassword () that user is immediately signed in. That is the only time when additionalUserInfo.isNewUser will be true.
I just noticed that you create the account out-of-bounds without calling the API. I'd still expect this same reason to be true though. You should be able to verify that through UserMetadata, which you can get from the User object.
I want to add logic to the pre:signin and post:signin hooks, but can't find where they are defined. In admin/server/api/session/signin.js, I find the callHook()s for both, but can't find the actual hooks. Help greatly appreciated!
KeystoneJS is using grappling-hook . and thats why you can use callHook.
in node_modules/keystone/index.js you can see this code
var grappling = require('grappling-hook');
grappling.mixin(this).allowHooks('pre:static', 'pre:bodyparser', 'pre:session', 'pre:routes', 'pre:render', 'updates', 'signout', 'signin', 'pre:logger');
I am not sure but you are not finding the actual hooks because they really don't exist.
The methods are defined in keystone and grapple simply makes them hoockable. e.g signin is the method defined in keystone instance and grapple made it hoockable.
And here is the way you will just tell what to do after signing is completed.
keystone.callHook(user, 'post:signin', function (err) {
if (err) return res.json({ error: 'post:signin error', detail: err });
res.json({ success: true, user: user });
});
So in layman term you r saying.. Hay keystone, call my function after This user is signed in. and for that there is really no code inside keystone.
MY knowledge is limited here how the user is passed and how it happens that only when this user logged in. I think, we still need some experts light here.
I had a use-case that required a function be called every time a specific model was added to the database. I found out that you can use the Mongoose middleware specifically the post middleware to achieve such results. So in my case I had a Product schema in which I wanted a function to run everytime a new product was added or updated. It was as easy as adding the following:
keystone.lists.Product.schema.post('save',function(){
console.log('called after new item saved');});
I've recently updated some parts of the code and want to check if they play well with production database, which has different data sets for different users. But I can only access the application as my own user.
How to see the Meteor application through the eyes of another user?
UPDATE: The best way to do this is to use a method
Server side
Meteor.methods({
logmein: function(user_id_to_log_in_as) {
this.setUserId(user_id_to_log_in_as);
}
}):
Client side
Meteor.call("logmein", "<some user_id of who you want to be>");
This is kept simple for sake of clarity, feel free to place in your own security measures.
I wrote a blog post about it. But here are the details:
On the server. Add a method that only an admin can call that would change the currently logged user programatically:
Meteor.methods(
"switchUser": (username) ->
user = Meteor.users.findOne("username": username)
if user
idUser = user["_id"]
this.setUserId(idUser)
return idUser
)
On the client. Call this method with the desired username and override the user on the client as well:
Meteor.call("switchUser", "usernameNew", function(idUser) {
Meteor.userId = function() { return idUser;};
});
Refresh client to undo.
This may not be a very elegant solution but it does the trick.
Slightly updated answer from the accepted to log the client in as new user as well as on the server.
logmein: function(user_id_to_log_in_as) {
if (Meteor.isServer) {
this.setUserId(user_id_to_log_in_as);
}
if (Meteor.isClient) {
Meteor.connection.setUserId(user_id_to_log_in_as);
}
},
More info here: http://docs.meteor.com/api/methods.html#DDPCommon-MethodInvocation-setUserId