1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-08-05 13:35:27 +02:00

feat: Add ldap authentication

This commit is contained in:
Erwan Boisard 2022-06-29 10:58:42 +02:00
parent ddf9a32ea9
commit 9cf7cbe0ca
18 changed files with 16581 additions and 7321 deletions

16551
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -63,16 +63,23 @@
} }
}, },
"dependencies": { "dependencies": {
"assert": "^2.0.0",
"buffer": "^6.0.3",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"connected-react-router": "^6.9.2", "connected-react-router": "^6.9.2",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"dequal": "^2.0.2", "dequal": "^2.0.2",
"dtrace-provider": "^0.8.8",
"express": "^4.18.1",
"history": "^4.10.1", "history": "^4.10.1",
"i18next": "^21.6.16", "i18next": "^21.6.16",
"i18next-browser-languagedetector": "^6.1.4", "i18next-browser-languagedetector": "^6.1.4",
"initials": "^3.1.2", "initials": "^3.1.2",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"ldap-authentication": "^2.2.9",
"ldapjs": "^2.3.2",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"net": "^1.0.2",
"node-sass": "^7.0.1", "node-sass": "^7.0.1",
"photoswipe": "^5.2.7", "photoswipe": "^5.2.7",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
@ -99,7 +106,11 @@
"sails.io.js": "^1.2.1", "sails.io.js": "^1.2.1",
"semantic-ui-react": "^2.1.2", "semantic-ui-react": "^2.1.2",
"socket.io-client": "^2.3.1", "socket.io-client": "^2.3.1",
"stream-browserify": "^3.0.0",
"tls": "^0.0.1",
"url": "^0.11.0",
"validator": "^13.7.0", "validator": "^13.7.0",
"webcrypto": "^0.1.1",
"whatwg-fetch": "^3.5.0" "whatwg-fetch": "^3.5.0"
}, },
"devDependencies": { "devDependencies": {

View file

@ -28,6 +28,11 @@ const createMessage = (error) => {
type: 'error', type: 'error',
content: 'common.invalidPassword', content: 'common.invalidPassword',
}; };
case 'Ldap authentication failed':
return {
type: 'error',
content: 'common.invalidLdap',
};
case 'Failed to fetch': case 'Failed to fetch':
return { return {
type: 'warning', type: 'warning',

View file

@ -27,6 +27,11 @@ const createMessage = (error) => {
type: 'error', type: 'error',
content: 'common.invalidCurrentPassword', content: 'common.invalidCurrentPassword',
}; };
case 'Action not possible':
return {
type: 'error',
content: 'common.impossibleAction',
};
default: default:
return { return {
type: 'warning', type: 'warning',

View file

@ -21,6 +21,11 @@ const createMessage = (error) => {
type: 'error', type: 'error',
content: 'common.invalidCurrentPassword', content: 'common.invalidCurrentPassword',
}; };
case 'Action not possible':
return {
type: 'error',
content: 'common.impossibleAction',
};
default: default:
return { return {
type: 'warning', type: 'warning',

View file

@ -88,6 +88,7 @@ export default {
fromComputer_title: 'From Computer', fromComputer_title: 'From Computer',
general: 'General', general: 'General',
hours: 'Hours', hours: 'Hours',
impossibleAction: 'Action not possible',
invalidCurrentPassword: 'Invalid current password', invalidCurrentPassword: 'Invalid current password',
labels: 'Labels', labels: 'Labels',
leaveBoard_title: 'Leave Board', leaveBoard_title: 'Leave Board',

View file

@ -3,6 +3,7 @@ export default {
common: { common: {
emailOrUsername: 'E-mail or username', emailOrUsername: 'E-mail or username',
invalidEmailOrUsername: 'Invalid e-mail or username', invalidEmailOrUsername: 'Invalid e-mail or username',
invalidLdap: 'Ldap connection failure',
invalidPassword: 'Invalid password', invalidPassword: 'Invalid password',
logInToPlanka: 'Log in to Planka', logInToPlanka: 'Log in to Planka',
noInternetConnection: 'No internet connection', noInternetConnection: 'No internet connection',

View file

@ -83,6 +83,7 @@ export default {
filterByMembers_title: 'Filtrer par membres', filterByMembers_title: 'Filtrer par membres',
fromComputer_title: "Depuis l'ordinateur", fromComputer_title: "Depuis l'ordinateur",
hours: 'Les heures', hours: 'Les heures',
impossibleAction: 'Action impossible',
invalidCurrentPassword: 'Mot de passe actuel invalide', invalidCurrentPassword: 'Mot de passe actuel invalide',
labels: 'Étiquettes', labels: 'Étiquettes',
list: 'Lister', list: 'Lister',

View file

@ -3,6 +3,7 @@ export default {
common: { common: {
emailOrUsername: "Email ou nom d'utilisateur", emailOrUsername: "Email ou nom d'utilisateur",
invalidEmailOrUsername: "Email ou nom d'utilisateur invalide", invalidEmailOrUsername: "Email ou nom d'utilisateur invalide",
invalidLdap: "Connexion à l'AD échouée",
invalidPassword: 'Mot de passe invalide', invalidPassword: 'Mot de passe invalide',
logInToPlanka: 'Se connecter à Planka', logInToPlanka: 'Se connecter à Planka',
noInternetConnection: 'Aucune connection internet', noInternetConnection: 'Aucune connection internet',

4755
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -39,8 +39,18 @@
] ]
}, },
"dependencies": { "dependencies": {
"buffer": "^6.0.3",
"concurrently": "^7.1.0", "concurrently": "^7.1.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"lint-staged": "^12.3.8" "knex": "^2.1.0",
"ldap-authentication": "^2.2.9",
"ldapjs": "^2.3.2",
"lint-staged": "^12.3.8",
"net": "^1.0.2",
"node": "^17.7.2",
"node-sass": "^7.0.1",
"path": "^0.12.7",
"stream-browserify": "^3.0.0",
"url": "^0.11.0"
} }
} }

View file

@ -2,3 +2,4 @@ TZ=UTC
BASE_URL=http://localhost:1337 BASE_URL=http://localhost:1337
DATABASE_URL=postgresql://postgres@localhost/planka DATABASE_URL=postgresql://postgres@localhost/planka
SECRET_KEY=notsecretkey SECRET_KEY=notsecretkey
LDAP_SERVER=

View file

@ -1,5 +1,8 @@
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
const validator = require('validator'); const validator = require('validator');
const ldap = require('ldapjs');
const createUser = require('../users/create');
const { NULL } = require('node-sass');
const Errors = { const Errors = {
INVALID_EMAIL_OR_USERNAME: { INVALID_EMAIL_OR_USERNAME: {
@ -8,6 +11,9 @@ const Errors = {
INVALID_PASSWORD: { INVALID_PASSWORD: {
invalidPassword: 'Invalid password', invalidPassword: 'Invalid password',
}, },
INVALID_LDAP: {
invalidLdap: 'Ldap authentication failed',
},
}; };
module.exports = { module.exports = {
@ -35,21 +41,102 @@ module.exports = {
invalidPassword: { invalidPassword: {
responseType: 'unauthorized', responseType: 'unauthorized',
}, },
invalidLdap: {
responseType: 'unauthorized',
},
}, },
async fn(inputs) { async fn(inputs) {
const user = await sails.helpers.users.getOneByEmailOrUsername(inputs.emailOrUsername);
if (!user) { if(process.env.LDAP_SERVER){
throw Errors.INVALID_EMAIL_OR_USERNAME; console.log('AUTH mode : LDAP');
const server = process.env.LDAP_SERVER;
const client = ldap.createClient({
url: `ldap://${server}`
});
var token_value = new Promise((resolve) => { client.bind(inputs.emailOrUsername, inputs.password, async (err) => {
var user;
var token;
if(!err){
console.log('AD connection success');
user = await sails.helpers.users.getOneByEmailOrUsername(inputs.emailOrUsername);
if (!user) {
console.log('Non-existent Planka user: creation in progress');
await createUser.fn({
"email": inputs.emailOrUsername,
"password": inputs.password,
"isAdmin": false,
"name": inputs.emailOrUsername,
"subscribeToOwnCards": false,
"createdAt": "date",
"updatedAt": "date"
});
user = await sails.helpers.users.getOneByEmailOrUsername(inputs.emailOrUsername);
}
token = await sails.helpers.utils.signToken(user.id);
resolve(token);
}if(err){
console.log('AD connection failure');
token = '';
resolve(token);
}
})});
// ADMIN CONNEXION
if (await token_value==''){
if(inputs.emailOrUsername=='admin' || inputs.emailOrUsername=='admin@admin.admin') {
console.log('ADMIN CONNEXION');
var user = await sails.helpers.users.getOneByEmailOrUsername(inputs.emailOrUsername);
if (!user) {
throw Errors.INVALID_EMAIL_OR_USERNAME;
}
if (!bcrypt.compareSync(inputs.password, user.password)) {
throw Errors.INVALID_PASSWORD;
}
return {
item: sails.helpers.utils.signToken(user.id),
};
}
throw Errors.INVALID_LDAP;
}
return {
item: await token_value,
};
}else{ // NO LDAP AUTH in .env
console.log('AUTH mode : Normal DB');
const user = await sails.helpers.users.getOneByEmailOrUsername(inputs.emailOrUsername);
if (!user) {
throw Errors.INVALID_EMAIL_OR_USERNAME;
}
if (!bcrypt.compareSync(inputs.password, user.password)) {
throw Errors.INVALID_PASSWORD;
}
return {
item: sails.helpers.utils.signToken(user.id),
};
} }
if (!bcrypt.compareSync(inputs.password, user.password)) {
throw Errors.INVALID_PASSWORD;
}
return {
item: sails.helpers.utils.signToken(user.id),
};
}, },
}; };

View file

@ -10,6 +10,9 @@ const Errors = {
EMAIL_ALREADY_IN_USE: { EMAIL_ALREADY_IN_USE: {
emailAlreadyInUse: 'Email already in use', emailAlreadyInUse: 'Email already in use',
}, },
IMPOSSIBLE_ACTION: {
impossibleAction: 'Action not possible',
},
}; };
module.exports = { module.exports = {
@ -40,44 +43,52 @@ module.exports = {
emailAlreadyInUse: { emailAlreadyInUse: {
responseType: 'conflict', responseType: 'conflict',
}, },
impossibleAction: {
responseType: 'forbidden',
},
}, },
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req;
if (inputs.id === currentUser.id) { if(!process.env.LDAP_SERVER){
if (!inputs.currentPassword) { const { currentUser } = this.req;
if (inputs.id === currentUser.id) {
if (!inputs.currentPassword) {
throw Errors.INVALID_CURRENT_PASSWORD;
}
} else if (!currentUser.isAdmin) {
throw Errors.USER_NOT_FOUND; // Forbidden
}
let user = await sails.helpers.users.getOne(inputs.id);
if (!user) {
throw Errors.USER_NOT_FOUND;
}
if (
inputs.id === currentUser.id &&
!bcrypt.compareSync(inputs.currentPassword, user.password)
) {
throw Errors.INVALID_CURRENT_PASSWORD; throw Errors.INVALID_CURRENT_PASSWORD;
} }
} else if (!currentUser.isAdmin) {
throw Errors.USER_NOT_FOUND; // Forbidden const values = _.pick(inputs, ['email']);
user = await sails.helpers.users
.updateOne(user, values, this.req)
.intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE);
if (!user) {
throw Errors.USER_NOT_FOUND;
}
return {
item: user,
};
} }
throw Errors.IMPOSSIBLE_ACTION;
let user = await sails.helpers.users.getOne(inputs.id);
if (!user) {
throw Errors.USER_NOT_FOUND;
}
if (
inputs.id === currentUser.id &&
!bcrypt.compareSync(inputs.currentPassword, user.password)
) {
throw Errors.INVALID_CURRENT_PASSWORD;
}
const values = _.pick(inputs, ['email']);
user = await sails.helpers.users
.updateOne(user, values, this.req)
.intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE);
if (!user) {
throw Errors.USER_NOT_FOUND;
}
return {
item: user,
};
}, },
}; };

View file

@ -7,6 +7,9 @@ const Errors = {
INVALID_CURRENT_PASSWORD: { INVALID_CURRENT_PASSWORD: {
invalidCurrentPassword: 'Invalid current password', invalidCurrentPassword: 'Invalid current password',
}, },
IMPOSSIBLE_ACTION: {
impossibleAction: 'Action not possible',
},
}; };
module.exports = { module.exports = {
@ -33,41 +36,47 @@ module.exports = {
invalidCurrentPassword: { invalidCurrentPassword: {
responseType: 'forbidden', responseType: 'forbidden',
}, },
impossibleAction: {
responseType: 'forbidden',
},
}, },
async fn(inputs) { async fn(inputs) {
const { currentUser } = this.req; if(!process.env.LDAP_SERVER){
const { currentUser } = this.req;
if (inputs.id === currentUser.id) { if (inputs.id === currentUser.id) {
if (!inputs.currentPassword) { if (!inputs.currentPassword) {
throw Errors.INVALID_CURRENT_PASSWORD;
}
} else if (!currentUser.isAdmin) {
throw Errors.USER_NOT_FOUND; // Forbidden
}
let user = await sails.helpers.users.getOne(inputs.id);
if (!user) {
throw Errors.USER_NOT_FOUND;
}
if (
inputs.id === currentUser.id &&
!bcrypt.compareSync(inputs.currentPassword, user.password)
) {
throw Errors.INVALID_CURRENT_PASSWORD; throw Errors.INVALID_CURRENT_PASSWORD;
} }
} else if (!currentUser.isAdmin) {
throw Errors.USER_NOT_FOUND; // Forbidden const values = _.pick(inputs, ['password']);
user = await sails.helpers.users.updateOne(user, values, this.req);
if (!user) {
throw Errors.USER_NOT_FOUND;
}
return {
item: user,
};
} }
throw Errors.IMPOSSIBLE_ACTION;
let user = await sails.helpers.users.getOne(inputs.id);
if (!user) {
throw Errors.USER_NOT_FOUND;
}
if (
inputs.id === currentUser.id &&
!bcrypt.compareSync(inputs.currentPassword, user.password)
) {
throw Errors.INVALID_CURRENT_PASSWORD;
}
const values = _.pick(inputs, ['password']);
user = await sails.helpers.users.updateOne(user, values, this.req);
if (!user) {
throw Errors.USER_NOT_FOUND;
}
return {
item: user,
};
}, },
}; };

View file

@ -63,7 +63,8 @@ module.exports = {
if ( if (
inputs.id === currentUser.id && inputs.id === currentUser.id &&
!bcrypt.compareSync(inputs.currentPassword, user.password) !bcrypt.compareSync(inputs.currentPassword, user.password) &&
!process.env.LDAP_SERVER
) { ) {
throw Errors.INVALID_CURRENT_PASSWORD; throw Errors.INVALID_CURRENT_PASSWORD;
} }

2293
server/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -44,6 +44,8 @@
"filenamify": "^4.3.0", "filenamify": "^4.3.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"knex": "^1.0.7", "knex": "^1.0.7",
"ldap-authentication": "^2.2.9",
"ldapjs": "^2.3.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.29.3", "moment": "^2.29.3",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",