-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Block user accounts if an incorrect password was entered 5 times #527
base: main
Are you sure you want to change the base?
Block user accounts if an incorrect password was entered 5 times #527
Conversation
- accountLocked: log when the account is locked. - loginFailedAccountLocked: log if the login fails while the account is locked.
- Considered race condition in the incrementFailedLoginAttempts and the ensureUserLoginActivityRecord functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, this is a great contribution!
Happy to chat about my feedback. They're loosely held opionions
accountLocked = 13, | ||
loginFailedAccountLocked = 14, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's worth adding some comments to these 2 lines so it's a bit clearer what the difference is.
await services.loginActivity.incrementFailedLoginAttempts(user); | ||
log(EventType.loginFailedAccountLocked, ctx.ip(), user.id, ctx.request.headers.get('User-Agent')); | ||
return this.redirectToLogin(ctx, '', `Too many failed login attempts, please contact ${requireSetting('smtp.emailFrom')} to unlock your account.`); | ||
} | ||
|
||
if (!await services.user.validatePassword(user, ctx.request.body.password)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit of a larger request, but I wonder if most of this logic should go into the service. The reason is that this is not the only place where we check a username and password, there are two more places:
- OAuth2 has a
password
flow - I recently added a 'authorization_challenge` OAuth2 flow.
These 3 features all do a username/password check so it kinda makes sense that they all increment the 'failed login' counter if a password fails.
I'm not sure if this should just be part of 'validatePassword' or a new function.
const MAX_FAILED_ATTEMPTS = 5; | ||
|
||
export function reachedMaxAttempts(attempts: number) { | ||
return attempts === MAX_FAILED_ATTEMPTS; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be >=
instead of ===
@@ -0,0 +1,65 @@ | |||
import { UserLoginActivityRecord } from 'knex/types/tables.js'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is login-attempts
a better name for this?
return attempts === MAX_FAILED_ATTEMPTS; | ||
} | ||
|
||
async function getLoginActivity(user: User): Promise<UserLoginActivityRecord | undefined> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of returning undefined
, how about always returning a record? Because maybe if there's no database record yet, you can guess what the record should be for the initial state
export async function incrementFailedLoginAttempts(user: User): Promise<number> { | ||
await ensureUserLoginActivityRecord(user); | ||
|
||
return await db.transaction(async (trx) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Feature Overview:
Key Changes:
Security Improvements:
This feature improves the security of user accounts by preventing brute-force attacks and unauthorized access through repeated incorrect password attempts.
Testing & Validation:
Next Steps:
Checklist