fix(users): allow superadmin to list all users without tenant restriction

- Modified ListUsers handler to check for admin/superadmin role
- Superadmins can now list all users across tenants
- Added ListUsers method to AdminService
- Added Status field to dto.User

Fixes 403 error when superadmin tries to access /api/v1/users
This commit is contained in:
Tiago Yamamoto 2025-12-26 00:51:54 -03:00
parent 546e253a5f
commit 73967ca52b
3 changed files with 85 additions and 3 deletions

View file

@ -279,15 +279,58 @@ func (h *CoreHandlers) CreateUser(w http.ResponseWriter, r *http.Request) {
// @Router /api/v1/users [get]
func (h *CoreHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Check if user is admin/superadmin (they can list all users)
roles := middleware.ExtractRoles(ctx.Value(middleware.ContextRoles))
isAdmin := false
for _, role := range roles {
if role == "ADMIN" || role == "SUPERADMIN" || role == "admin" || role == "superadmin" {
isAdmin = true
break
}
}
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
if page < 1 {
page = 1
}
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
if limit < 1 {
limit = 10
}
if limit > 100 {
limit = 100
}
if isAdmin {
// Admin view: List all users using AdminService
users, total, err := h.adminService.ListUsers(ctx, page, limit)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
response := map[string]interface{}{
"data": users,
"pagination": map[string]interface{}{
"page": page,
"limit": limit,
"total": total,
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
return
}
// Non-admin: require tenant ID
tenantID, ok := ctx.Value(middleware.ContextTenantID).(string)
if !ok || tenantID == "" {
http.Error(w, "Tenant ID not found in context", http.StatusForbidden)
return
}
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
users, err := h.listUsersUC.Execute(ctx, tenantID, page, limit)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)

View file

@ -53,6 +53,7 @@ type User struct {
Name string `json:"name"`
Email string `json:"email"`
Role string `json:"role"`
Status string `json:"status"`
CreatedAt time.Time `json:"createdAt"`
CompanyID *string `json:"companyId,omitempty"`
}

View file

@ -87,6 +87,44 @@ func (s *AdminService) ListCompanies(ctx context.Context, verified *bool, page,
return companies, total, nil
}
// ListUsers returns all users with pagination (for admin view)
func (s *AdminService) ListUsers(ctx context.Context, page, limit int) ([]dto.User, int, error) {
offset := (page - 1) * limit
// Count Total
var total int
if err := s.DB.QueryRowContext(ctx, `SELECT COUNT(*) FROM users`).Scan(&total); err != nil {
return nil, 0, err
}
// Fetch Data
query := `
SELECT id, COALESCE(name, full_name, identifier, ''), email, role, COALESCE(status, 'active'), created_at
FROM users
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`
rows, err := s.DB.QueryContext(ctx, query, limit, offset)
if err != nil {
return nil, 0, err
}
defer rows.Close()
users := []dto.User{}
for rows.Next() {
var u dto.User
var roleStr string
if err := rows.Scan(&u.ID, &u.Name, &u.Email, &roleStr, &u.Status, &u.CreatedAt); err != nil {
return nil, 0, err
}
u.Role = roleStr
users = append(users, u)
}
return users, total, nil
}
func (s *AdminService) UpdateCompanyStatus(ctx context.Context, id string, active *bool, verified *bool) (*models.Company, error) {
company, err := s.getCompanyByID(ctx, id)
if err != nil {