Files
akvorado/console/authentication/handlers.go
2025-10-26 12:14:20 +01:00

104 lines
2.8 KiB
Go

// SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only
package authentication
import (
"bufio"
"fmt"
"hash/fnv"
"image"
"image/draw"
"image/png"
"io/fs"
"math/rand/v2"
"net/http"
"akvorado/common/embed"
"github.com/gin-gonic/gin"
)
// UserInfoHandlerFunc returns the information about the currently logged user.
func (c *Component) UserInfoHandlerFunc(gc *gin.Context) {
info := gc.MustGet("user").(UserInformation)
gc.JSON(http.StatusOK, info)
}
// UserAvatarHandlerFunc returns an avatar for the currently logger user.
func (c *Component) UserAvatarHandlerFunc(gc *gin.Context) {
// Hash user login as a source
info := gc.MustGet("user").(UserInformation)
hash := fnv.New64()
hash.Write([]byte(info.Login))
randSource := rand.New(rand.NewPCG(hash.Sum64(), 0))
etag := fmt.Sprintf(`"%x"`, hash.Sum64())
// Do we have a If-None-Match header?
if header := gc.GetHeader("If-None-Match"); header == etag {
gc.Header("ETag", etag)
gc.Status(http.StatusNotModified)
return
}
// Grab list of parts
avatarParts, err := fs.Sub(embed.Data(), "console/authentication")
if err != nil {
c.r.Err(err).Msg("cannot open embedded archive")
gc.JSON(http.StatusInternalServerError, gin.H{"message": "Cannot build avatar."})
return
}
parts := []string{}
partList, err := avatarParts.Open("data/avatars/partlist.txt")
if err != nil {
c.r.Err(err).Msg("cannot open partlist.txt")
gc.JSON(http.StatusInternalServerError, gin.H{"message": "Cannot build avatar."})
return
}
defer partList.Close()
scanner := bufio.NewScanner(partList)
for scanner.Scan() {
parts = append(parts, scanner.Text())
}
// Choose an image for each part
for idx, part := range parts {
// Choose a file for each part
p, _ := fs.Glob(avatarParts, fmt.Sprintf("data/avatars/%s_*", part))
if len(p) == 0 {
c.r.Error().Msgf("missing part %s", part)
gc.JSON(http.StatusInternalServerError, gin.H{"message": "Cannot build avatar."})
return
}
parts[idx] = p[randSource.IntN(len(p))]
}
// Compose the images
var img *image.RGBA
for _, part := range parts {
filePart, err := avatarParts.Open(part)
if err != nil {
c.r.Err(err).Msgf("cannot open part %s", part)
gc.JSON(http.StatusInternalServerError, gin.H{"message": "Cannot build avatar."})
return
}
imgPart, err := png.Decode(filePart)
filePart.Close()
if err != nil {
c.r.Err(err).Msgf("cannot decode part %s", part)
gc.JSON(http.StatusInternalServerError, gin.H{"message": "Cannot build avatar."})
return
}
if img == nil {
img = image.NewRGBA(imgPart.Bounds())
}
draw.Draw(img, img.Bounds(), imgPart, imgPart.Bounds().Min, draw.Over)
}
// Serve the result
gc.Header("Content-Type", "image/png")
gc.Header("ETag", etag)
gc.Status(http.StatusOK)
png.Encode(gc.Writer, img)
}