ldap-authentication 3.3.6 → 4.0.4
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/integration-test.yml +2 -2
- package/.github/workflows/npm-publish.yml +8 -19
- package/README.md +25 -1
- package/index.d.ts +17 -0
- package/index.js +210 -22
- package/package.json +7 -4
- package/test/authentication-result.spec.js +425 -0
|
@@ -16,7 +16,7 @@ jobs:
|
|
|
16
16
|
runs-on: ubuntu-latest
|
|
17
17
|
strategy:
|
|
18
18
|
matrix:
|
|
19
|
-
node-version: [
|
|
19
|
+
node-version: [20.x, 22.x, 24.x]
|
|
20
20
|
|
|
21
21
|
name: 'Integration Node v${{ matrix.node-version }}'
|
|
22
22
|
|
|
@@ -46,7 +46,7 @@ jobs:
|
|
|
46
46
|
uses: actions/setup-node@v3
|
|
47
47
|
with:
|
|
48
48
|
node-version: ${{ matrix.node-version }}
|
|
49
|
-
- run: npm
|
|
49
|
+
- run: npm ci
|
|
50
50
|
- run: npm run test
|
|
51
51
|
|
|
52
52
|
- name: Stop containers
|
|
@@ -1,33 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
|
3
|
-
|
|
4
|
-
name: Publish to NPM
|
|
1
|
+
name: Publish Package
|
|
5
2
|
|
|
6
3
|
on:
|
|
7
4
|
release:
|
|
8
5
|
types: [created]
|
|
9
6
|
|
|
7
|
+
permissions:
|
|
8
|
+
id-token: write # Required for OIDC
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
10
11
|
jobs:
|
|
11
|
-
|
|
12
|
+
publish:
|
|
12
13
|
runs-on: ubuntu-latest
|
|
13
14
|
steps:
|
|
14
15
|
- uses: actions/checkout@v4
|
|
15
|
-
- uses: actions/setup-node@v4
|
|
16
|
-
with:
|
|
17
|
-
node-version: 20
|
|
18
|
-
- run: npm ci
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
publish-npm:
|
|
22
|
-
needs: build
|
|
23
|
-
runs-on: ubuntu-latest
|
|
24
|
-
steps:
|
|
25
|
-
- uses: actions/checkout@v4
|
|
26
17
|
- uses: actions/setup-node@v4
|
|
27
18
|
with:
|
|
28
|
-
node-version:
|
|
29
|
-
registry-url: https://registry.npmjs.org
|
|
19
|
+
node-version: '24'
|
|
20
|
+
registry-url: 'https://registry.npmjs.org'
|
|
30
21
|
- run: npm ci
|
|
31
22
|
- run: npm publish
|
|
32
|
-
env:
|
|
33
|
-
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
package/README.md
CHANGED
|
@@ -221,6 +221,28 @@ auth()
|
|
|
221
221
|
|
|
222
222
|
The user object if `authenticate()` is success.
|
|
223
223
|
|
|
224
|
+
In version 4, a new function is added: `authenticateResult()`. It has the same call signature as `authenticate()` but returns an object `AuthenticationResult` with more details.
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
### AuthenticationResult Object
|
|
228
|
+
|
|
229
|
+
AuthenticationResult object has the following fields:
|
|
230
|
+
|
|
231
|
+
- `code`: number. constants:
|
|
232
|
+
- `AUTH_RESULT_FAILURE` = 0
|
|
233
|
+
- `AUTH_RESULT_SUCCESS` = 1
|
|
234
|
+
- `AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND` = -1
|
|
235
|
+
- `AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS` = -2
|
|
236
|
+
- `AUTH_RESULT_FAILURE_CREDENTIAL_INVALID` = -3
|
|
237
|
+
- `AUTH_RESULT_FAILURE_UNCATEGORIZED` = -4
|
|
238
|
+
- `identity`: identity supplied as string
|
|
239
|
+
- `user`: user object if authentication is successful, otherwise null
|
|
240
|
+
- `message`: authentication message array, which contains server messages
|
|
241
|
+
- `client`: ldapClient instance
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
## Old Stuff
|
|
245
|
+
|
|
224
246
|
In version 2, The user object has a `raw` field that has the raw data from the LDAP/AD server. It can be used to access buffer objects (profile pics for example).
|
|
225
247
|
|
|
226
248
|
Buffer data can now be accessed by `user.raw.profilePhoto`, etc, instead of `user.profilePhoto`.
|
|
@@ -260,4 +282,6 @@ export async function verifyLogin(email: string, password: string) {
|
|
|
260
282
|
|
|
261
283
|
Version 2 supports Node version 12, 14, 15, 16, 17 and 18.
|
|
262
284
|
|
|
263
|
-
Version 3 supports Node version 16, 17, 18, 20 and 22
|
|
285
|
+
Version 3 supports Node version 16, 17, 18, 20 and 22.
|
|
286
|
+
|
|
287
|
+
Version 4 supports Node version 20 and above.
|
package/index.d.ts
CHANGED
|
@@ -20,7 +20,24 @@ declare module 'ldap-authentication' {
|
|
|
20
20
|
explicitBufferAttributes?: string[]
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export const AUTH_RESULT_FAILURE = 0
|
|
24
|
+
export const AUTH_RESULT_SUCCESS = 1
|
|
25
|
+
export const AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND = -1
|
|
26
|
+
export const AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS = -2
|
|
27
|
+
export const AUTH_RESULT_FAILURE_CREDENTIAL_INVALID = -3
|
|
28
|
+
export const AUTH_RESULT_FAILURE_UNCATEGORIZED = -4
|
|
29
|
+
|
|
30
|
+
export class AuthenticationResult {
|
|
31
|
+
constructor(authCode: number, identity: string, user: any, messages: string[], client: any)
|
|
32
|
+
readonly code: number
|
|
33
|
+
readonly identity: string
|
|
34
|
+
readonly user: any
|
|
35
|
+
readonly messages: string[]
|
|
36
|
+
readonly client: any
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
export function authenticate(options: AuthenticationOptions): Promise<any>
|
|
40
|
+
export function authenticateResult(options: AuthenticationOptions): Promise<AuthenticationResult>
|
|
24
41
|
|
|
25
42
|
export class LdapAuthenticationError extends Error {
|
|
26
43
|
constructor(message: any)
|
package/index.js
CHANGED
|
@@ -38,12 +38,64 @@ function _ldapEscapeDN(s) {
|
|
|
38
38
|
return ret
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
const AUTH_RESULT_FAILURE = 0
|
|
42
|
+
const AUTH_RESULT_SUCCESS = 1
|
|
43
|
+
const AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND = -1
|
|
44
|
+
const AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS = -2
|
|
45
|
+
const AUTH_RESULT_FAILURE_CREDENTIAL_INVALID = -3
|
|
46
|
+
const AUTH_RESULT_FAILURE_UNCATEGORIZED = -4
|
|
47
|
+
|
|
48
|
+
class AuthenticationResult {
|
|
49
|
+
#authCode = AUTH_RESULT_FAILURE_UNCATEGORIZED
|
|
50
|
+
#identity
|
|
51
|
+
#user
|
|
52
|
+
#messages = []
|
|
53
|
+
#client
|
|
54
|
+
|
|
55
|
+
constructor(authCode, identity, user, messages, client) {
|
|
56
|
+
this.#authCode = authCode // one of the above constants
|
|
57
|
+
this.#identity = identity // identity supplied as string
|
|
58
|
+
this.#user = user // user object found on ldap server OR null
|
|
59
|
+
this.#messages = messages // authentication messages array, which contains server messages
|
|
60
|
+
this.#client = client // ldapClient instance
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get code() {
|
|
64
|
+
return this.#authCode
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get identity() {
|
|
68
|
+
return this.#identity
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get messages() {
|
|
72
|
+
return this.#messages
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get client() {
|
|
76
|
+
return this.#client
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get user() {
|
|
80
|
+
return this.#user
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const authenticationMessages = {
|
|
85
|
+
AUTH_RESULT_FAILURE: 'Authentication failed',
|
|
86
|
+
AUTH_RESULT_SUCCESS: 'Authentication successful',
|
|
87
|
+
AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND: 'Authentication identity not found',
|
|
88
|
+
AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS: 'Authentication identity ambiguous',
|
|
89
|
+
AUTH_RESULT_FAILURE_CREDENTIAL_INVALID: 'Invalid credentials',
|
|
90
|
+
AUTH_RESULT_FAILURE_UNCATEGORIZED: 'Uncategorized authentication failure',
|
|
91
|
+
}
|
|
92
|
+
|
|
41
93
|
// bind and return the ldap client
|
|
42
94
|
async function _ldapBind(dn, password, starttls, ldapOpts) {
|
|
43
95
|
// TODO: check if ldapts expects escaped dn or not (possible double escaping problems?)
|
|
44
96
|
dn = _ldapEscapeDN(dn)
|
|
45
97
|
ldapOpts.connectTimeout = ldapOpts.connectTimeout || 5000
|
|
46
|
-
|
|
98
|
+
|
|
47
99
|
// When using StartTLS, we need to exclude tlsOptions from the Client constructor
|
|
48
100
|
// and only pass them to the startTLS() method to avoid connection conflicts.
|
|
49
101
|
// According to ldapts documentation:
|
|
@@ -52,7 +104,7 @@ async function _ldapBind(dn, password, starttls, ldapOpts) {
|
|
|
52
104
|
// - For plain LDAP (ldap://): do NOT pass tlsOptions to Client constructor
|
|
53
105
|
let clientOpts = ldapOpts
|
|
54
106
|
const isLdaps = ldapOpts.url && ldapOpts.url.startsWith('ldaps://')
|
|
55
|
-
|
|
107
|
+
|
|
56
108
|
// Only pass tlsOptions to Client constructor if using ldaps:// protocol
|
|
57
109
|
// For ldap:// protocol (plain or StartTLS), exclude tlsOptions from constructor
|
|
58
110
|
if (!isLdaps && ldapOpts.tlsOptions) {
|
|
@@ -60,7 +112,7 @@ async function _ldapBind(dn, password, starttls, ldapOpts) {
|
|
|
60
112
|
const { tlsOptions, ...optsWithoutTls } = ldapOpts
|
|
61
113
|
clientOpts = optsWithoutTls
|
|
62
114
|
}
|
|
63
|
-
|
|
115
|
+
|
|
64
116
|
let client = new ldapts.Client(clientOpts)
|
|
65
117
|
|
|
66
118
|
if (starttls) {
|
|
@@ -115,8 +167,24 @@ async function _searchUser(
|
|
|
115
167
|
!searchEntries[0] ||
|
|
116
168
|
!searchEntries[0].dn
|
|
117
169
|
) {
|
|
118
|
-
|
|
170
|
+
return new AuthenticationResult(
|
|
171
|
+
AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND,
|
|
172
|
+
username,
|
|
173
|
+
null,
|
|
174
|
+
[authenticationMessages.AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND],
|
|
175
|
+
ldapClient
|
|
176
|
+
)
|
|
119
177
|
} else {
|
|
178
|
+
if (searchEntries.length > 1) {
|
|
179
|
+
return new AuthenticationResult(
|
|
180
|
+
AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS,
|
|
181
|
+
username,
|
|
182
|
+
null,
|
|
183
|
+
[authenticationMessages.AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS],
|
|
184
|
+
ldapClient
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
120
188
|
user = searchEntries[0]
|
|
121
189
|
}
|
|
122
190
|
|
|
@@ -136,7 +204,14 @@ async function _searchUser(
|
|
|
136
204
|
}
|
|
137
205
|
}
|
|
138
206
|
}
|
|
139
|
-
|
|
207
|
+
|
|
208
|
+
return new AuthenticationResult(
|
|
209
|
+
AUTH_RESULT_SUCCESS,
|
|
210
|
+
username,
|
|
211
|
+
user,
|
|
212
|
+
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
213
|
+
ldapClient
|
|
214
|
+
)
|
|
140
215
|
}
|
|
141
216
|
|
|
142
217
|
// search a groups which user is member
|
|
@@ -215,9 +290,17 @@ async function authenticateWithAdmin(
|
|
|
215
290
|
if (ldapAdminClient && ldapAdminClient.isConnected) {
|
|
216
291
|
await ldapAdminClient.unbind()
|
|
217
292
|
}
|
|
218
|
-
|
|
293
|
+
|
|
294
|
+
return new AuthenticationResult(
|
|
295
|
+
AUTH_RESULT_FAILURE,
|
|
296
|
+
username,
|
|
297
|
+
null,
|
|
298
|
+
[authenticationMessages.AUTH_RESULT_FAILURE, error.message || 'admin bind failed'],
|
|
299
|
+
ldapAdminClient
|
|
300
|
+
)
|
|
219
301
|
}
|
|
220
|
-
|
|
302
|
+
|
|
303
|
+
let searchResult = await _searchUser(
|
|
221
304
|
ldapAdminClient,
|
|
222
305
|
userSearchBase,
|
|
223
306
|
usernameAttribute,
|
|
@@ -225,14 +308,21 @@ async function authenticateWithAdmin(
|
|
|
225
308
|
attributes,
|
|
226
309
|
explicitBufferAttributes
|
|
227
310
|
)
|
|
311
|
+
|
|
312
|
+
let user = searchResult.user
|
|
313
|
+
|
|
228
314
|
if (!user || !user.dn) {
|
|
229
315
|
ldapOpts.log &&
|
|
230
316
|
ldapOpts.log.trace(
|
|
231
317
|
`admin did not find user! (${usernameAttribute}=${username})`
|
|
232
318
|
)
|
|
233
319
|
await ldapAdminClient.unbind()
|
|
234
|
-
|
|
235
|
-
|
|
320
|
+
return new AuthenticationResult(
|
|
321
|
+
AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND,
|
|
322
|
+
username,
|
|
323
|
+
null,
|
|
324
|
+
[authenticationMessages.AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND],
|
|
325
|
+
ldapAdminClient
|
|
236
326
|
)
|
|
237
327
|
}
|
|
238
328
|
let userDn = user.dn
|
|
@@ -243,7 +333,14 @@ async function authenticateWithAdmin(
|
|
|
243
333
|
if (ldapUserClient && ldapUserClient.isConnected) {
|
|
244
334
|
await ldapUserClient.unbind()
|
|
245
335
|
}
|
|
246
|
-
|
|
336
|
+
|
|
337
|
+
return new AuthenticationResult(
|
|
338
|
+
AUTH_RESULT_FAILURE_CREDENTIAL_INVALID,
|
|
339
|
+
username,
|
|
340
|
+
null,
|
|
341
|
+
[authenticationMessages.AUTH_RESULT_FAILURE_CREDENTIAL_INVALID, error.message || 'invalid credentials'],
|
|
342
|
+
ldapAdminClient
|
|
343
|
+
)
|
|
247
344
|
}
|
|
248
345
|
if (groupsSearchBase && groupClass && groupMemberAttribute) {
|
|
249
346
|
let groups = await _searchUserGroups(
|
|
@@ -258,7 +355,14 @@ async function authenticateWithAdmin(
|
|
|
258
355
|
}
|
|
259
356
|
await ldapAdminClient.unbind()
|
|
260
357
|
await ldapUserClient.unbind()
|
|
261
|
-
|
|
358
|
+
|
|
359
|
+
return new AuthenticationResult(
|
|
360
|
+
AUTH_RESULT_SUCCESS,
|
|
361
|
+
username,
|
|
362
|
+
user,
|
|
363
|
+
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
364
|
+
ldapAdminClient
|
|
365
|
+
)
|
|
262
366
|
}
|
|
263
367
|
|
|
264
368
|
async function authenticateWithUser(
|
|
@@ -283,14 +387,28 @@ async function authenticateWithUser(
|
|
|
283
387
|
if (ldapUserClient && ldapUserClient.isConnected) {
|
|
284
388
|
await ldapUserClient.unbind()
|
|
285
389
|
}
|
|
286
|
-
|
|
390
|
+
|
|
391
|
+
return new AuthenticationResult(
|
|
392
|
+
AUTH_RESULT_FAILURE,
|
|
393
|
+
username,
|
|
394
|
+
null,
|
|
395
|
+
[authenticationMessages.AUTH_RESULT_FAILURE, error.message || 'user bind failed'],
|
|
396
|
+
ldapUserClient
|
|
397
|
+
)
|
|
287
398
|
}
|
|
288
399
|
if (!usernameAttribute || !userSearchBase) {
|
|
289
400
|
// if usernameAttribute is not provided, no user detail is needed.
|
|
290
401
|
await ldapUserClient.unbind()
|
|
291
|
-
return
|
|
402
|
+
return new AuthenticationResult(
|
|
403
|
+
AUTH_RESULT_SUCCESS,
|
|
404
|
+
username,
|
|
405
|
+
{},
|
|
406
|
+
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
407
|
+
ldapUserClient
|
|
408
|
+
)
|
|
292
409
|
}
|
|
293
|
-
|
|
410
|
+
|
|
411
|
+
let searchResult = await _searchUser(
|
|
294
412
|
ldapUserClient,
|
|
295
413
|
userSearchBase,
|
|
296
414
|
usernameAttribute,
|
|
@@ -298,14 +416,25 @@ async function authenticateWithUser(
|
|
|
298
416
|
attributes,
|
|
299
417
|
explicitBufferAttributes
|
|
300
418
|
)
|
|
419
|
+
|
|
420
|
+
let user = searchResult.user
|
|
421
|
+
|
|
301
422
|
if (!user || !user.dn) {
|
|
302
423
|
ldapOpts.log &&
|
|
303
424
|
ldapOpts.log.trace(
|
|
304
425
|
`user logged in, but user details could not be found. (${usernameAttribute}=${username}). Probabaly wrong attribute or searchBase?`
|
|
305
426
|
)
|
|
306
427
|
await ldapUserClient.unbind()
|
|
307
|
-
|
|
308
|
-
|
|
428
|
+
|
|
429
|
+
return new AuthenticationResult(
|
|
430
|
+
AUTH_RESULT_FAILURE,
|
|
431
|
+
username,
|
|
432
|
+
null,
|
|
433
|
+
[
|
|
434
|
+
authenticationMessages.AUTH_RESULT_FAILURE,
|
|
435
|
+
'user logged in, but user details could not be found. Probabaly usernameAttribute or userSearchBase is wrong?',
|
|
436
|
+
],
|
|
437
|
+
ldapUserClient
|
|
309
438
|
)
|
|
310
439
|
}
|
|
311
440
|
if (groupsSearchBase && groupClass && groupMemberAttribute) {
|
|
@@ -320,7 +449,14 @@ async function authenticateWithUser(
|
|
|
320
449
|
user.groups = groups
|
|
321
450
|
}
|
|
322
451
|
await ldapUserClient.unbind()
|
|
323
|
-
|
|
452
|
+
|
|
453
|
+
return new AuthenticationResult(
|
|
454
|
+
AUTH_RESULT_SUCCESS,
|
|
455
|
+
username,
|
|
456
|
+
user,
|
|
457
|
+
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
458
|
+
ldapUserClient
|
|
459
|
+
)
|
|
324
460
|
}
|
|
325
461
|
|
|
326
462
|
async function verifyUserExists(
|
|
@@ -350,9 +486,16 @@ async function verifyUserExists(
|
|
|
350
486
|
if (ldapAdminClient && ldapAdminClient.isConnected) {
|
|
351
487
|
await ldapAdminClient.unbind()
|
|
352
488
|
}
|
|
353
|
-
|
|
489
|
+
return new AuthenticationResult(
|
|
490
|
+
AUTH_RESULT_FAILURE,
|
|
491
|
+
username,
|
|
492
|
+
null,
|
|
493
|
+
[authenticationMessages.AUTH_RESULT_FAILURE, error.message || 'admin bind failed'],
|
|
494
|
+
ldapAdminClient
|
|
495
|
+
)
|
|
354
496
|
}
|
|
355
|
-
|
|
497
|
+
|
|
498
|
+
let searchResult = await _searchUser(
|
|
356
499
|
ldapAdminClient,
|
|
357
500
|
userSearchBase,
|
|
358
501
|
usernameAttribute,
|
|
@@ -360,14 +503,24 @@ async function verifyUserExists(
|
|
|
360
503
|
attributes,
|
|
361
504
|
explicitBufferAttributes
|
|
362
505
|
)
|
|
506
|
+
|
|
507
|
+
let user = searchResult.user
|
|
508
|
+
|
|
363
509
|
if (!user || !user.dn) {
|
|
364
510
|
ldapOpts.log &&
|
|
365
511
|
ldapOpts.log.trace(
|
|
366
512
|
`admin did not find user! (${usernameAttribute}=${username})`
|
|
367
513
|
)
|
|
368
514
|
await ldapAdminClient.unbind()
|
|
369
|
-
|
|
370
|
-
|
|
515
|
+
return new AuthenticationResult(
|
|
516
|
+
AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND,
|
|
517
|
+
username,
|
|
518
|
+
null,
|
|
519
|
+
[
|
|
520
|
+
authenticationMessages.AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND,
|
|
521
|
+
'user not found or usernameAttribute is wrong'
|
|
522
|
+
],
|
|
523
|
+
ldapAdminClient
|
|
371
524
|
)
|
|
372
525
|
}
|
|
373
526
|
if (groupsSearchBase && groupClass && groupMemberAttribute) {
|
|
@@ -382,10 +535,28 @@ async function verifyUserExists(
|
|
|
382
535
|
user.groups = groups
|
|
383
536
|
}
|
|
384
537
|
await ldapAdminClient.unbind()
|
|
385
|
-
return
|
|
538
|
+
return new AuthenticationResult(
|
|
539
|
+
AUTH_RESULT_SUCCESS,
|
|
540
|
+
username,
|
|
541
|
+
user,
|
|
542
|
+
[authenticationMessages.AUTH_RESULT_SUCCESS],
|
|
543
|
+
ldapAdminClient
|
|
544
|
+
)
|
|
386
545
|
}
|
|
387
546
|
|
|
388
547
|
async function authenticate(options) {
|
|
548
|
+
const result = await authenticateResult(options)
|
|
549
|
+
|
|
550
|
+
if (result.code !== AUTH_RESULT_SUCCESS) {
|
|
551
|
+
throw new LdapAuthenticationError(
|
|
552
|
+
result.messages[result.messages.length - 1]
|
|
553
|
+
)
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return result.user
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async function authenticateResult(options) {
|
|
389
560
|
if (!options.userDn) {
|
|
390
561
|
assert(options.adminDn, 'Admin mode adminDn must be provided')
|
|
391
562
|
assert(options.adminPassword, 'Admin mode adminPassword must be provided')
|
|
@@ -402,6 +573,7 @@ async function authenticate(options) {
|
|
|
402
573
|
options.ldapOpts && options.ldapOpts.url,
|
|
403
574
|
'ldapOpts.url must be provided'
|
|
404
575
|
)
|
|
576
|
+
|
|
405
577
|
if (options.verifyUserExists) {
|
|
406
578
|
assert(options.adminDn, 'Admin mode adminDn must be provided')
|
|
407
579
|
assert(
|
|
@@ -424,6 +596,7 @@ async function authenticate(options) {
|
|
|
424
596
|
options.explicitBufferAttributes
|
|
425
597
|
)
|
|
426
598
|
}
|
|
599
|
+
|
|
427
600
|
assert(options.userPassword, 'userPassword must be provided')
|
|
428
601
|
if (options.adminDn) {
|
|
429
602
|
assert(
|
|
@@ -447,6 +620,7 @@ async function authenticate(options) {
|
|
|
447
620
|
options.explicitBufferAttributes
|
|
448
621
|
)
|
|
449
622
|
}
|
|
623
|
+
|
|
450
624
|
assert(options.userDn, 'adminDn/adminPassword OR userDn must be provided')
|
|
451
625
|
return await authenticateWithUser(
|
|
452
626
|
options.userDn,
|
|
@@ -477,7 +651,21 @@ class LdapAuthenticationError extends Error {
|
|
|
477
651
|
}
|
|
478
652
|
}
|
|
479
653
|
|
|
654
|
+
module.exports.AUTH_RESULT_FAILURE = AUTH_RESULT_FAILURE
|
|
655
|
+
module.exports.AUTH_RESULT_SUCCESS = AUTH_RESULT_SUCCESS
|
|
656
|
+
module.exports.AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND =
|
|
657
|
+
AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND
|
|
658
|
+
module.exports.AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS =
|
|
659
|
+
AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS
|
|
660
|
+
module.exports.AUTH_RESULT_FAILURE_CREDENTIAL_INVALID =
|
|
661
|
+
AUTH_RESULT_FAILURE_CREDENTIAL_INVALID
|
|
662
|
+
module.exports.AUTH_RESULT_FAILURE_UNCATEGORIZED =
|
|
663
|
+
AUTH_RESULT_FAILURE_UNCATEGORIZED
|
|
664
|
+
|
|
665
|
+
module.exports.AuthenticationResult = AuthenticationResult
|
|
666
|
+
|
|
480
667
|
module.exports.authenticate = authenticate
|
|
668
|
+
module.exports.authenticateResult = authenticateResult
|
|
481
669
|
module.exports.LdapAuthenticationError = LdapAuthenticationError
|
|
482
670
|
|
|
483
671
|
module.exports.exportForTesting = {
|
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ldap-authentication",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.4",
|
|
4
4
|
"description": "A simple async nodejs library for LDAP user authentication",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20.0.0"
|
|
9
|
+
},
|
|
7
10
|
"scripts": {
|
|
8
11
|
"test": "node test/jasmine.js"
|
|
9
12
|
},
|
|
10
13
|
"repository": {
|
|
11
14
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/shaozi/ldap-authentication.git"
|
|
15
|
+
"url": "git+https://github.com/shaozi/ldap-authentication.git"
|
|
13
16
|
},
|
|
14
17
|
"keywords": [
|
|
15
18
|
"ldap",
|
|
@@ -36,9 +39,9 @@
|
|
|
36
39
|
},
|
|
37
40
|
"homepage": "https://github.com/shaozi/ldap-authentication#readme",
|
|
38
41
|
"dependencies": {
|
|
39
|
-
"ldapts": "^
|
|
42
|
+
"ldapts": "^8.1.3"
|
|
40
43
|
},
|
|
41
44
|
"devDependencies": {
|
|
42
|
-
"jasmine": "^
|
|
45
|
+
"jasmine": "^6.0.0"
|
|
43
46
|
}
|
|
44
47
|
}
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
const {
|
|
2
|
+
authenticateResult,
|
|
3
|
+
AuthenticationResult,
|
|
4
|
+
AUTH_RESULT_SUCCESS,
|
|
5
|
+
AUTH_RESULT_FAILURE,
|
|
6
|
+
AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND,
|
|
7
|
+
AUTH_RESULT_FAILURE_IDENTITY_AMBIGUOUS,
|
|
8
|
+
AUTH_RESULT_FAILURE_CREDENTIAL_INVALID,
|
|
9
|
+
AUTH_RESULT_FAILURE_UNCATEGORIZED
|
|
10
|
+
} = require('../index.js')
|
|
11
|
+
|
|
12
|
+
const url = process.env.INGITHUB ? 'ldap://localhost:1389' : 'ldap://ldap:1389'
|
|
13
|
+
|
|
14
|
+
describe('ldap-authentication test return AuthenticationResult', () => {
|
|
15
|
+
it('Use an admin user to check if user exists', async () => {
|
|
16
|
+
let options = {
|
|
17
|
+
ldapOpts: {
|
|
18
|
+
url: url,
|
|
19
|
+
},
|
|
20
|
+
adminDn: 'cn=read-only-admin,dc=example,dc=com',
|
|
21
|
+
adminPassword: 'password',
|
|
22
|
+
verifyUserExists: true,
|
|
23
|
+
userSearchBase: 'dc=example,dc=com',
|
|
24
|
+
usernameAttribute: 'uid',
|
|
25
|
+
username: 'gauss',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let result = await authenticateResult(options)
|
|
29
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
30
|
+
expect(result.code).toEqual(AUTH_RESULT_SUCCESS)
|
|
31
|
+
expect(result.user).toBeTruthy()
|
|
32
|
+
expect(result.user.uid).toEqual('gauss')
|
|
33
|
+
})
|
|
34
|
+
it('Use an admin user to check if user exists and return attributes', async () => {
|
|
35
|
+
let options = {
|
|
36
|
+
ldapOpts: {
|
|
37
|
+
url: url,
|
|
38
|
+
},
|
|
39
|
+
adminDn: 'cn=read-only-admin,dc=example,dc=com',
|
|
40
|
+
adminPassword: 'password',
|
|
41
|
+
verifyUserExists: true,
|
|
42
|
+
userSearchBase: 'dc=example,dc=com',
|
|
43
|
+
usernameAttribute: 'uid',
|
|
44
|
+
username: 'gauss',
|
|
45
|
+
attributes: ['uid', 'sn'],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let result = await authenticateResult(options)
|
|
49
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
50
|
+
expect(result.code).toEqual(AUTH_RESULT_SUCCESS)
|
|
51
|
+
expect(result.user).toBeTruthy()
|
|
52
|
+
expect(result.user.uid).toEqual('gauss')
|
|
53
|
+
expect(result.user.sn).toEqual('Bar1')
|
|
54
|
+
expect(result.user.cn).toBeUndefined()
|
|
55
|
+
})
|
|
56
|
+
it('Use an admin user to authenticate a regular user', async () => {
|
|
57
|
+
let options = {
|
|
58
|
+
ldapOpts: {
|
|
59
|
+
url: url,
|
|
60
|
+
},
|
|
61
|
+
adminDn: 'cn=read-only-admin,dc=example,dc=com',
|
|
62
|
+
adminPassword: 'password',
|
|
63
|
+
userPassword: 'password',
|
|
64
|
+
userSearchBase: 'dc=example,dc=com',
|
|
65
|
+
usernameAttribute: 'uid',
|
|
66
|
+
username: 'gauss',
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let result = await authenticateResult(options)
|
|
70
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
71
|
+
expect(result.code).toEqual(AUTH_RESULT_SUCCESS)
|
|
72
|
+
expect(result.user).toBeTruthy()
|
|
73
|
+
expect(result.user.uid).toEqual('gauss')
|
|
74
|
+
})
|
|
75
|
+
it('Use an admin user to authenticate a regular user and return attributes', async () => {
|
|
76
|
+
let options = {
|
|
77
|
+
ldapOpts: {
|
|
78
|
+
url: url,
|
|
79
|
+
},
|
|
80
|
+
adminDn: 'cn=read-only-admin,dc=example,dc=com',
|
|
81
|
+
adminPassword: 'password',
|
|
82
|
+
userPassword: 'password',
|
|
83
|
+
userSearchBase: 'dc=example,dc=com',
|
|
84
|
+
usernameAttribute: 'uid',
|
|
85
|
+
username: 'gauss',
|
|
86
|
+
attributes: ['uid', 'sn'],
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let result = await authenticateResult(options)
|
|
90
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
91
|
+
expect(result.code).toEqual(AUTH_RESULT_SUCCESS)
|
|
92
|
+
expect(result.user).toBeTruthy()
|
|
93
|
+
expect(result.user.uid).toEqual('gauss')
|
|
94
|
+
expect(result.user.sn).toEqual('Bar1')
|
|
95
|
+
expect(result.user.cn).toBeUndefined()
|
|
96
|
+
})
|
|
97
|
+
it('Use an regular user to authenticate iteself', async () => {
|
|
98
|
+
let options = {
|
|
99
|
+
ldapOpts: {
|
|
100
|
+
url: url,
|
|
101
|
+
},
|
|
102
|
+
userDn: 'cn=einstein,ou=users,dc=example,dc=com',
|
|
103
|
+
userPassword: 'password',
|
|
104
|
+
userSearchBase: 'dc=example,dc=com',
|
|
105
|
+
usernameAttribute: 'uid',
|
|
106
|
+
username: 'einstein',
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let result = await authenticateResult(options)
|
|
110
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
111
|
+
expect(result.code).toEqual(AUTH_RESULT_SUCCESS)
|
|
112
|
+
expect(result.user).toBeTruthy()
|
|
113
|
+
expect(result.user.uid).toEqual('einstein')
|
|
114
|
+
})
|
|
115
|
+
it('Use an regular user to authenticate iteself and return attributes', async () => {
|
|
116
|
+
let options = {
|
|
117
|
+
ldapOpts: {
|
|
118
|
+
url: url,
|
|
119
|
+
},
|
|
120
|
+
userDn: 'cn=einstein,ou=users,dc=example,dc=com',
|
|
121
|
+
userPassword: 'password',
|
|
122
|
+
userSearchBase: 'dc=example,dc=com',
|
|
123
|
+
usernameAttribute: 'uid',
|
|
124
|
+
username: 'einstein',
|
|
125
|
+
attributes: ['uid', 'sn'],
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let result = await authenticateResult(options)
|
|
129
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
130
|
+
expect(result.code).toEqual(AUTH_RESULT_SUCCESS)
|
|
131
|
+
expect(result.user).toBeTruthy()
|
|
132
|
+
expect(result.user.uid).toEqual('einstein')
|
|
133
|
+
expect(result.user.sn).toEqual('Bar2')
|
|
134
|
+
expect(result.user.cn).toBeUndefined()
|
|
135
|
+
})
|
|
136
|
+
it('Use an regular user to authenticate iteself without search', async () => {
|
|
137
|
+
let options = {
|
|
138
|
+
ldapOpts: {
|
|
139
|
+
url: url,
|
|
140
|
+
},
|
|
141
|
+
userDn: 'cn=einstein,ou=users,dc=example,dc=com',
|
|
142
|
+
userPassword: 'password',
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let result = await authenticateResult(options)
|
|
146
|
+
expect(result.code).toEqual(AUTH_RESULT_SUCCESS)
|
|
147
|
+
expect(result.user).toBeTruthy()
|
|
148
|
+
})
|
|
149
|
+
it('Use an admin user to authenticate a regular user and fetch user group information', async () => {
|
|
150
|
+
let options = {
|
|
151
|
+
ldapOpts: {
|
|
152
|
+
url: url,
|
|
153
|
+
},
|
|
154
|
+
adminDn: 'cn=read-only-admin,dc=example,dc=com',
|
|
155
|
+
adminPassword: 'password',
|
|
156
|
+
userPassword: 'password',
|
|
157
|
+
userSearchBase: 'dc=example,dc=com',
|
|
158
|
+
usernameAttribute: 'uid',
|
|
159
|
+
username: 'gauss',
|
|
160
|
+
groupsSearchBase: 'dc=example,dc=com',
|
|
161
|
+
groupClass: 'groupOfNames',
|
|
162
|
+
groupMemberAttribute: 'member',
|
|
163
|
+
groupMemberUserAttribute: 'dn',
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let result = await authenticateResult(options)
|
|
167
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
168
|
+
expect(result.code).toEqual(AUTH_RESULT_SUCCESS)
|
|
169
|
+
expect(result.user).toBeTruthy()
|
|
170
|
+
expect(result.user.groups.length).toBeGreaterThan(0)
|
|
171
|
+
expect(result.user.groups[0].dn).toEqual('cn=科学A部,ou=groups,dc=example,dc=com')
|
|
172
|
+
})
|
|
173
|
+
it('Use regular user to authenticate and fetch user group information', async () => {
|
|
174
|
+
let options = {
|
|
175
|
+
ldapOpts: {
|
|
176
|
+
url: url,
|
|
177
|
+
},
|
|
178
|
+
userDn: 'cn=gauss,ou=users,dc=example,dc=com',
|
|
179
|
+
userPassword: 'password',
|
|
180
|
+
userSearchBase: 'dc=example,dc=com',
|
|
181
|
+
usernameAttribute: 'uid',
|
|
182
|
+
username: 'gauss',
|
|
183
|
+
groupsSearchBase: 'dc=example,dc=com',
|
|
184
|
+
groupClass: 'groupOfNames',
|
|
185
|
+
groupMemberAttribute: 'member',
|
|
186
|
+
groupMemberUserAttribute: 'dn',
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let result = await authenticateResult(options)
|
|
190
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
191
|
+
expect(result.code).toEqual(AUTH_RESULT_SUCCESS)
|
|
192
|
+
expect(result.user).toBeTruthy()
|
|
193
|
+
expect(result.user.groups.length).toBeGreaterThan(0)
|
|
194
|
+
expect(result.user.groups[0].dn).toEqual('cn=科学A部,ou=groups,dc=example,dc=com')
|
|
195
|
+
// backward compatible with 3.2
|
|
196
|
+
expect(result.user.groups[0].objectName).toEqual(
|
|
197
|
+
'cn=科学A部,ou=groups,dc=example,dc=com'
|
|
198
|
+
)
|
|
199
|
+
})
|
|
200
|
+
it('Not specifying groupMemberAttribute or groupMemberUserAttribute should not cause an error and fallback to default values', async () => {
|
|
201
|
+
let options = {
|
|
202
|
+
ldapOpts: {
|
|
203
|
+
url: url,
|
|
204
|
+
},
|
|
205
|
+
userDn: 'cn=gauss,ou=users,dc=example,dc=com',
|
|
206
|
+
userPassword: 'password',
|
|
207
|
+
userSearchBase: 'dc=example,dc=com',
|
|
208
|
+
usernameAttribute: 'uid',
|
|
209
|
+
username: 'gauss',
|
|
210
|
+
groupsSearchBase: 'dc=example,dc=com',
|
|
211
|
+
groupClass: 'groupOfUniqueNames',
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let result = await authenticateResult(options)
|
|
215
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
216
|
+
expect(result.code).toEqual(AUTH_RESULT_SUCCESS)
|
|
217
|
+
expect(result.user).toBeTruthy()
|
|
218
|
+
expect(result.user.groups.length).toBeLessThan(1)
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
describe('ldap-authentication negative test returns AuthenticationResult', () => {
|
|
223
|
+
it('wrong admin user should fail', async () => {
|
|
224
|
+
let options = {
|
|
225
|
+
ldapOpts: {
|
|
226
|
+
url: url,
|
|
227
|
+
},
|
|
228
|
+
adminDn: 'cn=not-exist,dc=example,dc=com',
|
|
229
|
+
adminPassword: 'password',
|
|
230
|
+
userPassword: 'password',
|
|
231
|
+
userSearchBase: 'dc=example,dc=com',
|
|
232
|
+
usernameAttribute: 'uid',
|
|
233
|
+
username: 'gauss',
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let result = await authenticateResult(options)
|
|
237
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
238
|
+
expect(result.code).toEqual(AUTH_RESULT_FAILURE)
|
|
239
|
+
})
|
|
240
|
+
it('wrong admin password should fail', async () => {
|
|
241
|
+
let options = {
|
|
242
|
+
ldapOpts: {
|
|
243
|
+
url: url,
|
|
244
|
+
},
|
|
245
|
+
adminDn: 'cn=read-only-admin,dc=example,dc=com',
|
|
246
|
+
adminPassword: 'wrongpassword',
|
|
247
|
+
userPassword: 'password',
|
|
248
|
+
userSearchBase: 'dc=example,dc=com',
|
|
249
|
+
usernameAttribute: 'uid',
|
|
250
|
+
username: 'gauss',
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let result = await authenticateResult(options)
|
|
254
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
255
|
+
expect(result.code).toEqual(AUTH_RESULT_FAILURE)
|
|
256
|
+
})
|
|
257
|
+
it('admin auth wrong username should fail', async () => {
|
|
258
|
+
let options = {
|
|
259
|
+
ldapOpts: {
|
|
260
|
+
url: url,
|
|
261
|
+
},
|
|
262
|
+
adminDn: 'cn=read-only-admin,dc=example,dc=com',
|
|
263
|
+
adminPassword: 'password',
|
|
264
|
+
userPassword: 'password',
|
|
265
|
+
userSearchBase: 'dc=example,dc=com',
|
|
266
|
+
usernameAttribute: 'uid',
|
|
267
|
+
username: 'wrong',
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let result = await authenticateResult(options)
|
|
271
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
272
|
+
expect(result.code).toEqual(AUTH_RESULT_FAILURE_IDENTITY_NOT_FOUND)
|
|
273
|
+
})
|
|
274
|
+
it('admin auth wrong user password should fail', async () => {
|
|
275
|
+
let options = {
|
|
276
|
+
ldapOpts: {
|
|
277
|
+
url: url,
|
|
278
|
+
},
|
|
279
|
+
adminDn: 'cn=read-only-admin,dc=example,dc=com',
|
|
280
|
+
adminPassword: 'password',
|
|
281
|
+
userPassword: 'wrongpassword',
|
|
282
|
+
userSearchBase: 'dc=example,dc=com',
|
|
283
|
+
usernameAttribute: 'uid',
|
|
284
|
+
username: 'gauss',
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let result = await authenticateResult(options)
|
|
288
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
289
|
+
expect(result.code).toEqual(AUTH_RESULT_FAILURE_CREDENTIAL_INVALID)
|
|
290
|
+
})
|
|
291
|
+
it('user auth wrong username should fail', async () => {
|
|
292
|
+
let options = {
|
|
293
|
+
ldapOpts: {
|
|
294
|
+
url: url,
|
|
295
|
+
},
|
|
296
|
+
userDn: 'cn=not-exist,dc=example,dc=com',
|
|
297
|
+
userPassword: 'password',
|
|
298
|
+
userSearchBase: 'dc=example,dc=com',
|
|
299
|
+
usernameAttribute: 'uid',
|
|
300
|
+
username: 'gauss',
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let result = await authenticateResult(options)
|
|
304
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
305
|
+
expect(result.code).toEqual(AUTH_RESULT_FAILURE)
|
|
306
|
+
})
|
|
307
|
+
it('user auth wrong user password should fail', async () => {
|
|
308
|
+
let options = {
|
|
309
|
+
ldapOpts: {
|
|
310
|
+
url: url,
|
|
311
|
+
},
|
|
312
|
+
userDn: 'cn=gauss,dc=example,dc=com',
|
|
313
|
+
userPassword: 'wrongpassword',
|
|
314
|
+
userSearchBase: 'dc=example,dc=com',
|
|
315
|
+
usernameAttribute: 'uid',
|
|
316
|
+
username: 'gauss',
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
let result = await authenticateResult(options)
|
|
320
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
321
|
+
expect(result.code).toEqual(AUTH_RESULT_FAILURE)
|
|
322
|
+
})
|
|
323
|
+
it('Use an regular user to authenticate iteself without search with wrong password should fail', async () => {
|
|
324
|
+
let options = {
|
|
325
|
+
ldapOpts: {
|
|
326
|
+
url: url,
|
|
327
|
+
},
|
|
328
|
+
userDn: 'uid=einstein,dc=example,dc=com',
|
|
329
|
+
userPassword: 'wrongpassword',
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let result = await authenticateResult(options)
|
|
333
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
334
|
+
expect(result.code).toEqual(AUTH_RESULT_FAILURE)
|
|
335
|
+
})
|
|
336
|
+
it('Wrong attributes give failure', async () => {
|
|
337
|
+
let options = {
|
|
338
|
+
ldapOpts: {
|
|
339
|
+
url: url,
|
|
340
|
+
},
|
|
341
|
+
userDn: 'cn=einstein,ou=users,dc=example,dc=com',
|
|
342
|
+
userPassword: 'password',
|
|
343
|
+
usernameAttribute: 'wrongattribute',
|
|
344
|
+
userSearchBase: 'dc=example,dc=com',
|
|
345
|
+
username: 'einstein',
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
let result = await authenticateResult(options)
|
|
349
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
350
|
+
expect(result.code).toEqual(AUTH_RESULT_FAILURE)
|
|
351
|
+
})
|
|
352
|
+
it('Unreachable ldap server should return failure', async () => {
|
|
353
|
+
let options = {
|
|
354
|
+
ldapOpts: {
|
|
355
|
+
url: 'ldap://x.forumsys.com',
|
|
356
|
+
connectTimeout: 2000,
|
|
357
|
+
},
|
|
358
|
+
userDn: 'uid=einstein,dc=example,dc=com',
|
|
359
|
+
userPassword: 'password',
|
|
360
|
+
usernameAttribute: 'cn',
|
|
361
|
+
userSearchBase: 'dc=example,dc=com',
|
|
362
|
+
username: 'einstein',
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
let result = await authenticateResult(options)
|
|
366
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
367
|
+
expect(result.code).toEqual(AUTH_RESULT_FAILURE)
|
|
368
|
+
})
|
|
369
|
+
it('Unreachable ldap server should return failure (with starttls=true)', async () => {
|
|
370
|
+
let options = {
|
|
371
|
+
ldapOpts: {
|
|
372
|
+
url: 'ldap://x.forumsys.com',
|
|
373
|
+
connectTimeout: 2000,
|
|
374
|
+
},
|
|
375
|
+
userDn: 'uid=einstein,dc=example,dc=com',
|
|
376
|
+
userPassword: 'password',
|
|
377
|
+
usernameAttribute: 'cn',
|
|
378
|
+
userSearchBase: 'dc=example,dc=com',
|
|
379
|
+
username: 'einstein',
|
|
380
|
+
starttls: true,
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
let result = await authenticateResult(options)
|
|
384
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
385
|
+
expect(result.code).toEqual(AUTH_RESULT_FAILURE)
|
|
386
|
+
})
|
|
387
|
+
it('Unmatched supplied groupMemberUserAttribute should return empty group list', async () => {
|
|
388
|
+
let options = {
|
|
389
|
+
ldapOpts: {
|
|
390
|
+
url: url,
|
|
391
|
+
},
|
|
392
|
+
userDn: 'cn=gauss,ou=users,dc=example,dc=com',
|
|
393
|
+
userPassword: 'password',
|
|
394
|
+
userSearchBase: 'dc=example,dc=com',
|
|
395
|
+
usernameAttribute: 'uid',
|
|
396
|
+
username: 'gauss',
|
|
397
|
+
groupsSearchBase: 'dc=example,dc=com',
|
|
398
|
+
groupClass: 'groupOfNames',
|
|
399
|
+
groupMemberAttribute: 'member',
|
|
400
|
+
groupMemberUserAttribute: 'dnWRONG',
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let result = await authenticateResult(options)
|
|
404
|
+
expect(result).toBeInstanceOf(AuthenticationResult)
|
|
405
|
+
expect(result.user).toBeTruthy()
|
|
406
|
+
expect(result.user.groups.length).toBeLessThan(1)
|
|
407
|
+
})
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
describe('AuthenticationResult', () => {
|
|
411
|
+
it('AuthenticationResult creation and getters', async () => {
|
|
412
|
+
const result = new AuthenticationResult(
|
|
413
|
+
AUTH_RESULT_SUCCESS,
|
|
414
|
+
'testuser',
|
|
415
|
+
{ cn: 'Test User' },
|
|
416
|
+
['Authentication successful'],
|
|
417
|
+
{}
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
expect(result.code).toBe(AUTH_RESULT_SUCCESS)
|
|
421
|
+
expect(result.identity).toBe('testuser')
|
|
422
|
+
expect(result.user).toEqual({ cn: 'Test User' })
|
|
423
|
+
expect(result.messages).toEqual(['Authentication successful'])
|
|
424
|
+
})
|
|
425
|
+
})
|