ldap-authentication 4.3.0 → 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 +8 -6
- package/README.md +7 -0
- package/docker/ldap/10-ldap-test-data.ldif +14 -0
- package/index.d.ts +18 -3
- package/index.js +330 -469
- package/package.json +1 -1
- 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,27 @@ 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
|
+
|
|
51
72
|
/**
|
|
52
73
|
* Result object returned by {@link authenticateResult}. Inspect `code` (one
|
|
53
74
|
* of the AUTH_RESULT_* constants) and `messages` to classify failures.
|
|
@@ -97,11 +118,17 @@ const authenticationMessages = {
|
|
|
97
118
|
AUTH_RESULT_FAILURE_UNCATEGORIZED: 'Uncategorized authentication failure',
|
|
98
119
|
}
|
|
99
120
|
|
|
100
|
-
// bind and return the ldap client
|
|
101
|
-
|
|
102
|
-
|
|
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)
|
|
103
129
|
dn = _ldapEscapeDN(dn)
|
|
104
|
-
|
|
130
|
+
const opts = { ...ldapOpts }
|
|
131
|
+
opts.connectTimeout = ldapOpts.connectTimeout || 5000
|
|
105
132
|
|
|
106
133
|
// When using StartTLS, we need to exclude tlsOptions from the Client constructor
|
|
107
134
|
// and only pass them to the startTLS() method to avoid connection conflicts.
|
|
@@ -109,74 +136,93 @@ async function _ldapBind(dn, password, starttls, ldapOpts) {
|
|
|
109
136
|
// - For LDAPS (ldaps://): pass tlsOptions to Client constructor
|
|
110
137
|
// - For StartTLS (ldap://): do NOT pass tlsOptions to Client constructor, only to startTLS()
|
|
111
138
|
// - For plain LDAP (ldap://): do NOT pass tlsOptions to Client constructor
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// Only pass tlsOptions to Client constructor if using ldaps:// protocol
|
|
116
|
-
// For ldap:// protocol (plain or StartTLS), exclude tlsOptions from constructor
|
|
117
|
-
if (!isLdaps && ldapOpts.tlsOptions) {
|
|
118
|
-
// Create a shallow copy of ldapOpts without tlsOptions for the Client constructor
|
|
119
|
-
const { tlsOptions, ...optsWithoutTls } = ldapOpts
|
|
120
|
-
clientOpts = optsWithoutTls
|
|
139
|
+
const isLdaps = opts.url && opts.url.startsWith('ldaps://')
|
|
140
|
+
if (!isLdaps) {
|
|
141
|
+
delete opts.tlsOptions
|
|
121
142
|
}
|
|
122
143
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
127
159
|
}
|
|
128
|
-
|
|
129
|
-
await client.bind(dn, password)
|
|
130
160
|
ldapOpts.log && ldapOpts.log.trace('bind success!')
|
|
131
161
|
return client
|
|
132
162
|
}
|
|
133
163
|
|
|
134
|
-
//
|
|
135
|
-
|
|
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
|
+
}
|
|
136
188
|
|
|
137
189
|
// search a user and return the object
|
|
138
|
-
async function _searchUser(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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 {
|
|
152
207
|
filter = new ldapts.EqualityFilter({
|
|
153
208
|
attribute: usernameAttribute,
|
|
154
209
|
value: username,
|
|
155
210
|
})
|
|
156
211
|
}
|
|
157
|
-
|
|
212
|
+
|
|
158
213
|
let searchOptions = {
|
|
159
214
|
filter: filter,
|
|
160
215
|
scope: 'sub',
|
|
161
|
-
attributes: attributes,
|
|
162
216
|
}
|
|
163
217
|
if (attributes) {
|
|
164
218
|
searchOptions.attributes = attributes
|
|
165
219
|
}
|
|
166
|
-
if(explicitBufferAttributes) {
|
|
220
|
+
if (explicitBufferAttributes) {
|
|
167
221
|
searchOptions.explicitBufferAttributes = explicitBufferAttributes
|
|
168
222
|
}
|
|
169
223
|
|
|
170
224
|
// TODO: we don't support reference yet
|
|
171
|
-
|
|
172
|
-
// but could not search one or more non-local entries,
|
|
173
|
-
// the server may return one or more SearchResultReference messages,
|
|
174
|
-
// each containing a reference to another set of servers for continuing the operation.
|
|
175
|
-
// referral.uris
|
|
176
|
-
const { searchEntries, searchReferences } = await ldapClient.search(
|
|
177
|
-
searchBase,
|
|
178
|
-
searchOptions
|
|
179
|
-
)
|
|
225
|
+
const { searchEntries } = await client.search(userSearchBase, searchOptions)
|
|
180
226
|
|
|
181
227
|
let user
|
|
182
228
|
if (
|
|
@@ -185,64 +231,52 @@ async function _searchUser(
|
|
|
185
231
|
!searchEntries[0] ||
|
|
186
232
|
!searchEntries[0].dn
|
|
187
233
|
) {
|
|
234
|
+
user = null
|
|
235
|
+
} else if (searchEntries.length > 1) {
|
|
188
236
|
return new AuthenticationResult(
|
|
189
|
-
|
|
237
|
+
AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS,
|
|
190
238
|
username,
|
|
191
239
|
null,
|
|
192
|
-
[authenticationMessages.
|
|
193
|
-
|
|
240
|
+
[authenticationMessages.AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS],
|
|
241
|
+
client
|
|
194
242
|
)
|
|
195
243
|
} else {
|
|
196
|
-
if (searchEntries.length > 1) {
|
|
197
|
-
return new AuthenticationResult(
|
|
198
|
-
AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS,
|
|
199
|
-
username,
|
|
200
|
-
null,
|
|
201
|
-
[authenticationMessages.AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS],
|
|
202
|
-
ldapClient
|
|
203
|
-
)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
244
|
user = searchEntries[0]
|
|
207
245
|
}
|
|
208
246
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// when attribute is one of the explicitBufferAttributes, should convert to base64 string
|
|
218
|
-
if (user != null && explicitBufferAttributes != null) {
|
|
219
|
-
for (let attr of explicitBufferAttributes) {
|
|
220
|
-
if (Buffer.isBuffer(user[attr])) {
|
|
221
|
-
user[attr] = user[attr].toString('base64')
|
|
222
|
-
}
|
|
223
|
-
}
|
|
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
|
+
)
|
|
224
255
|
}
|
|
225
256
|
|
|
257
|
+
_toBase64Attributes(user, attributes, explicitBufferAttributes)
|
|
226
258
|
return new AuthenticationResult(
|
|
227
259
|
AUTH_RESULT_SUCCESS,
|
|
228
260
|
username,
|
|
229
261
|
user,
|
|
230
262
|
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
231
|
-
|
|
263
|
+
client
|
|
232
264
|
)
|
|
233
265
|
}
|
|
234
266
|
|
|
235
|
-
// search
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
+
|
|
246
280
|
const filter = new ldapts.AndFilter({
|
|
247
281
|
filters: [
|
|
248
282
|
new ldapts.EqualityFilter({
|
|
@@ -256,20 +290,12 @@ async function _searchUserGroups(
|
|
|
256
290
|
],
|
|
257
291
|
})
|
|
258
292
|
|
|
259
|
-
const { searchEntries
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
scope: 'sub',
|
|
264
|
-
}
|
|
265
|
-
)
|
|
293
|
+
const { searchEntries } = await client.search(groupsSearchBase, {
|
|
294
|
+
filter: filter,
|
|
295
|
+
scope: 'sub',
|
|
296
|
+
})
|
|
266
297
|
|
|
267
|
-
let groups
|
|
268
|
-
if (!searchEntries || searchEntries.length < 1) {
|
|
269
|
-
groups = []
|
|
270
|
-
} else {
|
|
271
|
-
groups = searchEntries
|
|
272
|
-
}
|
|
298
|
+
let groups = searchEntries || []
|
|
273
299
|
// ldapjs has group.objectName, ldapts does not have it. instead, use dn
|
|
274
300
|
// add objectName back for backward compatibility
|
|
275
301
|
for (let group of groups) {
|
|
@@ -277,22 +303,21 @@ async function _searchUserGroups(
|
|
|
277
303
|
group.objectName = group.dn
|
|
278
304
|
}
|
|
279
305
|
}
|
|
280
|
-
|
|
306
|
+
user.groups = groups
|
|
281
307
|
}
|
|
282
308
|
|
|
283
309
|
// search all users under the search base and return the list of user objects
|
|
284
|
-
async function _fetchAllUsers(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
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
|
|
293
318
|
|
|
294
319
|
let searchOptions = {
|
|
295
|
-
filter:
|
|
320
|
+
filter: userFilter || DEFAULT_FETCH_USERS_FILTER,
|
|
296
321
|
scope: 'sub',
|
|
297
322
|
// always use paged results, so more than the usual server-side limit
|
|
298
323
|
// (usually 1000 entries per page) can be returned
|
|
@@ -305,60 +330,24 @@ async function _fetchAllUsers(
|
|
|
305
330
|
searchOptions.explicitBufferAttributes = explicitBufferAttributes
|
|
306
331
|
}
|
|
307
332
|
|
|
308
|
-
const { searchEntries } = await
|
|
333
|
+
const { searchEntries } = await client.search(userSearchBase, searchOptions)
|
|
309
334
|
|
|
310
335
|
let users = searchEntries || []
|
|
311
|
-
// when attribute endwith ;binary, ldapts returns Buffer, we convert them into base64 string
|
|
312
336
|
for (let user of users) {
|
|
313
|
-
|
|
314
|
-
for (let attr of attributes) {
|
|
315
|
-
if (attr.endsWith(';binary') && Buffer.isBuffer(user[attr])) {
|
|
316
|
-
user[attr] = user[attr].toString('base64')
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
// when attribute is one of the explicitBufferAttributes, should convert to base64 string
|
|
321
|
-
if (user != null && explicitBufferAttributes != null) {
|
|
322
|
-
for (let attr of explicitBufferAttributes) {
|
|
323
|
-
if (Buffer.isBuffer(user[attr])) {
|
|
324
|
-
user[attr] = user[attr].toString('base64')
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
337
|
+
_toBase64Attributes(user, attributes, explicitBufferAttributes)
|
|
328
338
|
}
|
|
329
339
|
return users
|
|
330
340
|
}
|
|
331
341
|
|
|
332
|
-
async function authenticateWithAdmin(
|
|
333
|
-
|
|
334
|
-
adminPassword,
|
|
335
|
-
userSearchBase,
|
|
336
|
-
usernameFilter,
|
|
337
|
-
usernameAttribute,
|
|
338
|
-
username,
|
|
339
|
-
userPassword,
|
|
340
|
-
starttls,
|
|
341
|
-
ldapOpts,
|
|
342
|
-
groupsSearchBase,
|
|
343
|
-
groupClass,
|
|
344
|
-
groupMemberAttribute = 'member',
|
|
345
|
-
groupMemberUserAttribute = 'dn',
|
|
346
|
-
attributes = null,
|
|
347
|
-
explicitBufferAttributes = null
|
|
348
|
-
) {
|
|
342
|
+
async function authenticateWithAdmin(options) {
|
|
343
|
+
const { username, ldapOpts } = options
|
|
349
344
|
let ldapAdminClient
|
|
350
345
|
try {
|
|
351
|
-
ldapAdminClient = await _ldapBind(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
ldapOpts
|
|
356
|
-
)
|
|
346
|
+
ldapAdminClient = await _ldapBind(options.adminDn, options.adminPassword, {
|
|
347
|
+
starttls: options.starttls,
|
|
348
|
+
ldapOpts,
|
|
349
|
+
})
|
|
357
350
|
} catch (error) {
|
|
358
|
-
if (ldapAdminClient && ldapAdminClient.isConnected) {
|
|
359
|
-
await ldapAdminClient.unbind()
|
|
360
|
-
}
|
|
361
|
-
|
|
362
351
|
return new AuthenticationResult(
|
|
363
352
|
AUTH_RESULT_FAILURE,
|
|
364
353
|
username,
|
|
@@ -368,96 +357,69 @@ async function authenticateWithAdmin(
|
|
|
368
357
|
)
|
|
369
358
|
}
|
|
370
359
|
|
|
371
|
-
|
|
372
|
-
ldapAdminClient,
|
|
373
|
-
userSearchBase,
|
|
374
|
-
usernameFilter,
|
|
375
|
-
usernameAttribute,
|
|
376
|
-
username,
|
|
377
|
-
attributes,
|
|
378
|
-
explicitBufferAttributes
|
|
379
|
-
)
|
|
360
|
+
try {
|
|
361
|
+
let searchResult = await _searchUser(ldapAdminClient, options)
|
|
380
362
|
|
|
381
|
-
|
|
363
|
+
let user = searchResult.user
|
|
382
364
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
|
387
376
|
)
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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 {
|
|
403
407
|
await ldapUserClient.unbind()
|
|
404
408
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
AUTH_RESULT_FAILURE_CREDENTIAL_INVALID,
|
|
408
|
-
username,
|
|
409
|
-
null,
|
|
410
|
-
[authenticationMessages.AUTH_RESULT_FAILURE_CREDENTIAL_INVALID, error.message || 'invalid credentials'],
|
|
411
|
-
ldapAdminClient
|
|
412
|
-
)
|
|
413
|
-
}
|
|
414
|
-
if (groupsSearchBase && groupClass && groupMemberAttribute) {
|
|
415
|
-
let groups = await _searchUserGroups(
|
|
416
|
-
ldapAdminClient,
|
|
417
|
-
groupsSearchBase,
|
|
418
|
-
user,
|
|
419
|
-
groupClass,
|
|
420
|
-
groupMemberAttribute,
|
|
421
|
-
groupMemberUserAttribute
|
|
422
|
-
)
|
|
423
|
-
user.groups = groups
|
|
409
|
+
} finally {
|
|
410
|
+
await ldapAdminClient.unbind()
|
|
424
411
|
}
|
|
425
|
-
await ldapAdminClient.unbind()
|
|
426
|
-
await ldapUserClient.unbind()
|
|
427
|
-
|
|
428
|
-
return new AuthenticationResult(
|
|
429
|
-
AUTH_RESULT_SUCCESS,
|
|
430
|
-
username,
|
|
431
|
-
user,
|
|
432
|
-
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
433
|
-
ldapAdminClient
|
|
434
|
-
)
|
|
435
412
|
}
|
|
436
413
|
|
|
437
|
-
async function authenticateWithUser(
|
|
438
|
-
|
|
439
|
-
userSearchBase,
|
|
440
|
-
usernameFilter,
|
|
441
|
-
usernameAttribute,
|
|
442
|
-
username,
|
|
443
|
-
userPassword,
|
|
444
|
-
starttls,
|
|
445
|
-
ldapOpts,
|
|
446
|
-
groupsSearchBase,
|
|
447
|
-
groupClass,
|
|
448
|
-
groupMemberAttribute = 'member',
|
|
449
|
-
groupMemberUserAttribute = 'dn',
|
|
450
|
-
attributes = null,
|
|
451
|
-
explicitBufferAttributes = null
|
|
452
|
-
) {
|
|
414
|
+
async function authenticateWithUser(options) {
|
|
415
|
+
const { username, usernameAttribute, userSearchBase, ldapOpts } = options
|
|
453
416
|
let ldapUserClient
|
|
454
417
|
try {
|
|
455
|
-
ldapUserClient = await _ldapBind(userDn, userPassword,
|
|
418
|
+
ldapUserClient = await _ldapBind(options.userDn, options.userPassword, {
|
|
419
|
+
starttls: options.starttls,
|
|
420
|
+
ldapOpts,
|
|
421
|
+
})
|
|
456
422
|
} catch (error) {
|
|
457
|
-
if (ldapUserClient && ldapUserClient.isConnected) {
|
|
458
|
-
await ldapUserClient.unbind()
|
|
459
|
-
}
|
|
460
|
-
|
|
461
423
|
return new AuthenticationResult(
|
|
462
424
|
AUTH_RESULT_FAILURE,
|
|
463
425
|
username,
|
|
@@ -466,98 +428,61 @@ async function authenticateWithUser(
|
|
|
466
428
|
ldapUserClient
|
|
467
429
|
)
|
|
468
430
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
+
}
|
|
480
442
|
|
|
481
|
-
|
|
482
|
-
ldapUserClient,
|
|
483
|
-
userSearchBase,
|
|
484
|
-
usernameFilter,
|
|
485
|
-
usernameAttribute,
|
|
486
|
-
username,
|
|
487
|
-
attributes,
|
|
488
|
-
explicitBufferAttributes
|
|
489
|
-
)
|
|
443
|
+
let searchResult = await _searchUser(ldapUserClient, options)
|
|
490
444
|
|
|
491
|
-
|
|
445
|
+
let user = searchResult.user
|
|
492
446
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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
|
|
497
461
|
)
|
|
498
|
-
|
|
462
|
+
}
|
|
463
|
+
await _attachGroups(ldapUserClient, user, options)
|
|
499
464
|
|
|
500
465
|
return new AuthenticationResult(
|
|
501
|
-
|
|
466
|
+
AUTH_RESULT_SUCCESS,
|
|
502
467
|
username,
|
|
503
|
-
null,
|
|
504
|
-
[
|
|
505
|
-
authenticationMessages.AUTH_RESULT_FAILURE,
|
|
506
|
-
'user logged in, but user details could not be found. Probabaly usernameAttribute or userSearchBase is wrong?',
|
|
507
|
-
],
|
|
508
|
-
ldapUserClient
|
|
509
|
-
)
|
|
510
|
-
}
|
|
511
|
-
if (groupsSearchBase && groupClass && groupMemberAttribute) {
|
|
512
|
-
let groups = await _searchUserGroups(
|
|
513
|
-
ldapUserClient,
|
|
514
|
-
groupsSearchBase,
|
|
515
468
|
user,
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
groupMemberUserAttribute
|
|
469
|
+
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
470
|
+
ldapUserClient
|
|
519
471
|
)
|
|
520
|
-
|
|
472
|
+
} finally {
|
|
473
|
+
await ldapUserClient.unbind()
|
|
521
474
|
}
|
|
522
|
-
await ldapUserClient.unbind()
|
|
523
|
-
|
|
524
|
-
return new AuthenticationResult(
|
|
525
|
-
AUTH_RESULT_SUCCESS,
|
|
526
|
-
username,
|
|
527
|
-
user,
|
|
528
|
-
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
529
|
-
ldapUserClient
|
|
530
|
-
)
|
|
531
475
|
}
|
|
532
476
|
|
|
533
|
-
async function verifyUserExists(
|
|
534
|
-
|
|
535
|
-
adminPassword,
|
|
536
|
-
userSearchBase,
|
|
537
|
-
usernameFilter,
|
|
538
|
-
usernameAttribute,
|
|
539
|
-
username,
|
|
540
|
-
starttls,
|
|
541
|
-
ldapOpts,
|
|
542
|
-
groupsSearchBase,
|
|
543
|
-
groupClass,
|
|
544
|
-
groupMemberAttribute = 'member',
|
|
545
|
-
groupMemberUserAttribute = 'dn',
|
|
546
|
-
attributes = null,
|
|
547
|
-
explicitBufferAttributes = null
|
|
548
|
-
) {
|
|
477
|
+
async function verifyUserExists(options) {
|
|
478
|
+
const { username, ldapOpts } = options
|
|
549
479
|
let ldapAdminClient
|
|
550
480
|
try {
|
|
551
|
-
ldapAdminClient = await _ldapBind(
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
ldapOpts
|
|
556
|
-
)
|
|
481
|
+
ldapAdminClient = await _ldapBind(options.adminDn, options.adminPassword, {
|
|
482
|
+
starttls: options.starttls,
|
|
483
|
+
ldapOpts,
|
|
484
|
+
})
|
|
557
485
|
} catch (error) {
|
|
558
|
-
if (ldapAdminClient && ldapAdminClient.isConnected) {
|
|
559
|
-
await ldapAdminClient.unbind()
|
|
560
|
-
}
|
|
561
486
|
return new AuthenticationResult(
|
|
562
487
|
AUTH_RESULT_FAILURE,
|
|
563
488
|
username,
|
|
@@ -567,54 +492,79 @@ async function verifyUserExists(
|
|
|
567
492
|
)
|
|
568
493
|
}
|
|
569
494
|
|
|
570
|
-
|
|
571
|
-
ldapAdminClient,
|
|
572
|
-
userSearchBase,
|
|
573
|
-
usernameFilter,
|
|
574
|
-
usernameAttribute,
|
|
575
|
-
username,
|
|
576
|
-
attributes,
|
|
577
|
-
explicitBufferAttributes
|
|
578
|
-
)
|
|
495
|
+
try {
|
|
496
|
+
let searchResult = await _searchUser(ldapAdminClient, options)
|
|
579
497
|
|
|
580
|
-
|
|
498
|
+
let user = searchResult.user
|
|
581
499
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
|
586
514
|
)
|
|
587
|
-
|
|
515
|
+
}
|
|
516
|
+
await _attachGroups(ldapAdminClient, user, options)
|
|
588
517
|
return new AuthenticationResult(
|
|
589
|
-
|
|
518
|
+
AUTH_RESULT_SUCCESS,
|
|
590
519
|
username,
|
|
591
|
-
|
|
592
|
-
[
|
|
593
|
-
authenticationMessages.AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND,
|
|
594
|
-
'user not found or usernameAttribute is wrong'
|
|
595
|
-
],
|
|
520
|
+
user,
|
|
521
|
+
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
596
522
|
ldapAdminClient
|
|
597
523
|
)
|
|
524
|
+
} finally {
|
|
525
|
+
await ldapAdminClient.unbind()
|
|
598
526
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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(', ')}`
|
|
607
566
|
)
|
|
608
|
-
user.groups = groups
|
|
609
567
|
}
|
|
610
|
-
await ldapAdminClient.unbind()
|
|
611
|
-
return new AuthenticationResult(
|
|
612
|
-
AUTH_RESULT_SUCCESS,
|
|
613
|
-
username,
|
|
614
|
-
user,
|
|
615
|
-
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
616
|
-
ldapAdminClient
|
|
617
|
-
)
|
|
618
568
|
}
|
|
619
569
|
|
|
620
570
|
/**
|
|
@@ -629,45 +579,37 @@ async function verifyUserExists(
|
|
|
629
579
|
* See the types in index.d.ts and the README for details.
|
|
630
580
|
* @returns {Promise<LdapUserEntry[]>} one entry per matched user, each with
|
|
631
581
|
* its `dn` and the returned attributes.
|
|
632
|
-
* @throws {LdapAuthenticationError} if
|
|
582
|
+
* @throws {LdapAuthenticationError} if required options are missing, or if
|
|
583
|
+
* the admin bind or the search fails.
|
|
633
584
|
*/
|
|
634
585
|
async function fetchUsers(options) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
)
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
+
}
|
|
642
596
|
|
|
643
597
|
let ldapAdminClient
|
|
644
598
|
try {
|
|
645
|
-
ldapAdminClient = await _ldapBind(
|
|
646
|
-
options.
|
|
647
|
-
options.
|
|
648
|
-
|
|
649
|
-
options.ldapOpts
|
|
650
|
-
)
|
|
599
|
+
ldapAdminClient = await _ldapBind(options.adminDn, options.adminPassword, {
|
|
600
|
+
starttls: options.starttls,
|
|
601
|
+
ldapOpts: options.ldapOpts,
|
|
602
|
+
})
|
|
651
603
|
} catch (error) {
|
|
652
|
-
if (ldapAdminClient && ldapAdminClient.isConnected) {
|
|
653
|
-
await ldapAdminClient.unbind()
|
|
654
|
-
}
|
|
655
604
|
throw new LdapAuthenticationError(error.message || 'admin bind failed')
|
|
656
605
|
}
|
|
657
606
|
|
|
658
607
|
try {
|
|
659
|
-
return await _fetchAllUsers(
|
|
660
|
-
ldapAdminClient,
|
|
661
|
-
options.userSearchBase,
|
|
662
|
-
options.userFilter,
|
|
663
|
-
options.attributes,
|
|
664
|
-
options.explicitBufferAttributes,
|
|
665
|
-
options.pageSize || DEFAULT_FETCH_USERS_PAGE_SIZE
|
|
666
|
-
)
|
|
608
|
+
return await _fetchAllUsers(ldapAdminClient, options)
|
|
667
609
|
} catch (error) {
|
|
668
610
|
throw new LdapAuthenticationError(error.message || 'user search failed')
|
|
669
611
|
} finally {
|
|
670
|
-
if (ldapAdminClient
|
|
612
|
+
if (ldapAdminClient) {
|
|
671
613
|
await ldapAdminClient.unbind()
|
|
672
614
|
}
|
|
673
615
|
}
|
|
@@ -687,14 +629,17 @@ async function fetchUsers(options) {
|
|
|
687
629
|
*
|
|
688
630
|
* @param {AuthenticationOptions} options
|
|
689
631
|
* @returns {Promise<any>} the user object if authentication succeeded.
|
|
690
|
-
* @throws {LdapAuthenticationError} if authentication failed
|
|
632
|
+
* @throws {LdapAuthenticationError} if authentication failed (its `code`
|
|
633
|
+
* property then holds the corresponding AUTH_RESULT_* constant) or if
|
|
634
|
+
* required options are missing.
|
|
691
635
|
*/
|
|
692
636
|
async function authenticate(options) {
|
|
693
637
|
const result = await authenticateResult(options)
|
|
694
638
|
|
|
695
639
|
if (result.code !== AUTH_RESULT_SUCCESS) {
|
|
696
640
|
throw new LdapAuthenticationError(
|
|
697
|
-
result.messages[result.messages.length - 1]
|
|
641
|
+
result.messages[result.messages.length - 1],
|
|
642
|
+
result.code
|
|
698
643
|
)
|
|
699
644
|
}
|
|
700
645
|
|
|
@@ -708,105 +653,21 @@ async function authenticate(options) {
|
|
|
708
653
|
*
|
|
709
654
|
* @param {AuthenticationOptions} options
|
|
710
655
|
* @returns {Promise<AuthenticationResult>}
|
|
711
|
-
* @throws {LdapAuthenticationError
|
|
656
|
+
* @throws {LdapAuthenticationError} if required options are missing;
|
|
657
|
+
* network errors from the LDAP server propagate as-is.
|
|
712
658
|
*/
|
|
713
659
|
async function authenticateResult(options) {
|
|
714
|
-
|
|
715
|
-
assert(options.adminDn, 'Admin mode adminDn must be provided')
|
|
716
|
-
assert(options.adminPassword, 'Admin mode adminPassword must be provided')
|
|
717
|
-
assert(options.userSearchBase, 'Admin mode userSearchBase must be provided')
|
|
718
|
-
assert(
|
|
719
|
-
options.usernameAttribute || options.usernameFilter,
|
|
720
|
-
'Admin mode usernameAttribute or usernameFilter must be provided'
|
|
721
|
-
)
|
|
722
|
-
assert(options.username, 'Admin mode username must be provided')
|
|
723
|
-
} else {
|
|
724
|
-
assert(options.userDn, 'User mode userDn must be provided')
|
|
725
|
-
}
|
|
726
|
-
assert(
|
|
727
|
-
options.ldapOpts && options.ldapOpts.url,
|
|
728
|
-
'ldapOpts.url must be provided'
|
|
729
|
-
)
|
|
660
|
+
_validateOptions(options)
|
|
730
661
|
|
|
731
662
|
if (options.verifyUserExists) {
|
|
732
|
-
|
|
733
|
-
assert(
|
|
734
|
-
options.adminPassword,
|
|
735
|
-
'adminDn and adminPassword must be both provided.'
|
|
736
|
-
)
|
|
737
|
-
return await verifyUserExists(
|
|
738
|
-
options.adminDn,
|
|
739
|
-
options.adminPassword,
|
|
740
|
-
options.userSearchBase,
|
|
741
|
-
options.usernameFilter,
|
|
742
|
-
options.usernameAttribute,
|
|
743
|
-
options.username,
|
|
744
|
-
options.starttls,
|
|
745
|
-
options.ldapOpts,
|
|
746
|
-
options.groupsSearchBase,
|
|
747
|
-
options.groupClass,
|
|
748
|
-
options.groupMemberAttribute,
|
|
749
|
-
options.groupMemberUserAttribute,
|
|
750
|
-
options.attributes,
|
|
751
|
-
options.explicitBufferAttributes
|
|
752
|
-
)
|
|
663
|
+
return await verifyUserExists(options)
|
|
753
664
|
}
|
|
754
665
|
|
|
755
|
-
assert(options.userPassword, 'userPassword must be provided')
|
|
756
666
|
if (options.adminDn) {
|
|
757
|
-
|
|
758
|
-
options.adminPassword,
|
|
759
|
-
'adminDn and adminPassword must be both provided.'
|
|
760
|
-
)
|
|
761
|
-
return await authenticateWithAdmin(
|
|
762
|
-
options.adminDn,
|
|
763
|
-
options.adminPassword,
|
|
764
|
-
options.userSearchBase,
|
|
765
|
-
options.usernameFilter,
|
|
766
|
-
options.usernameAttribute,
|
|
767
|
-
options.username,
|
|
768
|
-
options.userPassword,
|
|
769
|
-
options.starttls,
|
|
770
|
-
options.ldapOpts,
|
|
771
|
-
options.groupsSearchBase,
|
|
772
|
-
options.groupClass,
|
|
773
|
-
options.groupMemberAttribute,
|
|
774
|
-
options.groupMemberUserAttribute,
|
|
775
|
-
options.attributes,
|
|
776
|
-
options.explicitBufferAttributes
|
|
777
|
-
)
|
|
667
|
+
return await authenticateWithAdmin(options)
|
|
778
668
|
}
|
|
779
669
|
|
|
780
|
-
|
|
781
|
-
return await authenticateWithUser(
|
|
782
|
-
options.userDn,
|
|
783
|
-
options.userSearchBase,
|
|
784
|
-
options.usernameFilter,
|
|
785
|
-
options.usernameAttribute,
|
|
786
|
-
options.username,
|
|
787
|
-
options.userPassword,
|
|
788
|
-
options.starttls,
|
|
789
|
-
options.ldapOpts,
|
|
790
|
-
options.groupsSearchBase,
|
|
791
|
-
options.groupClass,
|
|
792
|
-
options.groupMemberAttribute,
|
|
793
|
-
options.groupMemberUserAttribute,
|
|
794
|
-
options.attributes,
|
|
795
|
-
options.explicitBufferAttributes
|
|
796
|
-
)
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
/** Thrown by authenticate()/authenticateResult()/fetchUsers() on failure; `message` describes the failure. */
|
|
800
|
-
class LdapAuthenticationError extends Error {
|
|
801
|
-
constructor(message) {
|
|
802
|
-
super(message)
|
|
803
|
-
// Ensure the name of this error is the same as the class name
|
|
804
|
-
this.name = this.constructor.name
|
|
805
|
-
// This clips the constructor invocation from the stack trace.
|
|
806
|
-
// It's not absolutely essential, but it does make the stack trace a little nicer.
|
|
807
|
-
// @see Node.js reference (bottom)
|
|
808
|
-
Error.captureStackTrace(this, this.constructor)
|
|
809
|
-
}
|
|
670
|
+
return await authenticateWithUser(options)
|
|
810
671
|
}
|
|
811
672
|
|
|
812
673
|
module.exports.AUTH_RESULT_FAILURE = AUTH_RESULT_FAILURE
|