Migrate wekan-ldap to async API for Meteor 3.0

- Replace Meteor.wrapAsync with native Promises
- Convert all sync methods to async
- Use async DB operations (findOneAsync, updateAsync)
- Bump version 0.0.2 → 0.1.0
This commit is contained in:
Harry Adel 2026-01-29 19:58:23 +02:00
parent eb0c9ac1e6
commit 7d56dca80b
6 changed files with 102 additions and 83 deletions

View file

@ -1,6 +1,6 @@
Package.describe({
name: 'wekan-ldap',
version: '0.0.2',
version: '0.1.0',
// Brief, one-line summary of the package.
summary: 'Basic meteor login with ldap',
// URL to the Git repository containing the source code for this package.

View file

@ -73,19 +73,40 @@ export default class LDAP {
}
}
connectSync(...args) {
if (!this._connectSync) {
this._connectSync = Meteor.wrapAsync(this.connectAsync, this);
}
return this._connectSync(...args);
async connect() {
return new Promise((resolve, reject) => {
this.connectAsync((error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
searchAllSync(...args) {
async searchAll(BaseDN, options) {
return new Promise((resolve, reject) => {
this.searchAllAsync(BaseDN, options, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
if (!this._searchAllSync) {
this._searchAllSync = Meteor.wrapAsync(this.searchAllAsync, this);
}
return this._searchAllSync(...args);
async bind(dn, password) {
return new Promise((resolve, reject) => {
this.client.bind(dn, password, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
connectAsync(callback) {
@ -132,8 +153,6 @@ export default class LDAP {
this.client = ldapjs.createClient(connectionOptions);
this.bindSync = Meteor.wrapAsync(this.client.bind, this.client);
this.client.on('error', (error) => {
Log.error(`connection ${error}`);
if (replied === false) {
@ -223,7 +242,7 @@ export default class LDAP {
return `(&${filter.join('')})`;
}
bindUserIfNecessary(username, password) {
async bindUserIfNecessary(username, password) {
if (this.domainBinded === true) {
return;
@ -247,11 +266,11 @@ export default class LDAP {
Log.info(`Binding with User ${userDn}`);
this.bindSync(userDn, password);
await this.bind(userDn, password);
this.domainBinded = true;
}
bindIfNecessary() {
async bindIfNecessary() {
if (this.domainBinded === true) {
return;
}
@ -262,12 +281,12 @@ export default class LDAP {
Log.info(`Binding UserDN ${this.options.Authentication_UserDN}`);
this.bindSync(this.options.Authentication_UserDN, this.options.Authentication_Password);
await this.bind(this.options.Authentication_UserDN, this.options.Authentication_Password);
this.domainBinded = true;
}
searchUsersSync(username, page) {
this.bindIfNecessary();
async searchUsers(username, page) {
await this.bindIfNecessary();
const searchOptions = {
filter : this.getUserFilter(username),
scope : this.options.User_Search_Scope || 'sub',
@ -291,11 +310,11 @@ export default class LDAP {
return this.searchAllPaged(this.options.BaseDN, searchOptions, page);
}
return this.searchAllSync(this.options.BaseDN, searchOptions);
return await this.searchAll(this.options.BaseDN, searchOptions);
}
getUserByIdSync(id, attribute) {
this.bindIfNecessary();
async getUserById(id, attribute) {
await this.bindIfNecessary();
const Unique_Identifier_Field = this.constructor.settings_get('LDAP_UNIQUE_IDENTIFIER_FIELD').split(',');
@ -327,7 +346,7 @@ export default class LDAP {
Log.debug(`search filter ${searchOptions.filter.toString()}`);
Log.debug(`BaseDN ${this.options.BaseDN}`);
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
const result = await this.searchAll(this.options.BaseDN, searchOptions);
if (!Array.isArray(result) || result.length === 0) {
return;
@ -340,8 +359,8 @@ export default class LDAP {
return result[0];
}
getUserByUsernameSync(username) {
this.bindIfNecessary();
async getUserByUsername(username) {
await this.bindIfNecessary();
const searchOptions = {
filter: this.getUserFilter(username),
@ -352,7 +371,7 @@ export default class LDAP {
Log.debug(`searchOptions ${searchOptions}`);
Log.debug(`BaseDN ${this.options.BaseDN}`);
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
const result = await this.searchAll(this.options.BaseDN, searchOptions);
if (!Array.isArray(result) || result.length === 0) {
return;
@ -365,7 +384,7 @@ export default class LDAP {
return result[0];
}
getUserGroups(username, ldapUser) {
async getUserGroups(username, ldapUser) {
if (!this.options.group_filter_enabled) {
return true;
}
@ -394,7 +413,7 @@ export default class LDAP {
Log.debug(`Group list filter LDAP: ${searchOptions.filter}`);
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
const result = await this.searchAll(this.options.BaseDN, searchOptions);
if (!Array.isArray(result) || result.length === 0) {
return [];
@ -410,12 +429,12 @@ export default class LDAP {
}
isUserInGroup(username, ldapUser) {
async isUserInGroup(username, ldapUser) {
if (!this.options.group_filter_enabled) {
return true;
}
const grps = this.getUserGroups(username, ldapUser);
const grps = await this.getUserGroups(username, ldapUser);
const filter = ['(&'];
@ -444,7 +463,7 @@ export default class LDAP {
Log.debug(`Group filter LDAP: ${searchOptions.filter}`);
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
const result = await this.searchAll(this.options.BaseDN, searchOptions);
if (!Array.isArray(result) || result.length === 0) {
return false;
@ -580,14 +599,14 @@ export default class LDAP {
});
}
authSync(dn, password) {
async auth(dn, password) {
Log.info(`Authenticating ${dn}`);
try {
if (password === '') {
throw new Error('Password is not provided');
}
this.bindSync(dn, password);
await this.bind(dn, password);
Log.info(`Authenticated ${dn}`);
return true;
} catch (error) {

View file

@ -25,7 +25,7 @@ function fallbackDefaultAccountSystem(bind, username, password) {
return Accounts._runLoginHandlers(bind, loginRequest);
}
Accounts.registerLoginHandler('ldap', function(loginRequest) {
Accounts.registerLoginHandler('ldap', async function(loginRequest) {
if (!loginRequest.ldap || !loginRequest.ldapOptions) {
return undefined;
}
@ -42,27 +42,27 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
try {
ldap.connectSync();
await ldap.connect();
if (!!LDAP.settings_get('LDAP_USER_AUTHENTICATION')) {
ldap.bindUserIfNecessary(loginRequest.username, loginRequest.ldapPass);
ldapUser = ldap.searchUsersSync(loginRequest.username)[0];
await ldap.bindUserIfNecessary(loginRequest.username, loginRequest.ldapPass);
ldapUser = (await ldap.searchUsers(loginRequest.username))[0];
} else {
const users = ldap.searchUsersSync(loginRequest.username);
const users = await ldap.searchUsers(loginRequest.username);
if (users.length !== 1) {
log_info('Search returned', users.length, 'record(s) for', loginRequest.username);
throw new Error('User not Found');
}
if (ldap.isUserInGroup(loginRequest.username, users[0])) {
if (await ldap.isUserInGroup(loginRequest.username, users[0])) {
ldapUser = users[0];
} else {
throw new Error('User not in a valid group');
}
if (ldap.authSync(users[0].dn, loginRequest.ldapPass) !== true) {
if (await ldap.auth(users[0].dn, loginRequest.ldapPass) !== true) {
ldapUser = null;
log_info('Wrong password for', loginRequest.username)
}
@ -96,7 +96,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
log_info('Querying user');
log_debug('userQuery', userQuery);
user = Meteor.users.findOne(userQuery);
user = await Meteor.users.findOneAsync(userQuery);
}
// Attempt to find user by username
@ -137,7 +137,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
log_debug('userQuery', userQuery);
user = Meteor.users.findOne(userQuery);
user = await Meteor.users.findOneAsync(userQuery);
}
// Attempt to find user by e-mail address only
@ -159,7 +159,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
log_debug('userQuery', userQuery);
user = Meteor.users.findOne(userQuery);
user = await Meteor.users.findOneAsync(userQuery);
}
@ -182,15 +182,15 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
if (LDAP.settings_get('LDAP_SYNC_ADMIN_STATUS') === true) {
log_debug('Updating admin status');
const targetGroups = LDAP.settings_get('LDAP_SYNC_ADMIN_GROUPS').split(',');
const groups = ldap.getUserGroups(username, ldapUser).filter((value) => targetGroups.includes(value));
const groups = (await ldap.getUserGroups(username, ldapUser)).filter((value) => targetGroups.includes(value));
user.isAdmin = groups.length > 0;
Meteor.users.update({_id: user._id}, {$set: {isAdmin: user.isAdmin}});
await Meteor.users.updateAsync({_id: user._id}, {$set: {isAdmin: user.isAdmin}});
}
if( LDAP.settings_get('LDAP_SYNC_GROUP_ROLES') === true ) {
log_debug('Updating Groups/Roles');
const groups = ldap.getUserGroups(username, ldapUser);
const groups = await ldap.getUserGroups(username, ldapUser);
if( groups.length > 0 ) {
Roles.setUserRoles(user._id, groups );
@ -198,9 +198,9 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
}
}
Meteor.users.update(user._id, update_data );
await Meteor.users.updateAsync(user._id, update_data );
syncUserData(user, ldapUser);
await syncUserData(user, ldapUser);
if (LDAP.settings_get('LDAP_LOGIN_FALLBACK') === true) {
Accounts.setPassword(user._id, loginRequest.ldapPass, {logout: false});
@ -224,19 +224,19 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
loginRequest.ldapPass = undefined;
}
const result = addLdapUser(ldapUser, username, loginRequest.ldapPass);
const result = await addLdapUser(ldapUser, username, loginRequest.ldapPass);
if (LDAP.settings_get('LDAP_SYNC_ADMIN_STATUS') === true) {
log_debug('Updating admin status');
const targetGroups = LDAP.settings_get('LDAP_SYNC_ADMIN_GROUPS').split(',');
const groups = ldap.getUserGroups(username, ldapUser).filter((value) => targetGroups.includes(value));
const groups = (await ldap.getUserGroups(username, ldapUser)).filter((value) => targetGroups.includes(value));
result.isAdmin = groups.length > 0;
Meteor.users.update({_id: result.userId}, {$set: {isAdmin: result.isAdmin}});
await Meteor.users.updateAsync({_id: result.userId}, {$set: {isAdmin: result.isAdmin}});
}
if( LDAP.settings_get('LDAP_SYNC_GROUP_ROLES') === true ) {
const groups = ldap.getUserGroups(username, ldapUser);
const groups = await ldap.getUserGroups(username, ldapUser);
if( groups.length > 0 ) {
Roles.setUserRoles(result.userId, groups );
log_info(`Set roles to:${ groups.join(',')}`);

View file

@ -230,7 +230,7 @@ export function getDataToSyncUserData(ldapUser, user) {
}
export function syncUserData(user, ldapUser) {
export async function syncUserData(user, ldapUser) {
log_info('Syncing user data');
log_debug('user', {'email': user.email, '_id': user._id});
// log_debug('ldapUser', ldapUser.object);
@ -239,7 +239,7 @@ export function syncUserData(user, ldapUser) {
const username = slug(getLdapUsername(ldapUser));
if (user && user._id && username !== user.username) {
log_info('Syncing user username', user.username, '->', username);
Meteor.users.findOne({ _id: user._id }, { $set: { username }});
await Meteor.users.findOneAsync({ _id: user._id }, { $set: { username }});
}
}
@ -248,7 +248,7 @@ export function syncUserData(user, ldapUser) {
log_debug('fullname=',fullname);
if (user && user._id && fullname !== '') {
log_info('Syncing user fullname:', fullname);
Meteor.users.update({ _id: user._id }, { $set: { 'profile.fullname' : fullname, }});
await Meteor.users.updateAsync({ _id: user._id }, { $set: { 'profile.fullname' : fullname, }});
}
}
@ -258,7 +258,7 @@ export function syncUserData(user, ldapUser) {
if (user && user._id && email !== '') {
log_info('Syncing user email:', email);
Meteor.users.update({
await Meteor.users.updateAsync({
_id: user._id
}, {
$set: {
@ -270,7 +270,7 @@ export function syncUserData(user, ldapUser) {
}
export function addLdapUser(ldapUser, username, password) {
export async function addLdapUser(ldapUser, username, password) {
const uniqueId = getLdapUserUniqueID(ldapUser);
const userObject = {
@ -310,7 +310,7 @@ export function addLdapUser(ldapUser, username, password) {
userObject._id = Accounts.createUser(userObject);
// Add the services.ldap identifiers
Meteor.users.update({ _id: userObject._id }, {
await Meteor.users.updateAsync({ _id: userObject._id }, {
$set: {
'services.ldap': { id: uniqueId.value },
'emails.0.verified': true,
@ -321,14 +321,14 @@ export function addLdapUser(ldapUser, username, password) {
return error;
}
syncUserData(userObject, ldapUser);
await syncUserData(userObject, ldapUser);
return {
userId: userObject._id,
};
}
export function importNewUsers(ldap) {
export async function importNewUsers(ldap) {
if (LDAP.settings_get('LDAP_ENABLE') !== true) {
log_error('Can\'t run LDAP Import, LDAP is disabled');
return;
@ -336,16 +336,16 @@ export function importNewUsers(ldap) {
if (!ldap) {
ldap = new LDAP();
ldap.connectSync();
await ldap.connect();
}
let count = 0;
ldap.searchUsersSync('*', Meteor.bindEnvironment((error, ldapUsers, {next, end} = {}) => {
ldap.searchUsers('*', Meteor.bindEnvironment(async (error, ldapUsers, {next, end} = {}) => {
if (error) {
throw error;
}
ldapUsers.forEach((ldapUser) => {
for (const ldapUser of ldapUsers) {
count++;
const uniqueId = getLdapUserUniqueID(ldapUser);
@ -362,7 +362,7 @@ export function importNewUsers(ldap) {
}
// Add user if it was not added before
let user = Meteor.users.findOne(userQuery);
let user = await Meteor.users.findOneAsync(userQuery);
if (!user && username && LDAP.settings_get('LDAP_MERGE_EXISTING_USERS') === true) {
const userQuery = {
@ -371,20 +371,20 @@ export function importNewUsers(ldap) {
log_debug('userQuery merge', userQuery);
user = Meteor.users.findOne(userQuery);
user = await Meteor.users.findOneAsync(userQuery);
if (user) {
syncUserData(user, ldapUser);
await syncUserData(user, ldapUser);
}
}
if (!user) {
addLdapUser(ldapUser, username);
await addLdapUser(ldapUser, username);
}
if (count % 100 === 0) {
log_info('Import running. Users imported until now:', count);
}
});
}
if (end) {
log_info('Import finished. Users imported:', count);
@ -394,7 +394,7 @@ export function importNewUsers(ldap) {
}));
}
function sync() {
async function sync() {
if (LDAP.settings_get('LDAP_ENABLE') !== true) {
return;
}
@ -402,7 +402,7 @@ function sync() {
const ldap = new LDAP();
try {
ldap.connectSync();
await ldap.connect();
let users;
if (LDAP.settings_get('LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED') === true) {
@ -410,25 +410,25 @@ function sync() {
}
if (LDAP.settings_get('LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS') === true) {
importNewUsers(ldap);
await importNewUsers(ldap);
}
if (LDAP.settings_get('LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED') === true) {
users.forEach(function(user) {
for await (const user of users) {
let ldapUser;
if (user.services && user.services.ldap && user.services.ldap.id) {
ldapUser = ldap.getUserByIdSync(user.services.ldap.id, user.services.ldap.idAttribute);
ldapUser = await ldap.getUserById(user.services.ldap.id, user.services.ldap.idAttribute);
} else {
ldapUser = ldap.getUserByUsernameSync(user.username);
ldapUser = await ldap.getUserByUsername(user.username);
}
if (ldapUser) {
syncUserData(user, ldapUser);
await syncUserData(user, ldapUser);
} else {
log_info('Can\'t sync user', user.username);
}
});
}
}
} catch (error) {
log_error(error);
@ -459,8 +459,8 @@ const addCronJob = _.debounce(Meteor.bindEnvironment(function addCronJobDebounce
else {
return parser.recur().on(0).minute();
}},
job: function() {
sync();
job: async function() {
await sync();
},
});
sc.start();

View file

@ -2,7 +2,7 @@ import {importNewUsers} from './sync';
import LDAP from './ldap';
Meteor.methods({
ldap_sync_now() {
async ldap_sync_now() {
const user = Meteor.user();
if (!user) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'ldap_sync_users' });
@ -18,7 +18,7 @@ Meteor.methods({
this.unblock();
importNewUsers();
await importNewUsers();
return {
message: 'Sync_in_progress',

View file

@ -1,7 +1,7 @@
import LDAP from './ldap';
Meteor.methods({
ldap_test_connection() {
async ldap_test_connection() {
const user = Meteor.user();
if (!user) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'ldap_test_connection' });
@ -19,14 +19,14 @@ Meteor.methods({
let ldap;
try {
ldap = new LDAP();
ldap.connectSync();
await ldap.connect();
} catch (error) {
console.log(error);
throw new Meteor.Error(error.message);
}
try {
ldap.bindIfNecessary();
await ldap.bindIfNecessary();
} catch (error) {
throw new Meteor.Error(error.name || error.message);
}