Nissan: async soc refresh (#1011)
This commit is contained in:
parent
36582bff6a
commit
169d2ca11d
5 changed files with 365 additions and 263 deletions
|
|
@ -4,6 +4,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/provider"
|
||||
)
|
||||
|
||||
// Response structure for kamereon api
|
||||
|
|
@ -12,6 +13,7 @@ type Response struct {
|
|||
}
|
||||
|
||||
type data struct {
|
||||
Type, ID string // battery refresh
|
||||
Attributes attributes `json:"attributes"`
|
||||
}
|
||||
|
||||
|
|
@ -30,20 +32,24 @@ type attributes struct {
|
|||
RemainingTime *int `json:"chargingRemainingTime"`
|
||||
}
|
||||
|
||||
// API is a kamereon API implementation
|
||||
type API struct {
|
||||
// Provider is a kamereon provider
|
||||
type Provider struct {
|
||||
apiG func() (interface{}, error)
|
||||
}
|
||||
|
||||
// New returns a kamereon API implementation
|
||||
func New(apiG func() (interface{}, error)) *API {
|
||||
return &API{apiG: apiG}
|
||||
// NewProvider returns a kamereon provider
|
||||
func NewProvider(respG func() (Response, error), cache time.Duration) *Provider {
|
||||
return &Provider{
|
||||
apiG: provider.NewCached(func() (interface{}, error) {
|
||||
return respG()
|
||||
}, cache).InterfaceGetter(),
|
||||
}
|
||||
}
|
||||
|
||||
var _ api.Battery = (*API)(nil)
|
||||
var _ api.Battery = (*Provider)(nil)
|
||||
|
||||
// SoC implements the api.Vehicle interface
|
||||
func (v *API) SoC() (float64, error) {
|
||||
func (v *Provider) SoC() (float64, error) {
|
||||
res, err := v.apiG()
|
||||
|
||||
if res, ok := res.(Response); err == nil && ok {
|
||||
|
|
@ -53,10 +59,10 @@ func (v *API) SoC() (float64, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
var _ api.ChargeState = (*API)(nil)
|
||||
var _ api.ChargeState = (*Provider)(nil)
|
||||
|
||||
// Status implements the api.ChargeState interface
|
||||
func (v *API) Status() (api.ChargeStatus, error) {
|
||||
func (v *Provider) Status() (api.ChargeStatus, error) {
|
||||
status := api.StatusA // disconnected
|
||||
|
||||
res, err := v.apiG()
|
||||
|
|
@ -72,10 +78,10 @@ func (v *API) Status() (api.ChargeStatus, error) {
|
|||
return status, err
|
||||
}
|
||||
|
||||
var _ api.VehicleRange = (*API)(nil)
|
||||
var _ api.VehicleRange = (*Provider)(nil)
|
||||
|
||||
// Range implements the api.VehicleRange interface
|
||||
func (v *API) Range() (int64, error) {
|
||||
func (v *Provider) Range() (int64, error) {
|
||||
res, err := v.apiG()
|
||||
|
||||
if res, ok := res.(Response); err == nil && ok {
|
||||
|
|
@ -85,10 +91,10 @@ func (v *API) Range() (int64, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
var _ api.VehicleFinishTimer = (*API)(nil)
|
||||
var _ api.VehicleFinishTimer = (*Provider)(nil)
|
||||
|
||||
// FinishTime implements the api.VehicleFinishTimer interface
|
||||
func (v *API) FinishTime() (time.Time, error) {
|
||||
func (v *Provider) FinishTime() (time.Time, error) {
|
||||
res, err := v.apiG()
|
||||
|
||||
if res, ok := res.(Response); err == nil && ok {
|
||||
|
|
|
|||
|
|
@ -1,21 +1,14 @@
|
|||
package vehicle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/vehicle/kamereon"
|
||||
"github.com/andig/evcc/provider"
|
||||
"github.com/andig/evcc/internal/vehicle/nissan"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/oauth"
|
||||
"github.com/andig/evcc/util/request"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Credits to
|
||||
|
|
@ -26,27 +19,10 @@ import (
|
|||
// OAuth base url
|
||||
// https://prod.eu.auth.kamereon.org/kauth/oauth2/a-ncb-prod/.well-known/openid-configuration
|
||||
|
||||
// api constants
|
||||
const (
|
||||
nissanAPIVersion = "protocol=1.0,resource=2.1"
|
||||
nissanClientID = "a-ncb-prod-android"
|
||||
nissanClientSecret = "3LBs0yOx2XO-3m4mMRW27rKeJzskhfWF0A8KUtnim8i/qYQPl8ZItp3IaqJXaYj_"
|
||||
nissanScope = "openid profile vehicles"
|
||||
nissanAuthBaseURL = "https://prod.eu.auth.kamereon.org/kauth"
|
||||
nissanRealm = "a-ncb-prod"
|
||||
nissanRedirectURI = "org.kamereon.service.nci:/oauth2redirect"
|
||||
nissanCarAdapterBaseURL = "https://alliance-platform-caradapter-prod.apps.eu.kamereon.io/car-adapter"
|
||||
nissanUserAdapterBaseURL = "https://alliance-platform-usersadapter-prod.apps.eu.kamereon.io/user-adapter"
|
||||
nissanUserBaseURL = "https://nci-bff-web-prod.apps.eu.kamereon.io/bff-web"
|
||||
)
|
||||
|
||||
// Nissan is an api.Vehicle implementation for Nissan cars
|
||||
type Nissan struct {
|
||||
*embed
|
||||
*request.Helper
|
||||
log *util.Logger
|
||||
user, password, vin string
|
||||
*kamereon.API
|
||||
*kamereon.Provider
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -68,240 +44,28 @@ func NewNissanFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
log := util.NewLogger("nissan")
|
||||
|
||||
v := &Nissan{
|
||||
embed: &cc.embed,
|
||||
Helper: request.NewHelper(log),
|
||||
log: log,
|
||||
user: cc.User,
|
||||
password: cc.Password,
|
||||
vin: strings.ToUpper(cc.VIN),
|
||||
embed: &cc.embed,
|
||||
}
|
||||
|
||||
token, err := v.authFlow()
|
||||
if err == nil {
|
||||
// replace client transport with authenticated transport
|
||||
v.Client.Transport = &oauth2.Transport{
|
||||
Source: oauth.RefreshTokenSource((*oauth2.Token)(&token), v),
|
||||
Base: v.Client.Transport,
|
||||
}
|
||||
log := util.NewLogger("nissan")
|
||||
identity := nissan.NewIdentity(log)
|
||||
|
||||
err := identity.Login(cc.User, cc.Password)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("login failed: %w", err)
|
||||
}
|
||||
|
||||
api := nissan.NewAPI(log, identity, strings.ToUpper(cc.VIN))
|
||||
|
||||
if err == nil && cc.VIN == "" {
|
||||
v.vin, err = findVehicle(v.vehicles())
|
||||
api.VIN, err = findVehicle(api.Vehicles())
|
||||
if err == nil {
|
||||
log.DEBUG.Printf("found vehicle: %v", v.vin)
|
||||
log.DEBUG.Printf("found vehicle: %v", api.VIN)
|
||||
}
|
||||
}
|
||||
|
||||
v.API = kamereon.New(provider.NewCached(v.batteryAPI, cc.Cache).InterfaceGetter())
|
||||
v.Provider = kamereon.NewProvider(api.Battery, cc.Cache)
|
||||
|
||||
return v, err
|
||||
}
|
||||
|
||||
type nissanAuth struct {
|
||||
AuthID string `json:"authId"`
|
||||
Template string `json:"template"`
|
||||
Stage string `json:"stage"`
|
||||
Header string `json:"header"`
|
||||
Callbacks []nissanAuthCallback `json:"callbacks"`
|
||||
}
|
||||
|
||||
type nissanAuthCallback struct {
|
||||
Type string `json:"type"`
|
||||
Output []nissanAuthCallbackValue `json:"output"`
|
||||
Input []nissanAuthCallbackValue `json:"input"`
|
||||
}
|
||||
|
||||
type nissanAuthCallbackValue struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type nissanToken struct {
|
||||
TokenID string `json:"tokenId"`
|
||||
SuccessURL string `json:"successUrl"`
|
||||
Realm string `json:"realm"`
|
||||
}
|
||||
|
||||
func (v *Nissan) authFlow() (oauth.Token, error) {
|
||||
client := request.NewHelper(v.log) // no underlying oauth transport
|
||||
|
||||
uri := fmt.Sprintf("%s/json/realms/root/realms/%s/authenticate", nissanAuthBaseURL, nissanRealm)
|
||||
req, err := request.New(http.MethodPost, uri, nil, map[string]string{
|
||||
"Accept-Api-Version": nissanAPIVersion,
|
||||
"X-Username": "anonymous",
|
||||
"X-Password": "anonymous",
|
||||
"Accept": "application/json",
|
||||
})
|
||||
|
||||
var nToken nissanToken
|
||||
var realm string
|
||||
var resp *http.Response
|
||||
var code string
|
||||
|
||||
if err == nil {
|
||||
var res nissanAuth
|
||||
if err = client.DoJSON(req, &res); err != nil {
|
||||
return oauth.Token{}, err
|
||||
}
|
||||
|
||||
for id, cb := range res.Callbacks {
|
||||
switch cb.Type {
|
||||
case "NameCallback":
|
||||
res.Callbacks[id].Input[0].Value = v.user
|
||||
case "PasswordCallback":
|
||||
res.Callbacks[id].Input[0].Value = v.password
|
||||
}
|
||||
}
|
||||
|
||||
var body []byte
|
||||
body, err = json.Marshal(res)
|
||||
|
||||
if err == nil {
|
||||
req, err = request.New(http.MethodPost, uri, bytes.NewReader(body), map[string]string{
|
||||
"Content-type": "application/json",
|
||||
"Accept-Api-Version": nissanAPIVersion,
|
||||
"X-Username": "anonymous",
|
||||
"X-Password": "anonymous",
|
||||
"Accept": "application/json",
|
||||
})
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = client.DoJSON(req, &nToken)
|
||||
realm = strings.Trim(nToken.Realm, "/")
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
data := url.Values{
|
||||
"client_id": []string{nissanClientID},
|
||||
"redirect_uri": []string{nissanRedirectURI},
|
||||
"response_type": []string{"code"},
|
||||
"scope": []string{nissanScope},
|
||||
"nonce": []string{"sdfdsfez"},
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/oauth2/%s/authorize?%s", nissanAuthBaseURL, realm, data.Encode())
|
||||
req, err = request.New(http.MethodGet, uri, nil, map[string]string{
|
||||
"Cookie": "i18next=en-UK; amlbcookie=05; kauthSession=" + nToken.TokenID,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }
|
||||
resp, err = client.Do(req)
|
||||
client.CheckRedirect = nil
|
||||
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
|
||||
var location *url.URL
|
||||
if location, err = url.Parse(resp.Header.Get("Location")); err == nil {
|
||||
if code = location.Query().Get("code"); code == "" {
|
||||
err = fmt.Errorf("missing auth code: %v", location)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var res oauth.Token
|
||||
if err == nil {
|
||||
data := url.Values{
|
||||
"code": []string{code},
|
||||
"client_id": []string{nissanClientID},
|
||||
"client_secret": []string{nissanClientSecret},
|
||||
"redirect_uri": []string{nissanRedirectURI},
|
||||
"grant_type": []string{"authorization_code"},
|
||||
}
|
||||
|
||||
uri = fmt.Sprintf("%s/oauth2/%s/access_token?%s", nissanAuthBaseURL, realm, data.Encode())
|
||||
req, err = request.New(http.MethodPost, uri, nil, request.URLEncoding)
|
||||
if err == nil {
|
||||
err = client.DoJSON(req, &res)
|
||||
}
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (v *Nissan) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) {
|
||||
data := url.Values{
|
||||
"client_id": []string{nissanClientID},
|
||||
"client_secret": []string{nissanClientSecret},
|
||||
"grant_type": {"refresh_token"},
|
||||
"refresh_token": {token.RefreshToken},
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/oauth2/%s/access_token?%s", nissanAuthBaseURL, nissanRealm, data.Encode())
|
||||
req, err := request.New(http.MethodPost, uri, nil, request.URLEncoding)
|
||||
|
||||
var res oauth.Token
|
||||
if err == nil {
|
||||
client := request.NewHelper(v.log)
|
||||
err = client.DoJSON(req, &res)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
res, err = v.authFlow()
|
||||
}
|
||||
|
||||
return (*oauth2.Token)(&res), err
|
||||
}
|
||||
|
||||
type nissanVehicles struct {
|
||||
Data []nissanVehicle
|
||||
}
|
||||
|
||||
type nissanVehicle struct {
|
||||
VIN string
|
||||
ModelName string
|
||||
PictureURL string
|
||||
}
|
||||
|
||||
func (v *Nissan) vehicles() ([]string, error) {
|
||||
var user struct{ UserID string }
|
||||
uri := fmt.Sprintf("%s/v1/users/current", nissanUserAdapterBaseURL)
|
||||
err := v.GetJSON(uri, &user)
|
||||
|
||||
var res nissanVehicles
|
||||
if err == nil {
|
||||
uri := fmt.Sprintf("%s/v2/users/%s/cars", nissanUserBaseURL, user.UserID)
|
||||
err = v.GetJSON(uri, &res)
|
||||
}
|
||||
|
||||
var vehicles []string
|
||||
if err == nil {
|
||||
for _, v := range res.Data {
|
||||
vehicles = append(vehicles, v.VIN)
|
||||
}
|
||||
}
|
||||
|
||||
return vehicles, err
|
||||
}
|
||||
|
||||
// batteryAPI provides battery api response
|
||||
func (v *Nissan) batteryAPI() (interface{}, error) {
|
||||
// refresh battery status
|
||||
uri := fmt.Sprintf("%s/v1/cars/%s/actions/refresh-battery-status", nissanCarAdapterBaseURL, v.vin)
|
||||
|
||||
data := strings.NewReader(`{"data": {"type": "RefreshBatteryStatus"}}`)
|
||||
req, err := request.New(http.MethodPost, uri, data, map[string]string{
|
||||
"Content-Type": "application/vnd.api+json",
|
||||
})
|
||||
|
||||
var res kamereon.Response
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &res)
|
||||
}
|
||||
|
||||
// request battery status
|
||||
if err == nil {
|
||||
uri = fmt.Sprintf("%s/v1/cars/%s/battery-status", nissanCarAdapterBaseURL, v.vin)
|
||||
err = v.GetJSON(uri, &res)
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
|
|
|||
131
internal/vehicle/nissan/api.go
Normal file
131
internal/vehicle/nissan/api.go
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
package nissan
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/internal/vehicle/kamereon"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
refreshTimeout = 5 * time.Minute
|
||||
statusExpiry = time.Minute
|
||||
)
|
||||
|
||||
type API struct {
|
||||
*request.Helper
|
||||
VIN string
|
||||
refreshID string
|
||||
refreshTime time.Time
|
||||
}
|
||||
|
||||
func NewAPI(log *util.Logger, identity oauth2.TokenSource, vin string) *API {
|
||||
v := &API{
|
||||
Helper: request.NewHelper(log),
|
||||
VIN: vin,
|
||||
}
|
||||
|
||||
// replace client transport with authenticated transport
|
||||
v.Client.Transport = &oauth2.Transport{
|
||||
Source: identity,
|
||||
Base: v.Client.Transport,
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *API) Vehicles() ([]string, error) {
|
||||
var user struct{ UserID string }
|
||||
uri := fmt.Sprintf("%s/v1/users/current", UserAdapterBaseURL)
|
||||
err := v.GetJSON(uri, &user)
|
||||
|
||||
var res Vehicles
|
||||
if err == nil {
|
||||
uri := fmt.Sprintf("%s/v2/users/%s/cars", UserBaseURL, user.UserID)
|
||||
err = v.GetJSON(uri, &res)
|
||||
}
|
||||
|
||||
var vehicles []string
|
||||
if err == nil {
|
||||
for _, v := range res.Data {
|
||||
vehicles = append(vehicles, v.VIN)
|
||||
}
|
||||
}
|
||||
|
||||
return vehicles, err
|
||||
}
|
||||
|
||||
// Battery provides battery api response
|
||||
func (v *API) Battery() (kamereon.Response, error) {
|
||||
// request battery status
|
||||
uri := fmt.Sprintf("%s/v1/cars/%s/battery-status", CarAdapterBaseURL, v.VIN)
|
||||
|
||||
var res kamereon.Response
|
||||
err := v.GetJSON(uri, &res)
|
||||
|
||||
var ts time.Time
|
||||
if err == nil {
|
||||
ts, err = time.Parse(timeFormat, res.Data.Attributes.LastUpdateTime)
|
||||
|
||||
// return the current value
|
||||
if time.Since(ts) <= statusExpiry {
|
||||
v.refreshID = ""
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
// request a refresh, irrespective of a previous error
|
||||
if v.refreshID == "" {
|
||||
if err = v.refreshRequest(); err == nil {
|
||||
err = api.ErrMustRetry
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// refresh finally expired
|
||||
if time.Since(v.refreshTime) > refreshTimeout {
|
||||
v.refreshID = ""
|
||||
if err == nil {
|
||||
err = api.ErrTimeout
|
||||
}
|
||||
} else {
|
||||
// wait for refresh, irrespective of a previous error
|
||||
err = api.ErrMustRetry
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// refreshRequest requests battery status refresh
|
||||
func (v *API) refreshRequest() error {
|
||||
uri := fmt.Sprintf("%s/v1/cars/%s/actions/refresh-battery-status", CarAdapterBaseURL, v.VIN)
|
||||
|
||||
data := strings.NewReader(`{"data": {"type": "RefreshBatteryStatus"}}`)
|
||||
req, err := request.New(http.MethodPost, uri, data, map[string]string{
|
||||
"Content-Type": "application/vnd.api+json",
|
||||
})
|
||||
|
||||
var res kamereon.Response
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &res)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
v.refreshID = res.Data.ID
|
||||
v.refreshTime = time.Now()
|
||||
|
||||
if v.refreshID == "" {
|
||||
err = errors.New("refresh failed")
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
149
internal/vehicle/nissan/identity.go
Normal file
149
internal/vehicle/nissan/identity.go
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
package nissan
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/oauth"
|
||||
"github.com/andig/evcc/util/request"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type Identity struct {
|
||||
*request.Helper
|
||||
oauth2.TokenSource
|
||||
}
|
||||
|
||||
func NewIdentity(log *util.Logger) *Identity {
|
||||
return &Identity{
|
||||
Helper: request.NewHelper(log),
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Identity) Login(user, password string) error {
|
||||
uri := fmt.Sprintf("%s/json/realms/root/realms/%s/authenticate", AuthBaseURL, Realm)
|
||||
req, err := request.New(http.MethodPost, uri, nil, map[string]string{
|
||||
"Accept-Api-Version": APIVersion,
|
||||
"X-Username": "anonymous",
|
||||
"X-Password": "anonymous",
|
||||
"Accept": "application/json",
|
||||
})
|
||||
|
||||
var nToken Token
|
||||
var realm string
|
||||
var resp *http.Response
|
||||
var code string
|
||||
|
||||
if err == nil {
|
||||
var res Auth
|
||||
if err = v.DoJSON(req, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for id, cb := range res.Callbacks {
|
||||
switch cb.Type {
|
||||
case "NameCallback":
|
||||
res.Callbacks[id].Input[0].Value = user
|
||||
case "PasswordCallback":
|
||||
res.Callbacks[id].Input[0].Value = password
|
||||
}
|
||||
}
|
||||
|
||||
var body []byte
|
||||
body, err = json.Marshal(res)
|
||||
|
||||
if err == nil {
|
||||
req, err = request.New(http.MethodPost, uri, bytes.NewReader(body), map[string]string{
|
||||
"Content-type": "application/json",
|
||||
"Accept": "application/json",
|
||||
"Accept-Api-Version": APIVersion,
|
||||
"X-Username": "anonymous",
|
||||
"X-Password": "anonymous",
|
||||
})
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &nToken)
|
||||
realm = strings.Trim(nToken.Realm, "/")
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
data := url.Values{
|
||||
"client_id": []string{ClientID},
|
||||
"redirect_uri": []string{RedirectURI},
|
||||
"response_type": []string{"code"},
|
||||
"scope": []string{Scope},
|
||||
"nonce": []string{"sdfdsfez"},
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/oauth2/%s/authorize?%s", AuthBaseURL, realm, data.Encode())
|
||||
req, err = request.New(http.MethodGet, uri, nil, map[string]string{
|
||||
"Cookie": "i18next=en-UK; amlbcookie=05; kauthSession=" + nToken.TokenID,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
v.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }
|
||||
resp, err = v.Do(req)
|
||||
v.CheckRedirect = nil
|
||||
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
|
||||
var location *url.URL
|
||||
if location, err = url.Parse(resp.Header.Get("Location")); err == nil {
|
||||
if code = location.Query().Get("code"); code == "" {
|
||||
err = fmt.Errorf("missing auth code: %v", location)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var token oauth.Token
|
||||
if err == nil {
|
||||
data := url.Values{
|
||||
"code": []string{code},
|
||||
"client_id": []string{ClientID},
|
||||
"client_secret": []string{ClientSecret},
|
||||
"redirect_uri": []string{RedirectURI},
|
||||
"grant_type": []string{"authorization_code"},
|
||||
}
|
||||
|
||||
uri = fmt.Sprintf("%s/oauth2/%s/access_token?%s", AuthBaseURL, realm, data.Encode())
|
||||
req, err = request.New(http.MethodPost, uri, nil, request.URLEncoding)
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &token)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
v.TokenSource = oauth.RefreshTokenSource((*oauth2.Token)(&token), v)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) {
|
||||
data := url.Values{
|
||||
"client_id": []string{ClientID},
|
||||
"client_secret": []string{ClientSecret},
|
||||
"grant_type": {"refresh_token"},
|
||||
"refresh_token": {token.RefreshToken},
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s/oauth2/%s/access_token?%s", AuthBaseURL, Realm, data.Encode())
|
||||
req, err := request.New(http.MethodPost, uri, nil, request.URLEncoding)
|
||||
|
||||
var res oauth.Token
|
||||
if err == nil {
|
||||
err = v.DoJSON(req, &res)
|
||||
}
|
||||
|
||||
return (*oauth2.Token)(&res), err
|
||||
}
|
||||
52
internal/vehicle/nissan/types.go
Normal file
52
internal/vehicle/nissan/types.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package nissan
|
||||
|
||||
// api constants
|
||||
const (
|
||||
APIVersion = "protocol=1.0,resource=2.1"
|
||||
ClientID = "a-ncb-prod-android"
|
||||
ClientSecret = "3LBs0yOx2XO-3m4mMRW27rKeJzskhfWF0A8KUtnim8i/qYQPl8ZItp3IaqJXaYj_"
|
||||
Scope = "openid profile vehicles"
|
||||
AuthBaseURL = "https://prod.eu.auth.kamereon.org/kauth"
|
||||
Realm = "a-ncb-prod"
|
||||
RedirectURI = "org.kamereon.service.nci:/oauth2redirect"
|
||||
CarAdapterBaseURL = "https://alliance-platform-caradapter-prod.apps.eu.kamereon.io/car-adapter"
|
||||
UserAdapterBaseURL = "https://alliance-platform-usersadapter-prod.apps.eu.kamereon.io/user-adapter"
|
||||
UserBaseURL = "https://nci-bff-web-prod.apps.eu.kamereon.io/bff-web"
|
||||
)
|
||||
|
||||
const timeFormat = "2006-01-02T15:04:05Z"
|
||||
|
||||
type Auth struct {
|
||||
AuthID string `json:"authId"`
|
||||
Template string `json:"template"`
|
||||
Stage string `json:"stage"`
|
||||
Header string `json:"header"`
|
||||
Callbacks []AuthCallback `json:"callbacks"`
|
||||
}
|
||||
|
||||
type AuthCallback struct {
|
||||
Type string `json:"type"`
|
||||
Output []AuthCallbackValue `json:"output"`
|
||||
Input []AuthCallbackValue `json:"input"`
|
||||
}
|
||||
|
||||
type AuthCallbackValue struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
TokenID string `json:"tokenId"`
|
||||
SuccessURL string `json:"successUrl"`
|
||||
Realm string `json:"realm"`
|
||||
}
|
||||
|
||||
type Vehicles struct {
|
||||
Data []Vehicle
|
||||
}
|
||||
|
||||
type Vehicle struct {
|
||||
VIN string
|
||||
ModelName string
|
||||
PictureURL string
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue