mirror of
https://github.com/teamhanko/hanko.git
synced 2025-10-26 21:57:14 +08:00
189 lines
5.4 KiB
Go
189 lines
5.4 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
jsonpatch "github.com/evanphx/json-patch"
|
|
"github.com/gobuffalo/nulls"
|
|
"github.com/gofrs/uuid"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/teamhanko/hanko/backend/v2/dto/admin"
|
|
"github.com/teamhanko/hanko/backend/v2/persistence"
|
|
"github.com/teamhanko/hanko/backend/v2/persistence/models"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
type MetadataAdminHandler struct {
|
|
persister persistence.Persister
|
|
}
|
|
|
|
func NewMetadataAdminHandler(persister persistence.Persister) *MetadataAdminHandler {
|
|
return &MetadataAdminHandler{
|
|
persister: persister,
|
|
}
|
|
}
|
|
|
|
func (h *MetadataAdminHandler) GetMetadata(c echo.Context) error {
|
|
userID, err := uuid.FromString(c.Param("id"))
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "invalid user id")
|
|
}
|
|
|
|
userExists, err := h.persister.GetConnection().Where("id = ?", userID).Exists(&models.User{ID: userID})
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "could not fetch user").SetInternal(err)
|
|
}
|
|
|
|
if !userExists {
|
|
return echo.NewHTTPError(http.StatusNotFound, "user not found").SetInternal(err)
|
|
}
|
|
|
|
metadataModel, err := h.persister.GetUserMetadataPersister().Get(userID)
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "could not fetch metadata").SetInternal(err)
|
|
}
|
|
|
|
response := admin.NewMetadata(metadataModel)
|
|
|
|
if response == nil {
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
return c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (h *MetadataAdminHandler) PatchMetadata(c echo.Context) error {
|
|
userID, err := uuid.FromString(c.Param("id"))
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest, "invalid user id")
|
|
}
|
|
|
|
patchMetadataRequest, err := loadDto[admin.PatchMetadataRequest](c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userExists, err := h.persister.GetConnection().Where("id = ?", userID).Exists(&models.User{ID: userID})
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "could not fetch user").SetInternal(err)
|
|
}
|
|
|
|
if !userExists {
|
|
return echo.NewHTTPError(http.StatusNotFound, "user not found").SetInternal(err)
|
|
}
|
|
|
|
currentMetadataModel, err := h.persister.GetUserMetadataPersister().Get(userID)
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "could not fetch metadata").SetInternal(err)
|
|
}
|
|
|
|
_, err = h.applyMetadataPatch(currentMetadataModel, patchMetadataRequest)
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "could not patch metadata").SetInternal(err)
|
|
}
|
|
|
|
err = h.persister.GetUserMetadataPersister().Update(currentMetadataModel)
|
|
if err != nil {
|
|
if persistence.IsMetadataLimitExceededError(err) {
|
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error()).
|
|
SetInternal(errors.Unwrap(err))
|
|
}
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "could not save metadata").
|
|
SetInternal(err)
|
|
}
|
|
|
|
response := admin.NewMetadata(currentMetadataModel)
|
|
|
|
if response == nil {
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
return c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (h *MetadataAdminHandler) applyMetadataPatch(currentMetadataModel *models.UserMetadata, patchMetadataRequest *admin.PatchMetadataRequest) ([]byte, error) {
|
|
if patchMetadataRequest.Metadata.Raw == "null" {
|
|
currentMetadataModel.Public = nulls.String{}
|
|
currentMetadataModel.Private = nulls.String{}
|
|
currentMetadataModel.Unsafe = nulls.String{}
|
|
return []byte("{}"), nil
|
|
}
|
|
|
|
currentMetadataJSON, err := h.buildCurrentMetadataJSON(currentMetadataModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
patchedMetadataBytes, err := jsonpatch.MergePatch(
|
|
[]byte(currentMetadataJSON),
|
|
[]byte(patchMetadataRequest.Metadata.String()),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not apply merge patch: %w", err)
|
|
}
|
|
|
|
if !json.Valid(patchedMetadataBytes) {
|
|
return nil, fmt.Errorf("invalid metadata JSON after applying merge patch")
|
|
}
|
|
|
|
if err = h.updateMetadataModel(currentMetadataModel, patchedMetadataBytes); err != nil {
|
|
return nil, fmt.Errorf("could not update user metadata model: %w", err)
|
|
}
|
|
|
|
return patchedMetadataBytes, nil
|
|
}
|
|
|
|
func (h *MetadataAdminHandler) buildCurrentMetadataJSON(metadata *models.UserMetadata) (string, error) {
|
|
result := make(map[string]json.RawMessage)
|
|
|
|
if metadata.Public.Valid && metadata.Public.String != "{}" {
|
|
result["public_metadata"] = json.RawMessage(metadata.Public.String)
|
|
}
|
|
|
|
if metadata.Private.Valid && metadata.Private.String != "{}" {
|
|
result["private_metadata"] = json.RawMessage(metadata.Private.String)
|
|
}
|
|
|
|
if metadata.Unsafe.Valid && metadata.Unsafe.String != "{}" {
|
|
result["unsafe_metadata"] = json.RawMessage(metadata.Unsafe.String)
|
|
}
|
|
|
|
if len(result) == 0 {
|
|
return "{}", nil
|
|
}
|
|
|
|
bytes, err := json.Marshal(result)
|
|
if err != nil {
|
|
return "", fmt.Errorf("could not build JSON for current metadata: %w", err)
|
|
}
|
|
|
|
return string(bytes), nil
|
|
}
|
|
|
|
func (h *MetadataAdminHandler) updateMetadataModel(metadata *models.UserMetadata, patchedBytes []byte) error {
|
|
|
|
metadataTypes := map[string]*nulls.String{
|
|
"public_metadata": &metadata.Public,
|
|
"private_metadata": &metadata.Private,
|
|
"unsafe_metadata": &metadata.Unsafe,
|
|
}
|
|
|
|
for key, target := range metadataTypes {
|
|
if gjson.GetBytes(patchedBytes, key).Exists() {
|
|
value := gjson.GetBytes(patchedBytes, key).String()
|
|
if !json.Valid([]byte(value)) {
|
|
return fmt.Errorf("invalid JSON for %s", key)
|
|
}
|
|
*target = nulls.String{
|
|
Valid: true,
|
|
String: value,
|
|
}
|
|
} else {
|
|
*target = nulls.String{}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|