Implicit list of users in adminAuth

I'm looking into securing my company's Node-RED setup to only allow access to users in a specific GitLab group. I found a passport strategy for gitlab-oauth, which should work well enough with the strategy-type adminAuth setting. However, as it looks to me, I would still have to enter every member of the group individually in the users array, since the done function is supposed to check against it.

Does the done function have to be called from the verify-function, though, or could I skip this additional check after verification on the OAuth response by simply creating a user struct and calling the resolve function with the verified user directly? Or is the resolve function not available/applicable in this scope because of the callbackURI requirements (which are handled as part of the done function, I would guess) and I would have to write a custom authentication script with direct usage of passport?

All right, so I've tried a couple things myself so far, and think I have a bit of a better grip on the whole matter now. I manage to authenticate using a gitlab strategy, but I still need to have an entry in the users list. My settings so far:

adminAuth: {
          type:"strategy",
          strategy: {
              name:"gitlab",
              label: 'Sign-in via GitLab',
              icon:"fa-gitlab",
              strategy: require('passport-gitlab2').Strategy,
              options: {
                  clientID: GITLAB-CLIENT-ID,
                  clientSecret: TOP-SECRET-SECRET,
                  gitlabURL : "https://gitlab.com",
                  callbackURL: "http://localhost:1880/auth/strategy/callback",
                  verify: function(token, tokenSecret, profile, done) {
                      for (var i=0; i < profile.emails.length; ++i) {
                          if(profile.emails[i].value.search("@my-company\\.com") >= 0) {
                              return done(null, { username: profile.username });
                          }
                      };
                  }
              }
          },
          users: [{ username: "stgv", permissions: "*" }]
    },

Still, is there a way to do this log-on without having to hard-code all usernames with permission in the users array? Anything that can be passed to the done() function (or be used instead of it)?

Hi @stgv

Node-RED only uses the users property of adminAuth in order to retrieve an already authenticated user's permissions.

If all of your users have the same permission, you can replace your users array with a function:

users: function(user) {
    return Promise.resolve({ username: user, permissions: "*" });
}

Oh, nice! Didn't think of putting that function there directly - but it makes a lot of sense. Thanks a lot!

What if I want to give different permissions based on a group the user is on?

You still return the user's permissions - but you look them up from the group first. How you do that will depend entirely on how you choose to implement it.

1 Like

by the end of the group checking I'm calling done function like this

done(null, { username: { user: myuser, permissions: corret_permission }})

because it seems the function on users entry only gets what is set to username (please correct me if I'm wrong)

and my users entry is kinda like this

users: function(username) {
	user = username.user
	perm = username.permissions
	
    return Promise.resolve({ 'username': user, 'permissions': perm });
}

it seems that some loop happens on this entry users, if I console.log() user and perm, first time it will print exactly what I set on done, but in the next run of this loop both user and perm are set to undefined.

I'm no developer and I'm pretty sure I'm probably doing something wrong here, can you help?

Sorry for the bump, but I could really use some help here :confused:

Hi @marcus

without seeing those lines of code in context, I'm not clear where you are using them or what they are doing - in particular, that done() statement.

The username passed to the users function should be just the String username it wants you to return the permissions for. It shouldn't ever be an object like you are trying to use.

You mean I need to hard code the permission on users function? I don't understand how to specify different permission to different users, can you just show me an example?

No, you don't need to hardcode permissions in the users function. The users function can return a promise, which means you can do whatever work you need to lookup the permissions for a given username.

users: function(username) {
       return new Promise(function(resolve) {
           // Do whatever work is needed to check username
           // is a valid and lookup their permissions
           // ...
           // ...
           // get 'userPermissions' from somewhere
           if (valid) {
               // Resolve with the user object. It must contain
               // properties 'username' and 'permissions'
               var user = { username: "admin", permissions: userPermissions };
               resolve(user);
           } else {
               // Resolve with null to indicate this user does not exist
               resolve(null);
           }
       });
   },
1 Like

TL;DR

The only way to identify a valid user is using the username?

Let me explain my scenario.

I'm trying to setup OAuth2 authentication agains a working on premise CAS like this:

    adminAuth: {
        type:"strategy",
        strategy: {
            name: "oauth2",
            label: 'Sign in SSO',
            icon:"fa-users",
            strategy: require('passport-oauth2').Strategy,
            options: {
                authorizationURL: 'https://xxxxxx/cas/oauth2.0/authorize',
                tokenURL: 'https://xxxxxx/cas/oauth2.0/accessToken',
                clientID: "xxxxxxxxxxxxxxx",
                clientSecret: "xxxxxxxxxxxx",
                callbackURL: "http://localhost/auth/strategy/callback",
                verify: function(token, tokenSecret, profile, done) {
                  done(null, profile)
                }
            }
        },

problem is: the profile is never returned, I've tried everything but it always returns an empty hash. It returns access token though, and with this I can access /profile which will return information regarding an authenticated user (e.g, it's AD groups) based on its access_token. With that information I will setup it's credentials, either * or read.

Before you said I need to do this on the Users function, I was doing it on the verify, I can get the groups and the correct permissions there, and the problem was that I could never "send" this to the user function.

I'm now trying hit /profile directly on the user function, but to do that I need that token that I received at the authentication.

I even tried something like this on the verify function

verify: function(token, tokenSecret, profile, done) {
  profile.token = token
  done(null, profile)
}

and set users function like this:

users: function(profile) {
  return new Promise(function(resolve, profile) {
    const token = profile.token
    const https = require('https');
    console.log(token)
    https.get("https://xxxxxxxxxxxxxx/cas/oauth2.0/profile?access_token=" + token, (res) => {
      var bodyChunks = [];
      res.on('data', function(chunk) {
        bodyChunks.push(chunk);
      }).on('end', function() {
        var body = Buffer.concat(bodyChunks);
        var data = JSON.parse(body).attributes
        console.log(data)
        groups = data.memberOf.map(function(val, index){return val.split(',')[0].replace('CN=', '') })
        
        if (groups.include('mygroup')) {
          user = {'username': data.cn, 'permissions': '*'}
        } else {
          user = {'username': data.cn, 'permissions': 'read'}
        }
        
        resolve(user);
      })
    })
 });
}

I know the code is pretty ugly, but the real problem here is: token comes as undefined.

So, if I need to do whatever work is needed to validate the user on the users function how am I supposed to send the token? Or the only way to identify a valid user is using the username?

Sorry the long reply, let me know if I should open an different topic.

Best regards,
Marcus Castro

In your verify function of the oauth strategy, you could look up the user's permissions and cache them locally so you can return them in the later calls to the users function.

1 Like

Thanks very much knolleary!

Sorry to re-open that thread but it is the closest answer I found so far.
I am stuck at the same point, I don't understand how to pass the profile to the users section to check the permissions defined in the access token scope and map it to allow a read only or full access depending on the user scope permissions (defined in keycloak).
Otherwise I can from the verify section read the necessary information to determine which level of permissions I should apply.

I don't understand how to "cache them locally" so if some explanations can be provided here I would be thankful.

To "fix" that, I am using at the moment a global var defined outside of the module.exports scope that is accessible in both the verify and users section.
That said I am really not sure it is a clean or safe way to do it that way but it does the job so I can check the permissions from the access token in the users section either.