LDAP - Find user by name only - javascript

I am not too familiar with LDAP, however I am working on authentication in a Node.js app, and the user credentials for the web app is going to be gotten from the organization's Windows domain.
I have LDAP lookups working (using the Passport.js Node module), however to make it work, I have to put the user's full-fledged DN into Node. For example, let's say:
My FQDN is mydomain.private.net.
My users are stored in an organizational unit, let's say staff.
Now, if I want to lookup user joe, I have to put this string into Node:
var username = 'CN=joe,OU=staff,DC=mydomain,DC=private,DC=net';
do i really have to keep track of all that?
What if my users are in two different organizational units? The client-side browser doesn't know that! It just knows:
username = 'joe';
password = 'xxxxx';
What if you try to log on as administrator? Administrators are always in a totally different OU by default.
Is there a way to reference an LDAP object by just the name and nothing else?

This is a general LDAP problem. You need to get a unique identifier from the user, and then look for it.
Typically this is what the uid attribute is used for. Active Directory may or may not have that populated, and generally relies on sAMAccountName which must be unique within the domain.
So you need a two step process.
1) Query for uid=joe or samAccountName=joe
2) Use the results to test a bind or password compare.
You would then use the DC=mydomain,DC=private,DC=net value as the root to search from.

(answer to my own question)
geoffc's answer was correct, and this is the working solution adapted to my Node.js app using the activedirectory npm module:
// First search for the user itself in the domain.
// If successfully found, the findUser function
// will return the full DN string, which is
// subsequently used to properly query and authenticate
// the user.
var AD = self.ADs[domain];
AD.findUser(username, function(err, user) {
if (err) {
cb(false, 'AD error on findUser', err);
return;
}
if (!user) {
cb(false, 'User does not exist', void 0);
} else {
username = user.dn;
AD.authenticate(username, password, function(err, authenticated) {
if (authenticated == false) {
cb(false, err, void 0);
return;
} else {
cb(true, 'Authenticated', void 0);
}
});
}
});

Related

Check if Firebase Facebook user exists without creating a user starting from anonymous user

