Move ClaimsFromContext and MustUserID helpers from httpserver to new requestctx package for better separation of concerns. Update all imports across auth, device, policy, and user handlers. Fix Dockerfile to copy go.sum and run go mod tidy before download.
111 lines
3.2 KiB
Go
111 lines
3.2 KiB
Go
package user
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/nexavpn/nexavpn/backend/internal/apiutil"
|
|
"github.com/nexavpn/nexavpn/backend/internal/audit"
|
|
"github.com/nexavpn/nexavpn/backend/internal/requestctx"
|
|
)
|
|
|
|
type Handler struct {
|
|
service *Service
|
|
audit *audit.Service
|
|
}
|
|
|
|
func NewHandler(service *Service, auditService *audit.Service) *Handler {
|
|
return &Handler{service: service, audit: auditService}
|
|
}
|
|
|
|
func (h *Handler) List(w http.ResponseWriter, r *http.Request) {
|
|
users, err := h.service.List(r.Context())
|
|
if err != nil {
|
|
apiutil.Error(w, http.StatusInternalServerError, "users_list_failed", "unable to list users")
|
|
return
|
|
}
|
|
|
|
apiutil.JSON(w, http.StatusOK, users)
|
|
}
|
|
|
|
func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
|
|
var input CreateRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
apiutil.Error(w, http.StatusBadRequest, "invalid_json", "invalid request body")
|
|
return
|
|
}
|
|
if input.Role == "" {
|
|
input.Role = "user"
|
|
}
|
|
|
|
created, err := h.service.Create(r.Context(), input)
|
|
if err != nil {
|
|
apiutil.Error(w, http.StatusInternalServerError, "user_create_failed", "unable to create user")
|
|
return
|
|
}
|
|
|
|
if claims, ok := requestctx.ClaimsFromContext(r.Context()); ok {
|
|
_ = h.audit.Record(r.Context(), audit.Entry{
|
|
ActorUserID: &claims.UserID,
|
|
EntityType: "user",
|
|
EntityID: &created.ID,
|
|
EventType: "admin.user.created",
|
|
Status: "success",
|
|
Message: "admin created user",
|
|
Metadata: map[string]any{
|
|
"username": created.Username,
|
|
},
|
|
})
|
|
}
|
|
apiutil.JSON(w, http.StatusCreated, created)
|
|
}
|
|
|
|
func (h *Handler) Disable(w http.ResponseWriter, r *http.Request) {
|
|
targetID, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
apiutil.Error(w, http.StatusBadRequest, "invalid_user_id", "invalid user id")
|
|
return
|
|
}
|
|
if err := h.service.SetActive(r.Context(), targetID.String(), false); err != nil {
|
|
apiutil.Error(w, http.StatusBadRequest, "user_disable_failed", "unable to disable user")
|
|
return
|
|
}
|
|
if claims, ok := requestctx.ClaimsFromContext(r.Context()); ok {
|
|
_ = h.audit.Record(r.Context(), audit.Entry{
|
|
ActorUserID: &claims.UserID,
|
|
EntityType: "user",
|
|
EntityID: &targetID,
|
|
EventType: "admin.user.disabled",
|
|
Status: "success",
|
|
Message: "admin disabled user",
|
|
})
|
|
}
|
|
apiutil.JSON(w, http.StatusOK, map[string]any{"ok": true})
|
|
}
|
|
|
|
func (h *Handler) Enable(w http.ResponseWriter, r *http.Request) {
|
|
targetID, err := uuid.Parse(chi.URLParam(r, "id"))
|
|
if err != nil {
|
|
apiutil.Error(w, http.StatusBadRequest, "invalid_user_id", "invalid user id")
|
|
return
|
|
}
|
|
if err := h.service.SetActive(r.Context(), targetID.String(), true); err != nil {
|
|
apiutil.Error(w, http.StatusBadRequest, "user_enable_failed", "unable to enable user")
|
|
return
|
|
}
|
|
if claims, ok := requestctx.ClaimsFromContext(r.Context()); ok {
|
|
_ = h.audit.Record(r.Context(), audit.Entry{
|
|
ActorUserID: &claims.UserID,
|
|
EntityType: "user",
|
|
EntityID: &targetID,
|
|
EventType: "admin.user.enabled",
|
|
Status: "success",
|
|
Message: "admin enabled user",
|
|
})
|
|
}
|
|
apiutil.JSON(w, http.StatusOK, map[string]any{"ok": true})
|
|
}
|