# Admin username change (2026-09-30) Goal: get rid of the well-known `admin` login and of the built-in `admin/password` account. ## Design - The JWT carries `user_id`, not the name, and no other table references `users.username`, so a rename does not invalidate sessions. - Changing the username requires the current password and, when 2FA is enabled, a valid OTP. Wrong password/OTP counts toward the login rate limit. ## Changes | Area | Change | |---|---| | API (`APP_CORE/openvpn_api_v3.py`) | `POST /api/auth/change-username`: body `new_username`, `current_password`, optional `otp`. Rules: `^[A-Za-z][A-Za-z0-9_.-]{2,31}$`, reserved names rejected (`admin`, `administrator`, `root`, `user`, `test`, `guest`), case-insensitive uniqueness (409). Helper `_current_user_id()` | | 2FA | `setup_2fa` puts the real username into the authenticator URI (was hardcoded `admin`) | | Bootstrap | `ensure_default_admin` no longer creates `admin/password`. With an empty `users` table it creates a user only from `OVPMON_INITIAL_ADMIN_USER` / `OVPMON_INITIAL_ADMIN_PASSWORD`; otherwise it logs an error | | UI (`Account.vue`) | "Change Username" button and modal (OTP field shown only if 2FA is on) | | UI (`App.vue`) | Header name synced from `/user/me` on start and on route change, and on the `ovpmon-user-changed` event; fixed the stale/hardcoded `Admin` | ## Existing installations Rename directly in the DB (stop nothing; sessions stay valid): ```python import sqlite3 c = sqlite3.connect("/var/lib/ovpmon/openvpn_monitor.db") c.execute("UPDATE users SET username=? WHERE username='admin'", ("",)); c.commit() ``` Take a DB backup first. Recovery when `users` is empty: temporarily set `OVPMON_INITIAL_ADMIN_USER/PASSWORD`, restart `ovpmon-api`, then remove the variables. ## Verification | Check | Result | |---|---| | Login with new name / with `admin` | 200 / 401 | | No token | 401 | | `admin`, `root`, `ab`, `a/b`, `1abc` | 400 | | Wrong current password | 401 | | Rename to another valid name and back | 200, login with the new name works | | Empty `users` without / with seed variables (DB copy) | no user / user created | | Manual UI test (password change, 2FA enable) | passed; header-name bug found and fixed |