diff --git a/server/manage_users.py b/server/manage_users.py index ed18acf..cabb63c 100644 --- a/server/manage_users.py +++ b/server/manage_users.py @@ -84,9 +84,27 @@ def authenticate_operator() -> str: # not be able to lock an operator out of the tool that fixes the login group. result = ldap_auth.verify(who, pw, required_group="") if not result.ok: - if result.is_config_problem: - sys.exit(f"Could not reach the domain ({result.reason}): {result.detail}") - sys.exit("Authentication failed.") + # SAY WHY. The /api/auth/login endpoint deliberately returns one generic + # message so an unauthenticated caller cannot enumerate accounts; that + # reasoning does NOT transfer here. This is a local tool, the operator is + # the account holder, and there is nobody to leak to — so withholding the + # AD sub-code only makes a failure undiagnosable. An earlier version of + # this function printed "Authentication failed." and nothing else. + print(f"Authentication failed: {result.reason} — {result.detail}", file=sys.stderr) + print(f" bind attempted as : {ldap_auth.normalize_username(who)}@{ldap_auth.DOMAIN}", + file=sys.stderr) + print(f" server : ldaps://{ldap_auth.HOST}:{ldap_auth.PORT}", file=sys.stderr) + print(f" password length : {len(pw)} characters", file=sys.stderr) + if result.reason == ldap_auth.BAD_CREDENTIALS: + print(" The sub-code above is AD's own reason: 52e = wrong password, " + "775 = account locked out, 532 = password expired, " + "533 = account disabled, 525 = no such user.", file=sys.stderr) + print(" A 525 with a password you know is correct means the BIND NAME is " + "wrong, not the password. This binds as @LDAP_DOMAIN, " + "which only works where that matches your real UPN suffix — set " + "LDAP_DOMAIN to the UPN suffix if yours differs from the AD DNS name.", + file=sys.stderr) + sys.exit(1) print(f"Authenticated as {result.sam}.") return result.sam