1
0
forked from baron/baron-sso

fix(backend): fix CSV export authentication by moving role validation inside the handler

This commit is contained in:
2026-03-04 16:10:52 +09:00
parent d6a6e13678
commit 5034785582
2 changed files with 38 additions and 22 deletions

View File

@@ -643,7 +643,7 @@ func main() {
// Admin User Management // Admin User Management
admin.Get("/users", requireAdmin, userHandler.ListUsers) admin.Get("/users", requireAdmin, userHandler.ListUsers)
admin.Get("/users/export", requireAdmin, userHandler.ExportUsersCSV) admin.Get("/users/export", userHandler.ExportUsersCSV) // Removed requireAdmin to handle mock role in query param
admin.Post("/users", requireAdmin, userHandler.CreateUser) admin.Post("/users", requireAdmin, userHandler.CreateUser)
admin.Post("/users/bulk", requireAdmin, userHandler.BulkCreateUsers) admin.Post("/users/bulk", requireAdmin, userHandler.BulkCreateUsers)
admin.Put("/users/bulk", requireAdmin, userHandler.BulkUpdateUsers) admin.Put("/users/bulk", requireAdmin, userHandler.BulkUpdateUsers)

View File

@@ -549,24 +549,41 @@ func (h *UserHandler) ExportUsersCSV(c *fiber.Ctx) error {
var requesterRole string var requesterRole string
var manageableSlugs []string var manageableSlugs []string
var profile *domain.UserProfileResponse
profile, _ := c.Locals("user_profile").(*domain.UserProfileResponse) // [New] Manual profile resolution to support query-param role mocking
// This is needed because browsers cannot send custom headers for direct downloads
// [New] Support Role Mocking for Download (which doesn't have custom headers) mockRole := c.Query("x-test-role")
if profile == nil {
appEnv := strings.ToLower(os.Getenv("APP_ENV")) appEnv := strings.ToLower(os.Getenv("APP_ENV"))
isDev := appEnv == "dev" || appEnv == "development" || appEnv == "" isDev := appEnv == "dev" || appEnv == "development" || appEnv == ""
mockRole := c.Query("x-test-role")
if isDev && mockRole != "" { if isDev && mockRole != "" {
slog.Info("🔑 [AUTH] Using mock role from query for export", "role", mockRole) slog.Info("🔑 [AUTH] Using mock role from query for export", "role", mockRole)
requesterRole = mockRole requesterRole = mockRole
// For tenant_admin, we might need more data, but let's assume super_admin for full export in dev // In dev mocking, we might not have a full profile, but we need to know the manageable tenants if it's a tenant_admin
} else { if requesterRole == domain.RoleTenantAdmin {
return errorJSON(c, fiber.StatusUnauthorized, "invalid session (trace:export_profile)") // Try to get actual profile if possible to get manageableTenants
p, _ := c.Locals("user_profile").(*domain.UserProfileResponse)
if p != nil {
profile = p
}
} }
} else { } else {
// Use real profile from middleware
p, ok := c.Locals("user_profile").(*domain.UserProfileResponse)
if !ok || p == nil {
return errorJSON(c, fiber.StatusUnauthorized, "invalid session (trace:export_auth)")
}
profile = p
requesterRole = profile.Role requesterRole = profile.Role
if requesterRole == domain.RoleTenantAdmin { }
// [New] Access Control: only admin roles can export
if requesterRole != domain.RoleSuperAdmin && requesterRole != domain.RoleTenantAdmin {
return errorJSON(c, fiber.StatusForbidden, "forbidden: insufficient permissions for export")
}
if profile != nil && requesterRole == domain.RoleTenantAdmin {
for _, t := range profile.ManageableTenants { for _, t := range profile.ManageableTenants {
manageableSlugs = append(manageableSlugs, strings.ToLower(t.Slug)) manageableSlugs = append(manageableSlugs, strings.ToLower(t.Slug))
} }
@@ -574,7 +591,6 @@ func (h *UserHandler) ExportUsersCSV(c *fiber.Ctx) error {
manageableSlugs = append(manageableSlugs, strings.ToLower(profile.CompanyCode)) manageableSlugs = append(manageableSlugs, strings.ToLower(profile.CompanyCode))
} }
} }
}
// 1. Fetch Users using Repo for efficiency // 1. Fetch Users using Repo for efficiency
users, _, err := h.UserRepo.List(c.Context(), 10000, 0, search, companyCode) users, _, err := h.UserRepo.List(c.Context(), 10000, 0, search, companyCode)