node-ad-tools

1.2.1 • Public • Published

node-ad-tools

npm FOSSA Status
NodeJS Active Directory authentication and tools. - Requires ES6 support

This is a simple wrapper around ldapjs, which is a full ldap server & client. For custom or advanced setups please see https://github.com/joyent/node-ldapjs. This is highly opinionated and lacking in many features right now, but should work for simple AD authentication.

PR's that improve the project are welcomed, right now development is primarily on an as-needed basis.

API docs generated by jsdoc available here: https://tastypackets.github.io/node-ad-tools/

All binds and searchs are done using the credentials passed to the methods, that is why no credentials / service account is needed when creating the AD object.

Features

Current - v1.2.0

  • Logins process is fully implemented using users credentials, no service account needed.
  • Login with UPN, DN, and sAMAccountName - DN & sAMAccountName added in 1.2
  • Retrieves user object and user's groups on login
  • Can retrieve all groups in an OU and it's sub OUs (Using the root you can get all groups in AD)
  • Can retrieve all users in an OU and it's sub OUs (Using the root you can get all users in AD)
  • Can create user and group formatted objects with DN and GUID when retrieving all users and groups. - Added in 1.2
  • Resolves GUID, which is a unique ID in AD given to objects. This can be used to keep track of users and groups even if the names are changed.
  • Provides error message for user accounts that are locked out: Account is locked out
  • Override user search after bind for complex AD configurations - Added in 1.2

Planned features for 2.0.0

  • User password reset
  • Admin / service account password reset
  • Add / remove user from groups
  • Return group objects with DN/GUID for all user logins, this will help ensure consistency of group based permissions using GUID.
  • Change functions to object parameters / destructuring
  • Provide multiple ADs to be tried if one is unreachable

Important Notes

sAMAccountName can only bind with the domain name, if you want users to be able to type their sAMAccountName in the UPN format and still get a user object you will need to pre-process this string or provide a customSearch object. For example lets say we have a username with a sAMAccountName of test and the domain of test.local, but the user has a UPN of test@test.com.

These options will work by default with no changes:

  • Login with test\test
  • Login with test@test.com
  • Login with CN=Test,OU=Users,DC=test,DC=local

In order to let the user login with test@test.local we would need to convert the string to test\test or we would need to provide a customSearch on the loginUser method. Originally this was being added to v1.2.0 as an auto-fallback if no user was located with UPN, however due to unknown possible security issues and lack of testing time I decided it'd be safe to leave this process up to the dev and not include the auto-fallback process in v1.2.0.

Here is an example of providing a customSearch to address the issue:

    const username = 'test@test.local';
    const sam = username.split('@')[0];
    const customSearch = { filter: `(sAMAccountName=${sam})` }
 
    myAD.loginUser(username,'Welcome1',null , customSearch)

API

Install

yarn add node-ad-tools

Setup AD

The active directory class requires a basic configuration object that will inform ldapjs of the binding and searching parameters. This is configured once by creating a new ActiveDirectory object, if you need to change these settings dynamically you can construct the object right before performing the auth.

{
    url: 'ldap://192.168.1.1',
    base: 'dc=domain,dc=local',
    searchOptions: {scope: 'sub'}, // Optional
    idleTimeout: 3000, // Optional
    tlsOptions: { rejectUnauthorized: false } // Optional
}

Full Example

const { ActiveDirectory } = require('node-ad-tools');
 
const myADConfig = {
    url: 'ldap://192.168.1.1', // You can use DNS as well, like domain.local
    base: 'dc=domain,dc=local'
}
 
const myAD = new ActiveDirectory(myADConfig);
 
myAD.loginUser('test@domain.local','password')
    .then(res => {
        // If it failed to auth user find out why
        if(!res.success) {
            console.log(res.message);
            return;
        }
 
        const user = ActiveDirectory.createUserObj(res.entry);
        console.log(user);
    })
    .catch(err => console.error(err))

Both the class configuration and the methods that interact with Active Directory accept a base. The class one will be the default used if the base is not passed into specific methods. Here is an example of a base:

