Files
2026-09-17 21:56:06 +02:00

139 lines
3.6 KiB
Go

package controller
import (
"io"
"net/http"
"github.com/gin-gonic/gin"
"github.com/phishingclub/phishingclub/data"
"github.com/phishingclub/phishingclub/service"
)
// Branding is the controller for install wide UI branding.
type Branding struct {
Common
BrandingService *service.Branding
}
// GetState returns the branding mode of each slot. It is public so the login
// screen can read it before authentication.
func (c *Branding) GetState(g *gin.Context) {
state, err := c.BrandingService.GetState(g.Request.Context())
if err != nil {
c.Response.ServerError(g)
return
}
// never cache the state so an admin change is picked up on the next load
g.Header("Cache-Control", "no-store")
c.Response.OK(g, state)
}
// GetImage streams the uploaded PNG for a slot. It is public so the login
// screen can show a custom logo and side image before authentication. When no
// custom image is stored it returns 404 so the frontend falls back to the
// built in default.
func (c *Branding) GetImage(g *gin.Context) {
slot := g.Param("slot")
content, found, err := c.BrandingService.GetImage(slot)
if err != nil {
c.Response.ServerError(g)
return
}
if !found {
c.Response.NotFound(g)
return
}
g.Header("Cache-Control", "no-cache")
g.Header("X-Content-Type-Options", "nosniff")
g.Data(http.StatusOK, "image/png", content)
}
// Upload validates and stores an uploaded PNG for a slot.
func (c *Branding) Upload(g *gin.Context) {
session, _, ok := c.handleSession(g)
if !ok {
return
}
slot := g.Param("slot")
fileHeader, err := g.FormFile("file")
if err != nil {
c.Response.BadRequestMessage(g, "No file selected")
return
}
// reject an oversized upload before reading it into memory
if fileHeader.Size > data.BrandingMaxUploadBytes {
c.Response.BadRequestMessage(g, "File is too large")
return
}
f, err := fileHeader.Open()
if err != nil {
c.Response.BadRequest(g)
return
}
defer f.Close()
// cap the read so an oversized upload can not exhaust memory, one byte over
// the limit so the size validation still rejects it
content, err := io.ReadAll(io.LimitReader(f, data.BrandingMaxUploadBytes+1))
if err != nil {
c.Response.BadRequest(g)
return
}
err = c.BrandingService.SetImage(g.Request.Context(), session, slot, content)
if ok := c.handleErrors(g, err); !ok {
return
}
c.Response.OK(g, gin.H{})
}
// Reset removes the uploaded image for a slot, returning it to the default.
func (c *Branding) Reset(g *gin.Context) {
session, _, ok := c.handleSession(g)
if !ok {
return
}
slot := g.Param("slot")
err := c.BrandingService.Reset(g.Request.Context(), session, slot)
if ok := c.handleErrors(g, err); !ok {
return
}
c.Response.OK(g, gin.H{})
}
// SetDisplay stores the display fit settings for a slot.
func (c *Branding) SetDisplay(g *gin.Context) {
session, _, ok := c.handleSession(g)
if !ok {
return
}
slot := g.Param("slot")
var req service.BrandingDisplay
if ok := c.handleParseRequest(g, &req); !ok {
return
}
err := c.BrandingService.SetDisplay(g.Request.Context(), session, slot, req)
if ok := c.handleErrors(g, err); !ok {
return
}
c.Response.OK(g, gin.H{})
}
// SetSideImageVisibility shows or hides the login side image. Hiding centers
// the login form.
func (c *Branding) SetSideImageVisibility(g *gin.Context) {
session, _, ok := c.handleSession(g)
if !ok {
return
}
var req struct {
Hidden bool `json:"hidden"`
}
if ok := c.handleParseRequest(g, &req); !ok {
return
}
err := c.BrandingService.SetSideImageHidden(g.Request.Context(), session, req.Hidden)
if ok := c.handleErrors(g, err); !ok {
return
}
c.Response.OK(g, gin.H{})
}