Sync Gitea releases to GitHub / sync-releases (push) Canceled after 0s
- Local accounts with bcrypt password hashing; first-run setup via POST /api/setup - Personal API tokens (dmv_<48hex>, SHA-256 hashed at rest) for scripted access - Server-side in-memory sessions with 32-byte secure cookie (dockmv_session, 7-day TTL, sliding renewal) - Login rate limiting (exponential backoff 1s–30s cap) per IP - Refuse to bind non-loopback while no account exists, unless DOCKMV_TRUST_ADDR=1 (for Docker's port mapping) - Every account can manage every other account (no roles in v1) - Auth middleware: public-path allowlist (/api/setup, /api/login, /api/logout, /api/me, /api/health) + session cookie check + API token (X-Auth-Token or Authorization: Bearer) check - Frontend AuthGate gates app on GET /api/me; shows setup screen or login form or app tree as needed - Account tab for personal token management; sign-out button in topbar - Break: removed --token flag, DOCKMV_TOKEN env var, ?token= query param, /api/health no longer auto-responds when unauthenticated Verified: go build/vet clean, frontend tsc+vite clean. Sandbox cannot execute binaries to test setup→login→session→token flow at runtime; recommend manual pass before merge. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
50 lines
1.3 KiB
Go
50 lines
1.3 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/arescom/dockmv/internal/store"
|
|
)
|
|
|
|
func (s *Server) handleListUsers(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, http.StatusOK, s.users.List())
|
|
}
|
|
|
|
type createUserRequest struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
// handleCreateUser adds another account. v1 has no roles: any authenticated
|
|
// account can create or remove any other.
|
|
func (s *Server) handleCreateUser(w http.ResponseWriter, r *http.Request) {
|
|
var req createUserRequest
|
|
if err := decode(r, &req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "%v", err)
|
|
return
|
|
}
|
|
usr, err := s.users.Create(req.Username, req.Password)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "%v", err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, usr)
|
|
}
|
|
|
|
// handleDeleteUser removes an account, along with its sessions and personal
|
|
// API tokens so nothing left behind still authenticates as it.
|
|
func (s *Server) handleDeleteUser(w http.ResponseWriter, r *http.Request) {
|
|
id := r.PathValue("id")
|
|
if err := s.users.Delete(id); err != nil {
|
|
status := http.StatusNotFound
|
|
if err == store.ErrLastAccount {
|
|
status = http.StatusBadRequest
|
|
}
|
|
writeError(w, status, "%v", err)
|
|
return
|
|
}
|
|
s.sessions.DeleteAllForUser(id)
|
|
_ = s.tokens.RevokeAllForUser(id)
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|