In Firebase I need to check if a Facebook user exists without creating the user. Initially the user is anonymous, and they try to login with Facebook. I want this to fail if the Facebook account is not already linked to a user in my system. It won't be linked to the current user because they are anonymous,
If I use Auth.signInAndRetrieveDataWithCredential I expected a "auth/user-not-found" error, but instead the user is simply created. Is this a bug or expected?
let credential = firebase.auth.FacebookAuthProvider.credential(
event.authResponse.accessToken)
firebase.auth().signInAndRetrieveDataWithCredential(credential).then( (userCredential) => {
let user = userCredential.user
app.debug("DEBUG: Existing user signed in:"+user.uid)
this.loginSuccess(user)
}).catch( (err) => {
app.error("ERROR re-signing in:"+err.code)
$("#login_status_msg").text(err)
})
If I use User.reauthenticateAndRetrieveDataWithCredential instead I get the error "auth/user-mismatch" which makes sense because user is currently anonymous. However, I was expecting "auth/user-not-found" may be thrown instead if the credential doesn't exist, but that doesn't happen.
I don't see a way to take my anonymous user, have them login with Facebook and then see if another user is already linked to that Facebook credential without creating the user if it doesn't exist.
If you're wondering why? My scenario is:
The system allows anonymous users
A user logs in, then converts to a logged in user by registering with Facebook.
App uninstall
App reinstall
User starts up the app and is initially anonymous.
They try and login with Facebook again. At this point I want to stop them from creating a user if they don't have one already. If they have a user ID already, the code works fine and changes their anonymous account ID to the original user ID which is good.
I found a solution! It wasn't too hard to implement, but it does seem hacky.
So we know that when using signInAndRetrieveDataWithCredential(cred) for facebook login, the account is created even if it does not exist yet. To solve this, we need to make sure that we handle the following three things:
Detect if the account is new
Delete the current account that was created by firebase
Throw an error to get out of the current flow and return to wherever you were before.
I just implemented and tested this solution, and it seems to work great:
// ... do your stuff to do fb login, get credential, etc:
const userInfo = await firebase.auth().signInAndRetrieveDataWithCredential(credential)
// userInfo includes a property to check if the account is new:
const isNewUser = _.get(userInfo, 'additionalUserInfo.isNewUser', true)
// FIRST, delete the account we just made.
// SECOND, throw an error (or otherwise escape the current context.
if (isNewUser) {
firebase.auth().currentUser.delete()
throw new Error('Couldn\'t find an existing account.')
}
// If the user already exists, just handle normal login
return userInfo.user
The reason I did this was to ensure that users had to go through the "create account flow" in my app. Your case would be really easy to implement as well, something like the following:
let credential = firebase.auth.FacebookAuthProvider.credential(event.authResponse.accessToken)
firebase.auth().signInAndRetrieveDataWithCredential(credential)
.then(userCredential => {
const isNewUser = userCredential.additionalUserInfo.isNewUser
if (isNewUser) {
firebase.auth().currentUser.delete()
// The following error will be handled in your catch statement
throw new Error("Couldn't find an existing account.")
}
// Otherwise, handle login normally:
const user = userCredential.user
app.debug("DEBUG: Existing user signed in:"+user.uid)
this.loginSuccess(user)
}).catch( (err) => {
app.error("ERROR re-signing in:"+err.code)
$("#login_status_msg").text(err)
})
You can use linkAndRetrieveDataWithCredential:
let credential = firebase.auth.FacebookAuthProvider.credential(
event.authResponse.accessToken);
anonymousUser.linkAndRetrieveDataWithCredential(credential).then( (userCredential) => {
// Firebase Auth only allows linking a credential if it is not
// already linked to another account.
// Now the anonymous account is upgraded to a permanent Facebook account.
}).catch( (err) => {
// Check for code: auth/credential-already-in-use
// When this error is returned, it means the credential is already
// used by another account.
})
You can use the method fetchSignInMethodsForEmail to check if an specific email is already associated to an specific provider or not. Doing this you will be able to check if one if the SighInMethods of the email associated to your user contains Facebook.com or not.
I show you below an example about how I manage this cases on my application. I'm using an RxJavaWrapper on my code, but you will understand the point of how to manage it:
RxFirebaseAuth.fetchSignInMethodsForEmail(authInstance, email)
.flatMap { providerResult ->
if (!providerResult.signInMethods!!.contains(credential.provider)) {
return#flatMap Maybe.error<AuthResult>(ProviderNotLinkedException(credential.provider))
} else {
return#flatMap RxFirebaseAuth.signInWithCredential(authInstance, credential)
}
}
.subscribe({ authResult ->
//Manage success
}, { error ->
//Manage error
})
First I check the providers associated to the email of the user(You can retrieve it from the provider)
If the list of SignInMethods contains my credential provider, I throw an error, if not, I call my signInWithCredential method to create the user.
Continue your workflow.
What I did to solve this problem without relying on the call to linkAndRetrieveDataWithCredential to fail and using the catch block to sign in the already existing user is to save the userID field that getCurrentAccessToken returns.
const { userID } = data;
this.props.setFacebookId(userID); // saves the userID on the server
I can later check if this userID already exists next time the user signs up with facebook.

Administrator Views for a Firebase Web Application: How To

My Firebase web app requires administrator access, i.e., the UI should show a few things only for admins (an 'administrator' section). I came up with the below as a means to authorize the UI to display the admin section for valid admins only. My question is, good or bad? Is this a sound means of authorizing? ...so many ways to do this. This particular way requires me to configure admins in the security rules (vs in a node/tree in a db/firestore)
My idea is that if the .get() fails due to unauthorized access, I tell my app logic the user is not an admin, if the .get() succeeds my logic shows the 'admin' sections. Of course, the 'sections' are just HTML skeletons/empty elements populated by the database so even if the end user hacks the JS/logic, no real data will be there - only the empty 'admin section' framework.
function isAdmin(){
return new Promise(function(resolve, reject){
var docRef = firebase.firestore().collection("authorize").doc("admin");
docRef.get().then(function(result) {
if (result) {
resolve (true);
}
}).catch(function(error) {
resolve (false);
});
});
}
The firestore rule specifies the 'admins' by UID.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid == "9mB3UxxxxxxxxxxxxxxxxxxCk1";
}
}
}
You're storing the role of each user in the database, and then looking it up in the client to update its UI. This used to be the idiomatic way for a long time on realtime database, and it still works on Firestore.
The only thing I'd change is to have the rules also read from /authorize/admin, instead of hard-coding the UID in them. That way you only have the UID in one place, instead of having it in both the rules and the document.
But you may also want to consider an alternative: set a custom claim on your admin user, that you can then read in both the server-side security rules (to enforce authorized access) and the front-end (to optimize the UI).
To set a custom claim you use the Firebase Admin SDK. You can do this on a custom server, in Cloud Functions, but in your scenario it may be simpler to just run it from your development machine.
Detailed How To: Firebase has what's called Custom Claims for this functionality as detailed in their Control Access with Custom Claims and Security Rules. Basically, you stand up a separate node server, install the Firebase AdminSDK:
npm install firebase-admin --save
Generate/Download a Private Key from the Service Accounts tab in the Firebase Console and put that on your node server. Then simply create a bare bones node app to assign Custom Claims against each UID (user) that you wish. Something like below worked for me:
var admin = require('firebase-admin');
var serviceAccount = require("./the-key-you-generated-and-downloaded.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://xxxxxxxxxxxxxxxxxxxx.firebaseio.com"
});
admin.auth().setCustomUserClaims("whatever-uid-you-want-to-assign-claim-to", {admin: true}).then(() => {
console.log("Custom Claim Added to UID. You can stop this app now.");
});
That's it. You can now verify if the custom claim is applied by logging out of your app (if you were previously logged in) and logging back in after you update your web app's .onAuthStateChanged method:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
firebase.auth().currentUser.getIdToken()
.then((idToken) => {
// Parse the ID token.
const payload = JSON.parse(window.atob(idToken.split('.')[1]));
// Confirm the user is an Admin.
if (!!payload['admin']) {
//showAdminUI();
console.log("we ARE an admin");
}
else {
console.log("we ARE NOT an admin");
}
})
.catch((error) => {
console.log(error);
});
}
else {
//USER IS NOT SIGNED IN
}
});

Creating a user session - NODE js

I am new to node js & javascript in general. I have the below piece of code that will handle a login. I have a MYSQL database with a customer table. When the customer enters their username and password, it checks does it exist in the database. This part is working.
I now want to enhance this feature so that it will take the username and create some sort of a session variable, which can be used across the application. I am new to JS so I am not yet sure which inbuilt facilities already exist, or best practice around sessions.
I want to be able to use this session variable across the application, and for subsequent logout facility.
Can someone advise me on this, or point me in the right direction? Thanks.
case "/login":
var body = '';
console.log("user Login ");
request.on('data', function (data) {
body += data;
});
request.on('end', function () {
var obj = JSON.parse(body);
console.log(JSON.stringify(obj, null, 2));
var query = "SELECT * FROM Customer where name='"+obj.name+"'";
response.writeHead(200, {
'Access-Control-Allow-Origin': '*'
});
db.query(
query,
[],
function(err, rows) {
if (err) {
response.end('{"error": "1"}');
throw err;
}
if (rows!=null && rows.length>0) {
console.log(" user in database" );
theuserid = rows[0].customerID;
var obj = {
id: theuserid
}
response.end(JSON.stringify(obj));
}
else{
response.end('{"error": "1"}');
console.log(" user not in database");
}
}
);
});
}
There can be multiple ways of implementing a user session.
One, you could use a browser cookie, it comes with many pros and cons and you should read about it a bit to see how its managed. This would also depend on the server you are using (express, hapi, etc).
Two, you can set a JWT token on the backend, and include it in the header of the response, then you can either use your application state or the local storage of the browser to save that token on the UI. Any such follow up requests requiring authentication should contain this auth token as a header for verification.
For more clarity, you can look into related libraries (such as passport), which make this task a lot easier.
PS: If you choose cookies, please make sure the business is going to allow it or not as the end-users do not like being tracked always. :)

How to deny access for fetch operations

