npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

node-ad-tools

v1.2.1

Published

Basic Active Directory wrapper for ldapjs

Downloads

221

Readme

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 [email protected].

These options will work by default with no changes:

  • Login with test\test
  • Login with [email protected]
  • Login with CN=Test,OU=Users,DC=test,DC=local

In order to let the user login with [email protected] 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 = '[email protected]';
    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('[email protected]','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('[email protected]','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('[email protected]','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 | Entry is the ldapjs entry response | | 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('[email protected]','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('[email protected]','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: '[email protected]',
            guid: '579de45e-faf5-40f8-8eff-be2d76bd20d9',
            dn: 'CN=First User,OU=Users,DC=test,DC=com'
        },
        {
            groups: [ 'Staff', 'Users' ],
            phone: '',
            name: 'Second User',
            mail: '[email protected]',
            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('[email protected]','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