// This searches only in the Users OU inside example.local
myAD.loginUser('test@domain.local','password','cn=Users,dc=example,dc=local')

Constructor Config Options

Key Type Required Description
url String Required The url to the AD server, should start with ldap:// or ldaps://
base String Required AD base, example.local would be dc=example, dc=local
searchOptions Object Optional ldapjs searchOptions, defaults to scope: 'sub'
idleTimeout Number Optional How long to wait for response from AD before timing out
tlsOptions Object Optional Node TLS options used when connecting using TLS. See Node TLS API for details about options.

Methods

loginUser(username, password, base optional, customSearch optional)

This function takes a username and password and will return a Promise. The promise will only reject client connection issues, invalid authentication will still resolve the promise. This was done to make it easier to provide a different error or to try a 2ndry auth source easily. The success key is on all types of responses and should be used to verify if user was logged in. If success is false there will be 2 additional keys, message and error.

The param customSearch was added in v1.2.0 and allows you to override the search for the user object if the default process is not sufficient. To view all available options please look at ldapjs search options. For example you can modify the search by passing in a custom filter key.

If the bind is successful, but the method is unable to locate an account the res.entry will be undefined. This means the credentials passed are valid credentials, however the filter / AD was unable to locate a matching account. You will likely need to provide a customSearch in this case.

myAD.loginUser('test@domain.local','password')
    .then(res => {
        // If it failed to auth user find out why
        if(!res.success) {
            console.log(res.message);
            return;
        }
 
        const user = ActiveDirectory.createUserObj(res.entry);
        console.log(user);
    })
    .catch(err => console.error(err))

Both resolve & reject will be in the following format

Key Returned Type Description
success Always boolean Indicates if the login succeeded
entry Situational Object Undefined
message Situational String User friendly message from resolveBindError, only on success: false
error Situational String The original error generated, only on success: false

getAllGroups(username, password, base optional, detailed optional)

Look-up all the groups in active directory that the user can read, which is based on read permission configuration in active directory. All groups are returned in array of strings.

The detailed param was added in v1.2.0 and will create group objects for every group returned. These group objects contain additional useful information and in v2.0.0 will be on by default.

This is all groups the user can read, not just groups the user is a member of. Regular none-detailed groups

[
    'Domain Users',
    'Domain Guests',
    'Group 1',
    'Group 2'
]

Detailed groups

[
    { 
        name: 'My Group',
        dn: 'CN=My Group,CN=Users,DC=test,DC=local',
        guid: 'a4f84d99-e0c8-4e60-87e3-53444fd6fe0a',
        description: 'This is my test group!',
        created: '2018-07-27T20:44:07.000Z', // JS Date Obj - This is not a string, I showed a string for demonstration
        changed: '2019-02-27T16:39:18.000Z' // JS Date Obj
    }
]

This function takes a username and password and will return a Promise. The promise will only reject client connection issues, invalid authentication will still resolve the promise. This was done to make it easier to provide a different error or to try a 2ndry auth source easily. The success key is on all types of responses and should be used to verify if user was logged in. If success is false there will be 2 additional keys, message and error.

myAD.getAllGroups('test@domain.local','password')
    .then(res => {
        // If it failed to auth user find out why
        if(!res.success) {
            console.log(res.message);
            return;
        }
 
        console.log(res.groups);
    })
    .catch(err => console.error(err))

Get all groups in detailed mode and provide no custom base example:

myAD.getAllGroups('test@domain.local','password', true)
    .then(res => {
        // If it failed to auth user find out why
        if(!res.success) {
            console.log(res.message);
            return;
        }
 
        console.log(res.groups);
    })
    .catch(err => console.error(err))

Both resolve & reject will be in the following format

Key Returned Type Description
success Always boolean Indicates if the login succeeded
groups Situational Array An array of all the groups the user has permissions to read in AD.
message Situational String User friendly message from resolveBindError, only on success: false
error Situational String The original error generated, only on success: false

getAllUsers(username, password, base optional, formatted optional)

Look-up all the users in active directory that the user can read, which is based on read permission configuration in active directory. All user entry objects are returned in an array.

The formatted param was added in v1.2.0 and if set to true will convert all entries into user objects using the ActiveDirectory.createUserObj() method.

{
    success: true,
    users: [
        // This is a valid entry just like login user and can be passed to createUserObj() method.
        SearchEntry,
        SearchEntry
    ]
}

Example with formatted set to true:

{
    success: true,
    users: [
        {
            groups: [ 'Staff', 'Users' ],
            phone: '',
            name: 'First User',
            mail: 'firstuser@test.com',
            guid: '579de45e-faf5-40f8-8eff-be2d76bd20d9',
            dn: 'CN=First User,OU=Users,DC=test,DC=com'
        },
        {
            groups: [ 'Staff', 'Users' ],
            phone: '',
            name: 'Second User',
            mail: 'seconduser@test.com',
            guid: '502de45e-faf9-83f8-8eff-be6d76bd20d5',
            dn: 'CN=Second User,OU=Users,DC=test,DC=com'
        }
    ]
}

This function takes a username and password and will return a Promise. The promise will only reject client connection issues, invalid authentication will still resolve the promise. This was done to make it easier to provide a different error or to try a 2ndry auth source easily. The success key is on all types of responses and should be used to verify if user was logged in. If success is false there will be 2 additional keys, message and error.

myAD.getAllUsers('test@domain.local','password','cn=Users,dc=domain,dc=local', true) // Example of only searching Users OU inside the domain
    .then(res => {
        // If it failed to auth user find out why
        if(!res.success) {
            console.log(res.message);
            return;
        }
        
        console.log(res.users);
    })
    .catch(err => console.error(err))

Both resolve & reject will be in the following format

Key Returned Type Description
success Always Boolean Indicates if the login succeeded
Users Situational Array An array of all the user entries the user has permissions to read in AD and match the base / scope.
message Situational String User friendly message from resolveBindError, only on success: false
error Situational String The original error generated, only on success: false

createUserObj(entry)

Takes in the entry returned by ldapjs and creates a standardized user object. If you do not want to store all the users data it is recommended you extract the values you need from this object, because in the future there will likely be many more fields added to this. The first set of fields added were based on immediate needs.

If this does not have all the desired fields please feel free to add more in a PR or you can simply access them on the entry.objects or entry.attributes if you need the buffers.

The user DN was added to the user object in v1.2.0

const user = ActiveDirectory.resolveBindError(res.entry)
 
console.log(user) // {groups: [], phone: '', name: '', mail: '', guid: '', dn: ''}

Returns Object

Returned Type Description
groups Array An array of group name strings. This is the group names only, not the full AD location
phone String Users phone number
name String Users full name
mail String Users email address
guid String Unique AD key, this should be used to track and or link the user account to your app.

resolveBindError(entry)

This function takes in the ldapjs errors and checks if it's due to invalid credentials or if the account is locked out. This does not check if an account is disabled, so it will still return as invalid credentials

const message = ActiveDirectory.resolveBindError(res.entry)
// Examples: Account is locked out, Invalid username or password, or Error resolving account.

resolveGUID(entry)

Takes in the entry returned by ldapjs and creates a GUID string. This should be used as your unique ID in your app or somehow used to link to a unique ID in your app. This will not change for the life of the object in AD, so even if the users name or email is changed this will stay the same.

const guid = ActiveDirectory.resolveGUID(res.entry)
// Example: 17d4e710-624d-4978-900b-8549cb753699

resolveGroups(entry)

Takes in the entry returned by ldapjs and creates an array of the users groups.

const guid = ActiveDirectory.resolveGroups(res.entry)
// Example: ['Group1', 'Group2']

Potential Issues

Sometimes ldapjs has issues with newer version of Node, please see ldapjs for any of these issues.

License

FOSSA Status

Dependents (0)

Package Sidebar

Install

npm i node-ad-tools

Weekly Downloads

50

Version

1.2.1

License

MIT

Unpacked Size

618 kB

Total Files

27

Last publish

Collaborators

  • tastypackets