Every helpdesk survey says the same thing: password resets are the #1 ticket, every year, forever. Ours were ~40% of weekly volume. Not because users are dumb — because the only path to a new password ran through a human with a browser tab open.
Here's the self-service flow I built in one afternoon, and the script at the heart of it.
The 3-question flow that killed 80% of reset tickets
Self-service reset fails when it asks too much. Ours asks exactly three things, all of which we already have:
- Username — from LDAP/AD.
- One possession check — a code to the enrolled mobile number (SMS or authenticator push; both count).
- One knowledge check — the office/department code every employee gets on day one, rotatable.
Two independent factors. No security questions like "mother's maiden name" (attackers buy those in bulk). No manager vouching by chat ("yeah it's really him, reset it" — that's how account takeovers happen).
The script (the part everyone over-engineers)
The backend is smaller than you think. This is the core:
import ldap, secrets, smtplib, hashlib
def self_service_reset(username, otp, dept_code, new_password):
assert verify_otp(username, otp), "possession factor failed"
assert verify_dept_code(username, dept_code), "knowledge factor failed"
if not password_policy_ok(new_password):
return "REJECTED: policy"
ld = ldap.initialize("ldaps://dc01.corp.local")
ld.simple_bind_s(ADMIN_DN, ADMIN_PW)
ld.modify_s(f"CN={username},{USER_OU}",
[(ldap.MOD_REPLACE, "unicodePwd",
[quoted_utf16(new_password)])])
kill_all_sessions(username) # the step everyone forgets
audit_log(username, "self_service_reset")
return "OK"
def kill_all_sessions(username):
# force logoff + clear cached SSO tokens
subprocess.run(["logoff-user.sh", username], check=True)
Two details worth stealing:
-
kill_all_sessions()is the step everyone forgets. A reset that leaves the attacker's session alive protects nobody. -
audit_log()is non-optional. Your first incident report will ask "who reset what, when, from where." If the answer is "we don't log it," that's the finding.
Guardrails
- Rate-limit per username AND per source IP (3 attempts / 15 min). Credential-stuffing loves reset endpoints.
- New password can't match the last 5 — hash them, compare, don't store plaintext.
- Locked-out users still go to a human. Self-service is a fast path, not the only path.
- Test the "I lost my phone" branch before go-live, not during it.
What changed
Resets went from ~15/week to ~2/week (the 2 are the edge cases). Ticket volume down, mean-time-to-restore down from hours to minutes, and the helpdesk got its afternoons back. Total build time: one afternoon, most of which was arguing about the knowledge factor.
If you'd rather not write the whole thing from scratch — the working script, the audit-log module, and the full runbook are in:
- Ops Starter Kit — https://hive80lab.gumroad.com/l/ops-starter-kit (code LAUNCH50 = 50% off)
- Agent-Ops 24/7 — the monitoring + alerting half — https://hive80lab.gumroad.com/l/agent-ops-24-7
Either way: count your reset tickets this week. If it's double digits, that's your afternoon project.
Boring automation for small IT teams. More at https://hive80lab.gumroad.com
Top comments (0)