Skip to content

fix(auth): avoid AD lockout on stale credentials#18

Open
edospadoni wants to merge 2 commits into
masterfrom
fix-stale-credentials-ad-lockout
Open

fix(auth): avoid AD lockout on stale credentials#18
edospadoni wants to merge 2 commits into
masterfrom
fix-stale-credentials-ad-lockout

Conversation

@edospadoni

Copy link
Copy Markdown
Member

Problem

On installations where the NethVoice user provider is a Microsoft Active Directory, users get locked out of their AD account after changing their password (or after the password expires) while the mobile app still holds the old one.

Two compounding causes, both in this proxy:

  1. Duplicate LDAP binds per request. getAuthContext() always cascaded from the middleware login (POST /api/login) to the legacy one (POST /webrest/authentication/login). Both endpoints end up performing the same LDAP bind against the user provider, so every provisioning request with stale credentials produced 2 failed binds on AD instead of 1.

  2. The app never logs out. On any non-2xx provisioning response, Acrobits shows a generic error and keeps the stored account untouched, retrying at every app open and at every extProvInterval cycle. With a stale password this means failed binds repeating forever, until the AD lockout threshold (badPwdCount) is reached and the account is locked (Security event 4625, SubStatus 0xc000006a).

Fix

  • Skip the legacy fallback when the middleware explicitly rejects the credentials (401/403). The middleware has already performed the LDAP bind at that point; repeating it on /webrest is a guaranteed identical failure. The fallback still applies when the middleware is missing (404) or unreachable (network error / 5xx), so legacy-only installations are unaffected.

  • Tell the app to log out when credentials are rejected. The provisioning account XML now includes <extProvLogoutStatusCode>410</extProvLogoutStatusCode>, and the login provisioning answers 410 Gone (instead of a generic 404) when the upstream explicitly rejected the credentials — wrong password, or QR/persistent token revoked (both /user/me endpoints answering 401/403). On 410 the app logs the user out and shows the login screen, so the user can re-enter the new password or scan a new QR code. All other failures (license validation, missing mobile extension, transient errors) keep answering 404 and leave the app account untouched.

Verified end-to-end against a Microsoft AD: after a password change, the next re-provisioning gets a single failed bind, the app receives 410 and logs the user out; logging back in with the new password restores normal operation.

Note: apps already failing before this fix never received the extProvLogoutStatusCode parameter (it is delivered by a successful provisioning), so those users still need a one-time manual logout/login.

When the middleware rejects credentials (401/403) the LDAP bind has
already failed upstream: skip the legacy /webrest fallback instead of
repeating the same bind, which doubled the AD bad password count.

Answer 410 Gone on explicitly rejected credentials and provision
extProvLogoutStatusCode=410 in the account XML, so the app logs the
user out and asks for new credentials instead of retrying forever
with a stale password until the AD account gets locked.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts the authentication/provisioning flow in app/index.php to prevent duplicate upstream authentication attempts (which can trigger AD lockouts on stale credentials) and to instruct the mobile app to log out when credentials are explicitly rejected.

Changes:

  • Track upstream HTTP status codes for middleware/login and /user/me calls to detect explicit credential rejection (401/403).
  • Skip legacy fallback when the middleware already rejected credentials (avoids duplicate LDAP binds per request).
  • Return 410 Gone on explicit credential rejection and include <extProvLogoutStatusCode>410</extProvLogoutStatusCode> in successful provisioning XML so the app logs out instead of retrying indefinitely.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/index.php
When the upstream explicitly rejects the credentials the legacy
fallback is skipped, so the log must not claim that both middleware
and legacy modes were attempted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants