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,30 +549,46 @@ 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
mockRole := c.Query("x-test-role")
appEnv := strings.ToLower(os.Getenv("APP_ENV"))
isDev := appEnv == "dev" || appEnv == "development" || appEnv == ""
// [New] Support Role Mocking for Download (which doesn't have custom headers) if isDev && mockRole != "" {
if profile == nil { slog.Info("🔑 [AUTH] Using mock role from query for export", "role", mockRole)
appEnv := strings.ToLower(os.Getenv("APP_ENV")) requesterRole = mockRole
isDev := appEnv == "dev" || appEnv == "development" || appEnv == "" // In dev mocking, we might not have a full profile, but we need to know the manageable tenants if it's a tenant_admin
mockRole := c.Query("x-test-role") if requesterRole == domain.RoleTenantAdmin {
if isDev && mockRole != "" { // Try to get actual profile if possible to get manageableTenants
slog.Info("🔑 [AUTH] Using mock role from query for export", "role", mockRole) p, _ := c.Locals("user_profile").(*domain.UserProfileResponse)
requesterRole = mockRole if p != nil {
// For tenant_admin, we might need more data, but let's assume super_admin for full export in dev profile = p
} else { }
return errorJSON(c, fiber.StatusUnauthorized, "invalid session (trace:export_profile)")
} }
} 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 { }
for _, t := range profile.ManageableTenants {
manageableSlugs = append(manageableSlugs, strings.ToLower(t.Slug)) // [New] Access Control: only admin roles can export
} if requesterRole != domain.RoleSuperAdmin && requesterRole != domain.RoleTenantAdmin {
if profile.CompanyCode != "" { return errorJSON(c, fiber.StatusForbidden, "forbidden: insufficient permissions for export")
manageableSlugs = append(manageableSlugs, strings.ToLower(profile.CompanyCode)) }
}
if profile != nil && requesterRole == domain.RoleTenantAdmin {
for _, t := range profile.ManageableTenants {
manageableSlugs = append(manageableSlugs, strings.ToLower(t.Slug))
}
if profile.CompanyCode != "" {
manageableSlugs = append(manageableSlugs, strings.ToLower(profile.CompanyCode))
} }
} }