Smart-hello: persist token and device ID across restarts (#30581)
This commit is contained in:
parent
4a7cb7d6d8
commit
7ecd241c1a
1 changed files with 52 additions and 7 deletions
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue