package rest import ( "fmt" "net/http" "strconv" "time" "goAuth/internal/auth" "goAuth/internal/user" ) type changeUserPasswordRequest struct { Password string `json:"password"` } type userResponse struct { Id int64 `json:"id"` Login string `json:"login"` FullName *string `json:"fullName"` Email *string `json:"email"` IsAdmin bool `json:"isAdmin"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` ActivatedAt *time.Time `json:"activatedAt"` } func (h *Handler) listUsers(w http.ResponseWriter, r *http.Request) { limit, offset := pagination(r) includeInactive := r.URL.Query().Get("includeInactive") == "true" users, err := h.users.Users(r.Context(), includeInactive, limit, offset) if err != nil { h.fail(w, err) return } result := make([]userResponse, 0, len(users)) for _, u := range users { result = append(result, newUserResponse(u)) } writeJson(w, http.StatusOK, result) } func (h *Handler) changeUserPassword(w http.ResponseWriter, r *http.Request) { id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil { writeError(w, http.StatusBadRequest, "invalid user id") return } var req changeUserPasswordRequest if err := decodeJson(r, &req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if !verifyPassword(req.Password) { writeError(w, http.StatusBadRequest, "invalid password") return } u, err := h.users.UserById(r.Context(), id) if err != nil { h.fail(w, err) return } if _, err := h.users.SetPassword(r.Context(), u, req.Password); err != nil { h.fail(w, err) return } writeNoContent(w) } func (h *Handler) signoutUserSessions(w http.ResponseWriter, r *http.Request) { id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil { writeError(w, http.StatusBadRequest, "invalid user id") return } u, err := h.users.UserById(r.Context(), id) if err != nil { h.fail(w, err) return } if err := h.sessions.DeleteSessions(r.Context(), u); err != nil { h.fail(w, err) return } if current := userFromRequest(r); current.Id == id { auth.SignoutCleanup(w, h.cfg.Cookies.SessionName, h.cfg.Cookies.Secure) h.refreshCsrf(w) } writeNoContent(w) } // getUserAvatar serves any user's avatar without requiring a session. It backs // the avatar URL embedded in Jitsi JWTs, which the Jitsi server (a different // origin) fetches directly and cannot authenticate to /api/profile/avatar. func (h *Handler) getUserAvatar(w http.ResponseWriter, r *http.Request) { id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) if err != nil { writeError(w, http.StatusBadRequest, "invalid user id") return } u, err := h.users.UserById(r.Context(), id) if err != nil { h.fail(w, err) return } data, err := h.users.Avatar(r.Context(), u) if err != nil { h.fail(w, err) return } w.Header().Set("Content-Type", http.DetectContentType(data)) w.Header().Set("Cache-Control", "public, max-age=300") w.WriteHeader(http.StatusOK) w.Write(data) } // avatarURL builds the absolute URL of a user's avatar, preferring the // configured PublicBaseURL and falling back to the request's scheme and Host. func (h *Handler) avatarURL(r *http.Request, id int64) string { base := h.cfg.PublicBaseURL if base == "" { scheme := "http" if r.TLS != nil { scheme = "https" } if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" { scheme = proto } base = scheme + "://" + r.Host } return fmt.Sprintf("%s/api/users/%d/avatar", base, id) } func newUserResponse(u *user.User) userResponse { return userResponse{ Id: u.Id, Login: u.Login, FullName: u.FullName, Email: u.Email, IsAdmin: u.IsAdmin, CreatedAt: u.CreatedAt, UpdatedAt: u.UpdatedAt, ActivatedAt: u.ActivatedAt, } }