127 lines
6.9 KiB
Go

package externaladmin
import (
"hyapp-admin-server/internal/middleware"
"hyapp-admin-server/internal/modules/appconfig"
"hyapp-admin-server/internal/modules/appuser"
"hyapp-admin-server/internal/modules/hostorg"
"hyapp-admin-server/internal/modules/prettyid"
"hyapp-admin-server/internal/modules/resource"
"hyapp-admin-server/internal/modules/roomadmin"
"hyapp-admin-server/internal/modules/upload"
"hyapp-admin-server/internal/modules/vipconfig"
"github.com/gin-gonic/gin"
)
type BusinessHandlers struct {
AppUser *appuser.Handler
HostOrg *hostorg.Handler
RoomAdmin *roomadmin.Handler
Resource *resource.Handler
PrettyID *prettyid.Handler
AppConfig *appconfig.Handler
VIPConfig *vipconfig.Handler
Upload *upload.Handler
}
func RegisterAdminRoutes(protected *gin.RouterGroup, h *Handler) {
if protected == nil || h == nil {
return
}
protected.GET("/admin/external-admin-users", middleware.RequirePermission("external-admin-user:view"), h.ListAccounts)
protected.GET("/admin/external-admin-users/target", middleware.RequirePermission("external-admin-user:create"), h.ResolveTarget)
protected.POST("/admin/external-admin-users", middleware.RequirePermission("external-admin-user:create"), h.CreateAccount)
protected.PATCH("/admin/external-admin-users/:id/status", middleware.RequirePermission("external-admin-user:status"), h.SetStatus)
protected.POST("/admin/external-admin-users/:id/password", middleware.RequirePermission("external-admin-user:reset-password"), h.ResetPassword)
}
func RegisterExternalRoutes(api *gin.RouterGroup, h *Handler, handlers BusinessHandlers) {
if api == nil || h == nil {
return
}
external := api.Group("/external")
auth := external.Group("/auth")
// Credentials, session state and CSRF secrets must never be cached by browsers,
// service workers or intermediary proxies. This middleware runs before session
// auth so 4xx/5xx responses carry the same protection as successful responses.
auth.Use(noStore())
auth.GET("/apps", h.ListApps)
auth.POST("/login", h.Login)
protectedAuth := auth.Group("")
// Audit wraps CSRF so forged writes are recorded as failed attempts instead of disappearing
// before the audit middleware gets control.
protectedAuth.Use(h.AuthRequired(), h.Audit(), h.RequireCSRF())
protectedAuth.GET("/me", h.Me)
protectedAuth.POST("/logout", h.Logout)
protectedAuth.POST("/change-password", h.ChangePassword)
// Initial/reset credentials are deliberately limited to me/logout/change-password.
// Only after changing the temporary password can the account reach business handlers.
business := external.Group("")
business.Use(h.AuthRequired(), h.Audit(), h.RequireCSRF(), h.RequirePasswordChanged())
registerBusinessWhitelist(business, h, handlers)
}
func noStore() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Cache-Control", "no-store")
c.Header("Pragma", "no-cache")
c.Next()
}
}
func registerBusinessWhitelist(group *gin.RouterGroup, h *Handler, handlers BusinessHandlers) {
// "My team" is not a generic manager-list alias. Its handler derives the root
// manager from the external session and never accepts a client-selected user ID.
group.GET("/admin/my-team/agencies", middleware.RequirePermission("bd:view"), h.ListMyTeamAgencies)
if handlers.AppUser != nil {
group.GET("/app/users", middleware.RequirePermission("app-user:view"), handlers.AppUser.ListUsers)
group.GET("/app/users/bans", middleware.RequirePermission("app-user:view"), handlers.AppUser.ListBannedUsers)
group.GET("/app/users/:id", middleware.RequirePermission("app-user:view"), handlers.AppUser.GetUser)
group.PATCH("/app/users/:id", middleware.RequirePermission("app-user:update"), handlers.AppUser.UpdateUser)
group.PUT("/app/users/:id/levels", middleware.RequirePermission("app-user:level"), handlers.AppUser.AdjustTemporaryLevels)
group.POST("/app/users/:id/ban", middleware.RequirePermission("app-user:status"), handlers.AppUser.BanUser)
group.POST("/app/users/:id/unban", middleware.RequirePermission("app-user:status"), handlers.AppUser.UnbanUser)
}
if handlers.HostOrg != nil {
group.GET("/admin/hosts", middleware.RequirePermission("host:view"), handlers.HostOrg.ListHosts)
group.GET("/admin/agencies", middleware.RequirePermission("agency:view"), handlers.HostOrg.ListAgencies)
group.GET("/admin/bds", middleware.RequirePermission("bd:view"), handlers.HostOrg.ListBDs)
group.GET("/admin/bd-leaders", middleware.RequirePermission("bd:view"), handlers.HostOrg.ListBDLeaders)
group.GET("/admin/managers", middleware.RequirePermission("bd:view"), handlers.HostOrg.ListManagers)
}
if handlers.RoomAdmin != nil {
group.GET("/admin/rooms", middleware.RequirePermission("room:view"), handlers.RoomAdmin.ListRooms)
group.PATCH("/admin/rooms/:room_id", middleware.RequirePermission("room:update"), handlers.RoomAdmin.UpdateRoom)
}
if handlers.Resource != nil {
group.GET("/admin/resources", middleware.RequirePermission("resource:view"), handlers.Resource.ListResources)
group.GET("/admin/resource-grants", middleware.RequirePermission("resource-grant:view"), handlers.Resource.ListResourceGrants)
group.GET("/admin/resource-grants/target", middleware.RequirePermission("resource-grant:create"), handlers.Resource.LookupResourceGrantTarget)
group.POST("/admin/resource-grants/resource", middleware.RequirePermission("resource-grant:create"), handlers.Resource.GrantResource)
group.POST("/admin/resource-grants/group", middleware.RequirePermission("resource-grant:create"), handlers.Resource.GrantResourceGroup)
}
if handlers.PrettyID != nil {
group.GET("/admin/users/pretty-ids", middleware.RequirePermission("pretty-id:view"), handlers.PrettyID.ListIDs)
group.POST("/admin/users/pretty-ids/grant", middleware.RequirePermission("pretty-id:grant"), handlers.PrettyID.Grant)
}
if handlers.AppConfig != nil {
group.GET("/admin/app-config/banners", middleware.RequirePermission("app-config:view"), handlers.AppConfig.ListBanners)
group.POST("/admin/app-config/banners", middleware.RequirePermission("app-config:update"), handlers.AppConfig.CreateBanner)
}
if handlers.VIPConfig != nil {
// Grant mode and level IDs are owner-service facts. Read them with the same narrowly scoped
// grant permission so the portal can select direct-VIP vs trial-card without config write access.
group.GET("/admin/activity/vip-program", middleware.RequirePermission("vip-config:grant"), handlers.VIPConfig.GetProgram)
group.GET("/admin/activity/vip-levels", middleware.RequirePermission("vip-config:grant"), handlers.VIPConfig.ListLevels)
group.POST("/admin/activity/vip-trial-card-grants", middleware.RequirePermission("vip-config:grant"), handlers.VIPConfig.GrantVIPTrialCard)
group.POST("/admin/activity/vip-grants", middleware.RequirePermission("vip-config:grant"), handlers.VIPConfig.GrantVIP)
}
if handlers.Upload != nil {
// Banner/avatar forms need a real upload endpoint; no other file-management route is exposed.
group.POST("/admin/files/image/upload", middleware.RequirePermission("upload:create"), handlers.Upload.UploadImage)
}
}