Added LDAP Auth Implementation

This commit is contained in:
Brian C. Arnold 2022-12-21 12:12:51 -05:00 committed by Brian C. Arnold
parent c2793fe29b
commit 464a1ea121
6 changed files with 459 additions and 25 deletions

View file

@ -18,12 +18,20 @@ const PORT = process.env.PORT || 80
const HOST = process.env.HOST const HOST = process.env.HOST
const CONFIG_PATH = process.env.CONFIG_PATH || '/config' const CONFIG_PATH = process.env.CONFIG_PATH || '/config'
const METADATA_PATH = process.env.METADATA_PATH || '/metadata' const METADATA_PATH = process.env.METADATA_PATH || '/metadata'
const UID = process.env.AUDIOBOOKSHELF_UID const UID = process.env.AUDIOBOOKSHELF_UID || 99
const GID = process.env.AUDIOBOOKSHELF_GID const GID = process.env.AUDIOBOOKSHELF_GID || 100
const LDAP_URL = process.env.LDAP_URL
const LDAP_BASE_DN = process.env.LDAP_BASE_DN
const LDAP_BIND_USER = process.env.LDAP_BIND_USER
const LDAP_BIND_PASS = process.env.LDAP_BIND_PASS
const LDAP_SEARCH_FILTER = process.env.LDAP_SEARCH_FILTER
const LDAP_USERNAME_ATTRIBUTE = process.env.LDAP_USERNAME_ATTRIBUTE
const LDAP_ENABLED = process.env.LDAP_ENABLED
const SOURCE = process.env.SOURCE || 'docker' const SOURCE = process.env.SOURCE || 'docker'
const ROUTER_BASE_PATH = process.env.ROUTER_BASE_PATH || '' const ROUTER_BASE_PATH = process.env.ROUTER_BASE_PATH || ''
console.log('Config', CONFIG_PATH, METADATA_PATH) console.log('Config', CONFIG_PATH, METADATA_PATH)
const Server = new server(SOURCE, PORT, HOST, UID, GID, CONFIG_PATH, METADATA_PATH, ROUTER_BASE_PATH) const Server = new server(SOURCE, PORT, HOST, UID, GID, CONFIG_PATH, METADATA_PATH, ROUTER_BASE_PATH, LDAP_ENABLED, LDAP_URL, LDAP_BASE_DN, LDAP_BIND_USER, LDAP_BIND_PASS, LDAP_SEARCH_FILTER, LDAP_USERNAME_ATTRIBUTE)
Server.start() Server.start()

216
package-lock.json generated
View file

@ -13,6 +13,7 @@
"express": "^4.17.1", "express": "^4.17.1",
"graceful-fs": "^4.2.10", "graceful-fs": "^4.2.10",
"htmlparser2": "^8.0.1", "htmlparser2": "^8.0.1",
"ldapjs": "^2.3.3",
"node-tone": "^1.0.1", "node-tone": "^1.0.1",
"nodemailer": "^6.9.2", "nodemailer": "^6.9.2",
"sequelize": "^6.32.1", "sequelize": "^6.32.1",
@ -174,6 +175,11 @@
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
}, },
"node_modules/abstract-logging": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
"integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="
},
"node_modules/accepts": { "node_modules/accepts": {
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -311,6 +317,22 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
}, },
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"dependencies": {
"safer-buffer": "~2.1.0"
}
},
"node_modules/assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/asynckit": { "node_modules/asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -325,6 +347,17 @@
"form-data": "^4.0.0" "form-data": "^4.0.0"
} }
}, },
"node_modules/backoff": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
"integrity": "sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==",
"dependencies": {
"precond": "0.2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -545,6 +578,11 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
}, },
"node_modules/core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
},
"node_modules/cors": { "node_modules/cors": {
"version": "2.8.5", "version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
@ -835,6 +873,14 @@
"node": ">= 0.10.0" "node": ">= 0.10.0"
} }
}, },
"node_modules/extsprintf": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz",
"integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==",
"engines": [
"node >=0.6.0"
]
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@ -1308,6 +1354,35 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"optional": true "optional": true
}, },
"node_modules/ldap-filter": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/ldap-filter/-/ldap-filter-0.3.3.tgz",
"integrity": "sha512-/tFkx5WIn4HuO+6w9lsfxq4FN3O+fDZeO9Mek8dCD8rTUpqzRa766BOBO7BcGkn3X86m5+cBm1/2S/Shzz7gMg==",
"dependencies": {
"assert-plus": "^1.0.0"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/ldapjs": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.3.3.tgz",
"integrity": "sha512-75QiiLJV/PQqtpH+HGls44dXweviFwQ6SiIK27EqzKQ5jU/7UFrl2E5nLdQ3IYRBzJ/AVFJI66u0MZ0uofKYwg==",
"dependencies": {
"abstract-logging": "^2.0.0",
"asn1": "^0.2.4",
"assert-plus": "^1.0.0",
"backoff": "^2.5.0",
"ldap-filter": "^0.3.3",
"once": "^1.4.0",
"vasync": "^2.2.0",
"verror": "^1.8.1"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@ -1870,6 +1945,14 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/precond": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz",
"integrity": "sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/promise-inflight": { "node_modules/promise-inflight": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@ -2598,6 +2681,43 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/vasync": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/vasync/-/vasync-2.2.1.tgz",
"integrity": "sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ==",
"engines": [
"node >=0.6.0"
],
"dependencies": {
"verror": "1.10.0"
}
},
"node_modules/vasync/node_modules/verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
"engines": [
"node >=0.6.0"
],
"dependencies": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
},
"node_modules/verror": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
"integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
"dependencies": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
},
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/webidl-conversions": { "node_modules/webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@ -2818,6 +2938,11 @@
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
}, },
"abstract-logging": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
"integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="
},
"accepts": { "accepts": {
"version": "1.3.8", "version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -2922,6 +3047,19 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
}, },
"asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"requires": {
"safer-buffer": "~2.1.0"
}
},
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="
},
"asynckit": { "asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -2936,6 +3074,14 @@
"form-data": "^4.0.0" "form-data": "^4.0.0"
} }
}, },
"backoff": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
"integrity": "sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==",
"requires": {
"precond": "0.2"
}
},
"balanced-match": { "balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -3102,6 +3248,11 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
}, },
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
},
"cors": { "cors": {
"version": "2.8.5", "version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
@ -3324,6 +3475,11 @@
"vary": "~1.1.2" "vary": "~1.1.2"
} }
}, },
"extsprintf": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz",
"integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="
},
"fill-range": { "fill-range": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@ -3673,6 +3829,29 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"optional": true "optional": true
}, },
"ldap-filter": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/ldap-filter/-/ldap-filter-0.3.3.tgz",
"integrity": "sha512-/tFkx5WIn4HuO+6w9lsfxq4FN3O+fDZeO9Mek8dCD8rTUpqzRa766BOBO7BcGkn3X86m5+cBm1/2S/Shzz7gMg==",
"requires": {
"assert-plus": "^1.0.0"
}
},
"ldapjs": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.3.3.tgz",
"integrity": "sha512-75QiiLJV/PQqtpH+HGls44dXweviFwQ6SiIK27EqzKQ5jU/7UFrl2E5nLdQ3IYRBzJ/AVFJI66u0MZ0uofKYwg==",
"requires": {
"abstract-logging": "^2.0.0",
"asn1": "^0.2.4",
"assert-plus": "^1.0.0",
"backoff": "^2.5.0",
"ldap-filter": "^0.3.3",
"once": "^1.4.0",
"vasync": "^2.2.0",
"verror": "^1.8.1"
}
},
"lodash": { "lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@ -4080,6 +4259,11 @@
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true "dev": true
}, },
"precond": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz",
"integrity": "sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ=="
},
"promise-inflight": { "promise-inflight": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@ -4602,6 +4786,36 @@
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
}, },
"vasync": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/vasync/-/vasync-2.2.1.tgz",
"integrity": "sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ==",
"requires": {
"verror": "1.10.0"
},
"dependencies": {
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
}
}
},
"verror": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
"integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
},
"webidl-conversions": { "webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@ -4672,4 +4886,4 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
} }
} }
} }

View file

@ -34,6 +34,7 @@
"express": "^4.17.1", "express": "^4.17.1",
"graceful-fs": "^4.2.10", "graceful-fs": "^4.2.10",
"htmlparser2": "^8.0.1", "htmlparser2": "^8.0.1",
"ldapjs": "^2.3.3",
"node-tone": "^1.0.1", "node-tone": "^1.0.1",
"nodemailer": "^6.9.2", "nodemailer": "^6.9.2",
"sequelize": "^6.32.1", "sequelize": "^6.32.1",
@ -44,4 +45,4 @@
"devDependencies": { "devDependencies": {
"nodemon": "^2.0.20" "nodemon": "^2.0.20"
} }
} }

View file

@ -1,11 +1,28 @@
const bcrypt = require('./libs/bcryptjs') const bcrypt = require('./libs/bcryptjs')
const ldap = require('./authProviders/ldap')
const jwt = require('./libs/jsonwebtoken') const jwt = require('./libs/jsonwebtoken')
const requestIp = require('./libs/requestIp') const requestIp = require('./libs/requestIp')
const User = require('./objects/user/User')
const { getId } = require('./utils')
const Logger = require('./Logger') const Logger = require('./Logger')
const Database = require('./Database') const Database = require('./Database')
class Auth { class Auth {
constructor() { }
constructor() {
if (global.ldapEnabled) {
this.ldap = ldap
}
this.user = null
}
get username() {
return this.user ? this.user.username : 'nobody'
}
get users() {
return Database.users
}
cors(req, res, next) { cors(req, res, next) {
res.header('Access-Control-Allow-Origin', '*') res.header('Access-Control-Allow-Origin', '*')
@ -34,15 +51,18 @@ class Auth {
// New token secret creation added in v2.1.0 so generate new API tokens for each user // New token secret creation added in v2.1.0 so generate new API tokens for each user
if (Database.users.length) { if (Database.users.length) {
for (const user of Database.users) { for (const user of Database.users) {
user.token = await this.generateAccessToken({ userId: user.id, username: user.username }) if (user.token) {
Logger.warn(`[Auth] User ${user.username} api token has been updated using new token secret`) delete user.token
}
//Tokens should never be stored on the server, the entire point is that the server can cryptographically verify the token without storing it
Logger.warn(`[Auth] User ${user.username} api token security hole has been removed`)
} }
await Database.updateBulkUsers(Database.users) await Database.updateBulkUsers(Database.users)
} }
} }
async authMiddleware(req, res, next) { async authMiddleware(req, res, next) {
var token = null let token = null
// If using a get request, the token can be passed as a query string // If using a get request, the token can be passed as a query string
if (req.method === 'GET' && req.query && req.query.token) { if (req.method === 'GET' && req.query && req.query.token) {
@ -104,7 +124,16 @@ class Auth {
}) })
} }
getUserLoginResponsePayload(user) { async getLdapUsers() {
if (this.ldap) {
const ldapUsers = await this.ldap.findAllUsers();
return ldapUsers;
} else {
return [];
}
}
async getUserLoginResponsePayload(user) {
user.token = await this.generateAccessToken({ userId: user.id, username: user.username });
return { return {
user: user.toJSONForBrowser(), user: user.toJSONForBrowser(),
userDefaultLibraryId: user.getDefaultLibraryId(Database.libraries), userDefaultLibraryId: user.getDefaultLibraryId(Database.libraries),
@ -119,7 +148,28 @@ class Auth {
const username = (req.body.username || '').toLowerCase() const username = (req.body.username || '').toLowerCase()
const password = req.body.password || '' const password = req.body.password || ''
const user = Database.users.find(u => u.username.toLowerCase() === username) let user = Database.users.find(u => u.username.toLowerCase() === username)
if (!user && this.ldap) {
Logger.warn(`[Auth] Could not find user in database from ${ipAddress}, checking LDAP...`)
const ldapUsers = await this.getLdapUsers()
Logger.debug(ldapUsers);
if (ldapUsers.filter(u => u[global.ldapUsernameAttribute].toLowerCase() == username.toLowerCase())) {
const ldapUser = ldapUsers.find(v => v[global.ldapUsernameAttribute].toLowerCase() == username.toLowerCase())
const newUser = new User({
username: ldapUser[global.ldapUsernameAttribute],
id: getId('usr'),
isActive: true,
isLocked: false,
librariesAccessible: [],
itemTagsAccessible: [],
type: 'ldap'
})
const success = await Database.insertEntity('user', newUser)
if (success) {
user = Database.users.find(u => u.username.toLowerCase() === username)
}
}
}
if (!user?.isActive) { if (!user?.isActive) {
Logger.warn(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit} from ${ipAddress}`) Logger.warn(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit} from ${ipAddress}`)
@ -136,22 +186,46 @@ class Auth {
return res.status(401).send('Invalid root password (hint: there is none)') return res.status(401).send('Invalid root password (hint: there is none)')
} else { } else {
Logger.info(`[Auth] ${user.username} logged in from ${ipAddress}`) Logger.info(`[Auth] ${user.username} logged in from ${ipAddress}`)
return res.json(this.getUserLoginResponsePayload(user)) return res.json(await this.getUserLoginResponsePayload(user))
} }
} }
// Check password match if (!user.pash || user.pash === '') {
const compare = await bcrypt.compare(password, user.pash) // Check LDAP for user.
if (compare) {
Logger.info(`[Auth] ${user.username} logged in from ${ipAddress}`) const ldapUsers = await this.getLdapUsers()
res.json(this.getUserLoginResponsePayload(user)) const ldapUser = ldapUsers.find(v => v[global.ldapUsernameAttribute].toLowerCase() == username.toLowerCase())
} else { if (ldapUser != null) {
Logger.warn(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit} from ${ipAddress}`) const adUserName = ldapUser.dn
if (req.rateLimit.remaining <= 2) { const authResult = await this.ldap.authenticateUser(adUserName, password);
Logger.error(`[Auth] Failed login attempt for user ${user.username} from ip ${ipAddress}. Attempts: ${req.rateLimit.current}`) if (authResult) {
return res.status(401).send(`Invalid user or password (${req.rateLimit.remaining === 0 ? '1 attempt remaining' : `${req.rateLimit.remaining + 1} attempts remaining`})`) res.json(await this.getUserLoginResponsePayload(user))
} else {
Logger.warn(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit} from ${ipAddress}`)
if (req.rateLimit.remaining <= 2) {
Logger.error(`[Auth] Failed login attempt for user ${user.username} from ip ${ipAddress}. Attempts: ${req.rateLimit.current}`)
return res.status(401).send(`Invalid user or password (${req.rateLimit.remaining === 0 ? '1 attempt remaining' : `${req.rateLimit.remaining + 1} attempts remaining`})`)
}
return res.status(401).send('Invalid user or password')
}
} else {
return res.status(401).send('Invalid user or password')
}
} else {
// Check password match
const compare = await bcrypt.compare(password, user.pash)
if (compare) {
Logger.info(`[Auth] ${user.username} logged in from ${ipAddress}`)
res.json(await this.getUserLoginResponsePayload(user))
} else {
Logger.warn(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit} from ${ipAddress}`)
if (req.rateLimit.remaining <= 2) {
Logger.error(`[Auth] Failed login attempt for user ${user.username} from ip ${ipAddress}. Attempts: ${req.rateLimit.current}`)
return res.status(401).send(`Invalid user or password (${req.rateLimit.remaining === 0 ? '1 attempt remaining' : `${req.rateLimit.remaining + 1} attempts remaining`})`)
}
return res.status(401).send('Invalid user or password')
} }
return res.status(401).send('Invalid user or password')
} }
} }
@ -162,7 +236,7 @@ class Auth {
} }
async userChangePassword(req, res) { async userChangePassword(req, res) {
var { password, newPassword } = req.body const { password, newPassword } = req.body
newPassword = newPassword || '' newPassword = newPassword || ''
const matchingUser = Database.users.find(u => u.id === req.user.id) const matchingUser = Database.users.find(u => u.id === req.user.id)

View file

@ -38,7 +38,7 @@ const CronManager = require('./managers/CronManager')
const TaskManager = require('./managers/TaskManager') const TaskManager = require('./managers/TaskManager')
class Server { class Server {
constructor(SOURCE, PORT, HOST, UID, GID, CONFIG_PATH, METADATA_PATH, ROUTER_BASE_PATH) { constructor(SOURCE, PORT, HOST, UID, GID, CONFIG_PATH, METADATA_PATH, ROUTER_BASE_PATH, LDAP_ENABLED, LDAP_URL, LDAP_BASE_DN, LDAP_BIND_USER, LDAP_BIND_PASS, LDAP_SEARCH_FILTER, LDAP_USERNAME_ATTRIBUTE) {
this.Port = PORT this.Port = PORT
this.Host = HOST this.Host = HOST
global.Source = SOURCE global.Source = SOURCE
@ -49,6 +49,19 @@ class Server {
global.MetadataPath = fileUtils.filePathToPOSIX(Path.normalize(METADATA_PATH)) global.MetadataPath = fileUtils.filePathToPOSIX(Path.normalize(METADATA_PATH))
global.RouterBasePath = ROUTER_BASE_PATH global.RouterBasePath = ROUTER_BASE_PATH
global.XAccel = process.env.USE_X_ACCEL global.XAccel = process.env.USE_X_ACCEL
global.ldapUrl = LDAP_URL
global.ldapBaseDn = LDAP_BASE_DN
global.ldapBindUser = LDAP_BIND_USER
global.ldapBindPass = LDAP_BIND_PASS
global.ldapSearchFilter = LDAP_SEARCH_FILTER
global.ldapUsernameAttribute = LDAP_USERNAME_ATTRIBUTE
global.ldapEnabled = LDAP_ENABLED
// Fix backslash if not on Windows
if (process.platform !== 'win32') {
global.ConfigPath = global.ConfigPath.replace(/\\/g, '/')
global.MetadataPath = global.MetadataPath.replace(/\\/g, '/')
}
if (!fs.pathExistsSync(global.ConfigPath)) { if (!fs.pathExistsSync(global.ConfigPath)) {
fs.mkdirSync(global.ConfigPath) fs.mkdirSync(global.ConfigPath)

View file

@ -0,0 +1,124 @@
const ldap = require('ldapjs');
const Logger = require('../Logger');
const { res } = require('../libs/dateAndTime');
const { integer } = require('../libs/requestIp/isJs');
class LdapAuth {
constructor() {}
async getClient() {
return await new Promise((resolve, reject) => {
Logger.debug('Connecting...')
const client = ldap.createClient({
url: global.ldapUrl,
bindDN: global.ldapBindUser,
bindCredentials: global.ldapBindPass
});
let onError, onConnect, onTimeout;
onError = err => {
Logger.error('Error Connecting.')
client.removeListener('connect', onConnect);
reject(err);
};
onConnect = () => {
Logger.debug('Connected.')
Logger.debug('Binding...')
client.removeListener('error', onError);
client.bind(global.ldapBindUser, global.ldapBindPass, err => {
if (err) {
Logger.error('Error Binding.')
reject(err);
}
else {
Logger.debug('Bound.')
resolve(client);
}
// client.search
})
};
onTimeout = () => {
Logger.error('Timeout Connecting.')
reject(new Error('LDAP connection timeout'));
}
client.once('connect', onConnect)
client.once('error', onError)
client.once('connectTimeout', onTimeout)
})
}
async findAllUsers() {
const client = await this.getClient();
return await new Promise((resolve, reject) => {
Logger.info('Searching...')
Logger.debug(`global.ldapBaseDn: ${global.ldapBaseDn}`)
Logger.debug(`global.ldapSearchFilter: ${global.ldapSearchFilter}`)
client.search(global.ldapBaseDn, {
scope: 'sub',
filter: global.ldapSearchFilter
}, (err, res) => {
Logger.info('Search returned.')
const items = []
Logger.debug('Attaching to entry events...')
res.on('searchEntry', entry => {
Logger.debug('adding entry...')
items.push(entry.object)
})
Logger.debug('Attaching to error events...')
res.on('end', result => {
Logger.debug('end of entries.')
Logger.info(`Search complete, returning ${items.length} items.`)
resolve(items);
})
})
})
}
authenticateUser(username, password) {
Logger.info('Authenticating LDAP User...');
return new Promise((resolve, reject) => {
const tempClient = ldap.createClient({
url: global.ldapUrl,
bindDN: username,
bindCredentials: password
})
let onError, onConnect, onTimeout;
onError = err => {
Logger.error('Error Connecting.')
tempClient.removeListener('connect', onConnect);
resolve(false);
};
onConnect = () => {
Logger.debug('Connected.')
Logger.debug('Binding...')
tempClient.removeListener('error', onError);
tempClient.bind(username, password, err => {
if (err) {
Logger.error('Error Binding.')
resolve(false);
}
else {
Logger.debug('Bound.')
tempClient.unbind(err => {
if (err) {
Logger.error('Error Unbinding.')
resolve(false);
} else {
Logger.info('User Authenticated.')
resolve(true);
}
})
}
// client.search
})
};
onTimeout = () => {
Logger.error('Timeout Connecting.')
resolve(false);
}
tempClient.once('connect', onConnect)
tempClient.once('error', onError)
tempClient.once('connectTimeout', onTimeout)
});
}
}
module.exports = new LdapAuth()