Config UI: fix editing existing OAuth devices (#28012)

This commit is contained in:
lehmanju 2026-04-12 14:04:40 +02:00 • committed by GitHub
parent 20de0b9da1
commit b28be96d0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 98 additions and 9 deletions

View file

@ -279,6 +279,7 @@ func (s *HTTPd) RegisterSystemHandler(site *core.Site, pub publisher, cache *uti
routes := map[string]route{
"auth": {"POST", "/auth", authHandler},
"authmerged": {"POST", "/auth/{class:[a-z]+}/merge/{id:[0-9.]+}", authHandler},
"templates": {"GET", "/templates/{class:[a-z]+}", templatesHandler},
"products": {"GET", "/products/{class:[a-z]+}", productsHandler},
"devices": {"GET", "/devices/{class:[a-z]+}", devicesConfigHandler},

View file

@ -107,6 +107,7 @@ func templateForConfig(class templates.Class, conf map[string]any) (templates.Te
return templates.ByName(class, typ)
}
// filterValidTemplateParams removes all configuration properties that are not part of the template definition
func filterValidTemplateParams(tmpl *templates.Template, conf map[string]any) map[string]any {
res := make(map[string]any)
@ -169,6 +170,32 @@ func mergeMasked(class templates.Class, conf, old map[string]any) (map[string]an
})
}
// deviceOther looks up a stored device's `Other` config by class and id.
func deviceOther(class templates.Class, id int) (map[string]any, error) {
name := config.NameForID(id)
switch class {
case templates.Charger:
return deviceOtherFromHandler(name, config.Chargers())
case templates.Meter:
return deviceOtherFromHandler(name, config.Meters())
case templates.Vehicle:
return deviceOtherFromHandler(name, config.Vehicles())
case templates.Tariff:
return deviceOtherFromHandler(name, config.Tariffs())
case templates.Messenger:
return deviceOtherFromHandler(name, config.Messengers())
}
return nil, errors.New("unsupported class: " + class.String())
}
func deviceOtherFromHandler[T any](name string, h config.Handler[T]) (map[string]any, error) {
dev, err := h.ByName(name)
if err != nil {
return nil, err
}
return dev.Config().Other, nil
}
func startDeviceTimeout() (context.Context, context.CancelFunc, chan struct{}) {
done := make(chan struct{})
ctx, cancel := context.WithCancel(context.Background())

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"net/http"
"slices"
"strconv"
"strings"
"github.com/evcc-io/evcc/plugin/auth"
@ -42,6 +43,38 @@ func authHandler(w http.ResponseWriter, r *http.Request) {
return
}
// when editing existing device, merge masked values with stored config
if vars := mux.Vars(r); vars["class"] != "" && vars["id"] != "" {
id, err := strconv.Atoi(vars["id"])
if err != nil {
jsonError(w, http.StatusBadRequest, err)
return
}
class, err := templates.ClassString(vars["class"])
if err != nil {
jsonError(w, http.StatusBadRequest, err)
return
}
old, err := deviceOther(class, id)
if err != nil {
jsonError(w, http.StatusBadRequest, err)
return
}
merged, err := mergeMasked(class, cc.Other, old)
if err != nil {
jsonError(w, http.StatusBadRequest, err)
return
}
cc.Other = merged
}
// template is only needed by mergeMasked above; the auth decoder is strict
delete(cc.Other, typeTemplate)
ts, err := auth.NewFromConfig(context.Background(), cc.Type, cc.Other)
if err != nil {
jsonError(w, http.StatusBadRequest, err)