From 95fbe3a5b7b19f9da193994482840c2fa57df2a4 Mon Sep 17 00:00:00 2001 From: samjtro Date: Sun, 3 Nov 2024 19:02:08 -0600 Subject: [PATCH] major: ref: #67, v0.9.0 desc: - finally got x/oauth2 working - moved token storage into cwd/.json - refactored utils.go, removed unused funcs - edited README, gitignore to reflect changes --- .gitignore | 5 +- README.md | 9 ++- go.mod | 10 ++-- go.sum | 18 ++---- utils.go | 159 ++++++++++++++--------------------------------------- 5 files changed, 62 insertions(+), 139 deletions(-) diff --git a/.gitignore b/.gitignore index fe1d3d5..7c740bc 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,7 @@ # vendor/ # CUSTOM -*.env \ No newline at end of file +*.env +*.json +*.crt +*.key diff --git a/README.md b/README.md index 3956379..47c06df 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,6 @@ why should you use this project? - return structs are easily indexable - easy to setup, easy to use (personal preference, i know - but trust me!) ---- - ## docs ### 0.0 quick start @@ -31,6 +29,13 @@ SECRET=KEY1 // App Secret CBURL=https://127.0.0.1 // App Callback URL ``` +2. we use tls for the oauth handshake between your local machine & schwab to ensure secure transmission of your bearer token. run the following command to generate ssl certs: + +``` +openssl req -x509 -out localhost.crt -keyout localhost.key -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' -extensions EXT -config <( \ +printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS.1:localhost,IP:127.0.0.1\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth") +``` + 2. `go get github.com/go-schwab/trader@v0.9.0` ### 0.1 agent diff --git a/go.mod b/go.mod index a9ddec1..e015679 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23.2 require ( github.com/bytedance/sonic v1.12.2 - github.com/go-schwab/oauth2ns v0.0.0-20241027015119-9e1292705f91 + github.com/go-schwab/utils/oauth v0.0.0-20241103230919-01b09562dfc2 github.com/joho/godotenv v1.5.1 golang.org/x/oauth2 v0.23.0 ) @@ -13,14 +13,12 @@ require ( github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/nmrshll/rndm-go v0.0.0-20170430161430-8da3024e53de // indirect - github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177 // indirect - github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.25.0 // indirect ) diff --git a/go.sum b/go.sum index 7786146..01ea035 100644 --- a/go.sum +++ b/go.sum @@ -10,10 +10,10 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/go-schwab/oauth2ns v0.0.0-20241027015119-9e1292705f91 h1:2p/Kayfg67+S8/Cip+i/dGcxpkF7q1cwUPyDaVwRZPQ= -github.com/go-schwab/oauth2ns v0.0.0-20241027015119-9e1292705f91/go.mod h1:69d3XNxDSeVmN6g+8a0RT+a13ZxDc5LRKODEZB3d5go= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/go-schwab/utils/oauth v0.0.0-20241103230919-01b09562dfc2 h1:C7kojSdgemr586G27iYSol5L8TUHTR9TWxIwxcBHiag= +github.com/go-schwab/utils/oauth v0.0.0-20241103230919-01b09562dfc2/go.mod h1:8TuoJsd0EHSHnhDFNIQL9MTZKxYt5s0Xv45mGcUnE9U= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -26,14 +26,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/nmrshll/rndm-go v0.0.0-20170430161430-8da3024e53de h1:j+mSQhCm1H2d7apFbM5ODqrTultUvF3jt//DcRNkxVM= -github.com/nmrshll/rndm-go v0.0.0-20170430161430-8da3024e53de/go.mod h1:OeEnWnbCrUWnPl1xSCGM5/qtWqZ4L15KOAjR/wmxhXc= -github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177 h1:nRlQD0u1871kaznCnn1EvYiMbum36v7hw1DLPEjds4o= -github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177/go.mod h1:ao5zGxj8Z4x60IOVYZUbDSmt3R8Ddo080vEgPosHpak= 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/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -51,8 +45,8 @@ golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/utils.go b/utils.go index d6166f0..19eb6db 100644 --- a/utils.go +++ b/utils.go @@ -2,8 +2,8 @@ package trader import ( "context" + "crypto/tls" "errors" - "fmt" "io" "io/fs" "log" @@ -13,18 +13,25 @@ import ( "strings" "github.com/bytedance/sonic" - o "github.com/go-schwab/oauth2ns" + o "github.com/go-schwab/utils/oauth" "github.com/joho/godotenv" "golang.org/x/oauth2" ) -type Agent struct{ client *o.AuthorizedClient } +type Agent struct{ *o.AuthorizedClient } -var TOKENS oauth2.Token +var ( + APPKEY string + SECRET string + CBURL string +) func init() { err := godotenv.Load(findAllEnvFiles()...) isErrNil(err) + APPKEY = os.Getenv("APPKEY") + SECRET = os.Getenv("SECRET") + CBURL = os.Getenv("CBURL") } // is the err nil? @@ -53,119 +60,45 @@ func findAllEnvFiles() []string { return files } -// wrapper for os.UserHomeDir() -func homeDir() string { - dir, err := os.UserHomeDir() - isErrNil(err) - return dir -} - -// Credit: https://go.dev/play/p/C2sZRYC15XN -func getStringInBetween(str string, start string, end string) (result string) { - s := strings.Index(str, start) - if s == -1 { - return - } - s += len(start) - e := strings.Index(str[s:], end) - if e == -1 { - return - } - return str[s : s+e] -} - -// trim one FIRST character in the string -func trimOneFirst(s string) string { - if len(s) < 1 { - return "" - } - return s[1:] -} - -// trim one LAST character in the string -func trimOneLast(s string) string { - if len(s) < 1 { - return "" - } - return s[:len(s)-1] -} - -// trim one FIRST & one LAST character in the string -func trimOneFirstOneLast(s string) string { - if len(s) < 1 { - return "" - } - return s[1 : len(s)-1] -} - -// trim two FIRST & one LAST character in the string -func trimTwoFirstOneLast(s string) string { - if len(s) < 1 { - return "" - } - return s[2 : len(s)-1] -} - -// trim one FIRST & two LAST character in the string -func trimOneFirstTwoLast(s string) string { - if len(s) < 1 { - return "" - } - return s[1 : len(s)-2] -} - -// trim one FIRST & three LAST character in the string -func trimOneFirstThreeLast(s string) string { - if len(s) < 1 { - return "" - } - return s[1 : len(s)-3] -} - -// Read in tokens from ~/.trade/bar.json -func readDB() *o.AuthorizedClient { - body, err := os.ReadFile(fmt.Sprintf("%s/.trade/bar.json", homeDir())) +// Read in tokens from .json +func readDB() Agent { + var tok *oauth2.Token + body, err := os.ReadFile(".json") isErrNil(err) - var ctx context.Context - err = sonic.Unmarshal(body, &TOKENS) + err = sonic.Unmarshal(body, &tok) isErrNil(err) - c := &oauth2.Config{ - ClientID: os.Getenv("APPKEY"), - ClientSecret: os.Getenv("SECRET"), + conf := &oauth2.Config{ + ClientID: APPKEY, // Schwab App Key + ClientSecret: SECRET, // Schwab App Secret Endpoint: oauth2.Endpoint{ AuthURL: "https://api.schwabapi.com/v1/oauth/authorize", TokenURL: "https://api.schwabapi.com/v1/oauth/token", }, } - return &o.AuthorizedClient{ - c.Client(ctx, &TOKENS), - &TOKENS, + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + sslcli := &http.Client{Transport: tr} + ctx := context.WithValue(context.Background(), oauth2.HTTPClient, sslcli) + return Agent{ + &o.AuthorizedClient{ + conf.Client(ctx, tok), + tok, + }, } } func Initiate() *Agent { var agent Agent // TODO: test this block, this is to attempt to resolve the error described in #67 - if _, err := os.Stat(fmt.Sprintf("%s/.trade/bar.json", homeDir())); errors.Is(err, os.ErrNotExist) { - if _, err := os.Stat(fmt.Sprintf("%s/.trade", homeDir())); !errors.Is(err, os.ErrNotExist) { - err = os.RemoveAll(fmt.Sprintf("%s/.trade", homeDir())) - isErrNil(err) - } - err = os.Mkdir(fmt.Sprintf("%s/.trade", homeDir()), os.ModePerm) - isErrNil(err) - agent.client, err = o.Initiate(os.Getenv("APPKEY"), os.Getenv("SECRET"), os.Getenv("CBURL")) + if _, err := os.Stat(".json"); errors.Is(err, os.ErrNotExist) { + agent = Agent{o.Initiate(APPKEY, SECRET)} + bytes, err := sonic.Marshal(agent.Token) isErrNil(err) - TOKENS = *agent.client.Token - bytes, err := sonic.Marshal(TOKENS) - err = os.WriteFile(fmt.Sprintf("%s/.trade/bar.json", homeDir()), bytes, 0777) + err = os.WriteFile(".json", bytes, 0777) isErrNil(err) } else { - agent.client = readDB() - if TOKENS.AccessToken == "" { - err := os.RemoveAll(fmt.Sprintf("%s/.trade", homeDir())) - isErrNil(err) - log.Fatalf("[err] something went wrong - please reinitiate with 'Initiate'") - } + agent = readDB() } return &agent } @@ -177,32 +110,22 @@ func Initiate() *Agent { // req = a request of type *http.Request func (agent *Agent) Handler(req *http.Request) (*http.Response, error) { var err error - if TOKENS.AccessToken == "" { - log.Fatalf("[err] no access token found, please reinitiate with 'Initiate'") - } - if ((&Agent{}) == agent) || ((&o.AuthorizedClient{}) == agent.client) { - // TODO: this theoretically works but results in an error for the oauth implementation - // going to do some testing now, but pushing as it is in what is a theoretically working state - agent.client, err = o.Initiate(os.Getenv("APPKEY"), os.Getenv("SECRET"), os.Getenv("CBURL")) - isErrNil(err) - TOKENS = *agent.client.Token + if agent.Token.AccessToken == "" { + log.Fatal("[fatal] no access token found, please reinitiate with 'Initiate'") + // TODO: auto reinitiate? } - resp, err := agent.client.Do(req) + resp, err := agent.Do(req) if err != nil { return resp, err } - // TODO: test this block - var statusErr error switch true { case resp.StatusCode == 401: - err := os.RemoveAll(fmt.Sprintf("%s/.trade", homeDir())) - isErrNil(err) - statusErr = errors.New("[err] invalid token - please reinitiate with 'Initiate'") + log.Fatal("[fatal] invalid token - please reinitiate with 'Initiate'") case resp.StatusCode < 200, resp.StatusCode > 300: defer resp.Body.Close() body, err := io.ReadAll(resp.Body) isErrNil(err) - statusErr = errors.New(fmt.Sprintf("[err] %d - %s", resp.StatusCode, body)) + log.Println("[err] ", string(body)) } - return resp, statusErr + return resp, nil }