Smart-hello: persist token and device ID across restarts (#30581)

This commit is contained in:
Marcy 2026-06-10 16:42:16 -04:00 • committed by GitHub
parent 4a7cb7d6d8
commit 7ecd241c1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -6,8 +6,10 @@ import (
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/evcc-io/evcc/server/db/settings"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/oauth"
"github.com/evcc-io/evcc/util/request"
@ -15,41 +17,84 @@ import (
"golang.org/x/oauth2"
)
// savedState holds the identity data persisted across evcc restarts.
type savedState struct {
Token oauth2.Token `json:"token"`
UserID string `json:"userId"`
DeviceID string `json:"deviceId"`
}
type Identity struct {
*request.Helper
oauth2.TokenSource
log *util.Logger
user, password string
userID, deviceID string
subject string
mu sync.Mutex
}
func NewIdentity(log *util.Logger, user, password string) (*Identity, error) {
v := &Identity{
Helper: request.NewHelper(log),
log: log,
user: user,
password: password,
deviceID: lo.RandomString(16, lo.AlphanumericCharset),
subject: "smart-hello." + user,
}
v.TokenSource = oauth2.ReuseTokenSource(nil, oauth.BootstrapTokenSource(v.refreshToken))
var state savedState
if err := settings.Json(v.subject, &state); err != nil {
if !errors.Is(err, settings.ErrNotFound) {
v.log.WARN.Printf("load state: %v", err)
}
// no usable persisted state — generate a fresh device ID (sent in login headers)
state.DeviceID = lo.RandomString(16, lo.AlphanumericCharset)
}
_, err := v.Token()
// deviceID must be set before any login — it is sent in request headers.
v.deviceID = state.DeviceID
v.userID = state.UserID
return v, err
var token *oauth2.Token
if state.Token.Valid() {
token = &state.Token
} else {
var err error
token, err = v.refreshToken(nil)
if err != nil {
return nil, err
}
}
v.TokenSource = oauth.RefreshTokenSource(token, v.refreshToken)
return v, nil
}
func (v *Identity) refreshToken() (*oauth2.Token, error) {
token, err := v.login()
func (v *Identity) refreshToken(_ *oauth2.Token) (*oauth2.Token, error) {
v.mu.Lock()
defer v.mu.Unlock()
tok, err := v.login()
if err != nil {
return nil, err
}
appToken, userID, err := v.appToken(token)
appToken, userID, err := v.appToken(tok)
if err != nil {
return nil, err
}
v.userID = userID
if err := settings.SetJson(v.subject, savedState{
Token: *appToken,
UserID: userID,
DeviceID: v.deviceID,
}); err != nil {
return nil, err
}
return appToken, nil
}