From 19e9d5db3c6ecfdb7b8e559c575eba440b4a138c Mon Sep 17 00:00:00 2001 From: Bojidar Marinov Date: Tue, 26 Mar 2024 07:53:44 +0200 Subject: [PATCH] Allow using LDAP for user login --- .env.example | 5 + package-lock.json | 180 +++++++++++++++++++++++ package.json | 1 + server/config/passport.js | 91 +++++++++--- server/controllers/session.controller.js | 4 +- 5 files changed, 259 insertions(+), 22 deletions(-) diff --git a/.env.example b/.env.example index ea00adf9d9..88c94d58c4 100644 --- a/.env.example +++ b/.env.example @@ -30,3 +30,8 @@ SESSION_SECRET=whatever_you_want_this_to_be_it_only_matters_for_production TRANSLATIONS_ENABLED=true UI_ACCESS_TOKEN_ENABLED=false UPLOAD_LIMIT=250000000 +USE_LDAP=false +LDAP_URL=ldap://localhost:3890 +LDAP_BIND_DN=uid=test,ou=people,dc=example,dc=com +LDAP_BIND_CREDENTIALS=testpassword +LDAP_USER_SEARCH_BASE=ou=people,dc=example,dc=com diff --git a/package-lock.json b/package-lock.json index 91e1f505e6..bca89fb751 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,6 +72,7 @@ "passport-github2": "^0.1.12", "passport-google-oauth20": "^1.0.0", "passport-http": "^0.3.0", + "passport-ldapauth": "^3.0.1", "passport-local": "^1.0.0", "prettier": "2.2.1", "pretty-bytes": "^3.0.1", @@ -17608,6 +17609,14 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, + "node_modules/@types/ldapjs": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@types/ldapjs/-/ldapjs-2.2.5.tgz", + "integrity": "sha512-Lv/nD6QDCmcT+V1vaTRnEKE8UgOilVv5pHcQuzkU1LcRe4mbHHuUo/KHi0LKrpdHhQY8FJzryF38fcVdeUIrzg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/lodash": { "version": "4.14.199", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", @@ -18261,6 +18270,11 @@ "node": ">=6.5" } }, + "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": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -19755,6 +19769,17 @@ "@babel/core": "^7.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/bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -34120,6 +34145,57 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, + "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/ldapauth-fork": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/ldapauth-fork/-/ldapauth-fork-5.0.5.tgz", + "integrity": "sha512-LWUk76+V4AOZbny/3HIPQtGPWZyA3SW2tRhsWIBi9imP22WJktKLHV1ofd8Jo/wY7Ve6vAT7FCI5mEn3blZTjw==", + "dependencies": { + "@types/ldapjs": "^2.2.2", + "bcryptjs": "^2.4.0", + "ldapjs": "^2.2.1", + "lru-cache": "^7.10.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ldapauth-fork/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "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/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -37886,6 +37962,18 @@ "node": ">= 0.4.0" } }, + "node_modules/passport-ldapauth": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/passport-ldapauth/-/passport-ldapauth-3.0.1.tgz", + "integrity": "sha512-TRRx3BHi8GC8MfCT9wmghjde/EGeKjll7zqHRRfGRxXbLcaDce2OftbQrFG7/AWaeFhR6zpZHtBQ/IkINdLVjQ==", + "dependencies": { + "ldapauth-fork": "^5.0.1", + "passport-strategy": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/passport-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", @@ -39501,6 +39589,14 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "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/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -58098,6 +58194,14 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, + "@types/ldapjs": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@types/ldapjs/-/ldapjs-2.2.5.tgz", + "integrity": "sha512-Lv/nD6QDCmcT+V1vaTRnEKE8UgOilVv5pHcQuzkU1LcRe4mbHHuUo/KHi0LKrpdHhQY8FJzryF38fcVdeUIrzg==", + "requires": { + "@types/node": "*" + } + }, "@types/lodash": { "version": "4.14.199", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", @@ -58683,6 +58787,11 @@ "event-target-shim": "^5.0.0" } }, + "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": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -59836,6 +59945,14 @@ "babel-preset-current-node-syntax": "^1.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" + } + }, "bail": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", @@ -70655,6 +70772,47 @@ } } }, + "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" + } + }, + "ldapauth-fork": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/ldapauth-fork/-/ldapauth-fork-5.0.5.tgz", + "integrity": "sha512-LWUk76+V4AOZbny/3HIPQtGPWZyA3SW2tRhsWIBi9imP22WJktKLHV1ofd8Jo/wY7Ve6vAT7FCI5mEn3blZTjw==", + "requires": { + "@types/ldapjs": "^2.2.2", + "bcryptjs": "^2.4.0", + "ldapjs": "^2.2.1", + "lru-cache": "^7.10.1" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" + } + } + }, + "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" + } + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -73630,6 +73788,15 @@ "passport-strategy": "1.x.x" } }, + "passport-ldapauth": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/passport-ldapauth/-/passport-ldapauth-3.0.1.tgz", + "integrity": "sha512-TRRx3BHi8GC8MfCT9wmghjde/EGeKjll7zqHRRfGRxXbLcaDce2OftbQrFG7/AWaeFhR6zpZHtBQ/IkINdLVjQ==", + "requires": { + "ldapauth-fork": "^5.0.1", + "passport-strategy": "^1.0.0" + } + }, "passport-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", @@ -74834,6 +75001,11 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "precond": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", + "integrity": "sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ==" + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -78391,6 +78563,14 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "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" + } + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", diff --git a/package.json b/package.json index 8fefd2e9a0..1926cf7d31 100644 --- a/package.json +++ b/package.json @@ -217,6 +217,7 @@ "passport-google-oauth20": "^1.0.0", "passport-http": "^0.3.0", "passport-local": "^1.0.0", + "passport-ldapauth": "^3.0.1", "prettier": "2.2.1", "pretty-bytes": "^3.0.1", "primer-tooltips": "^1.5.11", diff --git a/server/config/passport.js b/server/config/passport.js index 29d38df139..f81e980985 100644 --- a/server/config/passport.js +++ b/server/config/passport.js @@ -6,6 +6,7 @@ import passport from 'passport'; import GitHubStrategy from 'passport-github2'; import LocalStrategy from 'passport-local'; import GoogleStrategy from 'passport-google-oauth20'; +import LdapStrategy from 'passport-ldapauth'; import { BasicStrategy } from 'passport-http'; import User from '../models/user'; @@ -34,28 +35,76 @@ passport.deserializeUser((id, done) => { /** * Sign in using Email/Username and Password. */ -passport.use( - new LocalStrategy({ usernameField: 'email' }, (email, password, done) => { - User.findByEmailOrUsername(email) - .then((user) => { - if (!user) { - done(null, false, { msg: `Email ${email} not found.` }); - return; - } else if (user.banned) { - done(null, false, { msg: accountSuspensionMessage }); - return; - } - user.comparePassword(password).then((isMatch) => { - if (isMatch) { - done(null, user); - } else { - done(null, false, { msg: 'Invalid email or password.' }); +const useLdap = process.env.USE_LDAP === 'true'; +if (!useLdap) { + passport.use( + new LocalStrategy({ usernameField: 'email' }, (email, password, done) => { + User.findByEmailOrUsername(email) + .then((user) => { + if (!user) { + done(null, false, { msg: `Email ${email} not found.` }); + return; + } else if (user.banned) { + done(null, false, { msg: accountSuspensionMessage }); + return; } - }); - }) - .catch((err) => done(null, false, { msg: err })); - }) -); + user.comparePassword(password).then((isMatch) => { + if (isMatch) { + done(null, user); + } else { + done(null, false, { msg: 'Invalid email or password.' }); + } + }); + }) + .catch((err) => done(null, false, { msg: err })); + }) + ); +} else { + // if (useLdap) + passport.use( + new LdapStrategy( + { + server: { + url: process.env.LDAP_URL, + bindDN: process.env.LDAP_BIND_DN, + bindCredentials: process.env.LDAP_BIND_CREDENTIALS, + searchBase: process.env.LDAP_USER_SEARCH_BASE, + searchFilter: + process.env.LDAP_USER_SEARCH_FILTER || + '(|(uid={{username}})(mail={{username}}))' + }, + usernameField: 'email' + }, + (ldapUser, done) => { + const email = ldapUser[process.env.LDAP_MAIL_ATTR || 'mail']; + const username = ldapUser[process.env.LDAP_USER_ATTR || 'uid']; + const displayName = ldapUser[process.env.LDAP_DISPLAY_ATTR || 'cn']; + User.findByEmailAndUsername(email, username) + .then(async (user) => { + if (!user) { + const newUser = new User({ + name: displayName, + username, + email, + verified: User.EmailConfirmation.Verified + }); + await newUser.save(); + return newUser; + } + return user; + }) + .then((user) => { + if (user.banned) { + done(null, false, { msg: accountSuspensionMessage }); + return; + } + done(null, user); + }) + .catch((err) => done(null, false, { msg: err })); + } + ) + ); +} /** * Authentificate using Basic Auth (Username + Api Key) diff --git a/server/controllers/session.controller.js b/server/controllers/session.controller.js index 6604dcdc69..bbdb910a3f 100644 --- a/server/controllers/session.controller.js +++ b/server/controllers/session.controller.js @@ -2,8 +2,10 @@ import passport from 'passport'; import { userResponse } from './user.controller'; +const useLdap = process.env.USE_LDAP === 'true'; + export function createSession(req, res, next) { - passport.authenticate('local', (err, user) => { + passport.authenticate(useLdap ? 'ldapauth' : 'local', (err, user) => { if (err) { next(err); return;