...
1 package client
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "net/http"
8 "net/http/cookiejar"
9 "net/url"
10 "time"
11
12 "github.com/shurcooL/graphql"
13 "golang.org/x/net/publicsuffix"
14
15 "edge-infra.dev/pkg/edge/api/graph/model"
16 )
17
18 const (
19
20 defaultEndpoint = "https://dev1.edge-preprod.dev/api"
21
22 defaultVersion = "v2"
23
24 defaultUseragent = "edge-go-client" + "/" + defaultVersion
25
26 defaultTimeout = 20 * time.Second
27
28 userAgentHeader = "user-agent"
29
30 edgeVersionHeader = "edge-version"
31
32 authorizationHeader = "authorization"
33
34 bearerToken = "bearer"
35
36 totpToken = "totp"
37 )
38
39 type EdgeClient struct {
40
41 Client *graphql.Client
42
43 HTTPClient *http.Client
44
45 jar *cookiejar.Jar
46
47 timeout time.Duration
48
49 userAgent string
50
51 version string
52
53
54 sessionExpires time.Time
55
56 BaseURL *url.URL
57
58 loginCredentials *LoginRequest
59
60 totpToken string
61
62 bearerToken string
63
64 headers map[string]string
65 }
66
67
68 func New(opts ...Option) (*EdgeClient, error) {
69 cl := &EdgeClient{
70 headers: make(map[string]string),
71 }
72 for _, o := range opts {
73 o(cl)
74 }
75 return cl.init()
76 }
77
78
79 func (c *EdgeClient) init() (*EdgeClient, error) {
80 if c.jar == nil {
81 jar, err := cookiejar.New(&cookiejar.Options{
82 PublicSuffixList: publicsuffix.List,
83 })
84 if err != nil {
85 return c, err
86 }
87 c.jar = jar
88 }
89 if c.timeout == 0 {
90 c.timeout = defaultTimeout
91 }
92 if c.userAgent == "" {
93 c.headers[userAgentHeader] = defaultUseragent
94 }
95 if c.version == "" {
96 c.headers[edgeVersionHeader] = c.version
97 }
98 if c.HTTPClient == nil {
99 c.HTTPClient = &http.Client{
100 Jar: c.jar,
101 Timeout: c.timeout,
102 Transport: &Transport{
103 T: http.DefaultTransport,
104 Headers: c.headers,
105 },
106 }
107 }
108 if c.BaseURL == nil {
109 baseURL, err := url.Parse(fmt.Sprintf("%s/%s", defaultEndpoint, defaultVersion))
110 if err != nil {
111 return c, err
112 }
113 c.BaseURL = baseURL
114 }
115 if c.Client == nil {
116 c.Client = graphql.NewClient(c.BaseURL.String(), c.HTTPClient)
117 }
118 if c.multipleAuthMethods() {
119 return c, errors.New("using multiple authentication methods is not supported")
120 }
121 if c.hasCredentials() {
122 _, err := c.Login(context.Background(), c.loginCredentials)
123 if err != nil {
124 return c, err
125 }
126 c.sessionExpires = time.Now()
127 }
128 return c, nil
129 }
130
131
132 func (c EdgeClient) Query(ctx context.Context, query interface{}, variables map[string]interface{}) error {
133 if err := c.handleRefresh(ctx); err != nil {
134 return err
135 }
136 return c.Client.Query(ctx, query, variables)
137 }
138
139
140 func (c EdgeClient) Mutate(ctx context.Context, mutation interface{}, variables map[string]interface{}) error {
141 if err := c.handleRefresh(ctx); err != nil {
142 return err
143 }
144 return c.Client.Mutate(ctx, mutation, variables)
145 }
146
147
148 func (c EdgeClient) handleRefresh(ctx context.Context) error {
149 if c.hasCredentials() && !c.sessionExpires.IsZero() {
150 diff := time.Since(c.sessionExpires)
151 expires := diff.Minutes()
152 if expires >= 13 && expires < 15 {
153 _, err := c.SessionRefresh(ctx, model.AuthProviderBsl)
154 if err != nil {
155
156 _, err := c.Login(ctx, c.loginCredentials)
157 if err != nil {
158 return err
159 }
160 }
161 c.sessionExpires = time.Now()
162 }
163 }
164 return nil
165 }
166
167
168 func (c EdgeClient) hasCredentials() bool {
169 return c.loginCredentials != nil && c.loginCredentials.Username != "" && c.loginCredentials.Password != "" && c.loginCredentials.Organization != ""
170 }
171
172
173 func (c EdgeClient) multipleAuthMethods() bool {
174 switch {
175 case c.bearerToken != "" && c.totpToken != "" && c.hasCredentials():
176 fallthrough
177 case c.bearerToken != "" && c.hasCredentials():
178 fallthrough
179 case c.totpToken != "" && c.hasCredentials():
180 fallthrough
181 case c.bearerToken != "" && c.totpToken != "":
182 return true
183 default:
184 return false
185 }
186 }
187
View as plain text