Skip to content

Commit 3fee9d0

Browse files
committed
Updates
1 parent e4fb35f commit 3fee9d0

File tree

7 files changed

+213
-33
lines changed

7 files changed

+213
-33
lines changed

pkg/bitwarden/client.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,10 @@ const (
6666
)
6767

6868
const (
69-
deviceName = "api"
70-
loginScope = "api offline_access"
71-
loginApiKeyScope = "api"
69+
deviceName = "github.com/mutablelogic/go-client"
70+
loginScope = "api offline_access"
71+
loginApiScope = "api"
72+
clientId = "connector"
7273
)
7374

7475
///////////////////////////////////////////////////////////////////////////////
@@ -113,18 +114,37 @@ func (c *Client) Prelogin(email, password string) (*Session, error) {
113114
return &response, nil
114115
}
115116

116-
// GetToken returns a token for a session
117-
func (c *Client) LoginToken(session *Session) error {
117+
// Login returns a token for a session, or a challenge for two-factor authentication
118+
func (c *Client) Login(session *Session) error {
118119
var request reqToken
120+
var response respToken
119121

122+
// Check parameters
123+
if session == nil {
124+
return ErrBadParameter
125+
}
126+
127+
// Set up the request
120128
request.GrantType = "password"
121129
request.Email = session.Email
122130
request.Password = session.Password
123131
request.Scope = loginScope
124-
request.ClientId = "browser"
132+
request.ClientId = clientId
125133
request.DeviceType = deviceType()
126134
request.DeviceName = deviceName
127-
request.DeviceIdentifier = "00000000-0000-0000-0000-000000000000" // TODO
135+
request.DeviceIdentifier = "aac2e34a-44db-42ab-a733-5322dd582c3d" // TODO
136+
137+
// Request -> Response
138+
if payload, err := client.NewFormRequest(request, client.ContentTypeJson); err != nil {
139+
return err
140+
} else if err := c.Do(payload, &response, client.OptReqEndpoint(identityUrl), client.OptPath("connect/token"), client.OptReqHeader("Host", "identity.bitwarden.com")); err != nil {
141+
return err
142+
}
143+
144+
// TODO
145+
146+
// Return success
147+
return nil
128148
}
129149

130150
///////////////////////////////////////////////////////////////////////////////

pkg/bitwarden/client_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,19 @@ func Test_client_002(t *testing.T) {
3131
data, _ := json.MarshalIndent(session, "", " ")
3232
t.Log(string(data))
3333
}
34+
35+
func Test_client_003(t *testing.T) {
36+
assert := assert.New(t)
37+
client, err := bitwarden.New(opts.OptTrace(os.Stderr, true))
38+
assert.NoError(err)
39+
40+
session, err := client.Prelogin("nobody@example.com", "p4ssw0rd")
41+
assert.NoError(err)
42+
assert.NotNil(session)
43+
44+
err = client.Login(session)
45+
assert.NoError(err)
46+
47+
data, _ := json.MarshalIndent(session, "", " ")
48+
t.Log(string(data))
49+
}

pkg/client/client.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package client
22

33
import (
4+
"context"
45
"encoding/json"
56
"encoding/xml"
67
"fmt"
@@ -51,13 +52,13 @@ const (
5152
DefaultTimeout = time.Second * 30
5253
DefaultUserAgent = "github.com/mutablelogic/go-client"
5354
PathSeparator = string(os.PathSeparator)
55+
ContentTypeAny = "*/*"
5456
ContentTypeJson = "application/json"
5557
ContentTypeTextXml = "text/xml"
5658
ContentTypeApplicationXml = "application/xml"
5759
ContentTypeTextPlain = "text/plain"
5860
ContentTypeTextHTML = "text/html"
5961
ContentTypeBinary = "application/octet-stream"
60-
ContentTypeForm = "multipart/form-data"
6162
)
6263

6364
///////////////////////////////////////////////////////////////////////////////
@@ -110,6 +111,12 @@ func (client *Client) String() string {
110111
// Do a JSON request with a payload, populate an object with the response
111112
// and return any errors
112113
func (client *Client) Do(in Payload, out any, opts ...RequestOpt) error {
114+
return client.DoWithContext(context.Background(), in, out, opts...)
115+
}
116+
117+
// Do a JSON request with a payload, populate an object with the response
118+
// and return any errors. The context can be used to cancel the request
119+
func (client *Client) DoWithContext(ctx context.Context, in Payload, out any, opts ...RequestOpt) error {
113120
client.Mutex.Lock()
114121
defer client.Mutex.Unlock()
115122

@@ -135,7 +142,7 @@ func (client *Client) Do(in Payload, out any, opts ...RequestOpt) error {
135142
accept = in.Accept()
136143
mimetype = in.Type()
137144
}
138-
req, err := client.request(method, accept, mimetype, in)
145+
req, err := client.request(ctx, method, accept, mimetype, in)
139146
if err != nil {
140147
return err
141148
}
@@ -182,14 +189,14 @@ func (client *Client) Request(req *http.Request, out any, opts ...RequestOpt) er
182189
// request creates a request which can be used to return responses. The accept
183190
// parameter is the accepted mime-type of the response. If the accept parameter is empty,
184191
// then the default is application/json.
185-
func (client *Client) request(method, accept, mimetype string, body io.Reader) (*http.Request, error) {
192+
func (client *Client) request(ctx context.Context, method, accept, mimetype string, body io.Reader) (*http.Request, error) {
186193
// Return error if no endpoint is set
187194
if client.endpoint == nil {
188195
return nil, ErrBadParameter.With("missing endpoint")
189196
}
190197

191198
// Make a request
192-
r, err := http.NewRequest(method, client.endpoint.String(), body)
199+
r, err := http.NewRequestWithContext(ctx, method, client.endpoint.String(), body)
193200
if err != nil {
194201
return nil, err
195202
}
@@ -203,6 +210,8 @@ func (client *Client) request(method, accept, mimetype string, body io.Reader) (
203210
}
204211
if accept != "" {
205212
r.Header.Set("Accept", accept)
213+
} else {
214+
r.Header.Set("Accept", ContentTypeAny)
206215
}
207216
if client.ua != "" {
208217
r.Header.Set("User-Agent", client.ua)
@@ -256,7 +265,7 @@ func do(client *http.Client, req *http.Request, accept string, strict bool, out
256265
}
257266

258267
// When in strict mode, check content type returned is as expected
259-
if strict && accept != "" {
268+
if strict && (accept != "" && accept != ContentTypeAny) {
260269
if mimetype != accept {
261270
return ErrUnexpectedResponse.Withf("strict mode: unexpected responsse with %q", mimetype)
262271
}

pkg/client/payload.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,28 @@ func NewMultipartRequest(payload any, accept string) (*Request, error) {
6363
this.buffer = new(bytes.Buffer)
6464

6565
// Encode the payload
66-
enc := multipart.NewEncoder(this.buffer)
66+
enc := multipart.NewMultipartEncoder(this.buffer)
67+
defer enc.Close()
68+
if err := enc.Encode(payload); err != nil {
69+
return nil, err
70+
} else {
71+
this.mimetype = enc.ContentType()
72+
}
73+
74+
// Return success
75+
return this, nil
76+
}
77+
78+
// Return a new request with a Form data payload which defaults to POST. The accept
79+
// parameter is the accepted mime-type of the response.
80+
func NewFormRequest(payload any, accept string) (*Request, error) {
81+
this := new(Request)
82+
this.method = http.MethodPost
83+
this.accept = accept
84+
this.buffer = new(bytes.Buffer)
85+
86+
// Encode the payload
87+
enc := multipart.NewFormEncoder(this.buffer)
6788
defer enc.Close()
6889
if err := enc.Encode(payload); err != nil {
6990
return nil, err

pkg/client/requestopts.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,27 @@ import (
55
"net/url"
66
"path/filepath"
77
"strings"
8+
9+
// Namespace imports
10+
. "github.com/djthorpe/go-errors"
811
)
912

13+
// OptReqEndpoint modifies the request endpoint for this request only
14+
func OptReqEndpoint(value string) RequestOpt {
15+
return func(r *http.Request) error {
16+
if url, err := url.Parse(value); err != nil {
17+
return err
18+
} else if url.Scheme == "" || url.Host == "" {
19+
return ErrBadParameter.Withf("endpoint: %q", value)
20+
} else if url.Scheme != "http" && url.Scheme != "https" {
21+
return ErrBadParameter.Withf("endpoint: %q", value)
22+
} else {
23+
r.URL = url
24+
}
25+
return nil
26+
}
27+
}
28+
1029
// OptPath appends path elements onto a request
1130
func OptPath(value ...string) RequestOpt {
1231
return func(r *http.Request) error {
@@ -44,3 +63,11 @@ func OptQuery(value url.Values) RequestOpt {
4463
return nil
4564
}
4665
}
66+
67+
// OptReqHeader adds a header value to the request
68+
func OptReqHeader(name, value string) RequestOpt {
69+
return func(r *http.Request) error {
70+
r.Header.Set(name, value)
71+
return nil
72+
}
73+
}

pkg/client/transport.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"mime"
1010
"net/http"
1111
"time"
12+
13+
"github.com/mutablelogic/go-client/pkg/multipart"
1214
// Packages
1315
)
1416

@@ -80,7 +82,7 @@ func (transport *logtransport) RoundTrip(req *http.Request) (*http.Response, err
8082
if transport.v {
8183
data, err := req.Body.(*readwrapper).as(req.Header.Get("Content-Type"))
8284
if err == nil {
83-
fmt.Fprintln(transport.w, " ", string(data))
85+
fmt.Fprintf(transport.w, " => %q\n", string(data))
8486
}
8587
}
8688

@@ -96,7 +98,7 @@ func (transport *logtransport) RoundTrip(req *http.Request) (*http.Response, err
9698
defer resp.Body.Close()
9799
body, err := io.ReadAll(resp.Body)
98100
if err == nil {
99-
fmt.Fprintln(transport.w, " ", string(body))
101+
fmt.Fprintf(transport.w, " <= %q\n", string(body))
100102
}
101103
resp.Body = io.NopCloser(bytes.NewReader(body))
102104
}
@@ -137,6 +139,8 @@ func (w *readwrapper) as(mimetype string) ([]byte, error) {
137139
} else {
138140
return dest.Bytes(), nil
139141
}
142+
case multipart.ContentTypeForm:
143+
return w.data.Bytes(), nil
140144
default:
141145
// TODO: Make this more like a hex dump
142146
return []byte(hex.EncodeToString(w.data.Bytes())), nil

0 commit comments

Comments
 (0)