Files
uPortal2/backend/internal/rest/handler_users.go
T
2026-10-21 19:41:26 +03:00

161 lines
3.8 KiB
Go

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,
}
}