mirror of
https://github.com/wekan/wekan.git
synced 2025-09-22 01:50:48 +02:00
Feature: Added brute force login protection settings to Admin Panel/People/Locked Users.
Added filtering of Admin Panel/People/People: All Users/Locked Users Only/Active/Not Active. Added visual indicators: red lock icon for locked users, green check for active users, and red X for inactive users. Added "Unlock All" button to quickly unlock all brute force locked users. Added ability to toggle user active status directly from the People page. Moved lockout settings from environment variables to database so admins can configure the lockout thresholds directly in the UI. Thanks to xet7.
This commit is contained in:
parent
0132b801b2
commit
ae0d059b6f
13 changed files with 912 additions and 11 deletions
33
server/accounts-lockout-config.js
Normal file
33
server/accounts-lockout-config.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { AccountsLockout } from 'meteor/wekan-accounts-lockout';
|
||||
import LockoutSettings from '/models/lockoutSettings';
|
||||
|
||||
Meteor.startup(() => {
|
||||
// Wait for the database to be ready
|
||||
Meteor.setTimeout(() => {
|
||||
try {
|
||||
// Get configurations from database
|
||||
const knownUsersConfig = {
|
||||
failuresBeforeLockout: LockoutSettings.findOne('known-failuresBeforeLockout')?.value || 3,
|
||||
lockoutPeriod: LockoutSettings.findOne('known-lockoutPeriod')?.value || 60,
|
||||
failureWindow: LockoutSettings.findOne('known-failureWindow')?.value || 15
|
||||
};
|
||||
|
||||
const unknownUsersConfig = {
|
||||
failuresBeforeLockout: LockoutSettings.findOne('unknown-failuresBeforeLockout')?.value || 3,
|
||||
lockoutPeriod: LockoutSettings.findOne('unknown-lockoutPeriod')?.value || 60,
|
||||
failureWindow: LockoutSettings.findOne('unknown-failureWindow')?.value || 15
|
||||
};
|
||||
|
||||
// Initialize the AccountsLockout with configuration
|
||||
const accountsLockout = new AccountsLockout({
|
||||
knownUsers: knownUsersConfig,
|
||||
unknownUsers: unknownUsersConfig,
|
||||
});
|
||||
|
||||
// Start the accounts lockout mechanism
|
||||
accountsLockout.startup();
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize accounts lockout:', error);
|
||||
}
|
||||
}, 2000); // Small delay to ensure database is ready
|
||||
});
|
107
server/methods/lockedUsers.js
Normal file
107
server/methods/lockedUsers.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
|
||||
// Method to find locked users and release them if needed
|
||||
Meteor.methods({
|
||||
getLockedUsers() {
|
||||
// Check if user has admin rights
|
||||
const userId = Meteor.userId();
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user');
|
||||
}
|
||||
const user = ReactiveCache.getUser(userId);
|
||||
if (!user || !user.isAdmin) {
|
||||
throw new Meteor.Error('error-not-allowed', 'Not allowed');
|
||||
}
|
||||
|
||||
// Current time to check against unlockTime
|
||||
const currentTime = Number(new Date());
|
||||
|
||||
// Find users that are locked (known users)
|
||||
const lockedUsers = Meteor.users.find(
|
||||
{
|
||||
'services.accounts-lockout.unlockTime': {
|
||||
$gt: currentTime,
|
||||
}
|
||||
},
|
||||
{
|
||||
fields: {
|
||||
_id: 1,
|
||||
username: 1,
|
||||
emails: 1,
|
||||
'services.accounts-lockout.unlockTime': 1,
|
||||
'services.accounts-lockout.failedAttempts': 1
|
||||
}
|
||||
}
|
||||
).fetch();
|
||||
|
||||
// Format the results for the UI
|
||||
return lockedUsers.map(user => {
|
||||
const email = user.emails && user.emails.length > 0 ? user.emails[0].address : 'No email';
|
||||
const remainingLockTime = Math.round((user.services['accounts-lockout'].unlockTime - currentTime) / 1000);
|
||||
|
||||
return {
|
||||
_id: user._id,
|
||||
username: user.username || 'No username',
|
||||
email,
|
||||
failedAttempts: user.services['accounts-lockout'].failedAttempts || 0,
|
||||
unlockTime: user.services['accounts-lockout'].unlockTime,
|
||||
remainingLockTime // in seconds
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
unlockUser(userId) {
|
||||
// Check if user has admin rights
|
||||
const adminId = Meteor.userId();
|
||||
if (!adminId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user');
|
||||
}
|
||||
const admin = ReactiveCache.getUser(adminId);
|
||||
if (!admin || !admin.isAdmin) {
|
||||
throw new Meteor.Error('error-not-allowed', 'Not allowed');
|
||||
}
|
||||
|
||||
// Make sure the user to unlock exists
|
||||
const userToUnlock = Meteor.users.findOne(userId);
|
||||
if (!userToUnlock) {
|
||||
throw new Meteor.Error('error-user-not-found', 'User not found');
|
||||
}
|
||||
|
||||
// Unlock the user
|
||||
Meteor.users.update(
|
||||
{ _id: userId },
|
||||
{
|
||||
$unset: {
|
||||
'services.accounts-lockout': 1
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
unlockAllUsers() {
|
||||
// Check if user has admin rights
|
||||
const adminId = Meteor.userId();
|
||||
if (!adminId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user');
|
||||
}
|
||||
const admin = ReactiveCache.getUser(adminId);
|
||||
if (!admin || !admin.isAdmin) {
|
||||
throw new Meteor.Error('error-not-allowed', 'Not allowed');
|
||||
}
|
||||
|
||||
// Unlock all users
|
||||
Meteor.users.update(
|
||||
{ 'services.accounts-lockout.unlockTime': { $exists: true } },
|
||||
{
|
||||
$unset: {
|
||||
'services.accounts-lockout': 1
|
||||
}
|
||||
},
|
||||
{ multi: true }
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
46
server/methods/lockoutSettings.js
Normal file
46
server/methods/lockoutSettings.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { AccountsLockout } from 'meteor/wekan-accounts-lockout';
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import LockoutSettings from '/models/lockoutSettings';
|
||||
|
||||
Meteor.methods({
|
||||
reloadAccountsLockout() {
|
||||
// Check if user has admin rights
|
||||
const userId = Meteor.userId();
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user');
|
||||
}
|
||||
const user = ReactiveCache.getUser(userId);
|
||||
if (!user || !user.isAdmin) {
|
||||
throw new Meteor.Error('error-not-allowed', 'Not allowed');
|
||||
}
|
||||
|
||||
try {
|
||||
// Get configurations from database
|
||||
const knownUsersConfig = {
|
||||
failuresBeforeLockout: LockoutSettings.findOne('known-failuresBeforeLockout')?.value || 3,
|
||||
lockoutPeriod: LockoutSettings.findOne('known-lockoutPeriod')?.value || 60,
|
||||
failureWindow: LockoutSettings.findOne('known-failureWindow')?.value || 15
|
||||
};
|
||||
|
||||
const unknownUsersConfig = {
|
||||
failuresBeforeLockout: LockoutSettings.findOne('unknown-failuresBeforeLockout')?.value || 3,
|
||||
lockoutPeriod: LockoutSettings.findOne('unknown-lockoutPeriod')?.value || 60,
|
||||
failureWindow: LockoutSettings.findOne('unknown-failureWindow')?.value || 15
|
||||
};
|
||||
|
||||
// Initialize the AccountsLockout with configuration
|
||||
const accountsLockout = new AccountsLockout({
|
||||
knownUsers: knownUsersConfig,
|
||||
unknownUsers: unknownUsersConfig,
|
||||
});
|
||||
|
||||
// Start the accounts lockout mechanism
|
||||
accountsLockout.startup();
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to reload accounts lockout:', error);
|
||||
throw new Meteor.Error('error-reloading-settings', 'Error reloading settings');
|
||||
}
|
||||
}
|
||||
});
|
6
server/publications/lockoutSettings.js
Normal file
6
server/publications/lockoutSettings.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import LockoutSettings from '/models/lockoutSettings';
|
||||
|
||||
Meteor.publish('lockoutSettings', function() {
|
||||
const ret = LockoutSettings.find();
|
||||
return ret;
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue