Skip to content

Commit

Permalink
Use link tokens instead of public key for linking/relinking accounts (#2
Browse files Browse the repository at this point in the history
)

* Use link tokens instead of public key for linking/relinking accounts

* Format

* Let language and countries be configurable

- Language and countries are configurable, but autodetected by default
- Remove Linker.ClientOpts

Co-authored-by: Mark Hudnall <[email protected]>
  • Loading branch information
jdormit and landakram authored Jan 29, 2021
1 parent 98b754b commit 1e13d33
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 203 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ plaid-cli will look at the following environment variables for API credentials:

```sh
PLAID_CLIENT_ID=<client id>
PLAID_PUBLIC_KEY=<public key>
PLAID_SECRET=<devlopment secret>
PLAID_ENVIRONMENT=development
PLAID_LANGUAGE=en # optional, detected using system's locale
PLAID_COUNTRIES=US # optional, detected using system's locale
```

I recommend setting and exporting these on shell startup.
Expand All @@ -39,7 +40,6 @@ API credentials can also be specified using a config file located at
```toml
[plaid]
client_id = "<client id>"
public_key = "<public key>"
secret = "<development secret>"
environment = "development"
```
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ module github.com/landakram/plaid-cli
go 1.14

require (
github.com/Xuanwo/go-locale v1.0.0
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/manifoldco/promptui v0.7.0
github.com/mitchellh/mapstructure v1.3.2 // indirect
github.com/pelletier/go-toml v1.8.0 // indirect
github.com/plaid/plaid-go v0.0.0-20200529200923-9627743aa512
github.com/plaid/plaid-go v0.0.0-20210112002311-0cf0e6f0ea3e
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cast v1.3.1 // indirect
Expand All @@ -16,6 +17,6 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.0
github.com/stretchr/testify v1.6.1 // indirect
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect
golang.org/x/text v0.3.3
gopkg.in/ini.v1 v1.57.0 // indirect
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Xuanwo/go-locale v1.0.0 h1:oqC32Kyiu2XZq+fxtwEg0mWiv9WyDhyHu+sT5cDkgME=
github.com/Xuanwo/go-locale v1.0.0/go.mod h1:kB9tcLfr4Sp+ByIE9SE7vbUkXkGQqel2XH3EHpL0haA=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
Expand Down Expand Up @@ -107,6 +109,7 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
Expand Down Expand Up @@ -157,6 +160,8 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/plaid/plaid-go v0.0.0-20200529200923-9627743aa512 h1:XmKvXLOGi20FfHv20HQYhct0y4vLOcSaT+cMz4MIDw0=
github.com/plaid/plaid-go v0.0.0-20200529200923-9627743aa512/go.mod h1:c7cDT1Lkcr0AgKJGVIG+oCa07jOrrg4Um8nduQ1eQN0=
github.com/plaid/plaid-go v0.0.0-20210112002311-0cf0e6f0ea3e h1:8uVgUfPCS63Ys/8BawzsnDZem8NQsCUlo2yKO0uSqac=
github.com/plaid/plaid-go v0.0.0-20210112002311-0cf0e6f0ea3e/go.mod h1:c7cDT1Lkcr0AgKJGVIG+oCa07jOrrg4Um8nduQ1eQN0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
Expand All @@ -178,7 +183,9 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
Expand Down Expand Up @@ -285,10 +292,14 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3 h1:qDJKu1y/1SjhWac4BQZjLljqvqiWUhjmDMnonmVGDAU=
golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
99 changes: 79 additions & 20 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,39 @@ import (
"github.com/spf13/cobra"

"github.com/spf13/viper"

"github.com/Xuanwo/go-locale"
"golang.org/x/text/language"
)

func sliceToMap(slice []string) map[string]bool {
set := make(map[string]bool, len(slice))
for _, s := range slice {
set[s] = true
}
return set
}

// See https://plaid.com/docs/link/customization/#language-and-country
var plaidSupportedCountries = []string{"US", "CA", "GB", "IE", "ES", "FR", "NL"}
var plaidSupportedLanguages = []string{"en", "fr", "es", "nl"}

func AreValidCountries(countries []string) bool {
supportedCountries := sliceToMap(plaidSupportedCountries)
for _, c := range countries {
if !supportedCountries[c] {
return false
}
}

return true
}

func IsValidLanguageCode(lang string) bool {
supportedLanguages := sliceToMap(plaidSupportedLanguages)
return supportedLanguages[lang]
}

func main() {
log.SetFlags(0)

Expand Down Expand Up @@ -53,6 +84,41 @@ func main() {
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
viper.AutomaticEnv()

tag, err := locale.Detect()
if err != nil {
tag = language.AmericanEnglish
}

region, _ := tag.Region()
base, _ := tag.Base()

var country string
if region.IsCountry() {
country = region.String()
} else {
country = "US"
}

lang := base.String()

viper.SetDefault("plaid.countries", []string{country})
countriesOpt := viper.GetStringSlice("plaid.countries")
var countries []string
for _, c := range countriesOpt {
countries = append(countries, strings.ToUpper(c))
}

viper.SetDefault("plaid.language", lang)
lang = viper.GetString("plaid.language")

if !AreValidCountries(countries) {
log.Fatalln("⚠️ Invalid countries. Please configure `plaid.countries` (using an envvar, PLAID_COUNTRIES, or in plaid-cli's config file) to a subset of countries that Plaid supports. Plaid supports the following countries: ", plaidSupportedCountries)
}

if !IsValidLanguageCode(lang) {
log.Fatalln("⚠️ Invalid language code. Please configure `plaid.language` (using an envvar, PLAID_LANGUAGE, or in plaid-cli's config file) to a language that Plaid supports. Plaid supports the following languages: ", plaidSupportedLanguages)
}

viper.SetDefault("plaid.environment", "development")
plaidEnvStr := strings.ToLower(viper.GetString("plaid.environment"))

Expand All @@ -69,7 +135,6 @@ func main() {
opts := plaid.ClientOptions{
viper.GetString("plaid.client_id"),
viper.GetString("plaid.secret"),
viper.GetString("plaid.public_key"),
plaidEnv,
&http.Client{},
}
Expand All @@ -80,7 +145,7 @@ func main() {
log.Fatal(err)
}

linker := plaid_cli.NewLinker(data, client, opts)
linker := plaid_cli.NewLinker(data, client, countries, lang)

linkCommand := &cobra.Command{
Use: "link [ITEM-ID-OR-ALIAS]",
Expand All @@ -102,13 +167,18 @@ func main() {
itemOrAlias = itemID
}

tokenPair, err = linker.Relink(itemOrAlias, port)
err = linker.Relink(itemOrAlias, port)
log.Println("Institution relinked!")
return
} else {
tokenPair, err = linker.Link(port)
if err != nil {
log.Fatalln(err)
}
data.Tokens[tokenPair.ItemID] = tokenPair.AccessToken
err = data.Save()
}

data.Tokens[tokenPair.ItemID] = tokenPair.AccessToken
err = data.Save()
if err != nil {
log.Fatalln(err)
}
Expand Down Expand Up @@ -334,7 +404,7 @@ func main() {
IncludeOptionalMetadata: withOptionalMetadataFlag,
IncludeStatus: withStatusFlag,
}
resp, err := client.GetInstitutionByIDWithOptions(instID, opts)
resp, err := client.GetInstitutionByIDWithOptions(instID, countries, opts)
if err != nil {
return err
}
Expand Down Expand Up @@ -374,9 +444,10 @@ Configuration:
plaid-cli will look at the following environment variables for API credentials:
PLAID_CLIENT_ID=<client id>
PLAID_PUBLIC_KEY=<public key>
PLAID_SECRET=<devlopment secret>
PLAID_ENVIRONMENT=development
PLAID_LANGUAGE=en # optional, detected using system's locale
PLAID_COUNTRIES=US # optional, detected using system's locale
I recommend setting and exporting these on shell startup.
Expand All @@ -385,7 +456,6 @@ Configuration:
[plaid]
client_id = "<client id>"
public_key = "<public key>"
secret = "<development secret>"
environment = "development"
Expand Down Expand Up @@ -416,11 +486,6 @@ Configuration:
rootCommand.Help()
os.Exit(1)
}
if !viper.IsSet("plaid.public_key") {
log.Println("⚠️ PLAID_PUBLIC_KEY not set. Please see the configuration instructions below.")
rootCommand.Help()
os.Exit(1)
}

rootCommand.Execute()
}
Expand Down Expand Up @@ -455,16 +520,10 @@ func WithRelinkOnAuthError(itemID string, data *plaid_cli.Data, linker *plaid_cl
if e.ErrorCode == "ITEM_LOGIN_REQUIRED" {
log.Println("Login expired. Relinking...")

// TODO: this relink logic duplicated from the link command above

port := viper.GetString("link.port")

var tokenPair *plaid_cli.TokenPair

tokenPair, err = linker.Relink(itemID, port)
err = linker.Relink(itemID, port)

data.Tokens[tokenPair.ItemID] = tokenPair.AccessToken
err = data.Save()
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 1e13d33

Please sign in to comment.