ldap-authentication 4.2.1 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/codeql-analysis.yml +12 -10
- package/AGENTS.md +99 -0
- package/README.md +52 -0
- package/docker/ldap/10-ldap-test-data.ldif +14 -0
- package/example/fetch-users.mjs +36 -0
- package/example/index.js +34 -36
- package/example/starttls.mjs +40 -0
- package/example/verify-user-exists.js +45 -0
- package/index.d.ts +48 -2
- package/index.js +370 -468
- package/package.json +3 -2
- package/scripts/test-local.sh +33 -0
- package/test/authentication-result.spec.js +82 -41
- package/test/binary.spec.js +11 -10
- package/test/config.js +9 -0
- package/test/fetch-users.spec.js +23 -32
- package/test/fixtures/jpeg-photo.b64 +1 -0
- package/test/starttls.spec.js +12 -12
- package/test/test.spec.js +123 -41
- package/.github/workflows/publish.yml +0 -53
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const assert = require('assert')
|
|
2
1
|
const ldapts = require('ldapts')
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
// escape the , in the value of the first RDN of a DN
|
|
4
4
|
function _ldapEscapeDN(s) {
|
|
5
5
|
let ret = ''
|
|
6
6
|
let comaPositions = []
|
|
@@ -48,6 +48,31 @@ const AUTH_RESULT_FAILURE_UNCATEGORIZED = -4
|
|
|
48
48
|
const DEFAULT_FETCH_USERS_FILTER = '(|(uid=*)(sAMAccountName=*))'
|
|
49
49
|
const DEFAULT_FETCH_USERS_PAGE_SIZE = 1000
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Thrown by authenticate()/authenticateResult()/fetchUsers() on failure.
|
|
53
|
+
* `message` describes the failure; `code` (when set) is one of the
|
|
54
|
+
* AUTH_RESULT_* constants, mirroring the outcome that authenticateResult()
|
|
55
|
+
* reports for the same failure. Missing required options also throw this
|
|
56
|
+
* error, with all the missing fields listed in `message`.
|
|
57
|
+
*/
|
|
58
|
+
class LdapAuthenticationError extends Error {
|
|
59
|
+
constructor(message, code) {
|
|
60
|
+
super(message)
|
|
61
|
+
// Ensure the name of this error is the same as the class name
|
|
62
|
+
this.name = this.constructor.name
|
|
63
|
+
if (code !== undefined) {
|
|
64
|
+
this.code = code
|
|
65
|
+
}
|
|
66
|
+
// This clips the constructor invocation from the stack trace.
|
|
67
|
+
// It's not absolutely essential, but it does make the stack trace a little nicer.
|
|
68
|
+
Error.captureStackTrace(this, this.constructor)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Result object returned by {@link authenticateResult}. Inspect `code` (one
|
|
74
|
+
* of the AUTH_RESULT_* constants) and `messages` to classify failures.
|
|
75
|
+
*/
|
|
51
76
|
class AuthenticationResult {
|
|
52
77
|
#authCode = AUTH_RESULT_FAILURE_UNCATEGORIZED
|
|
53
78
|
#identity
|
|
@@ -93,11 +118,17 @@ const authenticationMessages = {
|
|
|
93
118
|
AUTH_RESULT_FAILURE_UNCATEGORIZED: 'Uncategorized authentication failure',
|
|
94
119
|
}
|
|
95
120
|
|
|
96
|
-
// bind and return the ldap client
|
|
97
|
-
|
|
98
|
-
|
|
121
|
+
// bind with (dn, password) and return the connected ldap client.
|
|
122
|
+
// If the connection or the bind fails, the client is unbound again before
|
|
123
|
+
// the error is rethrown, so callers never leak a connected socket.
|
|
124
|
+
async function _ldapBind(dn, password, { starttls, ldapOpts }) {
|
|
125
|
+
// ldapts passes a string DN through to the server as-is (no escaping is
|
|
126
|
+
// done by ldapts), so the value of the first RDN is escaped here (a DN
|
|
127
|
+
// like `cn=Doe, John,ou=users,...` would otherwise be parsed by the server
|
|
128
|
+
// as three RDNs instead of one)
|
|
99
129
|
dn = _ldapEscapeDN(dn)
|
|
100
|
-
|
|
130
|
+
const opts = { ...ldapOpts }
|
|
131
|
+
opts.connectTimeout = ldapOpts.connectTimeout || 5000
|
|
101
132
|
|
|
102
133
|
// When using StartTLS, we need to exclude tlsOptions from the Client constructor
|
|
103
134
|
// and only pass them to the startTLS() method to avoid connection conflicts.
|
|
@@ -105,74 +136,93 @@ async function _ldapBind(dn, password, starttls, ldapOpts) {
|
|
|
105
136
|
// - For LDAPS (ldaps://): pass tlsOptions to Client constructor
|
|
106
137
|
// - For StartTLS (ldap://): do NOT pass tlsOptions to Client constructor, only to startTLS()
|
|
107
138
|
// - For plain LDAP (ldap://): do NOT pass tlsOptions to Client constructor
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// Only pass tlsOptions to Client constructor if using ldaps:// protocol
|
|
112
|
-
// For ldap:// protocol (plain or StartTLS), exclude tlsOptions from constructor
|
|
113
|
-
if (!isLdaps && ldapOpts.tlsOptions) {
|
|
114
|
-
// Create a shallow copy of ldapOpts without tlsOptions for the Client constructor
|
|
115
|
-
const { tlsOptions, ...optsWithoutTls } = ldapOpts
|
|
116
|
-
clientOpts = optsWithoutTls
|
|
139
|
+
const isLdaps = opts.url && opts.url.startsWith('ldaps://')
|
|
140
|
+
if (!isLdaps) {
|
|
141
|
+
delete opts.tlsOptions
|
|
117
142
|
}
|
|
118
143
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
const client = new ldapts.Client(opts)
|
|
145
|
+
try {
|
|
146
|
+
if (starttls) {
|
|
147
|
+
await client.startTLS(ldapOpts.tlsOptions)
|
|
148
|
+
}
|
|
149
|
+
await client.bind(dn, password)
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (client.isConnected) {
|
|
152
|
+
try {
|
|
153
|
+
await client.unbind()
|
|
154
|
+
} catch (unbindError) {
|
|
155
|
+
// the socket was probably already closed; nothing else to do
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
throw error
|
|
123
159
|
}
|
|
124
|
-
|
|
125
|
-
await client.bind(dn, password)
|
|
126
160
|
ldapOpts.log && ldapOpts.log.trace('bind success!')
|
|
127
161
|
return client
|
|
128
162
|
}
|
|
129
163
|
|
|
130
|
-
//
|
|
131
|
-
|
|
164
|
+
// convert attribute values that ldapts returned as Buffer (attributes with
|
|
165
|
+
// a `;binary` suffix, or the ones listed in explicitBufferAttributes) into
|
|
166
|
+
// base64 strings
|
|
167
|
+
function _toBase64Attributes(user, attributes, explicitBufferAttributes) {
|
|
168
|
+
if (user == null) {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
// when attribute endwith ;binary, ldapts returns Buffer, we convert them into base64 string
|
|
172
|
+
if (attributes != null) {
|
|
173
|
+
for (let attr of attributes) {
|
|
174
|
+
if (attr.endsWith(';binary') && Buffer.isBuffer(user[attr])) {
|
|
175
|
+
user[attr] = user[attr].toString('base64')
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// when attribute is one of the explicitBufferAttributes, should convert to base64 string
|
|
180
|
+
if (explicitBufferAttributes != null) {
|
|
181
|
+
for (let attr of explicitBufferAttributes) {
|
|
182
|
+
if (Buffer.isBuffer(user[attr])) {
|
|
183
|
+
user[attr] = user[attr].toString('base64')
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
132
188
|
|
|
133
189
|
// search a user and return the object
|
|
134
|
-
async function _searchUser(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
190
|
+
async function _searchUser(client, options) {
|
|
191
|
+
const {
|
|
192
|
+
userSearchBase,
|
|
193
|
+
usernameFilter,
|
|
194
|
+
usernameAttribute,
|
|
195
|
+
username,
|
|
196
|
+
attributes = null,
|
|
197
|
+
explicitBufferAttributes = null,
|
|
198
|
+
} = options
|
|
199
|
+
|
|
200
|
+
let filter
|
|
201
|
+
if (usernameFilter) {
|
|
202
|
+
// replace `{{username}}` with the RFC 2254-escaped username, so LDAP
|
|
203
|
+
// filter metacharacters inside the username cannot change the structure
|
|
204
|
+
// of the filter
|
|
205
|
+
filter = usernameFilter.replaceAll('{{username}}', ldapts.Filter.escape(username))
|
|
206
|
+
} else {
|
|
148
207
|
filter = new ldapts.EqualityFilter({
|
|
149
208
|
attribute: usernameAttribute,
|
|
150
209
|
value: username,
|
|
151
210
|
})
|
|
152
211
|
}
|
|
153
|
-
|
|
212
|
+
|
|
154
213
|
let searchOptions = {
|
|
155
214
|
filter: filter,
|
|
156
215
|
scope: 'sub',
|
|
157
|
-
attributes: attributes,
|
|
158
216
|
}
|
|
159
217
|
if (attributes) {
|
|
160
218
|
searchOptions.attributes = attributes
|
|
161
219
|
}
|
|
162
|
-
if(explicitBufferAttributes) {
|
|
220
|
+
if (explicitBufferAttributes) {
|
|
163
221
|
searchOptions.explicitBufferAttributes = explicitBufferAttributes
|
|
164
222
|
}
|
|
165
223
|
|
|
166
224
|
// TODO: we don't support reference yet
|
|
167
|
-
|
|
168
|
-
// but could not search one or more non-local entries,
|
|
169
|
-
// the server may return one or more SearchResultReference messages,
|
|
170
|
-
// each containing a reference to another set of servers for continuing the operation.
|
|
171
|
-
// referral.uris
|
|
172
|
-
const { searchEntries, searchReferences } = await ldapClient.search(
|
|
173
|
-
searchBase,
|
|
174
|
-
searchOptions
|
|
175
|
-
)
|
|
225
|
+
const { searchEntries } = await client.search(userSearchBase, searchOptions)
|
|
176
226
|
|
|
177
227
|
let user
|
|
178
228
|
if (
|
|
@@ -181,64 +231,52 @@ async function _searchUser(
|
|
|
181
231
|
!searchEntries[0] ||
|
|
182
232
|
!searchEntries[0].dn
|
|
183
233
|
) {
|
|
234
|
+
user = null
|
|
235
|
+
} else if (searchEntries.length > 1) {
|
|
184
236
|
return new AuthenticationResult(
|
|
185
|
-
|
|
237
|
+
AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS,
|
|
186
238
|
username,
|
|
187
239
|
null,
|
|
188
|
-
[authenticationMessages.
|
|
189
|
-
|
|
240
|
+
[authenticationMessages.AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS],
|
|
241
|
+
client
|
|
190
242
|
)
|
|
191
243
|
} else {
|
|
192
|
-
if (searchEntries.length > 1) {
|
|
193
|
-
return new AuthenticationResult(
|
|
194
|
-
AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS,
|
|
195
|
-
username,
|
|
196
|
-
null,
|
|
197
|
-
[authenticationMessages.AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS],
|
|
198
|
-
ldapClient
|
|
199
|
-
)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
244
|
user = searchEntries[0]
|
|
203
245
|
}
|
|
204
246
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// when attribute is one of the explicitBufferAttributes, should convert to base64 string
|
|
214
|
-
if (user != null && explicitBufferAttributes != null) {
|
|
215
|
-
for (let attr of explicitBufferAttributes) {
|
|
216
|
-
if (Buffer.isBuffer(user[attr])) {
|
|
217
|
-
user[attr] = user[attr].toString('base64')
|
|
218
|
-
}
|
|
219
|
-
}
|
|
247
|
+
if (!user) {
|
|
248
|
+
return new AuthenticationResult(
|
|
249
|
+
AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND,
|
|
250
|
+
username,
|
|
251
|
+
null,
|
|
252
|
+
[authenticationMessages.AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND],
|
|
253
|
+
client
|
|
254
|
+
)
|
|
220
255
|
}
|
|
221
256
|
|
|
257
|
+
_toBase64Attributes(user, attributes, explicitBufferAttributes)
|
|
222
258
|
return new AuthenticationResult(
|
|
223
259
|
AUTH_RESULT_SUCCESS,
|
|
224
260
|
username,
|
|
225
261
|
user,
|
|
226
262
|
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
227
|
-
|
|
263
|
+
client
|
|
228
264
|
)
|
|
229
265
|
}
|
|
230
266
|
|
|
231
|
-
// search
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
267
|
+
// search the groups which user is member and attach them to user.groups;
|
|
268
|
+
// does nothing when group lookup is not configured
|
|
269
|
+
async function _attachGroups(client, user, options) {
|
|
270
|
+
const {
|
|
271
|
+
groupsSearchBase,
|
|
272
|
+
groupClass,
|
|
273
|
+
groupMemberAttribute = 'member',
|
|
274
|
+
groupMemberUserAttribute = 'dn',
|
|
275
|
+
} = options
|
|
276
|
+
if (!groupsSearchBase || !groupClass || !groupMemberAttribute) {
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
|
|
242
280
|
const filter = new ldapts.AndFilter({
|
|
243
281
|
filters: [
|
|
244
282
|
new ldapts.EqualityFilter({
|
|
@@ -252,20 +290,12 @@ async function _searchUserGroups(
|
|
|
252
290
|
],
|
|
253
291
|
})
|
|
254
292
|
|
|
255
|
-
const { searchEntries
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
scope: 'sub',
|
|
260
|
-
}
|
|
261
|
-
)
|
|
293
|
+
const { searchEntries } = await client.search(groupsSearchBase, {
|
|
294
|
+
filter: filter,
|
|
295
|
+
scope: 'sub',
|
|
296
|
+
})
|
|
262
297
|
|
|
263
|
-
let groups
|
|
264
|
-
if (!searchEntries || searchEntries.length < 1) {
|
|
265
|
-
groups = []
|
|
266
|
-
} else {
|
|
267
|
-
groups = searchEntries
|
|
268
|
-
}
|
|
298
|
+
let groups = searchEntries || []
|
|
269
299
|
// ldapjs has group.objectName, ldapts does not have it. instead, use dn
|
|
270
300
|
// add objectName back for backward compatibility
|
|
271
301
|
for (let group of groups) {
|
|
@@ -273,22 +303,21 @@ async function _searchUserGroups(
|
|
|
273
303
|
group.objectName = group.dn
|
|
274
304
|
}
|
|
275
305
|
}
|
|
276
|
-
|
|
306
|
+
user.groups = groups
|
|
277
307
|
}
|
|
278
308
|
|
|
279
309
|
// search all users under the search base and return the list of user objects
|
|
280
|
-
async function _fetchAllUsers(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
let filter = userFilter || DEFAULT_FETCH_USERS_FILTER
|
|
310
|
+
async function _fetchAllUsers(client, options) {
|
|
311
|
+
const {
|
|
312
|
+
userSearchBase,
|
|
313
|
+
userFilter,
|
|
314
|
+
attributes = null,
|
|
315
|
+
explicitBufferAttributes = null,
|
|
316
|
+
pageSize = DEFAULT_FETCH_USERS_PAGE_SIZE,
|
|
317
|
+
} = options
|
|
289
318
|
|
|
290
319
|
let searchOptions = {
|
|
291
|
-
filter:
|
|
320
|
+
filter: userFilter || DEFAULT_FETCH_USERS_FILTER,
|
|
292
321
|
scope: 'sub',
|
|
293
322
|
// always use paged results, so more than the usual server-side limit
|
|
294
323
|
// (usually 1000 entries per page) can be returned
|
|
@@ -301,60 +330,24 @@ async function _fetchAllUsers(
|
|
|
301
330
|
searchOptions.explicitBufferAttributes = explicitBufferAttributes
|
|
302
331
|
}
|
|
303
332
|
|
|
304
|
-
const { searchEntries } = await
|
|
333
|
+
const { searchEntries } = await client.search(userSearchBase, searchOptions)
|
|
305
334
|
|
|
306
335
|
let users = searchEntries || []
|
|
307
|
-
// when attribute endwith ;binary, ldapts returns Buffer, we convert them into base64 string
|
|
308
336
|
for (let user of users) {
|
|
309
|
-
|
|
310
|
-
for (let attr of attributes) {
|
|
311
|
-
if (attr.endsWith(';binary') && Buffer.isBuffer(user[attr])) {
|
|
312
|
-
user[attr] = user[attr].toString('base64')
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
// when attribute is one of the explicitBufferAttributes, should convert to base64 string
|
|
317
|
-
if (user != null && explicitBufferAttributes != null) {
|
|
318
|
-
for (let attr of explicitBufferAttributes) {
|
|
319
|
-
if (Buffer.isBuffer(user[attr])) {
|
|
320
|
-
user[attr] = user[attr].toString('base64')
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
337
|
+
_toBase64Attributes(user, attributes, explicitBufferAttributes)
|
|
324
338
|
}
|
|
325
339
|
return users
|
|
326
340
|
}
|
|
327
341
|
|
|
328
|
-
async function authenticateWithAdmin(
|
|
329
|
-
|
|
330
|
-
adminPassword,
|
|
331
|
-
userSearchBase,
|
|
332
|
-
usernameFilter,
|
|
333
|
-
usernameAttribute,
|
|
334
|
-
username,
|
|
335
|
-
userPassword,
|
|
336
|
-
starttls,
|
|
337
|
-
ldapOpts,
|
|
338
|
-
groupsSearchBase,
|
|
339
|
-
groupClass,
|
|
340
|
-
groupMemberAttribute = 'member',
|
|
341
|
-
groupMemberUserAttribute = 'dn',
|
|
342
|
-
attributes = null,
|
|
343
|
-
explicitBufferAttributes = null
|
|
344
|
-
) {
|
|
342
|
+
async function authenticateWithAdmin(options) {
|
|
343
|
+
const { username, ldapOpts } = options
|
|
345
344
|
let ldapAdminClient
|
|
346
345
|
try {
|
|
347
|
-
ldapAdminClient = await _ldapBind(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
ldapOpts
|
|
352
|
-
)
|
|
346
|
+
ldapAdminClient = await _ldapBind(options.adminDn, options.adminPassword, {
|
|
347
|
+
starttls: options.starttls,
|
|
348
|
+
ldapOpts,
|
|
349
|
+
})
|
|
353
350
|
} catch (error) {
|
|
354
|
-
if (ldapAdminClient && ldapAdminClient.isConnected) {
|
|
355
|
-
await ldapAdminClient.unbind()
|
|
356
|
-
}
|
|
357
|
-
|
|
358
351
|
return new AuthenticationResult(
|
|
359
352
|
AUTH_RESULT_FAILURE,
|
|
360
353
|
username,
|
|
@@ -364,96 +357,69 @@ async function authenticateWithAdmin(
|
|
|
364
357
|
)
|
|
365
358
|
}
|
|
366
359
|
|
|
367
|
-
|
|
368
|
-
ldapAdminClient,
|
|
369
|
-
userSearchBase,
|
|
370
|
-
usernameFilter,
|
|
371
|
-
usernameAttribute,
|
|
372
|
-
username,
|
|
373
|
-
attributes,
|
|
374
|
-
explicitBufferAttributes
|
|
375
|
-
)
|
|
360
|
+
try {
|
|
361
|
+
let searchResult = await _searchUser(ldapAdminClient, options)
|
|
376
362
|
|
|
377
|
-
|
|
363
|
+
let user = searchResult.user
|
|
378
364
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
365
|
+
if (!user || !user.dn) {
|
|
366
|
+
ldapOpts.log &&
|
|
367
|
+
ldapOpts.log.trace(
|
|
368
|
+
`admin did not find user! (${options.usernameAttribute}=${username})`
|
|
369
|
+
)
|
|
370
|
+
return new AuthenticationResult(
|
|
371
|
+
AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND,
|
|
372
|
+
username,
|
|
373
|
+
null,
|
|
374
|
+
[authenticationMessages.AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND],
|
|
375
|
+
ldapAdminClient
|
|
383
376
|
)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
377
|
+
}
|
|
378
|
+
let userDn = user.dn
|
|
379
|
+
let ldapUserClient
|
|
380
|
+
try {
|
|
381
|
+
ldapUserClient = await _ldapBind(userDn, options.userPassword, {
|
|
382
|
+
starttls: options.starttls,
|
|
383
|
+
ldapOpts,
|
|
384
|
+
})
|
|
385
|
+
} catch (error) {
|
|
386
|
+
return new AuthenticationResult(
|
|
387
|
+
AUTH_RESULT_FAILURE_CREDENTIAL_INVALID,
|
|
388
|
+
username,
|
|
389
|
+
null,
|
|
390
|
+
[
|
|
391
|
+
authenticationMessages.AUTH_RESULT_FAILURE_CREDENTIAL_INVALID,
|
|
392
|
+
error.message || 'invalid credentials',
|
|
393
|
+
],
|
|
394
|
+
ldapAdminClient
|
|
395
|
+
)
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
await _attachGroups(ldapAdminClient, user, options)
|
|
399
|
+
return new AuthenticationResult(
|
|
400
|
+
AUTH_RESULT_SUCCESS,
|
|
401
|
+
username,
|
|
402
|
+
user,
|
|
403
|
+
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
404
|
+
ldapAdminClient
|
|
405
|
+
)
|
|
406
|
+
} finally {
|
|
399
407
|
await ldapUserClient.unbind()
|
|
400
408
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
AUTH_RESULT_FAILURE_CREDENTIAL_INVALID,
|
|
404
|
-
username,
|
|
405
|
-
null,
|
|
406
|
-
[authenticationMessages.AUTH_RESULT_FAILURE_CREDENTIAL_INVALID, error.message || 'invalid credentials'],
|
|
407
|
-
ldapAdminClient
|
|
408
|
-
)
|
|
409
|
-
}
|
|
410
|
-
if (groupsSearchBase && groupClass && groupMemberAttribute) {
|
|
411
|
-
let groups = await _searchUserGroups(
|
|
412
|
-
ldapAdminClient,
|
|
413
|
-
groupsSearchBase,
|
|
414
|
-
user,
|
|
415
|
-
groupClass,
|
|
416
|
-
groupMemberAttribute,
|
|
417
|
-
groupMemberUserAttribute
|
|
418
|
-
)
|
|
419
|
-
user.groups = groups
|
|
409
|
+
} finally {
|
|
410
|
+
await ldapAdminClient.unbind()
|
|
420
411
|
}
|
|
421
|
-
await ldapAdminClient.unbind()
|
|
422
|
-
await ldapUserClient.unbind()
|
|
423
|
-
|
|
424
|
-
return new AuthenticationResult(
|
|
425
|
-
AUTH_RESULT_SUCCESS,
|
|
426
|
-
username,
|
|
427
|
-
user,
|
|
428
|
-
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
429
|
-
ldapAdminClient
|
|
430
|
-
)
|
|
431
412
|
}
|
|
432
413
|
|
|
433
|
-
async function authenticateWithUser(
|
|
434
|
-
|
|
435
|
-
userSearchBase,
|
|
436
|
-
usernameFilter,
|
|
437
|
-
usernameAttribute,
|
|
438
|
-
username,
|
|
439
|
-
userPassword,
|
|
440
|
-
starttls,
|
|
441
|
-
ldapOpts,
|
|
442
|
-
groupsSearchBase,
|
|
443
|
-
groupClass,
|
|
444
|
-
groupMemberAttribute = 'member',
|
|
445
|
-
groupMemberUserAttribute = 'dn',
|
|
446
|
-
attributes = null,
|
|
447
|
-
explicitBufferAttributes = null
|
|
448
|
-
) {
|
|
414
|
+
async function authenticateWithUser(options) {
|
|
415
|
+
const { username, usernameAttribute, userSearchBase, ldapOpts } = options
|
|
449
416
|
let ldapUserClient
|
|
450
417
|
try {
|
|
451
|
-
ldapUserClient = await _ldapBind(userDn, userPassword,
|
|
418
|
+
ldapUserClient = await _ldapBind(options.userDn, options.userPassword, {
|
|
419
|
+
starttls: options.starttls,
|
|
420
|
+
ldapOpts,
|
|
421
|
+
})
|
|
452
422
|
} catch (error) {
|
|
453
|
-
if (ldapUserClient && ldapUserClient.isConnected) {
|
|
454
|
-
await ldapUserClient.unbind()
|
|
455
|
-
}
|
|
456
|
-
|
|
457
423
|
return new AuthenticationResult(
|
|
458
424
|
AUTH_RESULT_FAILURE,
|
|
459
425
|
username,
|
|
@@ -462,98 +428,61 @@ async function authenticateWithUser(
|
|
|
462
428
|
ldapUserClient
|
|
463
429
|
)
|
|
464
430
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
431
|
+
try {
|
|
432
|
+
if (!usernameAttribute || !userSearchBase) {
|
|
433
|
+
// if usernameAttribute is not provided, no user detail is needed.
|
|
434
|
+
return new AuthenticationResult(
|
|
435
|
+
AUTH_RESULT_SUCCESS,
|
|
436
|
+
username,
|
|
437
|
+
{},
|
|
438
|
+
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
439
|
+
ldapUserClient
|
|
440
|
+
)
|
|
441
|
+
}
|
|
476
442
|
|
|
477
|
-
|
|
478
|
-
ldapUserClient,
|
|
479
|
-
userSearchBase,
|
|
480
|
-
usernameFilter,
|
|
481
|
-
usernameAttribute,
|
|
482
|
-
username,
|
|
483
|
-
attributes,
|
|
484
|
-
explicitBufferAttributes
|
|
485
|
-
)
|
|
443
|
+
let searchResult = await _searchUser(ldapUserClient, options)
|
|
486
444
|
|
|
487
|
-
|
|
445
|
+
let user = searchResult.user
|
|
488
446
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
447
|
+
if (!user || !user.dn) {
|
|
448
|
+
ldapOpts.log &&
|
|
449
|
+
ldapOpts.log.trace(
|
|
450
|
+
`user logged in, but user details could not be found. (${usernameAttribute}=${username}). Probabaly wrong attribute or searchBase?`
|
|
451
|
+
)
|
|
452
|
+
return new AuthenticationResult(
|
|
453
|
+
AUTH_RESULT_FAILURE,
|
|
454
|
+
username,
|
|
455
|
+
null,
|
|
456
|
+
[
|
|
457
|
+
authenticationMessages.AUTH_RESULT_FAILURE,
|
|
458
|
+
'user logged in, but user details could not be found. Probabaly usernameAttribute or userSearchBase is wrong?',
|
|
459
|
+
],
|
|
460
|
+
ldapUserClient
|
|
493
461
|
)
|
|
494
|
-
|
|
462
|
+
}
|
|
463
|
+
await _attachGroups(ldapUserClient, user, options)
|
|
495
464
|
|
|
496
465
|
return new AuthenticationResult(
|
|
497
|
-
|
|
466
|
+
AUTH_RESULT_SUCCESS,
|
|
498
467
|
username,
|
|
499
|
-
null,
|
|
500
|
-
[
|
|
501
|
-
authenticationMessages.AUTH_RESULT_FAILURE,
|
|
502
|
-
'user logged in, but user details could not be found. Probabaly usernameAttribute or userSearchBase is wrong?',
|
|
503
|
-
],
|
|
504
|
-
ldapUserClient
|
|
505
|
-
)
|
|
506
|
-
}
|
|
507
|
-
if (groupsSearchBase && groupClass && groupMemberAttribute) {
|
|
508
|
-
let groups = await _searchUserGroups(
|
|
509
|
-
ldapUserClient,
|
|
510
|
-
groupsSearchBase,
|
|
511
468
|
user,
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
groupMemberUserAttribute
|
|
469
|
+
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
470
|
+
ldapUserClient
|
|
515
471
|
)
|
|
516
|
-
|
|
472
|
+
} finally {
|
|
473
|
+
await ldapUserClient.unbind()
|
|
517
474
|
}
|
|
518
|
-
await ldapUserClient.unbind()
|
|
519
|
-
|
|
520
|
-
return new AuthenticationResult(
|
|
521
|
-
AUTH_RESULT_SUCCESS,
|
|
522
|
-
username,
|
|
523
|
-
user,
|
|
524
|
-
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
525
|
-
ldapUserClient
|
|
526
|
-
)
|
|
527
475
|
}
|
|
528
476
|
|
|
529
|
-
async function verifyUserExists(
|
|
530
|
-
|
|
531
|
-
adminPassword,
|
|
532
|
-
userSearchBase,
|
|
533
|
-
usernameFilter,
|
|
534
|
-
usernameAttribute,
|
|
535
|
-
username,
|
|
536
|
-
starttls,
|
|
537
|
-
ldapOpts,
|
|
538
|
-
groupsSearchBase,
|
|
539
|
-
groupClass,
|
|
540
|
-
groupMemberAttribute = 'member',
|
|
541
|
-
groupMemberUserAttribute = 'dn',
|
|
542
|
-
attributes = null,
|
|
543
|
-
explicitBufferAttributes = null
|
|
544
|
-
) {
|
|
477
|
+
async function verifyUserExists(options) {
|
|
478
|
+
const { username, ldapOpts } = options
|
|
545
479
|
let ldapAdminClient
|
|
546
480
|
try {
|
|
547
|
-
ldapAdminClient = await _ldapBind(
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
ldapOpts
|
|
552
|
-
)
|
|
481
|
+
ldapAdminClient = await _ldapBind(options.adminDn, options.adminPassword, {
|
|
482
|
+
starttls: options.starttls,
|
|
483
|
+
ldapOpts,
|
|
484
|
+
})
|
|
553
485
|
} catch (error) {
|
|
554
|
-
if (ldapAdminClient && ldapAdminClient.isConnected) {
|
|
555
|
-
await ldapAdminClient.unbind()
|
|
556
|
-
}
|
|
557
486
|
return new AuthenticationResult(
|
|
558
487
|
AUTH_RESULT_FAILURE,
|
|
559
488
|
username,
|
|
@@ -563,209 +492,182 @@ async function verifyUserExists(
|
|
|
563
492
|
)
|
|
564
493
|
}
|
|
565
494
|
|
|
566
|
-
|
|
567
|
-
ldapAdminClient,
|
|
568
|
-
userSearchBase,
|
|
569
|
-
usernameFilter,
|
|
570
|
-
usernameAttribute,
|
|
571
|
-
username,
|
|
572
|
-
attributes,
|
|
573
|
-
explicitBufferAttributes
|
|
574
|
-
)
|
|
495
|
+
try {
|
|
496
|
+
let searchResult = await _searchUser(ldapAdminClient, options)
|
|
575
497
|
|
|
576
|
-
|
|
498
|
+
let user = searchResult.user
|
|
577
499
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
500
|
+
if (!user || !user.dn) {
|
|
501
|
+
ldapOpts.log &&
|
|
502
|
+
ldapOpts.log.trace(
|
|
503
|
+
`admin did not find user! (${options.usernameAttribute}=${username})`
|
|
504
|
+
)
|
|
505
|
+
return new AuthenticationResult(
|
|
506
|
+
AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND,
|
|
507
|
+
username,
|
|
508
|
+
null,
|
|
509
|
+
[
|
|
510
|
+
authenticationMessages.AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND,
|
|
511
|
+
'user not found or usernameAttribute is wrong',
|
|
512
|
+
],
|
|
513
|
+
ldapAdminClient
|
|
582
514
|
)
|
|
583
|
-
|
|
515
|
+
}
|
|
516
|
+
await _attachGroups(ldapAdminClient, user, options)
|
|
584
517
|
return new AuthenticationResult(
|
|
585
|
-
|
|
518
|
+
AUTH_RESULT_SUCCESS,
|
|
586
519
|
username,
|
|
587
|
-
|
|
588
|
-
[
|
|
589
|
-
authenticationMessages.AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND,
|
|
590
|
-
'user not found or usernameAttribute is wrong'
|
|
591
|
-
],
|
|
520
|
+
user,
|
|
521
|
+
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
592
522
|
ldapAdminClient
|
|
593
523
|
)
|
|
524
|
+
} finally {
|
|
525
|
+
await ldapAdminClient.unbind()
|
|
594
526
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// validate the options of authenticate()/authenticateResult() and throw a
|
|
530
|
+
// single LdapAuthenticationError listing all missing fields at once
|
|
531
|
+
function _validateOptions(options) {
|
|
532
|
+
if (!options) {
|
|
533
|
+
throw new LdapAuthenticationError('authenticate: options object is required')
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
let missing = []
|
|
537
|
+
if (!options.ldapOpts || !options.ldapOpts.url) {
|
|
538
|
+
missing.push('ldapOpts.url')
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (options.verifyUserExists) {
|
|
542
|
+
if (!options.adminDn) missing.push('adminDn')
|
|
543
|
+
if (!options.adminPassword) missing.push('adminPassword')
|
|
544
|
+
if (!options.userSearchBase) missing.push('userSearchBase')
|
|
545
|
+
if (!options.usernameAttribute && !options.usernameFilter) {
|
|
546
|
+
missing.push('usernameAttribute or usernameFilter')
|
|
547
|
+
}
|
|
548
|
+
if (!options.username) missing.push('username')
|
|
549
|
+
} else if (options.adminDn) {
|
|
550
|
+
if (!options.adminPassword) missing.push('adminPassword')
|
|
551
|
+
if (!options.userSearchBase) missing.push('userSearchBase')
|
|
552
|
+
if (!options.usernameAttribute && !options.usernameFilter) {
|
|
553
|
+
missing.push('usernameAttribute or usernameFilter')
|
|
554
|
+
}
|
|
555
|
+
if (!options.username) missing.push('username')
|
|
556
|
+
if (!options.userPassword) missing.push('userPassword')
|
|
557
|
+
} else if (options.userDn) {
|
|
558
|
+
if (!options.userPassword) missing.push('userPassword')
|
|
559
|
+
} else {
|
|
560
|
+
missing.push('adminDn or userDn')
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (missing.length > 0) {
|
|
564
|
+
throw new LdapAuthenticationError(
|
|
565
|
+
`authenticate: missing required option(s): ${missing.join(', ')}`
|
|
603
566
|
)
|
|
604
|
-
user.groups = groups
|
|
605
567
|
}
|
|
606
|
-
await ldapAdminClient.unbind()
|
|
607
|
-
return new AuthenticationResult(
|
|
608
|
-
AUTH_RESULT_SUCCESS,
|
|
609
|
-
username,
|
|
610
|
-
user,
|
|
611
|
-
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
612
|
-
ldapAdminClient
|
|
613
|
-
)
|
|
614
568
|
}
|
|
615
569
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
570
|
+
/**
|
|
571
|
+
* Fetch all users under `userSearchBase`, using the admin account to search.
|
|
572
|
+
* No individual username or password is required. The search always uses
|
|
573
|
+
* LDAP paged results, so the common server-side limit of 1000 entries does
|
|
574
|
+
* not apply. Returns an empty array if no user matches.
|
|
575
|
+
*
|
|
576
|
+
* @param {FetchUsersOptions} options - required: `ldapOpts` (with `url`),
|
|
577
|
+
* `adminDn`, `adminPassword`, `userSearchBase`; optional: `userFilter`,
|
|
578
|
+
* `attributes`, `explicitBufferAttributes`, `pageSize`, `starttls`.
|
|
579
|
+
* See the types in index.d.ts and the README for details.
|
|
580
|
+
* @returns {Promise<LdapUserEntry[]>} one entry per matched user, each with
|
|
581
|
+
* its `dn` and the returned attributes.
|
|
582
|
+
* @throws {LdapAuthenticationError} if required options are missing, or if
|
|
583
|
+
* the admin bind or the search fails.
|
|
584
|
+
*/
|
|
619
585
|
async function fetchUsers(options) {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
)
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
586
|
+
let missing = []
|
|
587
|
+
if (!options.ldapOpts || !options.ldapOpts.url) missing.push('ldapOpts.url')
|
|
588
|
+
if (!options.adminDn) missing.push('adminDn')
|
|
589
|
+
if (!options.adminPassword) missing.push('adminPassword')
|
|
590
|
+
if (!options.userSearchBase) missing.push('userSearchBase')
|
|
591
|
+
if (missing.length > 0) {
|
|
592
|
+
throw new LdapAuthenticationError(
|
|
593
|
+
`fetchUsers: missing required option(s): ${missing.join(', ')}`
|
|
594
|
+
)
|
|
595
|
+
}
|
|
627
596
|
|
|
628
597
|
let ldapAdminClient
|
|
629
598
|
try {
|
|
630
|
-
ldapAdminClient = await _ldapBind(
|
|
631
|
-
options.
|
|
632
|
-
options.
|
|
633
|
-
|
|
634
|
-
options.ldapOpts
|
|
635
|
-
)
|
|
599
|
+
ldapAdminClient = await _ldapBind(options.adminDn, options.adminPassword, {
|
|
600
|
+
starttls: options.starttls,
|
|
601
|
+
ldapOpts: options.ldapOpts,
|
|
602
|
+
})
|
|
636
603
|
} catch (error) {
|
|
637
|
-
if (ldapAdminClient && ldapAdminClient.isConnected) {
|
|
638
|
-
await ldapAdminClient.unbind()
|
|
639
|
-
}
|
|
640
604
|
throw new LdapAuthenticationError(error.message || 'admin bind failed')
|
|
641
605
|
}
|
|
642
606
|
|
|
643
607
|
try {
|
|
644
|
-
return await _fetchAllUsers(
|
|
645
|
-
ldapAdminClient,
|
|
646
|
-
options.userSearchBase,
|
|
647
|
-
options.userFilter,
|
|
648
|
-
options.attributes,
|
|
649
|
-
options.explicitBufferAttributes,
|
|
650
|
-
options.pageSize || DEFAULT_FETCH_USERS_PAGE_SIZE
|
|
651
|
-
)
|
|
608
|
+
return await _fetchAllUsers(ldapAdminClient, options)
|
|
652
609
|
} catch (error) {
|
|
653
610
|
throw new LdapAuthenticationError(error.message || 'user search failed')
|
|
654
611
|
} finally {
|
|
655
|
-
if (ldapAdminClient
|
|
612
|
+
if (ldapAdminClient) {
|
|
656
613
|
await ldapAdminClient.unbind()
|
|
657
614
|
}
|
|
658
615
|
}
|
|
659
616
|
}
|
|
660
617
|
|
|
618
|
+
/**
|
|
619
|
+
* Authenticate a user against the LDAP server.
|
|
620
|
+
*
|
|
621
|
+
* Modes (see the README for a full option reference):
|
|
622
|
+
* - Admin mode: `adminDn` + `adminPassword` + `userSearchBase` +
|
|
623
|
+
* `usernameAttribute` (or `usernameFilter`) + `username` + `userPassword`.
|
|
624
|
+
* The library binds as admin, finds the user's DN, then binds as the user.
|
|
625
|
+
* - Self mode: `userDn` + `userPassword`. Optionally `userSearchBase` and
|
|
626
|
+
* `usernameAttribute` to also return the user's details.
|
|
627
|
+
* - Verify mode: `verifyUserExists: true` with admin credentials; verifies
|
|
628
|
+
* that the user exists without checking the password.
|
|
629
|
+
*
|
|
630
|
+
* @param {AuthenticationOptions} options
|
|
631
|
+
* @returns {Promise<any>} the user object if authentication succeeded.
|
|
632
|
+
* @throws {LdapAuthenticationError} if authentication failed (its `code`
|
|
633
|
+
* property then holds the corresponding AUTH_RESULT_* constant) or if
|
|
634
|
+
* required options are missing.
|
|
635
|
+
*/
|
|
661
636
|
async function authenticate(options) {
|
|
662
637
|
const result = await authenticateResult(options)
|
|
663
638
|
|
|
664
639
|
if (result.code !== AUTH_RESULT_SUCCESS) {
|
|
665
640
|
throw new LdapAuthenticationError(
|
|
666
|
-
result.messages[result.messages.length - 1]
|
|
641
|
+
result.messages[result.messages.length - 1],
|
|
642
|
+
result.code
|
|
667
643
|
)
|
|
668
644
|
}
|
|
669
645
|
|
|
670
646
|
return result.user
|
|
671
647
|
}
|
|
672
648
|
|
|
649
|
+
/**
|
|
650
|
+
* Same options and behavior as {@link authenticate}, but never throws on
|
|
651
|
+
* authentication failure - it returns an {@link AuthenticationResult} whose
|
|
652
|
+
* `code` identifies the outcome (useful for custom error handling).
|
|
653
|
+
*
|
|
654
|
+
* @param {AuthenticationOptions} options
|
|
655
|
+
* @returns {Promise<AuthenticationResult>}
|
|
656
|
+
* @throws {LdapAuthenticationError} if required options are missing;
|
|
657
|
+
* network errors from the LDAP server propagate as-is.
|
|
658
|
+
*/
|
|
673
659
|
async function authenticateResult(options) {
|
|
674
|
-
|
|
675
|
-
assert(options.adminDn, 'Admin mode adminDn must be provided')
|
|
676
|
-
assert(options.adminPassword, 'Admin mode adminPassword must be provided')
|
|
677
|
-
assert(options.userSearchBase, 'Admin mode userSearchBase must be provided')
|
|
678
|
-
assert(
|
|
679
|
-
options.usernameAttribute || options.usernameFilter,
|
|
680
|
-
'Admin mode usernameAttribute or usernameFilter must be provided'
|
|
681
|
-
)
|
|
682
|
-
assert(options.username, 'Admin mode username must be provided')
|
|
683
|
-
} else {
|
|
684
|
-
assert(options.userDn, 'User mode userDn must be provided')
|
|
685
|
-
}
|
|
686
|
-
assert(
|
|
687
|
-
options.ldapOpts && options.ldapOpts.url,
|
|
688
|
-
'ldapOpts.url must be provided'
|
|
689
|
-
)
|
|
660
|
+
_validateOptions(options)
|
|
690
661
|
|
|
691
662
|
if (options.verifyUserExists) {
|
|
692
|
-
|
|
693
|
-
assert(
|
|
694
|
-
options.adminPassword,
|
|
695
|
-
'adminDn and adminPassword must be both provided.'
|
|
696
|
-
)
|
|
697
|
-
return await verifyUserExists(
|
|
698
|
-
options.adminDn,
|
|
699
|
-
options.adminPassword,
|
|
700
|
-
options.userSearchBase,
|
|
701
|
-
options.usernameFilter,
|
|
702
|
-
options.usernameAttribute,
|
|
703
|
-
options.username,
|
|
704
|
-
options.starttls,
|
|
705
|
-
options.ldapOpts,
|
|
706
|
-
options.groupsSearchBase,
|
|
707
|
-
options.groupClass,
|
|
708
|
-
options.groupMemberAttribute,
|
|
709
|
-
options.groupMemberUserAttribute,
|
|
710
|
-
options.attributes,
|
|
711
|
-
options.explicitBufferAttributes
|
|
712
|
-
)
|
|
663
|
+
return await verifyUserExists(options)
|
|
713
664
|
}
|
|
714
665
|
|
|
715
|
-
assert(options.userPassword, 'userPassword must be provided')
|
|
716
666
|
if (options.adminDn) {
|
|
717
|
-
|
|
718
|
-
options.adminPassword,
|
|
719
|
-
'adminDn and adminPassword must be both provided.'
|
|
720
|
-
)
|
|
721
|
-
return await authenticateWithAdmin(
|
|
722
|
-
options.adminDn,
|
|
723
|
-
options.adminPassword,
|
|
724
|
-
options.userSearchBase,
|
|
725
|
-
options.usernameFilter,
|
|
726
|
-
options.usernameAttribute,
|
|
727
|
-
options.username,
|
|
728
|
-
options.userPassword,
|
|
729
|
-
options.starttls,
|
|
730
|
-
options.ldapOpts,
|
|
731
|
-
options.groupsSearchBase,
|
|
732
|
-
options.groupClass,
|
|
733
|
-
options.groupMemberAttribute,
|
|
734
|
-
options.groupMemberUserAttribute,
|
|
735
|
-
options.attributes,
|
|
736
|
-
options.explicitBufferAttributes
|
|
737
|
-
)
|
|
667
|
+
return await authenticateWithAdmin(options)
|
|
738
668
|
}
|
|
739
669
|
|
|
740
|
-
|
|
741
|
-
return await authenticateWithUser(
|
|
742
|
-
options.userDn,
|
|
743
|
-
options.userSearchBase,
|
|
744
|
-
options.usernameFilter,
|
|
745
|
-
options.usernameAttribute,
|
|
746
|
-
options.username,
|
|
747
|
-
options.userPassword,
|
|
748
|
-
options.starttls,
|
|
749
|
-
options.ldapOpts,
|
|
750
|
-
options.groupsSearchBase,
|
|
751
|
-
options.groupClass,
|
|
752
|
-
options.groupMemberAttribute,
|
|
753
|
-
options.groupMemberUserAttribute,
|
|
754
|
-
options.attributes,
|
|
755
|
-
options.explicitBufferAttributes
|
|
756
|
-
)
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
class LdapAuthenticationError extends Error {
|
|
760
|
-
constructor(message) {
|
|
761
|
-
super(message)
|
|
762
|
-
// Ensure the name of this error is the same as the class name
|
|
763
|
-
this.name = this.constructor.name
|
|
764
|
-
// This clips the constructor invocation from the stack trace.
|
|
765
|
-
// It's not absolutely essential, but it does make the stack trace a little nicer.
|
|
766
|
-
// @see Node.js reference (bottom)
|
|
767
|
-
Error.captureStackTrace(this, this.constructor)
|
|
768
|
-
}
|
|
670
|
+
return await authenticateWithUser(options)
|
|
769
671
|
}
|
|
770
672
|
|
|
771
673
|
module.exports.AUTH_RESULT_FAILURE = AUTH_RESULT_FAILURE
|