ldap-authentication 3.2.1 → 3.2.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/.devcontainer/docker-compose.yml +1 -0
- package/.github/workflows/integration-test.yml +2 -2
- package/.github/workflows/npm-publish.yml +33 -0
- package/README.md +2 -2
- package/docker-compose.yml +1 -0
- package/index.js +50 -3
- package/package.json +2 -2
- package/test/string.spec.js +43 -0
- package/test/test.spec.js +27 -1
|
@@ -27,7 +27,7 @@ jobs:
|
|
|
27
27
|
run: sudo apt-get install -y ldap-utils
|
|
28
28
|
|
|
29
29
|
- name: Start containers
|
|
30
|
-
run: docker
|
|
30
|
+
run: docker compose -f "docker-compose.yml" up -d
|
|
31
31
|
|
|
32
32
|
- name: Wait for LDAP server to become available
|
|
33
33
|
uses: nick-fields/retry@v2
|
|
@@ -51,4 +51,4 @@ jobs:
|
|
|
51
51
|
|
|
52
52
|
- name: Stop containers
|
|
53
53
|
if: always()
|
|
54
|
-
run: docker
|
|
54
|
+
run: docker compose -f "docker-compose.yml" down
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
|
3
|
+
|
|
4
|
+
name: Publish to NPM
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [created]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: 20
|
|
18
|
+
- run: npm ci
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
publish-npm:
|
|
22
|
+
needs: build
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: 20
|
|
29
|
+
registry-url: https://registry.npmjs.org/
|
|
30
|
+
- run: npm ci
|
|
31
|
+
- run: npm publish
|
|
32
|
+
env:
|
|
33
|
+
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
package/README.md
CHANGED
|
@@ -187,7 +187,7 @@ In version 2, The user object has a `raw` field that has the raw data from the L
|
|
|
187
187
|
Buffer data can now be accessed by `user.raw.profilePhoto`, etc, instead of `user.profilePhoto`.
|
|
188
188
|
|
|
189
189
|
In version 3, the `raw` field is no longer used. Instead, append `;binary` to the attributes you
|
|
190
|
-
want to get back as
|
|
190
|
+
want to get back as base64-encoded string. Check the following example on how to get a user's profile photo:
|
|
191
191
|
|
|
192
192
|
```javascript
|
|
193
193
|
export async function verifyLogin(email: string, password: string) {
|
|
@@ -210,7 +210,7 @@ export async function verifyLogin(email: string, password: string) {
|
|
|
210
210
|
const profilePhoto = ldapUser['thumbnailPhoto;binary'];
|
|
211
211
|
|
|
212
212
|
/* using the image
|
|
213
|
-
<img src={`data:image/*;base64,${
|
|
213
|
+
<img src={`data:image/*;base64,${profilePhoto}`} />
|
|
214
214
|
*/
|
|
215
215
|
return { user: ldapUser };
|
|
216
216
|
}
|
package/docker-compose.yml
CHANGED
package/index.js
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
const assert = require('assert')
|
|
2
2
|
const ldap = require('ldapjs')
|
|
3
3
|
|
|
4
|
+
// convert an escaped utf8 string returned from ldapjs
|
|
5
|
+
function _isHex(c) {
|
|
6
|
+
return (
|
|
7
|
+
(c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
|
|
8
|
+
)
|
|
9
|
+
}
|
|
10
|
+
function _parseEscapedHexToUtf8(s) {
|
|
11
|
+
// convert 'cn=\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8,ou=users,dc=example,dc=com'
|
|
12
|
+
// to 'cn=研发A部,ou=users,dc=example,dc=com'
|
|
13
|
+
let ret = Buffer.alloc(0)
|
|
14
|
+
let len = s.length
|
|
15
|
+
for (let i = 0; i < len; i++) {
|
|
16
|
+
let c = s[i]
|
|
17
|
+
let item
|
|
18
|
+
if (c == '\\' && i < len - 2 && _isHex(s[i + 1]) && _isHex(s[i + 2])) {
|
|
19
|
+
item = Buffer.from(s.substring(i + 1, i + 3), 'hex')
|
|
20
|
+
i += 2
|
|
21
|
+
} else {
|
|
22
|
+
item = Buffer.from(c)
|
|
23
|
+
}
|
|
24
|
+
ret = Buffer.concat([ret, item])
|
|
25
|
+
}
|
|
26
|
+
return ret.toString()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function _recursiveParseHexString(obj) {
|
|
30
|
+
if (Array.isArray(obj)) {
|
|
31
|
+
return obj.map((ele) => _recursiveParseHexString(ele))
|
|
32
|
+
}
|
|
33
|
+
if (typeof obj == 'string') {
|
|
34
|
+
return _parseEscapedHexToUtf8(obj)
|
|
35
|
+
}
|
|
36
|
+
if (typeof obj == 'object') {
|
|
37
|
+
for (let key in obj) {
|
|
38
|
+
obj[key] = _recursiveParseHexString(obj[key])
|
|
39
|
+
}
|
|
40
|
+
return obj
|
|
41
|
+
}
|
|
42
|
+
return obj
|
|
43
|
+
}
|
|
4
44
|
// convert a SearchResultEntry object in ldapjs 3.0
|
|
5
45
|
// to a user object to maintain backward compatibility
|
|
6
46
|
|
|
@@ -11,7 +51,7 @@ function _searchResultToUser(pojo) {
|
|
|
11
51
|
user[attribute.type] =
|
|
12
52
|
attribute.values.length == 1 ? attribute.values[0] : attribute.values
|
|
13
53
|
})
|
|
14
|
-
return user
|
|
54
|
+
return _recursiveParseHexString(user)
|
|
15
55
|
}
|
|
16
56
|
// bind and return the ldap client
|
|
17
57
|
function _ldapBind(dn, password, starttls, ldapOpts) {
|
|
@@ -142,7 +182,7 @@ async function _searchUserGroups(
|
|
|
142
182
|
return
|
|
143
183
|
}
|
|
144
184
|
res.on('searchEntry', function (entry) {
|
|
145
|
-
groups.push(entry.pojo)
|
|
185
|
+
groups.push(_recursiveParseHexString(entry.pojo))
|
|
146
186
|
})
|
|
147
187
|
res.on('searchReference', function (referral) {})
|
|
148
188
|
res.on('error', function (err) {
|
|
@@ -366,7 +406,8 @@ async function authenticate(options) {
|
|
|
366
406
|
options.groupsSearchBase,
|
|
367
407
|
options.groupClass,
|
|
368
408
|
options.groupMemberAttribute,
|
|
369
|
-
options.groupMemberUserAttribute
|
|
409
|
+
options.groupMemberUserAttribute,
|
|
410
|
+
options.attributes
|
|
370
411
|
)
|
|
371
412
|
}
|
|
372
413
|
assert(options.userPassword, 'userPassword must be provided')
|
|
@@ -422,3 +463,9 @@ class LdapAuthenticationError extends Error {
|
|
|
422
463
|
|
|
423
464
|
module.exports.authenticate = authenticate
|
|
424
465
|
module.exports.LdapAuthenticationError = LdapAuthenticationError
|
|
466
|
+
|
|
467
|
+
module.exports.exportForTesting = {
|
|
468
|
+
_isHex,
|
|
469
|
+
_parseEscapedHexToUtf8,
|
|
470
|
+
_recursiveParseHexString,
|
|
471
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ldap-authentication",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.4",
|
|
4
4
|
"description": "A simple async nodejs library for LDAP user authentication",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -39,6 +39,6 @@
|
|
|
39
39
|
"ldapjs": "^3.0.7"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"jasmine": "^5.
|
|
42
|
+
"jasmine": "^5.3.0"
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const { exportForTesting } = require('../index.js')
|
|
2
|
+
const { _isHex, _parseEscapedHexToUtf8, _recursiveParseHexString } =
|
|
3
|
+
exportForTesting
|
|
4
|
+
|
|
5
|
+
describe('string conversion test', () => {
|
|
6
|
+
it('unescape string test', () => {
|
|
7
|
+
let s =
|
|
8
|
+
'cn=\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8,ou=users,dc=example,dc=com'
|
|
9
|
+
let us = _parseEscapedHexToUtf8(s)
|
|
10
|
+
expect(us).toEqual('cn=研发A部,ou=users,dc=example,dc=com')
|
|
11
|
+
})
|
|
12
|
+
it('unescape string test2', () => {
|
|
13
|
+
let s =
|
|
14
|
+
'cn=\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8\\c2\\a9,ou=users,dc=example,dc=com'
|
|
15
|
+
let us = _parseEscapedHexToUtf8(s)
|
|
16
|
+
expect(us).toEqual('cn=研发A部©,ou=users,dc=example,dc=com')
|
|
17
|
+
})
|
|
18
|
+
it('unescape string test3', () => {
|
|
19
|
+
let s = 'cn=ABC,ou=users,dc=example,dc=com'
|
|
20
|
+
let us = _parseEscapedHexToUtf8(s)
|
|
21
|
+
expect(us).toEqual('cn=ABC,ou=users,dc=example,dc=com')
|
|
22
|
+
})
|
|
23
|
+
it('convert obj', () => {
|
|
24
|
+
let target = {
|
|
25
|
+
a: ['研发A部©', 'abc'],
|
|
26
|
+
b: 'xyz',
|
|
27
|
+
c: true,
|
|
28
|
+
d: null,
|
|
29
|
+
e: '研发A部©',
|
|
30
|
+
f: 1000,
|
|
31
|
+
}
|
|
32
|
+
let obj = {
|
|
33
|
+
a: ['\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8\\c2\\a9', 'abc'],
|
|
34
|
+
b: 'xyz',
|
|
35
|
+
c: true,
|
|
36
|
+
d: null,
|
|
37
|
+
e: '\\e7\\a0\\94\\e5\\8f\\91A\\e9\\83\\a8\\c2\\a9',
|
|
38
|
+
f: 1000,
|
|
39
|
+
}
|
|
40
|
+
let converted = _recursiveParseHexString(obj)
|
|
41
|
+
expect(converted).toEqual(target)
|
|
42
|
+
})
|
|
43
|
+
})
|
package/test/test.spec.js
CHANGED
|
@@ -20,6 +20,26 @@ describe('ldap-authentication test', () => {
|
|
|
20
20
|
expect(user).toBeTruthy()
|
|
21
21
|
expect(user.uid).toEqual('gauss')
|
|
22
22
|
})
|
|
23
|
+
it('Use an admin user to check if user exists and return attributes', async () => {
|
|
24
|
+
let options = {
|
|
25
|
+
ldapOpts: {
|
|
26
|
+
url: url,
|
|
27
|
+
},
|
|
28
|
+
adminDn: 'cn=read-only-admin,dc=example,dc=com',
|
|
29
|
+
adminPassword: 'password',
|
|
30
|
+
verifyUserExists: true,
|
|
31
|
+
userSearchBase: 'dc=example,dc=com',
|
|
32
|
+
usernameAttribute: 'uid',
|
|
33
|
+
username: 'gauss',
|
|
34
|
+
attributes: ['uid', 'sn'],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let user = await authenticate(options)
|
|
38
|
+
expect(user).toBeTruthy()
|
|
39
|
+
expect(user.uid).toEqual('gauss')
|
|
40
|
+
expect(user.sn).toEqual('Bar1')
|
|
41
|
+
expect(user.cn).toBeUndefined()
|
|
42
|
+
})
|
|
23
43
|
it('Use an admin user to authenticate a regular user', async () => {
|
|
24
44
|
let options = {
|
|
25
45
|
ldapOpts: {
|
|
@@ -37,7 +57,7 @@ describe('ldap-authentication test', () => {
|
|
|
37
57
|
expect(user).toBeTruthy()
|
|
38
58
|
expect(user.uid).toEqual('gauss')
|
|
39
59
|
})
|
|
40
|
-
it('Use an admin user to authenticate a regular user and return
|
|
60
|
+
it('Use an admin user to authenticate a regular user and return attributes', async () => {
|
|
41
61
|
let options = {
|
|
42
62
|
ldapOpts: {
|
|
43
63
|
url: url,
|
|
@@ -124,6 +144,9 @@ describe('ldap-authentication test', () => {
|
|
|
124
144
|
let user = await authenticate(options)
|
|
125
145
|
expect(user).toBeTruthy()
|
|
126
146
|
expect(user.groups.length).toBeGreaterThan(0)
|
|
147
|
+
expect(user.groups[0].objectName).toEqual(
|
|
148
|
+
'cn=科学A部,ou=users,dc=example,dc=com'
|
|
149
|
+
)
|
|
127
150
|
})
|
|
128
151
|
it('Use regular user to authenticate and fetch user group information', async () => {
|
|
129
152
|
let options = {
|
|
@@ -144,6 +167,9 @@ describe('ldap-authentication test', () => {
|
|
|
144
167
|
let user = await authenticate(options)
|
|
145
168
|
expect(user).toBeTruthy()
|
|
146
169
|
expect(user.groups.length).toBeGreaterThan(0)
|
|
170
|
+
expect(user.groups[0].objectName).toEqual(
|
|
171
|
+
'cn=科学A部,ou=users,dc=example,dc=com'
|
|
172
|
+
)
|
|
147
173
|
})
|
|
148
174
|
it('Not specifying groupMemberAttribute or groupMemberUserAttribute should not cause an error and fallback to default values', async () => {
|
|
149
175
|
let options = {
|