I'm trying to create an access control system at the document level in Meteor, and I seem to be missing how to prevent users from fetching documents.
I've read the documentation around collection.allow and collection.deny. Via these objects we can control "who" can update, remove and insert. The problem is that Meteor doesn't seem to supply similar functionality for fetch operations. What is proper way to deny unauthorized users from reading documents?
Extra requirement: this needs to happen server side so we don't leak documents to unauthorized users over the network.
There is no way to deny reads to collection data once it has arrived on the client. In theory, there's no way to actually enforce anything on the client because the user could modify the code. You can, however, enforce which documents get published.
A publish function can handle authorization rules of arbirtary complexity. Here's a simple example where we want to publish documents from the Messages collection only to users who are members of the given group:
Meteor.publish('messagesForGroup', function(groupId) {
check(groupId, String);
var group = Groups.findOne(groupId);
// make sure we have a valid group
if (!group)
throw new Meteor.Error(404, 'Group not found');
// make sure the user is a member
if (!_.contains(group.members, this.userId))
throw new Meteor.Error(403, 'You are not a member of the group');
return Messages.find({groupId: groupId});
});
1) Remove autopublish package from your app.
2) Create your own publish and deny access to unauthorized users
Meteor.publish("userData", function () {
if (this.userId) { // Check user authorized
return MyCollection.find(); // Share data
} else {
this.ready(); // Share nothing
}
});

meteor-useraccounts and alanning meteor-roles - Check role on sign-in

Is it possible to check users role on Sign in, and than if user is in role "admin" to display one page, and if is it in role "basic-user" to display another page ( go to another route).
Lets have a look at the Routes section of the documentation for the useraccounts:iron-routing package.
this should solve your problem
AccountsTemplates.configureRoute('signIn', {
redirect: function(){
var user = Meteor.user();
if (user && Roles.userIsInRole(user, ['admin'])) {
Router.go('admin');
}
else {
Router.go('home');
}
}
});
Be careful to check you can access the user roles field from the client side: lets check the allanning:roles official documentation
To define a default role for a user I use this:
// server
Accounts.onLogin(function(user) {
var user = user.user;
var defaultRole = ['student'];
if (!user.roles){
Roles.addUsersToRoles(user, defaultRole)
};
})
I'm using meteor-useraccounts and alanning meteor-roles packages and that work fine for me.
If I'm not outdated (and a look at http://docs.meteor.com/#/full/meteor_users suggests I'm not) there is no built in way for user roles. There should be some extensions for that task with which you could go and depending on what you choose you would have to check their documentation.
However it's not very hard to implement a own simple roles logic in Meteor:
First in your Accounts.onCreateUser function give your users object a new attribute role and assign them to a default role. If you don't have a Accounts.onCreateUser yet create one server side. It could look like something like this:
Accounts.onCreateUser(function(options, user) {
// Add an user roles array
user.roles = ["default-user"];
if (options.profile)
user.profile = options.profile;
return user;
}
Next you would need to implement some logic to add "admin" or whatever you like for trusted users to their roles array. That's up to you and for the beginning if you don't have dozens of admins you could also choose to do that manually in your MongoDB.
Now make sure you publish the new attribute of your user object to the currently logged in user. To do so make use of Meteor.publish with null as first parameter to address the current user like so:
Meteor.publish(null, function () {
return Meteor.users.find({_id: this.userId}, {fields: {
'roles': 1,
'profile': 1, // You probably want to publish the profile of a user to himself
// And here would be any other custom stuff you need
}});
});
And with that you are already in a state where you can do individual styling or routing client side. For example you could do something like this:
if (Meteor.user().roles.indexOf("admin") > -1) {
// Route for admins!
}
You could also parse through your array and add the user roles as class to your body element to for example only show certain elements to admins. This could be done this way:
Meteor.user().roles.forEach(function(role){
$('body').addClass(role);
});
Note that this will only be "cosmetic" but you can also implement real security with that as long as you do it server side. So if you want a Meteoer subscription or Meteor method only to be available for admins add something like this to it:
var requestingUser = Meteor.users.findOne({ '_id': this.userId});
if (!_.contains(requestingUser.roles, "admin")) {
// Terminate the pbulish function or Meteor method here when there is no "admin" role
return;
}
As said, this only works sever side and should be at the start of Meteor.publish functions or at the start of functions within Meteor.methods blocks.

Categories