Config UI: add redirect uri suggestion service (#25478)

This commit is contained in:
andig 2025-11-23 18:03:56 +01:00 • committed by GitHub
parent 694816512e
commit 8d94166d93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 101 additions and 121 deletions

View file

@ -1,33 +0,0 @@
package network
import (
"sync"
"github.com/evcc-io/evcc/api/globalconfig"
)
var (
mu sync.Mutex
registry []func()
)
var config globalconfig.Network
func Config() globalconfig.Network {
return config
}
func Register(fun func()) {
mu.Lock()
defer mu.Unlock()
registry = append(registry, fun)
}
func Start(conf globalconfig.Network) {
config = conf
for _, fun := range registry {
go fun()
}
}

34
server/network/service.go Normal file
View file

@ -0,0 +1,34 @@
package network
import (
"encoding/json"
"net/http"
"github.com/evcc-io/evcc/api/globalconfig"
"github.com/evcc-io/evcc/server/service"
)
var config globalconfig.Network
const CallbackPath = "/providerauth/callback"
func init() {
// auth service is registered here to avoid import cycle
mux := http.NewServeMux()
mux.HandleFunc("GET /redirecturi", getRedirectUri)
service.Register("auth", mux)
}
func Start(conf globalconfig.Network) {
config = conf
}
func Config() globalconfig.Network {
return config
}
func getRedirectUri(w http.ResponseWriter, req *http.Request) {
uri := config.ExternalURL() + CallbackPath
json.NewEncoder(w).Encode([]string{uri})
}

View file

@ -29,6 +29,7 @@ params:
help:
en: "Redirect URI of the evcc instance. Must match the redirect URI set in the Viessmann developer portal."
de: "Redirect-URI der evcc-Instanz. Muss mit der Redirect URI übereinstimmen, die Viessmann Developer Portal konfiguriert ist."
service: auth/redirecturi
example: "https://evcc.example.org/providerauth/callback"
- name: gateway_serial
required: true

View file

@ -50,6 +50,7 @@ params:
help:
en: "Redirect URI of your evcc instance. Must match the redirect URI set in your Volvo Developer App."
de: "Redirect-URI deiner evcc-Instanz. Muss mit der Redirect-URI übereinstimmen, die in deiner Volvo Developer App festgelegt ist."
service: auth/redirecturi
example: "https://evcc.example.org/providerauth/callback"
- name: vin
example: WF0FXX...

View file

@ -1,23 +1,50 @@
package homeassistant
import (
"fmt"
"sync"
"golang.org/x/oauth2"
)
type instance struct {
URI string
type proxyInstance struct {
mu sync.Mutex
home, uri string
oauth2.TokenSource
}
var (
mu sync.Mutex
instances = make(map[string]*instance)
)
func (inst *proxyInstance) URI() string {
inst.mu.Lock()
defer inst.mu.Unlock()
func instanceByName(name string) *instance {
mu.Lock()
defer mu.Unlock()
return instances[name]
if inst.uri == "" {
inst.uri = instanceUriByName(inst.home)
if inst.uri == "" {
return ""
}
}
return inst.uri
}
func (inst *proxyInstance) Token() (*oauth2.Token, error) {
uri := inst.URI()
if uri == "" {
return nil, fmt.Errorf("unknown instance: %s", inst.home)
}
inst.mu.Lock()
defer inst.mu.Unlock()
if inst.TokenSource == nil {
ts, err := NewHomeAssistant(inst.home, uri)
if err != nil {
return nil, err
}
inst.TokenSource = ts
}
return inst.TokenSource.Token()
}

View file

@ -26,17 +26,17 @@ func NewHomeAssistantFromConfig(other map[string]any) (oauth2.TokenSource, error
return nil, err
}
inst := instanceByName(cc.Home)
if inst == nil {
uri := instanceUriByName(cc.Home)
if uri == "" {
return nil, fmt.Errorf("unknown instance: %s", cc.Home)
}
return NewHomeAssistant(cc.Home, inst.URI)
return NewHomeAssistant(cc.Home, uri)
}
func NewHomeAssistant(name, uri string) (oauth2.TokenSource, error) {
func NewHomeAssistant(home, uri string) (oauth2.TokenSource, error) {
extUrl := network.Config().ExternalURL()
redirectUri := extUrl + "/providerauth/callback"
redirectUri := extUrl + network.CallbackPath
log := util.NewLogger("homeassistant")
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, request.NewClient(log))
@ -50,5 +50,5 @@ func NewHomeAssistant(name, uri string) (oauth2.TokenSource, error) {
},
}
return auth.NewOAuth(ctx, "HomeAssistant", name, &oc)
return auth.NewOAuth(ctx, "HomeAssistant", home, &oc)
}

View file

@ -1,44 +0,0 @@
package homeassistant
import (
"fmt"
"sync"
"golang.org/x/oauth2"
)
type proxyInstance struct {
mu sync.Mutex
home string
instance *instance
}
func (inst *proxyInstance) URI() string {
inst.mu.Lock()
defer inst.mu.Unlock()
if inst.instance == nil {
inst.instance = instanceByName(inst.home)
if inst.instance == nil {
return ""
}
}
return inst.instance.URI
}
func (inst *proxyInstance) Token() (*oauth2.Token, error) {
inst.mu.Lock()
defer inst.mu.Unlock()
if inst.instance == nil {
inst.instance = instanceByName(inst.home)
if inst.instance == nil {
return nil, fmt.Errorf("unknown instance: %s", inst.home)
}
}
return inst.instance.Token()
}

View file

@ -32,7 +32,7 @@ func getHomes(w http.ResponseWriter, req *http.Request) {
func getEntities(w http.ResponseWriter, req *http.Request) {
home := req.PathValue("home")
if instanceByName(home) == nil {
if instanceUriByName(home) == "" {
w.WriteHeader(http.StatusBadRequest)
return
}

View file

@ -4,20 +4,36 @@ import (
"context"
"fmt"
"strings"
"sync"
"github.com/evcc-io/evcc/server/network"
"github.com/evcc-io/evcc/util"
"github.com/libp2p/zeroconf/v2"
)
var (
mu sync.Mutex
instances = make(map[string]string)
)
func init() {
network.Register(scan)
go scan()
}
func instanceUriByName(name string) string {
mu.Lock()
defer mu.Unlock()
return instances[name]
}
func addInstance(name, uri string) {
mu.Lock()
defer mu.Unlock()
instances[name] = uri
}
func scan() {
ctx := context.Background()
log := util.NewLogger("homeassistant")
entries := make(chan *zeroconf.ServiceEntry, 1)
go func() {
@ -36,9 +52,7 @@ func scan() {
}
}
if err := authorize(se.Instance, uri); err != nil {
log.ERROR.Println(err)
}
addInstance(se.Instance, uri)
case <-ctx.Done():
return
@ -47,27 +61,7 @@ func scan() {
}()
if err := zeroconf.Browse(ctx, "_home-assistant._tcp.", "local.", entries); err != nil {
log := util.NewLogger("homeassistant")
log.ERROR.Println("zeroconf: failed to browse:", err.Error())
}
}
func authorize(name, uri string) error {
mu.Lock()
defer mu.Unlock()
if _, ok := instances[name]; ok {
return nil
}
ts, err := NewHomeAssistant(name, uri)
if err != nil {
return err
}
instances[name] = &instance{
URI: uri,
TokenSource: ts,
}
return nil
}