UNPKG

node-expose-sspi-strict

Version:

Expose the Microsoft Windows SSPI interface in order to do NTLM and Kerberos authentication.

149 lines (140 loc) 4.21 kB
import { adsi } from '../../lib/api'; import { IDirectorySearch, IADs } from '../../lib/adsi'; import { isOnDomain, isActiveDirectoryReachable } from './domain'; import dbg from 'debug'; import { Database, ADUser, ADUsers } from './interfaces'; import { activeDirectoryMutex } from './mutex'; import { openADConnection, closeADConnection } from './adConnection'; const debug = dbg('node-expose-sspi:userdb'); export const database: Database = { users: [] }; /** * * This function is recommanded to be called before starting a server. * * Purpose is to cache all Active Directory (AD) users for * performance during authentication, just for increasing performance. * * Useless if you do not use AD. * * @export * @returns {Promise<void>} */ export async function init(): Promise<void> { if (!isOnDomain()) { return; } try { debug('init'); // request all accounts from domain let users = await getUsers(); if (users) database.users = users; } catch (e) { debug('Cannot get users from AD. e: ', e); } } export async function getUser(ldapFilter: string): Promise<ADUser|undefined> { debug('getUser start '); if (!isOnDomain()) { return; } const adRelease = await activeDirectoryMutex.acquire(); if (!isActiveDirectoryReachable()) { console.error('Warning: Active Directory not reachable'); return; } openADConnection(); let dirsearch: IDirectorySearch|undefined; try { const distinguishedName = await getDistinguishedName(); dirsearch = await adsi.ADsOpenObject<IDirectorySearch>({ binding: `LDAP://${distinguishedName}`, riid: 'IID_IDirectorySearch', }); dirsearch.SetSearchPreference(); dirsearch.ExecuteSearch({ filter: `(&(objectClass=user)(objectCategory=person)${ldapFilter})`, }); const hr = dirsearch.GetNextRow(); if (hr === adsi.S_ADS_NOMORE_ROWS) { return undefined; } const row: ADUser = {}; let colName = dirsearch.GetNextColumnName(); while (colName !== adsi.S_ADS_NOMORE_COLUMNS) { const value = await dirsearch.GetColumn(colName as string); row[colName] = value; colName = dirsearch.GetNextColumnName(); } return row; } finally { if (dirsearch) { dirsearch.Release(); } closeADConnection(); adRelease(); debug('getUser end'); } } export async function getUsers(): Promise<ADUsers|undefined> { debug('getUsers start '); if (!isOnDomain()) { return; } const adRelease = await activeDirectoryMutex.acquire(); if (!isActiveDirectoryReachable()) { console.error('Warning: Active Directory not reachable'); return; } const result: ADUsers = []; openADConnection(); let dirsearch; try { const distinguishedName = await getDistinguishedName(); dirsearch = await adsi.ADsOpenObject<IDirectorySearch>({ binding: `LDAP://${distinguishedName}`, riid: 'IID_IDirectorySearch', }); dirsearch.SetSearchPreference(); dirsearch.ExecuteSearch({ filter: '(&(objectClass=user)(objectCategory=person)(sn=*))', }); while (true) { if (dirsearch.GetNextRow() === adsi.S_ADS_NOMORE_ROWS) { break; } const row: ADUser = {}; let colName = dirsearch.GetNextColumnName(); while (colName !== adsi.S_ADS_NOMORE_COLUMNS) { const value = await dirsearch.GetColumn(colName as string); row[colName] = value; colName = dirsearch.GetNextColumnName(); } result.push(row); } } catch (error) { console.error('error: ', error); } finally { if (dirsearch) { dirsearch.Release(); } closeADConnection(); adRelease(); debug('getUsers end'); } return result; } export async function getDistinguishedName(): Promise<string> { let root: IADs|undefined; try { root = await adsi.ADsGestObject('LDAP://rootDSE'); const distinguishedName = await root.Get('defaultNamingContext'); return distinguishedName; } finally { if (root) { root.Release(); } } }