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