PSA: change authentication from user/password to token (#13612)
This commit is contained in:
parent
fd653ec838
commit
db8808627a
10 changed files with 380 additions and 97 deletions
10
cmd/token.go
10
cmd/token.go
|
|
@ -51,11 +51,19 @@ func runToken(cmd *cobra.Command, args []string) {
|
|||
var token *oauth2.Token
|
||||
var err error
|
||||
|
||||
switch strings.ToLower(vehicleConf.Type) {
|
||||
typ := strings.ToLower(vehicleConf.Type)
|
||||
if typ == "template" {
|
||||
typ = strings.ToLower(vehicleConf.Other["template"].(string))
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case "mercedes":
|
||||
token, err = mercedesToken()
|
||||
case "tronity":
|
||||
token, err = tronityToken(conf, vehicleConf)
|
||||
case "citroen", "ds", "opel", "peugeot":
|
||||
token, err = psaToken(typ)
|
||||
|
||||
default:
|
||||
log.FATAL.Fatalf("vehicle type '%s' does not support token authentication", vehicleConf.Type)
|
||||
}
|
||||
|
|
|
|||
44
cmd/token_psa.go
Normal file
44
cmd/token_psa.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/request"
|
||||
"github.com/evcc-io/evcc/vehicle/psa"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func psaToken(brand string) (*oauth2.Token, error) {
|
||||
var country string
|
||||
prompt_country := &survey.Input{
|
||||
Message: "Please enter your country code:",
|
||||
}
|
||||
if err := survey.AskOne(prompt_country, &country, survey.WithValidator(survey.Required)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cv := oauth2.GenerateVerifier()
|
||||
oc := psa.Oauth2Config(brand, strings.ToLower(country))
|
||||
|
||||
authorize_url := oc.AuthCodeURL("", oauth2.S256ChallengeOption(cv))
|
||||
|
||||
fmt.Println("Please visit: ", authorize_url)
|
||||
fmt.Println("And grab the authorization code like described here: https://github.com/flobz/psa_car_controller/discussions/779")
|
||||
|
||||
var code string
|
||||
prompt_code := &survey.Input{
|
||||
Message: "Please enter your authorization code:",
|
||||
}
|
||||
if err := survey.AskOne(prompt_code, &code, survey.WithValidator(survey.Required)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := request.NewClient(util.NewLogger(brand))
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, client)
|
||||
|
||||
return oc.Exchange(ctx, code, oauth2.VerifierOption(cv))
|
||||
}
|
||||
|
|
@ -1,10 +1,44 @@
|
|||
template: citroen
|
||||
products:
|
||||
- brand: Citroën
|
||||
requirements:
|
||||
description:
|
||||
de: |
|
||||
Benötigt `access` und `refresh` Tokens. Diese können über den Befehl "evcc token [name]" generiert werden.
|
||||
en: |
|
||||
Citroën `access` and `refresh` tokens are required. These can be generated with command "evcc token [name]".
|
||||
params:
|
||||
- preset: vehicle-base
|
||||
- name: title
|
||||
- name: icon
|
||||
default: car
|
||||
advanced: true
|
||||
- name: user
|
||||
required: true
|
||||
- name: password
|
||||
deprecated: true
|
||||
- name: tokens
|
||||
required: true
|
||||
mask: true
|
||||
help:
|
||||
en: "See https://docs.evcc.io/en/docs/devices/vehicles#citroen"
|
||||
de: "Siehe https://docs.evcc.io/docs/devices/vehicles#citroen"
|
||||
- name: vin
|
||||
example: V...
|
||||
- name: capacity
|
||||
- name: phases
|
||||
advanced: true
|
||||
- name: password
|
||||
deprecated: true
|
||||
- preset: vehicle-identify
|
||||
render: |
|
||||
type: citroen
|
||||
{{ include "vehicle-base" . }}
|
||||
title: {{ .title }}
|
||||
icon: {{ .icon }}
|
||||
user: {{ .user }}
|
||||
tokens:
|
||||
access: {{ .accessToken }}
|
||||
refresh: {{ .refreshToken }}
|
||||
capacity: {{ .capacity }}
|
||||
phases: {{ .phases }}
|
||||
vin: {{ .vin }}
|
||||
{{ include "vehicle-identify" . }}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,44 @@
|
|||
template: ds
|
||||
products:
|
||||
- brand: DS
|
||||
requirements:
|
||||
description:
|
||||
de: |
|
||||
Benötigt `access` und `refresh` Tokens. Diese können über den Befehl "evcc token [name]" generiert werden.
|
||||
en: |
|
||||
DS `access` and `refresh` tokens are required. These can be generated with command "evcc token [name]".
|
||||
params:
|
||||
- preset: vehicle-base
|
||||
- name: title
|
||||
- name: icon
|
||||
default: car
|
||||
advanced: true
|
||||
- name: user
|
||||
required: true
|
||||
- name: password
|
||||
deprecated: true
|
||||
- name: tokens
|
||||
required: true
|
||||
mask: true
|
||||
help:
|
||||
en: "See https://docs.evcc.io/en/docs/devices/vehicles#ds"
|
||||
de: "Siehe https://docs.evcc.io/docs/devices/vehicles#ds"
|
||||
- name: vin
|
||||
example: V...
|
||||
- name: capacity
|
||||
- name: phases
|
||||
advanced: true
|
||||
- name: password
|
||||
deprecated: true
|
||||
- preset: vehicle-identify
|
||||
render: |
|
||||
type: ds
|
||||
{{ include "vehicle-base" . }}
|
||||
title: {{ .title }}
|
||||
icon: {{ .icon }}
|
||||
user: {{ .user }}
|
||||
tokens:
|
||||
access: {{ .accessToken }}
|
||||
refresh: {{ .refreshToken }}
|
||||
capacity: {{ .capacity }}
|
||||
phases: {{ .phases }}
|
||||
vin: {{ .vin }}
|
||||
{{ include "vehicle-identify" . }}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,44 @@
|
|||
template: opel
|
||||
products:
|
||||
- brand: Opel
|
||||
requirements:
|
||||
description:
|
||||
de: |
|
||||
Benötigt `access` und `refresh` Tokens. Diese können über den Befehl "evcc token [name]" generiert werden.
|
||||
en: |
|
||||
Opel `access` and `refresh` tokens are required. These can be generated with command "evcc token [name]".
|
||||
params:
|
||||
- preset: vehicle-base
|
||||
- preset: vehicle-identify
|
||||
- name: title
|
||||
- name: icon
|
||||
default: car
|
||||
advanced: true
|
||||
- name: user
|
||||
required: true
|
||||
- name: password
|
||||
deprecated: true
|
||||
- name: tokens
|
||||
required: true
|
||||
mask: true
|
||||
help:
|
||||
en: "See https://docs.evcc.io/en/docs/devices/vehicles#opel"
|
||||
de: "Siehe https://docs.evcc.io/docs/devices/vehicles#opel"
|
||||
- name: vin
|
||||
example: WP0...
|
||||
example: V...
|
||||
- name: capacity
|
||||
- name: phases
|
||||
advanced: true
|
||||
- name: password
|
||||
deprecated: true
|
||||
- preset: vehicle-identify
|
||||
render: |
|
||||
type: opel
|
||||
{{ include "vehicle-base" . }}
|
||||
title: {{ .title }}
|
||||
icon: {{ .icon }}
|
||||
user: {{ .user }}
|
||||
tokens:
|
||||
access: {{ .accessToken }}
|
||||
refresh: {{ .refreshToken }}
|
||||
capacity: {{ .capacity }}
|
||||
phases: {{ .phases }}
|
||||
vin: {{ .vin }}
|
||||
{{ include "vehicle-identify" . }}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,44 @@
|
|||
template: peugeot
|
||||
products:
|
||||
- brand: Peugeot
|
||||
requirements:
|
||||
description:
|
||||
de: |
|
||||
Benötigt `access` und `refresh` Tokens. Diese können über den Befehl "evcc token [name]" generiert werden.
|
||||
en: |
|
||||
Peugeot `access` and `refresh` tokens are required. These can be generated with command "evcc token [name]".
|
||||
params:
|
||||
- preset: vehicle-base
|
||||
- name: title
|
||||
- name: icon
|
||||
default: car
|
||||
advanced: true
|
||||
- name: user
|
||||
required: true
|
||||
- name: password
|
||||
deprecated: true
|
||||
- name: tokens
|
||||
required: true
|
||||
mask: true
|
||||
help:
|
||||
en: "See https://docs.evcc.io/en/docs/devices/vehicles#peugeot"
|
||||
de: "Siehe https://docs.evcc.io/docs/devices/vehicles#peugeot"
|
||||
- name: vin
|
||||
example: V...
|
||||
- name: capacity
|
||||
- name: phases
|
||||
advanced: true
|
||||
- name: password
|
||||
deprecated: true
|
||||
- preset: vehicle-identify
|
||||
render: |
|
||||
type: peugeot
|
||||
{{ include "vehicle-base" . }}
|
||||
title: {{ .title }}
|
||||
icon: {{ .icon }}
|
||||
user: {{ .user }}
|
||||
tokens:
|
||||
access: {{ .accessToken }}
|
||||
refresh: {{ .refreshToken }}
|
||||
capacity: {{ .capacity }}
|
||||
phases: {{ .phases }}
|
||||
vin: {{ .vin }}
|
||||
{{ include "vehicle-identify" . }}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package vehicle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
|
|
@ -12,46 +12,18 @@ import (
|
|||
// https://github.com/TA2k/ioBroker.psa
|
||||
|
||||
func init() {
|
||||
registry.Add("citroen", NewCitroenFromConfig)
|
||||
registry.Add("ds", NewDSFromConfig)
|
||||
registry.Add("opel", NewOpelFromConfig)
|
||||
registry.Add("peugeot", NewPeugeotFromConfig)
|
||||
}
|
||||
|
||||
// NewCitroenFromConfig creates a new vehicle
|
||||
func NewCitroenFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
||||
log := util.NewLogger("citroen")
|
||||
return newPSA(log,
|
||||
"citroen.com", "clientsB2CCitroen",
|
||||
"5364defc-80e6-447b-bec6-4af8d1542cae", "iE0cD8bB0yJ0dS6rO3nN1hI2wU7uA5xR4gP7lD6vM0oH0nS8dN",
|
||||
other)
|
||||
}
|
||||
|
||||
// NewDSFromConfig creates a new vehicle
|
||||
func NewDSFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
||||
log := util.NewLogger("ds")
|
||||
return newPSA(log,
|
||||
"driveds.com", "clientsB2CDS",
|
||||
"cbf74ee7-a303-4c3d-aba3-29f5994e2dfa", "X6bE6yQ3tH1cG5oA6aW4fS6hK0cR0aK5yN2wE4hP8vL8oW5gU3",
|
||||
other)
|
||||
}
|
||||
|
||||
// NewOpelFromConfig creates a new vehicle
|
||||
func NewOpelFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
||||
log := util.NewLogger("opel")
|
||||
return newPSA(log,
|
||||
"opel.com", "clientsB2COpel",
|
||||
"07364655-93cb-4194-8158-6b035ac2c24c", "F2kK7lC5kF5qN7tM0wT8kE3cW1dP0wC5pI6vC0sQ5iP5cN8cJ8",
|
||||
other)
|
||||
}
|
||||
|
||||
// NewPeugeotFromConfig creates a new vehicle
|
||||
func NewPeugeotFromConfig(other map[string]interface{}) (api.Vehicle, error) {
|
||||
log := util.NewLogger("peugeot")
|
||||
return newPSA(log,
|
||||
"peugeot.com", "clientsB2CPeugeot",
|
||||
"1eebc2d5-5df3-459b-a624-20abfcf82530", "T5tP7iS0cO8sC0lA2iE2aR7gK6uE5rF3lJ8pC3nO1pR7tL8vU1",
|
||||
other)
|
||||
registry.Add("citroen", func(other map[string]any) (api.Vehicle, error) {
|
||||
return newPSA("citroen", "clientsB2CCitroen", other)
|
||||
})
|
||||
registry.Add("ds", func(other map[string]any) (api.Vehicle, error) {
|
||||
return newPSA("ds", "clientsB2CDS", other)
|
||||
})
|
||||
registry.Add("opel", func(other map[string]any) (api.Vehicle, error) {
|
||||
return newPSA("opel", "clientsB2COpel", other)
|
||||
})
|
||||
registry.Add("peugeot", func(other map[string]any) (api.Vehicle, error) {
|
||||
return newPSA("peugeot", "clientsB2CPeugeot", other)
|
||||
})
|
||||
}
|
||||
|
||||
// PSA is an api.Vehicle implementation for PSA cars
|
||||
|
|
@ -61,17 +33,16 @@ type PSA struct {
|
|||
}
|
||||
|
||||
// newPSA creates a new vehicle
|
||||
func newPSA(log *util.Logger, brand, realm, id, secret string, other map[string]interface{}) (api.Vehicle, error) {
|
||||
func newPSA(brand, realm string, other map[string]interface{}) (api.Vehicle, error) {
|
||||
cc := struct {
|
||||
embed `mapstructure:",squash"`
|
||||
Credentials ClientCredentials
|
||||
User, Password, VIN string
|
||||
VIN string
|
||||
User string
|
||||
Password string `mapstructure:"password"`
|
||||
Country string
|
||||
Tokens Tokens
|
||||
Cache time.Duration
|
||||
}{
|
||||
Credentials: ClientCredentials{
|
||||
ID: id,
|
||||
Secret: secret,
|
||||
},
|
||||
Cache: interval,
|
||||
}
|
||||
|
||||
|
|
@ -79,22 +50,30 @@ func newPSA(log *util.Logger, brand, realm, id, secret string, other map[string]
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if cc.User == "" || cc.Password == "" {
|
||||
if cc.User == "" {
|
||||
return nil, api.ErrMissingCredentials
|
||||
}
|
||||
|
||||
token, err := cc.Tokens.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := &PSA{
|
||||
embed: &cc.embed,
|
||||
}
|
||||
|
||||
log.Redact(cc.User, cc.Password, cc.VIN)
|
||||
identity := psa.NewIdentity(log, brand, cc.Credentials.ID, cc.Credentials.Secret)
|
||||
log := util.NewLogger(brand)
|
||||
log.Redact(cc.User, cc.Tokens.Access, cc.Tokens.Refresh)
|
||||
|
||||
if err := identity.Login(cc.User, cc.Password); err != nil {
|
||||
return v, fmt.Errorf("login failed: %w", err)
|
||||
oc := psa.Oauth2Config(brand, strings.ToLower(cc.Country))
|
||||
identity, err := psa.NewIdentity(log, brand, cc.User, oc, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
api := psa.NewAPI(log, identity, realm, cc.Credentials.ID)
|
||||
// TODO still needed?
|
||||
api := psa.NewAPI(log, identity, realm, oc.ClientID)
|
||||
|
||||
vehicle, err := ensureVehicleEx(
|
||||
cc.VIN, api.Vehicles,
|
||||
|
|
|
|||
20
vehicle/psa/helper.go
Normal file
20
vehicle/psa/helper.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package psa
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
identities = make(map[string]oauth2.TokenSource)
|
||||
)
|
||||
|
||||
func getInstance(subject string) oauth2.TokenSource {
|
||||
return identities[subject]
|
||||
}
|
||||
|
||||
func addInstance(subject string, identity oauth2.TokenSource) {
|
||||
identities[subject] = identity
|
||||
}
|
||||
|
|
@ -2,48 +2,80 @@ package psa
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"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"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type Identity struct {
|
||||
*request.Helper
|
||||
oc *oauth2.Config
|
||||
oauth2.TokenSource
|
||||
mu sync.Mutex
|
||||
oc *oauth2.Config
|
||||
log *util.Logger
|
||||
subject string
|
||||
}
|
||||
|
||||
// NewIdentity creates PSA identity
|
||||
func NewIdentity(log *util.Logger, brand, id, secret string) *Identity {
|
||||
return &Identity{
|
||||
Helper: request.NewHelper(log),
|
||||
oc: &oauth2.Config{
|
||||
ClientID: id,
|
||||
ClientSecret: secret,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: "https://api.mpsa.com/api/connectedcar/v2/oauth/authorize",
|
||||
TokenURL: fmt.Sprintf("https://idpcvs.%s/am/oauth2/access_token", brand),
|
||||
AuthStyle: oauth2.AuthStyleInHeader,
|
||||
},
|
||||
Scopes: []string{"openid profile"},
|
||||
},
|
||||
}
|
||||
}
|
||||
func NewIdentity(log *util.Logger, brand, user string, oc *oauth2.Config, token *oauth2.Token) (oauth2.TokenSource, error) {
|
||||
// serialise instance handling
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
func (v *Identity) Login(user, password string) error {
|
||||
ctx := context.WithValue(
|
||||
context.Background(),
|
||||
oauth2.HTTPClient,
|
||||
v.Client,
|
||||
)
|
||||
|
||||
// replace client with authenticated oauth client
|
||||
token, err := v.oc.PasswordCredentialsToken(ctx, user, password)
|
||||
if err == nil {
|
||||
v.TokenSource = v.oc.TokenSource(ctx, token)
|
||||
// reuse identity instance
|
||||
subject := "psa." + strings.ToLower(brand) + "." + strings.ToLower(user)
|
||||
if instance := getInstance(subject); instance != nil {
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
return err
|
||||
v := &Identity{
|
||||
log: log,
|
||||
oc: oc,
|
||||
subject: subject,
|
||||
}
|
||||
|
||||
var tok oauth2.Token
|
||||
if err := settings.Json(v.subject, &tok); err == nil {
|
||||
token = &tok
|
||||
}
|
||||
|
||||
if !token.Valid() {
|
||||
if tok, err := v.RefreshToken(token); err == nil {
|
||||
token = tok
|
||||
}
|
||||
}
|
||||
|
||||
if !token.Valid() {
|
||||
return nil, errors.New("token expired")
|
||||
}
|
||||
|
||||
v.TokenSource = oauth.RefreshTokenSource(token, v)
|
||||
|
||||
// add instance
|
||||
addInstance(v.subject, v)
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) {
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
|
||||
client := request.NewClient(v.log)
|
||||
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, client)
|
||||
|
||||
tok, err := v.oc.TokenSource(ctx, token).Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v.TokenSource = oauth.RefreshTokenSource(tok, v)
|
||||
err = settings.SetJson(v.subject, tok)
|
||||
|
||||
return tok, err
|
||||
}
|
||||
|
|
|
|||
66
vehicle/psa/oauth2.go
Normal file
66
vehicle/psa/oauth2.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package psa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func Oauth2Config(brand, country string) *oauth2.Config {
|
||||
switch brand {
|
||||
case "citroen":
|
||||
return &oauth2.Config{
|
||||
ClientID: "5364defc-80e6-447b-bec6-4af8d1542cae",
|
||||
ClientSecret: "iE0cD8bB0yJ0dS6rO3nN1hI2wU7uA5xR4gP7lD6vM0oH0nS8dN",
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: "https://idpcvs.citroen.com/am/oauth2/authorize",
|
||||
TokenURL: "https://idpcvs.citroen.com/am/oauth2/access_token",
|
||||
AuthStyle: oauth2.AuthStyleInHeader,
|
||||
},
|
||||
RedirectURL: fmt.Sprintf("mymacsdk://oauth2redirect/%s", country),
|
||||
Scopes: []string{"openid", "profile"},
|
||||
}
|
||||
|
||||
case "ds":
|
||||
return &oauth2.Config{
|
||||
ClientID: "cbf74ee7-a303-4c3d-aba3-29f5994e2dfa",
|
||||
ClientSecret: "X6bE6yQ3tH1cG5oA6aW4fS6hK0cR0aK5yN2wE4hP8vL8oW5gU3",
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: "https://idpcvs.driveds.com/am/oauth2/authorize",
|
||||
TokenURL: "https://idpcvs.driveds.com/am/oauth2/access_token",
|
||||
AuthStyle: oauth2.AuthStyleInHeader,
|
||||
},
|
||||
RedirectURL: fmt.Sprintf("mymdssdk://oauth2redirect/%s", country),
|
||||
Scopes: []string{"openid", "profile"},
|
||||
}
|
||||
|
||||
case "opel":
|
||||
return &oauth2.Config{
|
||||
ClientID: "07364655-93cb-4194-8158-6b035ac2c24c",
|
||||
ClientSecret: "F2kK7lC5kF5qN7tM0wT8kE3cW1dP0wC5pI6vC0sQ5iP5cN8cJ8",
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: "https://idpcvs.opel.com/am/oauth2/authorize",
|
||||
TokenURL: "https://idpcvs.opel.com/am/oauth2/access_token",
|
||||
AuthStyle: oauth2.AuthStyleInHeader,
|
||||
},
|
||||
RedirectURL: fmt.Sprintf("mymopsdk://oauth2redirect/%s", country),
|
||||
Scopes: []string{"openid", "profile"},
|
||||
}
|
||||
|
||||
case "peugeot":
|
||||
return &oauth2.Config{
|
||||
ClientID: "1eebc2d5-5df3-459b-a624-20abfcf82530",
|
||||
ClientSecret: "T5tP7iS0cO8sC0lA2iE2aR7gK6uE5rF3lJ8pC3nO1pR7tL8vU1",
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: "https://idpcvs.peugeot.com/am/oauth2/authorize",
|
||||
TokenURL: "https://idpcvs.peugeot.com/am/oauth2/access_token",
|
||||
AuthStyle: oauth2.AuthStyleInHeader,
|
||||
},
|
||||
RedirectURL: fmt.Sprintf("mymap://oauth2redirect/%s", country),
|
||||
Scopes: []string{"openid", "profile"},
|
||||
}
|
||||
|
||||
default:
|
||||
panic("invalid brand: " + brand)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue