Add per-user TOTP 2FA, client-level user assignment, self-service portal

- TOTP (RFC 6238, stdlib-only) enrollment in profile, login step-up,
  admin emergency reset.
- Admins can grant a user visibility into individual clients
  (User.ClientIDs) in addition to whole-server access (User.ServerIDs).
- New "My Access" page: non-admin users see only their assigned clients
  (view/QR/download only, no management), reachable from the main nav.
- GetUser/GetUsers now redact TOTPSecret before returning JSON.

No Go toolchain was available while writing this - not yet build-verified.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PvrfUytqd74H6WcQkRzFM4
This commit is contained in:
sysops
2026-07-25 00:42:05 +02:00
co-authored by Claude Sonnet 5
parent c29edfdcc3
commit 34bc8f76f9
12 changed files with 1213 additions and 36 deletions
+8
View File
@@ -222,8 +222,16 @@ func main() {
if !util.DisableLogin {
app.GET(util.BasePath+"/login", handler.LoginPage())
app.POST(util.BasePath+"/login", handler.Login(db), handler.ContentTypeJson)
app.POST(util.BasePath+"/login/totp", handler.VerifyTOTPLogin(db), handler.ContentTypeJson)
app.GET(util.BasePath+"/logout", handler.Logout(), handler.ValidSession)
app.GET(util.BasePath+"/my-access", handler.MyAccessPage(db), handler.ValidSession, handler.RefreshSession)
app.GET(util.BasePath+"/my-access/api/clients", handler.GetMyAccessClients(db), handler.ValidSession)
app.GET(util.BasePath+"/my-access/client/:cid/qrcode", handler.GetMyAccessClientQRCode(db), handler.ValidSession)
app.GET(util.BasePath+"/my-access/client/:cid/download", handler.GetMyAccessClientDownload(db), handler.ValidSession)
app.GET(util.BasePath+"/profile", handler.LoadProfile(), handler.ValidSession, handler.RefreshSession)
app.GET(util.BasePath+"/profile/totp/enroll", handler.EnrollTOTP(db), handler.ValidSession)
app.POST(util.BasePath+"/profile/totp/confirm", handler.ConfirmTOTP(db), handler.ValidSession, handler.ContentTypeJson)
app.POST(util.BasePath+"/profile/totp/disable", handler.DisableTOTP(db), handler.ValidSession, handler.ContentTypeJson)
app.GET(util.BasePath+"/users-settings", handler.UsersSettings(), handler.ValidSession, handler.RefreshSession, handler.NeedsAdmin)
app.POST(util.BasePath+"/update-user", handler.UpdateUser(db), handler.ValidSession, handler.ContentTypeJson)
app.POST(util.BasePath+"/create-user", handler.CreateUser(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin)